Programmatic Model Generation
Navigation: User Guide ➔ COM Automation ➔ Example Python Files ➔ Example - Programmatic Model Generation
Overview
SysCAD's COM interface enables advanced automation capabilities, including the ability to programmatically create models and generate graphics. However, this functionality is experimental, undocumented, and unsupported, so it should be used with caution. Future versions of SysCAD may modify, enhance, or remove this feature without notice.
The following four examples, each with varying levels of complexity, illustrate how to:
- Create a new SysCAD project via COM.
- Add unit models and links to the project.
- This step adds the mathematical models to the project. Note: it does not include graphical representations—these must be added separately.
- AddUnit Parameters: class_name, tag_name
- AddLink Parameters: link class, link tag, source unit, source connection, destination unit, destination connection
- Generate associated graphics to the project.
- This step adds visual elements corresponding to each unit model and pipe.
- Parameters: unit tag, X, Y, Z, symbol name, X-scale, Y-scale, rotation
Examples
import syscadif # Import SysCAD COM class
sc = syscadif.SysCADCom() # Launch a new instance of the SysCAD application using COM
ScdDir = 'C:\\SysCAD%d' % sc.build ## Installation folder, change if using a different location
root = ScdDir+'\\Examples\\25 Gold\\' ## Define the root directory for the SysCAD example project
# Create a new SysCAD project: sc.SysCAD.CreateProject(cfg, prj)
Prj = sc.SysCAD.CreateProject(root + 'CfgFiles\\Gold Example.cfg', root + "NewProject.spf\\")
# Access the Solver's Nodes interface to manipulate unit operations and links
Nodes = Prj.Solver.Nodes
# Add unit operations
Nodes.AddUnit("FeederSink", "OreFeed")
Nodes.AddUnit("Tank-1", "StockPile_001")
Nodes.AddUnit("FeederSink", "Product")
# Add links (Pipe-1) to connect up the units
# Parameters: link class, link tag, source unit, source connection, destination unit, destination connection
Nodes.AddLink("Pipe-1", "P_001", "OreFeed", "Src", "StockPile_001", "Top")
Nodes.AddLink("Pipe-1", "P_002", "StockPile_001", "Base", "Product", "Snk")
# Access the graphics page named "05_Flowsheet" to add visual elements
Gx = Prj.Graphics.Page("05_Flowsheet")
y_pos = 150.0
# Add graphical symbols for Unit Models
# Parameters: unit tag, X, Y, Z, symbol name, X-scale, Y-scale, rotation
Gx.AddUnit("OreFeed", 20, y_pos, 0, "*Default*", 1, 1, 0)
Gx.AddUnit("StockPile_001", 210, y_pos, 0, "Stockpile02_Shading", 1, 1, 0)
Gx.AddUnit("Product", 400, y_pos, 0, "*Default*", 1, 1, 0)
# Add a graphical link between the two units
# Parameters: link tag, list of XYZ coordinates (Z = 0 for 2D)
Gx.AddLinkA("P_001", [34., y_pos, 0., 195., y_pos, 0.])
Gx.AddLinkA("P_002", [225., y_pos, 0., 385., y_pos, 0.])
# Define a cleanup function to delete all COM objects and free resources
def clean():
global Gx, Nodes, Prj, sc
print("Cleaning up COM objects...")
del Gx, Nodes, Prj, sc
input('Press Enter to finish...')
sc.Close()
clean()
|
- This example shows how to add a basic flowsheet with a few units and links.
import syscadif
# === CONFIGURATION ===
CONFIG = {
"num_inputs": 3,
"num_outputs": 1,
"num_intermediates": 6,
"unitlinktype": 2,
"x_start": 20.0,
"x_Max": 420 - 20.0 * 2,
"y_start": 10.0,
"y_Max": 300,
"x_offset": 5,
"y_offset": 50.0,
"y_int_offset": 22.0,
"x_int_offset": 16.0,
"input_symbol": "*Default*",
"intermediate_symbol1": "Agitated",
"intermediate_symbol2": "CIL_Tank",
"output_symbol": "*Default*"
}
# === HELPER FUNCTIONS ===
def round_to_multiple(value, multiple):
return round(value / multiple) * multiple
def add_unit(tag, unit_type, x, y, symbol, x_scale=1, y_scale=1):
Nodes.AddUnit(unit_type, tag)
Gx.AddUnit(tag, x, y, 0, symbol, x_scale, y_scale, 0)
unit_tags.append((tag, x, y))
def add_link(tag, src_tag, src_port, dst_tag, dst_port, points):
Nodes.AddLink("Pipe-1", tag, src_tag, src_port, dst_tag, dst_port)
Gx.AddLinkA(tag, points)
def calculate_positions():
raw_spacing = CONFIG["x_Max"] / (CONFIG["num_intermediates"] + 1)
x_spacing = max(10, round_to_multiple(raw_spacing, 5))
y_spacing = round_to_multiple(15.0, 5)
x_center = (CONFIG["x_Max"] - CONFIG["x_start"]) / 2 + CONFIG["x_start"]
y_center = (CONFIG["y_Max"] - CONFIG["y_start"]) / 2 + CONFIG["y_start"]
return x_spacing, y_spacing, x_center, y_center
# === INITIALIZE SYSCAD ===
print("Launching SysCAD and creating project...")
sc = syscadif.SysCADCom()
ScdDir = f'C:\\SysCAD{sc.build}_37043'
root = ScdDir + '\\Examples\\25 Gold\\'
Prj = sc.SysCAD.CreateProject(root + 'CfgFiles\\Gold Example.cfg', root + "NewProject.spf\\")
Nodes = Prj.Solver.Nodes
Gx = Prj.Graphics.Page("05_Flowsheet")
unit_tags = []
x_spacing, y_spacing, x_center, y_center = calculate_positions()
intermediate_y = y_center
intermediate_x_start = x_center - ((CONFIG["num_intermediates"] - 1) * x_spacing) / 2 + CONFIG["x_offset"]
# === ADD INTERMEDIATES ===
for i in range(CONFIG["num_intermediates"]):
tag = f"Reactor_{i+1:03}"
x = intermediate_x_start + i * x_spacing
symbol = CONFIG["intermediate_symbol1"] if i < 2 else CONFIG["intermediate_symbol2"]
x_scale = 2 if i < 2 else 1.5
y_scale = 2
add_unit(tag, "Tank-1", x, intermediate_y, symbol, x_scale, y_scale)
# === ADD INPUTS ===
for i in range(CONFIG["num_inputs"]):
tag = f"Input_{i+1:03}"
x = CONFIG["x_start"]
y = intermediate_y + CONFIG["y_offset"] + i * y_spacing
add_unit(tag, "FeederSink", x, y, CONFIG["input_symbol"])
link_tag = f"P_{i+1:03}"
add_link(link_tag, tag, "Src", "Reactor_001", "Top", [
x + 14, y, 0,
intermediate_x_start - CONFIG["x_offset"], y, 0,
intermediate_x_start - CONFIG["x_offset"], intermediate_y + CONFIG["y_int_offset"]/1.5, 0
])
# === ADD OUTPUTS ===
output_x = intermediate_x_start + (CONFIG["num_intermediates"] - 1) * x_spacing + x_spacing
if output_x > CONFIG["x_start"] + CONFIG["x_Max"]:
raise ValueError(f"Output X position ({output_x}) exceeds maximum allowed ({CONFIG['x_start'] + CONFIG['x_Max']}).")
for i in range(CONFIG["num_outputs"]):
tag = f"Output_{i+1:03}"
x = output_x
y = intermediate_y - CONFIG["y_offset"] - i * y_spacing
add_unit(tag, "FeederSink", x, y, CONFIG["output_symbol"])
link_tag = f"P_{CONFIG['num_inputs'] + CONFIG['num_intermediates'] + i + 1:03}"
add_link(link_tag, f"Reactor_{CONFIG['num_intermediates']:03}", "Base", tag, "Snk", [
x - x_spacing + CONFIG["x_offset"]*2, intermediate_y - CONFIG["y_int_offset"], 0,
x - x_spacing + CONFIG["x_offset"]*2, y, 0,
x - CONFIG["x_int_offset"] + CONFIG["x_offset"]/2, y, 0
])
# === CONNECT INTERMEDIATES ===
for i in range(CONFIG["num_intermediates"] - 1):
src_tag = f"Reactor_{i+1:03}"
dst_tag = f"Reactor_{i+2:03}"
link_tag = f"P_{CONFIG['num_inputs'] + i + 1:03}"
x_src = intermediate_x_start + CONFIG["x_int_offset"] + 1 + i * x_spacing
x_dst = intermediate_x_start - CONFIG["x_int_offset"] + 1 + CONFIG["x_offset"]/2 + (i + 1) * x_spacing
y = intermediate_y
if CONFIG["unitlinktype"] == 1:
points = [
x_src, y - CONFIG["y_int_offset"], 0,
x_src, y - CONFIG["y_int_offset"]*2, 0,
(x_src + x_dst)/2, y - CONFIG["y_int_offset"]*2, 0,
(x_src + x_dst)/2, y + CONFIG["y_int_offset"]*2, 0,
x_dst, y + CONFIG["y_int_offset"]*2, 0,
x_dst, y + CONFIG["y_int_offset"], 0
]
else:
points = [x_src, y, 0, x_dst, y, 0]
add_link(link_tag, src_tag, "Base", dst_tag, "Top", points)
print(f"Added {CONFIG['num_inputs']} input, {CONFIG['num_intermediates']} units and {CONFIG['num_outputs']} outputs")
# === CLEANUP ===
def clean():
global Gx, Nodes, Prj, sc
print("Cleaning up COM objects...")
del Gx, Nodes, Prj, sc
input('Press Enter to finish...')
sc.Close()
clean()
|
- This example automates part of the Gold flowsheet by generating the required number of Leach and Adsorption tanks in series. The code can be easily adapted to create other sequential flowsheets.
import syscadif
# === CONFIGURATION ===
num_inputs = 20 # Number of input units
print("Launching SysCAD and creating project...")
sc = syscadif.SysCADCom()
ScdDir = 'C:\\SysCAD%d' % sc.build
root = ScdDir + '\\Examples\\25 Gold\\'
Prj = sc.SysCAD.CreateProject(root + 'CfgFiles\\Gold Example.cfg', root + "NewProject.spf\\")
Nodes = Prj.Solver.Nodes
# Add initial units
Nodes.AddUnit("FeederSink", "ProductSink")
Nodes.AddUnit("Tie-1", "X_001")
print("Added base units: ProductSink and X_001")
# Add input units and corresponding pipes (reverse order)
input_links = [] # Store link data for deferred creation
for i in reversed(range(1, num_inputs + 1)):
unit_tag = f"Input{i:02d}" # Input01 to Input20
pipe_tag = f"P_{i:03d}" # P_001 to P_020
Nodes.AddUnit("FeederSink", unit_tag)
Nodes.AddLink("Pipe-1", pipe_tag, unit_tag, "Src", "X_001", "In")
input_links.append((pipe_tag, unit_tag)) # Store for graphics
print(f"Added {num_inputs} input units and corresponding links to X_001")
# Add output pipe from X_001 to ProductSink
print("Adding link: P_021 from X_001 to ProductSink")
Nodes.AddLink("Pipe-1", "P_021", "X_001", "Out", "ProductSink", "Snk")
# Graphics
print("Accessing graphics page: 05_Flowsheet")
Gx = Prj.Graphics.Page("05_Flowsheet")
# Centered X position
x_center = 200.0
# Determine spacing
if num_inputs <= 10:
y_spacing = 20.0
x_scale = 1
y_start = 20.0
else:
y_spacing = (300.0 - 0.0) / num_inputs
x_scale = 1.55
y_start = 5.0
print(f"Y spacing: {y_spacing}")
y_positions = []
# Add ProductSink and X_001 units
Gx.AddUnit("ProductSink", x_center + 180, 150, 0, "*Default*", 1, 1, 0)
Gx.AddUnit("X_001", x_center, 150 - y_start, 0, "TitleBox01", x_scale, 0.1, 90)
print("Added graphical units: ProductSink and X_001")
# Add graphical output pipe
print("Adding graphical link: P_021 from X_001 to ProductSink")
Gx.AddLinkA("P_021", [
x_center + 1., 150., 0., # Right side of X_001
x_center + 165., 150., 0. # Left side of ProductSink
])
# Add input units and store y positions
for i, (pipe_tag, unit_tag) in enumerate(input_links):
y_pos = y_start + i * y_spacing
y_positions.append(y_pos)
Gx.AddUnit(unit_tag, x_center - 180, y_pos, 0, "*Default*", 1, 1, 0)
# Add input pipes using stored y positions
for i, (pipe_tag, unit_tag) in enumerate(input_links):
y_pos = y_positions[i]
Gx.AddLinkA(pipe_tag, [
x_center - 166., y_pos, 0., # Output of input unit
x_center - 1., y_pos, 0. # Left side of X_001
])
# time.sleep(0.1) # Optional delay
print(f"Added {num_inputs} graphical input units and pipes")
# === CLEANUP ===
def clean():
global Gx, Nodes, Prj, sc
print("Cleaning up COM objects...")
del Gx, Nodes, Prj, sc
input('Press Enter to finish...')
sc.Close()
clean()
|
- This example is suitable for consolidating multiple streams into a single unit, such as combining all process water return flows.
import syscadif
# === CONFIGURATION ===
num_outputs = 20 # Change this value to set number of output units
print("Launching SysCAD and creating project...")
sc = syscadif.SysCADCom()
ScdDir = 'C:\\SysCAD%d' % sc.build
root = ScdDir + '\\Examples\\25 Gold\\'
Prj = sc.SysCAD.CreateProject(root + 'CfgFiles\\Gold Example.cfg', root + "NewProject.spf\\")
Nodes = Prj.Solver.Nodes
# Add initial units
Nodes.AddUnit("FeederSink", "OreFeed")
Nodes.AddUnit("Tie-1", "X_001")
print("Added base units: OreFeed and X_001")
# Add product units and corresponding output pipes (reverse order)
output_links = [] # Store link data for deferred creation
for i in reversed(range(1, num_outputs + 1)):
unit_tag = f"Output{i:02d}" # Output01 to OutputXX
pipe_tag = f"P_{i+1:03d}" # P_002 to P_0XX
Nodes.AddUnit("FeederSink", unit_tag)
Nodes.AddLink("Pipe-1", pipe_tag, "X_001", "Out", unit_tag, "Snk")
output_links.append((pipe_tag, unit_tag)) # Store for graphics
print(f"Added {num_outputs} output units and corresponding links from X_001")
# Add input pipe from OreFeed to X_001
print("Adding link: P_001 from OreFeed to X_001")
Nodes.AddLink("Pipe-1", "P_001", "OreFeed", "Src", "X_001", "In")
# Graphics
print("Accessing graphics page: 05_Flowsheet")
Gx = Prj.Graphics.Page("05_Flowsheet")
# Centered X position
x_center = 200.0
# Store y positions for output units
if num_outputs<=10:
y_spacing = 20.0
x_scale =1
y_start = 20.0
else:
y_spacing = (300.0 - 0.0)/num_outputs
x_scale =1.55
y_start = 5.0
print({y_spacing})
y_positions = []
# Add OreFeed and X_001 units
Gx.AddUnit("OreFeed", x_center - 180, 150, 0, "*Default*", 1, 1, 0)
Gx.AddUnit("X_001", x_center, 150-y_start, 0, "TitleBox01", x_scale, 0.1, 90)
print("Added graphical units: OreFeed and X_001")
# Add graphical input pipe
print("Adding graphical link: P_001 from OreFeed to X_001")
Gx.AddLinkA("P_001", [
x_center - 166., 150., 0., # OreFeed output
x_center - 1., 150., 0. # Center of X_001
])
# Add product units and store y positions
for i, (pipe_tag, unit_tag) in enumerate(output_links):
y_pos = y_start + i * y_spacing
y_positions.append(y_pos)
Gx.AddUnit(unit_tag, x_center + 180, y_pos, 0, "*Default*", 1, 1, 0)
# Add output pipes using stored y positions
for i, (pipe_tag, unit_tag) in enumerate(output_links):
y_pos = y_positions[i]
Gx.AddLinkA(pipe_tag, [
x_center + 1., y_pos, 0., # Right side of X_001
x_center + 165., y_pos, 0. # Left side of output unit
])
# time.sleep(0.1) # Optional delay
print(f"Added {num_outputs} graphical output units and pipes")
# === CLEANUP ===
def clean():
global Gx, Nodes, Prj, sc
print("Cleaning up COM objects...")
del Gx, Nodes, Prj, sc
input('Press Enter to finish...')
sc.Close()
clean()
|
- This example is suitable for distributing flow to multiple units, such as supplying process water to various operational areas.
The following images showcase flowsheets generated using the examples described above.
NOTE: If you're satisfied with the resulting flowsheet, you’ll need to manually save the project or modify the script to include save functionality.
- The first image shows unit models being added to the project.
- The second image shows how the script can be modified to create multiple units arranged in series.
- The third image illustrates how the script can be extended to generate a flowsheet with multiple inlet streams.
- The fourth image demonstrates how the script can be expanded to include multiple output streams.