Manager 2
APSIM’s ‘Manager2’ component is a powerful management tool for controlling APSIM simulations. It differs significantly from the original manager which had its own programming language by giving the user the option of coding their scripts in either VB.NET or C#, both of which are standardized languages developed by Microsoft for the .NET platform on Windows (also available on Linux via Mono). Whereas the initial manager had somewhat limited functionality yet was simpler to code, ‘Manager2’ has vastly increased functionality with a small increase in complexity and the advantage of utilizing a well-known language that new-comers will hopefully be familiar with. Coding a new Manager2 script is identical to coding a new APSIM module in VB.NET or C#, however the environment you use is somewhat different. Where one would usually use an IDE such as Visual Studio or MonoDevelop to create a new APSIM module the Manager2 component provides an editor within APSIM that automatically imports the required DLLs and provides a code ‘skeleton’ as a starting point. This tutorial will introduce some basics of the VisualBasic.NET programming language with the purpose of getting you into programming APSIM Manager2 modules as fast as possible. This is by no means a comprehensive introduction to VB.NET or object oriented programming, there are plenty of those available on the internet (a good starting place – and community – for learning to program in any language is www.dreamincode.net). If you are already well-versed in either VB.NET or C# then it is still advisable to briefly skim this section to pick up a few APSIM-specific pieces.
2 Programming Basics
2.1 Imports
As far as coding for APSIM Manager2 scripts goes most people will never need to modify the Imports section of the script as all the required Imports are already set. Imports are used to add references to external libraries you use within your code.
2.2 Classes
For the purpose of this tutorial a Class may be thought of as a ‘container’ of sorts for your code. Defined using the following terminology it is compulsory to put everything except ‘Imports’ statements inside a Class:
VB.NET
Class Script End Class
C#
public class Script
{
}
2.3 Variables
A Variable is a container used to store information your code uses. Variables have names which can consist of any number of letters, underscores and numerals (but cannot begin with a numeral). All variables must be ‘declared’ before they may be used. The general format for doing so is as follows:
VB.NET
Dim *var_name* As *VarType*
Where *var_name* is the name of your variable and *VarType* is the type (see ‘Types’ – 3.3.1). Optionally you may also set the value of this variable at the same time as you declare, example:
Dim limit As Integer = 10 'declare a variable called ‘limit’ and set it to 10
C#
int limit = 10; // declare a variable called 'limit' and set it to 10
Once the variable has been declared it may then be used just like any other programming language.
2.3.1 Types
In VB.NET and C# all variables have a Type and can only store values that are of the same Type (there are exceptions but these are beyond the scope of this tutorial). While there are a large number of types available to use, most of the time you will only be using a handful:
VB.NET
Dim a As Integer 'used for whole numbers between -2147483648 and 2147483647 Dim b As Long 'used for very large whole numbers between +/- ~9.2e18 Dim c As String 'used for holding string values (text) Dim d As Single 'used for holding numbers with decimal point Dim e As Double 'as with single, but with greater precision Dim f As DateTime 'used for holding/processing dates and times, very useful
C#
private int a; // used for whole numbers between -2147483648 and 2147483647 private long b; // used for very large whole numbers between +/- ~9.2e18 private string c; // used for holding string values (text) private float d; // used for holding numbers with decimal point private double e; // as with float, but with greater precision private DateTime f; // used for holding/processing dates and times, very useful
There are also a number of ‘APSIM’ types available for use, one for every APSIM model in fact. These are very useful as they allow the programmer easy access to that model’s output variables as well as events that it subscribes to and publishes.
2.3.2 Arrays
Arrays are data structures used to hold any number of values of the same type that are usually all related, such as the soil water contents of multiple soil layers in the profile. Arrays can be ‘n-dimensional’, that is may consist of any number of dimension, however for the sake of simplicity we will only refer to 1-dimensional arrays here (numerous examples of arrays with more dimensions may be found online). When using arrays (1-D) it can be helpful to think of them as a collection of ‘cells’, similar to a row or column of data in a spreadsheet. Each ‘cell’ can hold a single value and is accessed using the name of the array variable as well as an ‘index’. Arrays are declared and ‘typed’ just like any other kind of variable, several examples of declaration and usage are below, of particular note is the ‘.Length’ property that every array has which is very useful in ‘For’ loops (see 3.5).
2.3.3 Scope
‘Scope’ is a concept that affects the availability of a given variable for use in different parts of code. A variable may be declared within a Class or Sub/Function (see 3.4), the exact location defines the ‘scope’. When a variable is declared within a Class (a ‘global’ variable) it is accessible from any Sub or Function within that class, however when a variable is declared within a Sub or Function it is only accessible inside that Sub or Function. Global variables are usually used for model parameters, inputs and outputs. If it is at all possible to declare a variable inside a Sub or Function instead of globally it is best to do so, having too many global variables can lead to confusion.
2.4 Subs and Functions
Sub is short for ‘subroutine’ and is used to contain a piece of code within a class that performs a specific task. Subs are generally ‘called’ (or ‘run’) from within other Subs; however some of the Subs in your manager script will be made visible to APSIM which will call them as part of the simulation (see section 4) – thus forming an ‘entry point’ (a point where some external program calls your code). Depending on the intended function of the code within the sub you may want to pass some parameters into it when it is called, see the code example in section 3.5 to see how this is done. A ‘Function’ is like a ‘Sub’ except the code within it must return a value before it finishes. All code statements, barring variable declarations and ‘Properties’ (beyond the scope of this tutorial), must be contained within either a ‘Sub’ or a ‘Function’.
2.5 For and While loops
‘For’ and ‘While’ loops can be used for any number of repetitive tasks such as cycling through the elements of an array or the paddocks in a simulation. While technically they may be used interchangeably, ‘For’ loops are usually used for rigidly defined tasks (such as processing every element in an array) whereas ‘While’ loops are usually used for more open-ended tasks, such performing a given action until the required result is achieved. Examples:
VB.NET
'sum an array Function CalcArray(ByVal a As Double()) As Double Dim result As Double = 0 For i As Integer = 0 To a.Length - 1 result += a(i) Next result = Math.Sqrt(result) Return result End Function Function CalcWhile(ByVal a As Double, ByVal max_err As Double) As Double Dim prev_y As Double = Double.MaxValue Dim y As Double = 0 Dim err As Double = max_err + 1 Dim x As Double = 0 'solve y = a / x, increasing x until (y - prev_y) < max_err While err > max_err x += 1 y = a / x err = prev_y - y prev_y = y End While Return x End Function
//sum an array public double CalcArray(double[] a) { double result = 0; for (int i = 0; i <= a.Length - 1; i++) { result += a[i]; } result = Math.Sqrt(result); return result; } public double CalcWhile(double a, double max_err) { double prev_y = double.MaxValue; double y = 0; double err = max_err + 1; double x = 0; //solve y = a / x, increasing x until (y - prev_y) < max_err while (err > max_err) { x += 1; y = a / x; err = prev_y - y; prev_y = y; } return x; }
2.6 If Statements and Select/Case Blocks
‘If’ statements are simple conditional ‘if something is true then do this’ with the option of saying ‘else do something else’ if the given condition is false. It is possible to chain several ‘If/Else’ statements together in order to define different actions depending on the value of a given variable. Sometimes if a lot of ‘If/Else’ statements are chained together for this purpose it is more appropriate to use a ‘Select/Case’ block. Examples (contained in 2 functions to give context):
VB.NET
'”ByVal input As Double” specifies that whatever calls this function must give it a 'double (or ‘real’ number) as a parameter, example (assigning result to ‘x’): 'x = ExampleIf(3.2) Function ExampleIf(ByVal input As Double) As Double 'If If input > 2 Then Return input * 0.5 End If 'If/Else If input < 1 Then Return input * 2 Else Return input End If 'NOTE: Usually you would chain these two 'if' statements together into an 'If/ElseIf/Else' statement End Function Function Calc (ByVal a As Double, ByVal b As Double, ByVal mthd As String) As Double If mthd = "method_A" Then Return a * b ElseIf mthd = "method_B" Then Return ExampleIf(a * b) ElseIf mthd = "method_C" Then Dim x As Double = 2 x = x - a Return x + b / a Else Return 0 End If 'does the same as above Select Case mthd Case "method_A" Return a * b Case "method_B" Return ExampleIf(a * b) Case "method_C" Dim x As Double = 2 x = x - a Return x + b / a Case Else Return 0 End Select End Function
C#
/// <summary> /// "double input" specifies that whatever calls this function must give it a /// double (or ‘real’ number) as a parameter, example (assigning result to ‘x’): /// x = ExampleIf(3.2) /// </summary> /// <param name="input"></param> /// <returns></returns> public double ExampleIf(double input) { if (input > 2) { return input * 0.5; } if (input < 1) { return input * 2; } else { return input; } //NOTE: Usually you would chain these two 'if' statements together into an //If/ElseIf/Else' statement } public double Calc(double a, double b, string mthd) { if (mthd == "method_A") { return a * b; } else if (mthd == "method_B") { return ExampleIf(a * b); } else if (mthd == "method_C") { double x = 2; x = x - a; return x + b / a; } else { return 0; } //does the same as above switch (mthd) { case "method_A": return a * b; case "method_B": return ExampleIf(a * b); case "method_C": double x = 2; x = x - a; return x + b / a; default: return 0; } }
3. Reflection Tags/Attributes – Exposing Variables and Subs to APSIM
While all the concepts introduced so far are important for programming APSIM Models/Manager2 scripts we have not yet covered how you let APSIM know what data you need from the simulation, what events you want to subscribe to or what data and events you want to make available to other models. The way we do this is by inserting ‘reflection tags’ above the variable/sub we want to expose. A reflection tag is simply a word surrounded by ‘<’ ‘>’ characters and there are several different tags that APSIM uses, each with its own meaning. While reading this section it is recommended you refer to section 5.1
3.1 Input
The <Input()> tag denotes that a value for this variable needs to be supplied by APSIM. APSIM will use the name of the variable you tag with ‘Input’ to try and find a corresponding ‘Output’ (3.1.3) from another model. If APSIM cannot find the specified variable, then it will throw a fatal error. This error can be prevented by specifying the Input as optional by setting the ‘IsOptional’ parameter to ‘true’ e.g.
VB.NET
VB.NET <Input(IsOptional:=True)>
C#
[Input(IsOptional = true)]
3.2 Param
The <Param()> tag denotes that the variable is a parameter. APSIM looks for parameters in the XML configuration for this model with the same name as the variable with the <Param()> tag. When the code is part of a script component the parameter values will be on the “Properties” tab. If APSIM cannot find the specified parameter then it will throw a fatal error. As with ‘Input’, this error can be prevented by specifying the Input as optional by setting the ‘IsOptional’ parameter to ‘true’. In addition to ‘IsOptional’ there are two other named parameters that can be used to ‘bound’ the incoming data and another that allows us to use an alias for the expected Param so our internal variable can have a different name. Perhaps the best explanation for this is an example, the following example defines an optional parameter bounded between 1 and 10 that is called ‘Param_1’ in the user interface but ‘x’ in our code:
VB.NET
<Param(IsOptional:=True, MaxVal:=10, MinVal:=1, Name:="Param_1")> Public x As Double
C#
[Param(IsOptional = true, MaxVal = 10, MinVal = 1, Name = "Param_1")]
3.3 Output
The <Output()> tag denotes a variable that APSIM can supply to other APSIM models when requested. Like Param, an alias can be specified for the output if you want to use a different name for your variable e.g.
VB.NET
<Output(Name:="Output1")> Public x As double
C#
[Output(Name = "Output1")] public double x;
3.4 Writable
The Writable tag allows other sub model components to set the value of this property or field. This tag can be used in conjunction with the Output tag. When setting this property from another Manager use the .Set() function. If the type of the property is a more complex type (ApsimType) then use the .SetObject() function.
C#
[Writable, Output] public double amount
When the property is set, it is possible to trigger an action within the Manager component. Give Writable property a setter function as shown below:
[Writable, Output] public double ForageAmount {get {return forageAmount;} set {setAmount(value);}} private double forageAmount; private void setAmount(double forage) { // code in response to the set action }
3.5 Units
The <Units(“”)> tag supplies metadata to APSIM for an <Output()> and should be placed in between the <Output()> tag and the variable declaration. The REPORT module writes the units to the output file. Some examples:
VB.NET
<Units("")>: <Units("mm")> <Units("g")> <Units("kg/ha")>
C#
[Units("")] [Units("mm")] [Units("g")] [Units("kg/ha")]
3.6 EventHandler
The <EventHandler()> tag signals to APSIM that the following ‘Sub’ is an event handler that needs to be called whenever the APSIM event is published. The convention is that all handlers will have an On prefix. The name of the method (minus the On prefix) denotes the APSIM event name that will be trapped. For example to handle the ‘prepare’ event, name your Sub ‘OnPrepare’ and insert an <EventHandler()> tag before it.
3.7 Event
The <[Event]()> tag signals that the following .NET event should be published to the whole APSIM simulation. Other components in APSIM will be able to subscribe to this event. The name of the event will be the name of the event that APSIM sees. Events must be declared using one of the APSIM types. The reason for the square brackets around ‘Event’ is that ‘Event’ is a reserved word in the VB.NET language and the square brackets ‘escape’ it (this does not apply to C#)
3.8 Link
The <Link()> tag tells APSIM to establish a permanent link between the variable tagged and a corresponding model in the simulation. Unlike inputs and parameters, APSIM doesn’t care about the name of the variable and instead looks at its ‘type’ to determine which model it should link into. Because each APSIM model has its own .NET ‘type’ (2.3.1) this task is easy and the resulting variable allows access to all outputs, events and event handlers as well as some inputs of the model. The most common link used is that to the ‘paddock’ in which the model/Manager2 script resides, however you may create a link to any other component that resides at the same level as yours, ie:
VB.NET
<Link()> Dim mypaddock As Paddock <Link()> Dim fert As Fertiliser
C#
[Link()] Paddock mypaddock; [Link()] Fertiliser fert;
The ‘Paddock’ you get from this link is the parent paddock that contains your model or Manager2 script. From there you will be able to access other components at the same level as your component or cycle through sub-paddocks to retrieve the components within them – provided sub paddocks exist within the paddock you are in. If your component is at the ‘Simulation’ level (above all paddocks) APSIM will treat the entire simulation as one ‘Paddock’ and give you that.
VB.NET
For Each subpaddock As Paddock In mypaddock.ChildPaddocks 'do someting with subpaddocks Next For Each crop As Component In mypaddock.Crops 'do something with each crop Next
C#
foreach (Paddock subpaddock in mypaddock.ChildPaddocks) { //do someting with subpaddocks } foreach (Component crop in mypaddock.Crops) { //do something with each crop }
Each paddock also has a Parent method that you can call to get the parent paddock. This is particular useful in multi paddock simulations.
VB.NET
Dim ParentPaddock as Paddock = mypaddock.Parent;
C#
Paddock ParentPaddock = mypaddock.Parent;
Apply fertiliser by getting the component via the paddock
Dim fert As Fertiliser = mypaddock.LinkByType("fertiliser") fert.Apply(50, 10, "NO3_N")
C#
Fertiliser fert = mypaddock.LinkByType("fertiliser"); fert.Apply(50, 10, "NO3_N");
APSIM will look for these models within the current ‘scope’ of the component that is running. Currently, this will be in the current paddock or the parent paddock. Once you have a link or variable that represents another model in the simulation you can then query that model for the values of its variables. You can also set the values of some APSIM variables, i.e.
<Link()> Dim SoilWaterModel As SoilWat '... Dim sw As Single() = SoilWaterModel.sw sw(1) = 0.29 sw(2) = 0.28 '... SoilWaterModel.sw = sw '...
C#
[Link()] SoilWat SoilWaterModel; float[] sw = SoilWaterModel.sw; sw[1] = 0.29; sw[2] = 0.28; SoilWaterModel.sw = sw
4. Hints and Helper Libraries
To retrieve a list of APSIM variables and events, use the “Variables” and “Events” components under the “Output file” in the simulation tree in the APSIM User Interface – pretend as though you are adding a variable to your output file (or changing the reporting frequency) and browse the lists of outputs and events. To investigate what accessible data or functions a variable has, simply type the name of the variable followed by ‘.’ then hit ‘CTRL-SPACE’ to bring up a box with more information. There are two main ‘helper’ libraries available that contain several useful functions (for some examples see section 7)
DateUtility
Contains functions for turning Julian Dates and day/month strings into ‘DateTime’ or ‘Date’ variables – essential for turning user inputs into usable variables. For a list of all functions type ‘DateUtility.’ into the script editor and hit ‘CTRL-SPACE’ to bring up a box with more information. Ensure that the line Imports CSGeneral for VB.NET or using CSGeneral; for C# is included at the top of the script.
ManagerUtility
Contains a function that will take any delimited string (comma, space or tab) and turn it into an array of a given type. Very handy if the user-interface component of your manager script makes use of these strings. Also contains a function that will change the type of an array as well as a new ‘Type’ that can be used to track the last ‘x’ values of a given variable and return the average, sum or raw values. For a list of all functions type ‘ManagerUtility.’ into the script editor and hit ‘CTRL-SPACE’ to bring up a box with more information. Ensure that the line Imports CSGeneral for VB.NET or using CSGeneral; for C# is included at the top of the script.
5. A Basic Shell for a Manager2 Script/APSIM Model
5.1 VB.NET
Imports System Imports ModelFramework Imports CSGeneral Public Class Script <Link()> Dim MyPaddock As Paddock <Param> Private A As String ' The value for this will come from the Properties page. <Output> Public B As Double ' An example of how to make a variable available to other APSIM modules <Input> Dim Today As DateTime ' Equates to the value of the current simulation date - value comes from CLOCK <EventHandler()> Public Sub OnInitialised() ' INITIALISATION ' delete this Sub if not required End Sub <EventHandler()> Public Sub OnPrepare() ' START OF DAY ' delete this Sub if not required End Sub <EventHandler()> Public Sub OnProcess() ' MAIN DAILY PROCESSING STEP ' delete this Sub if not required End Sub <EventHandler()> Public Sub OnPost() ' END OF DAY ' delete this Sub if not required End Sub End Class
5.2 C#
using System; using ModelFramework; using CSGeneral; public class Script { [Link] Paddock MyPaddock; [Input] DateTime Today; // Equates to the value of the current simulation date - value comes from CLOCK [Param] string A; // The value for this will come from the Properties page. [Output] double B; // An example of how to make a variable available to other APSIM modules [EventHandler] public void OnInitialised() { // INITIALISATION // can delete if not required } [EventHandler] public void OnPrepare() { // START OF DAY // can delete if not required } [EventHandler] public void OnProcess() { // MAIN DAILY PROCESSING STEP // can delete if not required } [EventHandler] public void OnPost() { // END OF DAY // can delete if not required } }
6. Conversions from Original Manager in ‘Continuous Wheat’ Simulation
Note that the ‘Imports’ and ‘using’s have been removed from the top of the VB.NET and C# script examples to preserve space. They are the same as the ‘Imports’ and ‘using’s in section 5.
6.1 Sowing Rule
Original Code – Script Name = Sow on Date
if (paddock_is_fallow() = 1 and FallowIn <> 'yes' and (NextCrop = 0 or NextCrop = '[crop]')) then if (date_within('[date1], [date2]') = 1) then if (rain[[rainnumdays]] >= [raincrit] AND esw >= [esw_amount]) OR ('[must_sow]' = 'yes' AND today = date('[date2]'))) THEN ChooseNextCrop = 'yes' ! for rotations [crop] sow plants =[density], sowing_depth = [depth], cultivar = [cultivar], row_spacing = [row_spacing], crop_class = [class] endif if today = date('[date2]') then ChooseNextCrop = 'yes' endif endif endif
VB.NET
Public Class SowingRule <Param()> Private date1 As String <Param()> Private date2 As String <Param()> Private must_sow As String <Param()> Private raincrit As Double <Param()> Private rainnumdays As Integer <Param()> Private esw_amount As Double <Param()> Private crop As String <Param()> <Output()> Private density As Double <Param()> Private depth As Double <Param()> Private cultivar As String <Param()> Private [class] As String <Param()> Private row_spacing As Double <Link()> Private mypaddock As Paddock <Input()> Private Today As System.DateTime <Input()> Private rain As Double <Input()> Private esw As Double <Output()> Private ChooseNextCrop As String = "no" Private rain_tracker As ManagerUtility.Tracker(Of Double) Private default_sowtype As SowType <EventHandler()> _ Public Sub OnInitialised() rain_tracker = New ManagerUtility.Tracker(Of Double)(rainnumdays) End Sub <EventHandler()> _ Public Sub OnPost() rain_tracker.Add(rain) If ManagerUtility.PaddockIsFallow(mypaddock) Then If DateUtility.WithinDates(date1, Today, date2) Then If rain_tracker.Sum() > raincrit And esw > esw_amount Or must_sow = "yes" And DateUtility.DatesEqual(date2, Today) Then ChooseNextCrop = "yes" Dim data As New SowType() data.plants = density data.sowing_depth = depth data.Cultivar = cultivar data.row_spacing = row_spacing data.crop_class = [class] mypaddock.LinkByName(crop).Publish("sow", data) End If If DateUtility.DatesEqual(date2, Today) Then ChooseNextCrop = "yes" End If End If End If End Sub End Class
C#
public class SowingRule { [Param()] private string date1; [Param()] private string date2; [Param()] private string must_sow; [Param()] private double raincrit; [Param()] private int rainnumdays; [Param()] private double esw_amount; [Param()] private string crop; [Param()] [Output()] private double density; [Param()] private double depth; [Param()] private string cultivar; [Param()] private string @class; [Param()] private double row_spacing; [Link()] private Paddock mypaddock; [Input()] private System.DateTime Today; [Input()] private double rain; [Input()] private double esw; [Output()] private string ChooseNextCrop = "no"; private ManagerUtility.Tracker<double> rain_tracker; private SowType default_sowtype; [EventHandler()] public void OnInitialised() { rain_tracker = new ManagerUtility.Tracker<double>(rainnumdays); } [EventHandler()] public void OnPost() { rain_tracker.Add(rain); if (ManagerUtility.PaddockIsFallow(mypaddock)) { if (DateUtility.WithinDates(date1, Today, date2)) { if (((rain_tracker.Sum() > raincrit) && (esw > esw_amount)) || (must_sow == "yes" && DateUtility.DatesEqual(date2, Today))) { ChooseNextCrop = "yes"; SowType data = new SowType(); data.plants = density; data.sowing_depth = depth; data.Cultivar = cultivar; data.row_spacing = row_spacing; data.crop_class = @class; mypaddock.LinkByName(crop).Publish("sow", data); } if (DateUtility.DatesEqual(date2, Today)) { ChooseNextCrop = "yes"; } } } } }
6.2 Sowing Fertiliser
Original Code – Script Name = Fertilise on Event
[fertmodule] apply amount = [fert_amount_sow] (kg/ha), depth = 50 (mm), type = [fert_type_sow]
VB.NET
Public Class Script <Param()> Private modulename As String <Param()> Private eventname As String <Param()> Private fertmodule As String <Param()> Private fert_amount_sow As Single <Param()> Private fert_type_sow As String <Link()> Private mypaddock As Paddock Private fert As Fertiliser <EventHandler()> _ Private Sub OnInitialised() mypaddock.LinkByName(modulename).Subscribe(eventname, AddressOf ApplyFertEventHandler) 'grab a reference To the fertiliser Module specified so we can use it later fert = DirectCast(mypaddock.LinkByName(fertmodule), Fertiliser) End Sub Private Sub ApplyFertEventHandler() Dim fert_type As New FertiliserApplicationType() fert_type.Amount = fert_amount_sow fert_type.Depth = 50 fert_type.Type = fert_type_sow fert.Apply(fert_type) End Sub End Class
C#
public class Script { [Param()] private string modulename; [Param()] private string eventname; [Param()] private string fertmodule; [Param()] private float fert_amount_sow; [Param()] private string fert_type_sow; [Link()] private Paddock mypaddock; private Fertiliser fert; [EventHandler()] private void OnInitialised() { mypaddock.LinkByName(modulename).Subscribe(eventname, ApplyFertEventHandler); //grab a reference To the fertiliser Module specified so we can use it later fert = (Fertiliser)mypaddock.LinkByName(fertmodule); } private void ApplyFertEventHandler() { FertiliserApplicationType fert_type = new FertiliserApplicationType(); fert_type.Amount = fert_amount_sow; fert_type.Depth = 50; fert_type.Type = fert_type_sow; fert.Apply(fert_type); } }
6.3 Harvesting rule
Original Code – Script Name = Harvesting rule
if [crop].StageName = 'harvest_ripe' or [crop].plant_status = 'dead' then [crop] harvest [crop] end_crop endif
VB.NET
Public Class Script <Param()> Private crop As String <Input(IsOptional:=True)> Private density As Double = 100 <Link()> Private mypaddock As Paddock Private mycrop As Wheat <EventHandler()> Private Sub OnInit2() mycrop = DirectCast(mypaddock.LinkByName(crop), Wheat) End Sub <EventHandler()> Private Sub OnPost() If mycrop.Variable("StageName").ToString() = "harvest_ripe" OrElse mycrop.Variable("plant_status").ToString() = "dead" Then Dim ht As New HarvestType() ht.Height = 50 ht.Plants = density ht.Remove = 0 ht.Report = "yes" mycrop.Harvest(ht) mycrop.EndCrop() End If End Sub End Class
C#
public class Script { [Param()] private string crop; [Input(IsOptional = true)] private double density = 100; [Link()] private Paddock mypaddock; private Wheat mycrop; [EventHandler()] private void OnInit2() { mycrop = (Wheat)mypaddock.LinkByName(crop); } [EventHandler()] private void OnPost() { if (mycrop.Variable("StageName").ToString() == "harvest_ripe" || mycrop.Variable("plant_status").ToString() == "dead") { HarvestType ht = new HarvestType(); ht.Height = 50; ht.Plants = density; ht.Remove = 0; ht.Report = "yes"; mycrop.Harvest(ht); mycrop.EndCrop(); } } }