Recipes in FactoryTalk Optix provide a structured way to store and manage sets of parameters that can be loaded into your application. They are particularly useful for manufacturing processes where different product variants require different machine settings.
Warning
This section is a work in progress and might be updated with more examples and explanations in the future. Please report any issues or suggestions to the FactoryTalk Optix support or community forums.
Note
The legacy recipes engine in FactoryTalk Optix allows a maximum of 2048 ingredients per recipe. This limit is imposed by the underlying database system and cannot be changed.
Note
The content of this page can only be applied to the legacy recipe module of FactoryTalk Optix. The reference guide for the new recipe module can be accessed here.
The recipe system in FactoryTalk Optix consists of three main components:
- Recipe Schema: Defines what variables are included in a recipe
- Recipe Store: The database where recipe data is stored
- Recipe Controller: Manages loading, saving, and applying recipes
Note
Recipes work by mapping values between a database table and variables in your project. The schema defines this mapping.
A recipe schema links variables in your project model to columns in a database table. When recipes are loaded or saved, values are transferred between these locations.
- Add a Schema to the Recipes folder
- Set the target node (typically the Model folder)
- Set the store where recipes will be saved
- Use the UI to select variables to include in the schema
Recipe schemas can be modified at runtime using NetLogic. This is useful when you need to dynamically change which variables are included in recipes.
[ExportMethod]
public void UpdateRecipeSchema()
{
// Get the recipe schema from the project
var legacyRecipeSchema = Project.Current.Get<FTOptix.Recipe.RecipeSchema>("Recipes/MyRecipeSchema");
// Get the EditModel and Root objects
var editModel = legacyRecipeSchema.GetObject("EditModel");
var root = legacyRecipeSchema.GetObject("Root");
// Clear existing schema
if (editModel != null)
editModel.Children.ToList().ForEach(child => child.Delete());
if (root != null)
root.Children.ToList().ForEach(child => child.Delete());
// Add variables to the schema
var targetNode = InformationModel.Get(legacyRecipeSchema.TargetNode);
var speedVar = targetNode.GetVariable("MotorSpeed");
if (speedVar != null)
{
// Add to EditModel and Root
var editVar = InformationModel.MakeVariable("MotorSpeed", speedVar.DataType);
var rootVar = InformationModel.MakeVariable("MotorSpeed", speedVar.DataType);
editModel.Add(editVar);
root.Add(rootVar);
// Update database columns
var store = InformationModel.Get<Store>(legacyRecipeSchema.Store);
var table = store.Tables.Get(legacyRecipeSchema.BrowseName);
if (table != null)
{
var column = InformationModel.Make<StoreColumn>("MotorSpeed");
column.DataType = speedVar.DataType;
table.Columns.Add(column);
}
}
}Variables in nested folders or objects require special handling. The code below demonstrates how to add variables from various locations in your project model:
[ExportMethod]
public void AddVariablesToRecipeSchema()
{
// Get the recipe schema
var legacyRecipeSchema = Project.Current.Get<FTOptix.Recipe.RecipeSchema>("Recipes/MyRecipeSchema");
if (legacyRecipeSchema == null)
{
Log.Error("Recipe schema not found");
return;
}
// Get or create EditModel and Root
var editModel = legacyRecipeSchema.GetObject("EditModel") ?? InformationModel.MakeObject("EditModel");
var root = legacyRecipeSchema.GetObject("Root") ?? InformationModel.MakeObject("Root");
if (legacyRecipeSchema.GetObject("EditModel") == null)
legacyRecipeSchema.Add(editModel);
if (legacyRecipeSchema.GetObject("Root") == null)
legacyRecipeSchema.Add(root);
// Get the database table
var store = InformationModel.Get<Store>(legacyRecipeSchema.Store);
var table = store.Tables.Get(legacyRecipeSchema.BrowseName);
// Variables to add
var variablePaths = new List<string>
{
"Settings/Temperature",
"Settings/Speed",
"Settings/BatchSize"
};
// Add each variable to the schema
var targetNode = InformationModel.Get(legacyRecipeSchema.TargetNode);
foreach (var path in variablePaths)
{
AddVariableToSchema(targetNode, path, editModel, root, table);
}
}
private void AddVariableToSchema(IUANode targetNode, string path, IUAObject editModel,
IUAObject root, Table table)
{
// Get the variable
var variable = targetNode.GetVariable(path);
if (variable == null)
{
Log.Error($"Variable {path} not found");
return;
}
// Create path segments
var segments = path.Split('/');
var varName = segments.Last();
var folders = segments.Take(segments.Length - 1).ToArray();
// Create folders in EditModel and Root if needed
var currentEditFolder = editModel;
var currentRootFolder = root;
foreach (var folder in folders)
{
var editChildFolder = currentEditFolder.GetObject(folder);
if (editChildFolder == null)
{
editChildFolder = InformationModel.MakeObject(folder);
currentEditFolder.Add(editChildFolder);
}
currentEditFolder = editChildFolder;
var rootChildFolder = currentRootFolder.GetObject(folder);
if (rootChildFolder == null)
{
rootChildFolder = InformationModel.MakeObject(folder);
currentRootFolder.Add(rootChildFolder);
}
currentRootFolder = rootChildFolder;
}
// Add variables to the folders
var editVar = InformationModel.MakeVariable(varName, variable.DataType);
var rootVar = InformationModel.MakeVariable(varName, variable.DataType);
currentEditFolder.Add(editVar);
currentRootFolder.Add(rootVar);
// Add column to the table
if (table != null)
{
var columnName = string.Join("/", segments);
var column = InformationModel.Make<StoreColumn>(columnName);
column.DataType = variable.DataType;
table.Columns.Add(column);
}
}The following example demonstrates how to populate a recipe schema with variables from different locations within your project model:
[ExportMethod]
public void PopulateRecipeSchema()
{
// List of tags with relative path from the root node to be added to the FTOptix.Recipe.RecipeSchema
var tagsToAdd = new List<string>
{
"Folder1/Variable3", // Variable in a folder
"Variable2", // Variable in the Model
"Object3/Object1/Variable2", // Variable in a nested object
"Object2/Variable1", // Variable in an object
"Object1" // Entire object (may contain other objects or variables)
};
//get the FTOptix.Recipe.RecipeSchema object
var legacyRecipeSchema = Project.Current.Get<FTOptix.Recipe.RecipeSchema>("Recipes/LegacyRecipeSchema1");
// Check if the FTOptix.Recipe.RecipeSchema exists and if the Store and TargetNode are set
if (legacyRecipeSchema == null ||
legacyRecipeSchema.Store == NodeId.Empty ||
legacyRecipeSchema.TargetNode == NodeId.Empty)
{
Log.Error("Invalid recipe schema configuration");
return;
}
// Get the root node of the Information Model
var rootNode = InformationModel.Get(legacyRecipeSchema.TargetNode);
// Process each tag
foreach (string path in tagsToAdd)
{
var node = rootNode.Get(path);
if (node == null)
{
Log.Error($"Node '{path}' not found");
continue;
}
if (node is IUAObject obj)
{
// Process all variables in the object
foreach (var childVar in obj.FindNodesByType<IUAVariable>())
AddNodeToSchema(childVar, legacyRecipeSchema);
}
else
{
// Process single variable
AddNodeToSchema(node, legacyRecipeSchema);
}
}
}/// <summary>
/// Add variables and corresponding store columns to an existing FTOptix.Recipe.RecipeSchema.
/// Inputs: two model variables present in the project (Model/Variable2, Model/Variable3).
/// This is a DesignTime helper typically invoked from a DesignTime NetLogic or exported method.
/// </summary>
[ExportMethod]
public void ModifyRecipeSchema()
{
// Get variables from the model
IUAVariable var2 = Project.Current.GetVariable("Model/Variable2");
IUAVariable var3 = Project.Current.GetVariable("Model/Variable3");
// Get the recipe schema to modify
FTOptix.Recipe.RecipeSchema legacyRecipeSchema = Project.Current.Get<FTOptix.Recipe.RecipeSchema>("Recipes/LegacyRecipeSchema1");
// Add variables to the EditModel (UI edit form)
IUAObject editModelObj = legacyRecipeSchema.GetObject("EditModel");
IUAVariable var2ToAddEditModel = InformationModel.MakeVariable(var2.BrowseName, var2.DataType);
IUAVariable var3ToAddEditModel = InformationModel.MakeVariable(var3.BrowseName, var3.DataType);
editModelObj.Add(var2ToAddEditModel);
editModelObj.Add(var3ToAddEditModel);
// Add variables to the Root (actual storage mapping)
IUAObject rootObj = legacyRecipeSchema.GetObject("Root");
IUAVariable var2ToAddRoot = InformationModel.MakeVariable(var2.BrowseName, var2.DataType);
IUAVariable var3ToAddRoot = InformationModel.MakeVariable(var3.BrowseName, var3.DataType);
rootObj.Add(var2ToAddRoot);
rootObj.Add(var3ToAddRoot);
// Create corresponding columns in the store table
var DB_Table_Columns = Project.Current.Get("DataStores/EmbeddedDatabase1/Tables/LegacyRecipeSchema1/Columns");
var TableColumn_Var2 = InformationModel.Make<StoreColumn>("/" + var2.BrowseName);
TableColumn_Var2.DataType = var2.DataType;
var TableColumn_Var3 = InformationModel.Make<StoreColumn>("/" + var3.BrowseName);
TableColumn_Var3.DataType = var3.DataType;
DB_Table_Columns.Add(TableColumn_Var2);
DB_Table_Columns.Add(TableColumn_Var3);
}/// <summary>
/// Helper methods to clear the EditModel of a recipe schema by resetting variables to default values.
/// Use with caution: this will set variables to empty/zero values depending on type.
/// </summary>
private void ClearEditModel(IUANode obj)
{
foreach (var item in obj.Children)
{
switch (item.NodeClass)
{
case NodeClass.Object:
// Recurse into nested objects
ClearEditModel(item);
break;
case NodeClass.Variable:
// Reset the variable value based on its current type
ResetVariableValue(item as IUAVariable);
break;
default:
break;
}
}
}
private void ResetVariableValue(IUAVariable iUAVariable)
{
var val = iUAVariable.Value.Value;
switch (val)
{
case string:
iUAVariable.Value = string.Empty;
break;
case int:
case short:
case long:
case float:
iUAVariable.Value = 0;
break;
case bool:
iUAVariable.Value = false;
break;
default:
iUAVariable.Value = 0;
break;
}
}Warning
When adding entire objects to a recipe schema, all variables within that object (including those in nested objects) will be included. Be careful with large object hierarchies as they might significantly increase the size of your recipe database.
The following examples demonstrate common recipe operations you might need to perform in your application.
Note
Please see the LegacyRecipesController.cs file in the Scripts library of the FactoryTalk Optix IDE to see how to load, apply, create or update recipes at runtime.
Note
Please use the code in the LegacyRecipesController.cs file in the Scripts library of the FactoryTalk Optix IDE to import and export recipes to CSV format.
[ExportMethod]
public bool RecipeExists(string recipeName)
{
var legacyRecipeSchema = Project.Current.Get<FTOptix.Recipe.RecipeSchema>("Recipes/LegacyRecipeSchema1");
if (legacyRecipeSchema == null || string.IsNullOrEmpty(recipeName))
return false;
var store = InformationModel.Get<Store>(legacyRecipeSchema.Store);
if (store == null)
return false;
var tableName = string.IsNullOrEmpty(legacyRecipeSchema.TableName) ? legacyRecipeSchema.BrowseName : legacyRecipeSchema.TableName;
// Query the database to check if the recipe exists
object[,] resultSet;
string[] header;
string query = $"SELECT * FROM \"{tableName}\" WHERE Name LIKE '{recipeName.Replace("'", "''")}'";
store.Query(query, out header, out resultSet);
return resultSet != null && resultSet.GetLength(0) > 0;
}