SysCAD COM Interface Class
Navigation: User Guide ➔ COM Automation ➔ Python Automation ➔ Python Class
| Python Setup | Python Examples | Python Script | Optimisation | Visulisation | Python GUI | Tags and Data | |||
|---|---|---|---|---|---|---|---|---|---|
| Installation & Troubleshooting | List of Examples | Simple Script (pywin32) | SysCAD COM Python Class | Automated Model Testing | Constrained FEM (numpy|matplotlib) | Optimisation (numpy|scipy) | Adding Plots (numpy|matplotlib) | Dynamic with GUI | Accessing Data (sqlite3|pandas) |
Encapsulating COM functionality in a Python Class
t’s good practice to encapsulate all required COM functionality within a Python class. This keeps your code tidy, centralised, and easy to reuse. The syscadif.py module demonstrates this by defining a class called SysCADCom, which wraps basic SysCAD COM operations. We’ll be using this class in the examples that follow.
syscadif.py Python Class
Creating an instance of the SysCADCom class will fire up SysCAD (or attach if SysCAD is already running). To see the code below click on the Expand link:
import win32com.client
import time, os
from tkinter import filedialog
PrjFolder = "C:/SysCAD139/Examples" # Where to look for SysCAD projects when opening with the file dialog
class SysCADCom:
def __init__(self):
self.ProgID = r"SysCADSimulator93.Application"
self.SysCAD = self.Prj = self.Tags = self.Solver = None
self.Probal = self.Dynamic = None
self.SysCAD = win32com.client.DispatchEx(self.ProgID)
self.build = self.SysCAD.VersionNumber(2)
def OpenProject(self, fn=None, checkFile = True):
print (self.ProgID)
if fn is None:
self.fn = filedialog.askopenfilename(filetypes=[("SysCAD Projects", "*spj")],
initialdir = PrjFolder,
defaultextension="spj")
else:
self.fn = fn
if not self.fn: return 0
if checkFile and not os.path.isfile(self.fn):
print ("Project File not found")
return 0
self.Prj = self.SysCAD.OpenProject(self.fn)
self.Tags = self.Prj.Tags
self.Solver = self.Prj.Solver
self.RunMode()
time.sleep(1)
def RunMode(self):
'''Select Run mode depending if project is SS or dynamic'''
if self.Solver.RunMode & 1:
self.Probal = self.Solver.Probal
self.Dynamic = None
else:
self.Dynamic = self.Solver.Dynamic
self.Probal = None
def CloseProject(self):
self.SysCAD.CloseProject()
def Close(self):
'''remove all references to COM objects...'''
del self.Probal
del self.Dynamic
del self.Solver
del self.Tags
del self.Prj
del self.SysCAD
def startSysCAD(self):
if self.Solver.RunMode & 1:
self.Probal.Start()
else:
self.Dynamic.Start()
def run(self, sleepTime=0.1):
'''Run a Probal project to completion. For a project that takes a longer time to solve
increase sleepTime'''
if self.Probal is not None:
self.Probal.Start()
while not self.Probal.IsStopped:
time.sleep(sleepTime)
elif self.Dynamic is not None:
self.Dynamic.Start()
else:
print ("No project")
def stop(self):
if self.Solver.RunMode & 1:
self.Probal.Stop()
else:
self.Dynamic.Stop()
def RunScenarios(self, XVals, Tag, getTagList, verbose=True):
'''Run a number of scenarios.
If Tag is a string, XVals should be a list or iterable of appropriate elements
Otherwise Tag should be a list of tags or iterable, and XVals a list of multiple values
Accumulate the results and return'''
Y = []
for x in XVals:
if verbose: print (x)
if isinstance(Tag, str):
self.setTag(Tag, x) ## Change tag value
else:
self.setTags(Tag, x)
self.run()
Y.append(self.getTags(getTagList))
return Y
def getTag(self, tag):
'''Fetch a single tag'''
return self.Tags.TagValue(tag)
def __getitem__(self, tag):
return self.Tags.TagValue(tag)
def getTags(self, tagList):
'''Fetch a list of tags'''
return [self.Tags.TagValue(tag) for tag in tagList]
def setTag(self, tag, value):
'''Set a single tag'''
self.Tags.SetTagValue(tag, value)
def __setitem__(self, tag, value):
self.Tags.SetTagValue(tag, value)
def setTags(self, tagLis, valLis):
'''Set a list of tags to corresponding values'''
for t, v in zip(tagLis, valLis):
self.Tags.SetTagValue(t, v)
def state(self, timeout = 0.0):
if timeout: time.sleep(timeout)
return self.Probal.IsStopped
|
Defined Class functions
| Method | Description |
|---|---|
| sc = SysCADCom() | Create a SysCADCom class instance. |
| OpenProject(file) | Open a SysCAD project |
| CloseProject() | Close the current project |
| Close(): | Close SysCAD and clean up COM objects |
| getTag(str) | Get the value of tag str |
| setTag(str, val) | Get the value of tag str to val |
| getTags(tagLis) | Get the value of tags in list or iterable |
| setTags(tagLis, vals) | Set the value of tags in list to items in vals |
| run() | Run a Probal model |
| runScenarios(Vals, TagsIn, TagsOut) | Run scenarios setting a single or mutiple tags |
Making the Python Class Available for use
To use the module, save the provided code in a file named syscadif.py. Make sure this file is either:
- In the working directory of your Python script, or
- Located somewhere on your PYTHONPATH, this ensures the module can be imported from any script.
Alternatively, you can manually add the module’s path to your import path using Python code. Once the module is accessible, you can import it and create an instance of the SysCADCom class as shown below:
import sys
sys.path.append(r"C:\SysCAD139\BaseFilesUser\Scripts") ## syscadif.py is saved in a subdirectory of BaseFilesUser
import syscadif
sc = syscadif.SysCADCom()
sc.OpenProject( ) ## Open project, add the path and name of project inside ()
Using the Python Class to run Scenarios
Running simple Scenarios
After a project is loaded, you can get and set tags and run the model or scenarios.
import syscadif
sc = syscadif.SysCADCom()
ScdDir = r'C:\SysCAD%d' % sc.build ## Installation folder, change if using a different location
ScdPrj = r'\Examples\25 Gold\Demo Gold Project.spf\Project.spj' ## Saved Project version
sc.OpenProject(ScdDir+ScdPrj) ## Open project
sc.setTag("SLURRY_IN.QmReqd (t/h)", 50.0)
sc["SLURRY_IN.QmReqd (t/h)"] =50.0 ## equivalent to the line above
sc.run()
prod = sc.getTag("P_007.Qo.QM.Au(s) (oz/d)")
prod = sc["P_007.Qo.QM.Au(s) (oz/d)"] ## equivalent to the line above
## Setting a single tag for three different scenarios: Flow in P_001 and P_003 for feeds of 1, 3, and 5 tph
print(sc.RunScenarios([45,50,55], "SLURRY_IN.QmReqd (t/h)", ["P_007.Qo.Calc.GoldinOre_ppm", "P_016.Qo.Calc.GoldonCarbon_ppm"]))
# Four different scenarios, setting two tags
print(sc.RunScenarios([[250,18], [250,20], [300, 18], [300, 20]], ## List of value lists
["CSTR.TankVolume (m^3)","CSTR.CarbonConc (g/L)"],
["P_001.Qo.Calc.GoldinOre_ppm", "P_007.Qo.Calc.GoldinOre_ppm"]))
input('Enter to finish')
sc.Close()
Streamlined Data Capture for SysCAD Dynamic Projects
The following example Python script automates the running and monitoring of a SysCAD dynamic simulation. It connects to a specified SysCAD project, executes the simulation until a user-defined simulated time is reached, and records key process variables at regular intervals. The collected data is saved to a CSV file, optionally visualised in plots, and added to a summary report for comparing results across multiple runs. The script supports interactive control, making it easy to initiate, repeat, and analyse simulation runs efficiently.
This example uses the Dynamic Plant Example Project.
import os
import sys
import csv
import time
import matplotlib.pyplot as plt
# Import SysCAD interface module
import syscadif # Ensure syscadif is installed and accessible
# Define project and report paths
ScdDir = r'C:\SysCAD139'
ScdPrj = r'\ExamplesDynamic\Dynamic Plant Example.spf\Project.spj'
project_path = os.path.join(ScdDir, ScdPrj.lstrip("\\"))
project_folder = os.path.dirname(project_path)
report_dir = os.path.join(project_folder, "Reports")
os.makedirs(report_dir, exist_ok=True) # Create report directory if it doesn't exist
# Connect to SysCAD
sc = syscadif.SysCADCom()
sc.OpenProject(project_path)
# Define tags to monitor during simulation
tags = [
"PlantModel.Stats.SimTime.Total (h)", # Simulated time
"Acid.Qm (t/h)", # Acid flow rate
"REACTION_TANK.Lvl (%)", # Reaction tank level
"Output.Qo.CMC.H2SO4 (g/L)", # Sulfuric acid concentration
"General_Control.Valve_1Position (%)", # Valve position
"General_Control.PumpSpeed (%)" # Pump speed
]
def run_simulation(run_number, required_sim_time=10.0):
"""
Runs the SysCAD simulation until the required simulated time is reached.
Collects tag data at regular intervals and saves to CSV.
"""
sc.run()
data = []
interval_seconds = 0.1 # Sampling interval
print(f"\n🚀 Run {run_number} started...")
while sc.Dynamic.IsRunning:
try:
sim_time = sc[tags[0]]
if sim_time >= required_sim_time:
sc.stop()
print(f"✅ Simulated time {required_sim_time} h reached. Simulation stopped.")
break
# Record current values for all tags
row = [sim_time]
for tag in tags[1:]:
try:
value = sc[tag]
except Exception as e:
print(f"⚠️ Error reading tag {tag}: {e}")
value = None
row.append(value)
data.append(row)
time.sleep(interval_seconds)
except Exception as e:
print(f"❌ Unexpected error: {e}")
break
if not sc.Dynamic.IsRunning:
print("🛑 Simulation stopped.")
else:
print("✅ Run completed.")
# Save collected data to CSV
csv_filename = os.path.join(report_dir, f"syscad_data_log_run_{run_number}.csv")
with open(csv_filename, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(["Simulated Time (h)"] + tags[1:])
writer.writerows(data)
print(f"📊 Collected {len(data)} data points.")
print(f"📁 Data saved to: {csv_filename}\n")
return data
def plot_data(data, run_number):
"""
Generates plots for each tag against simulated time and saves as PDF.
"""
sim_times = [row[0] for row in data]
# Define y-axis ranges based on units
y_ranges = {
"%": (0, 100),
"t/h": (0, 20),
"g/L": (0, 50)
}
num_plots = len(tags) - 1
cols = (num_plots + 1) // 2
fig, axs = plt.subplots(2, cols, figsize=(12, 6), sharex=True)
axs = axs.flatten()
for i, tag in enumerate(tags[1:]):
values = [row[i + 1] for row in data]
axs[i].plot(sim_times, values)
axs[i].set_ylabel(tag)
axs[i].set_title(tag)
axs[i].grid(True)
# Apply y-axis range based on unit match
for unit, (ymin, ymax) in y_ranges.items():
if unit in tag:
axs[i].set_ylim(ymin, ymax)
break
else:
axs[i].set_ylim(bottom=0) # Default lower bound
# Hide unused subplots
for ax in axs[num_plots:]:
ax.axis('off')
axs[-1].set_xlabel("Simulated Time (h)")
plt.xticks(rotation=45)
plt.tight_layout()
# Save plot to PDF
plot_filename = os.path.join(report_dir, f"syscad_plot_run_{run_number}.pdf")
plt.savefig(plot_filename)
plt.show()
print(f"🖼️ Plot saved to: {plot_filename}")
def update_output_summary(run_number):
"""
Updates the OutputSummary.csv file with final tag values from the current run.
"""
summary_path = os.path.join(report_dir, "OutputSummary.csv")
# Read existing summary
with open(summary_path, 'r', newline='') as f:
reader = csv.reader(f)
rows = list(reader)
# Extract variable tags from first column (ignoring header cell)
variable_tags = [row[0] for row in rows[1:]]
# Fetch values from SysCAD
new_column = [f"Run {run_number}"]
for tag in variable_tags:
try:
value = sc[tag]
except Exception as e:
print(f"⚠️ Error reading summary tag {tag}: {e}")
value = None
new_column.append(value)
# Append new column to each row
for i in range(1, len(rows)):
rows[i].append(new_column[i])
# Update header
rows[0].append(new_column[0])
# Write updated summary
with open(summary_path, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(rows)
print(f"📄 OutputSummary.csv updated with results from Run {run_number}")
# Main loop for interactive simulation runs
run_number = 1
while True:
if input("Start run? (y/n): ").strip().lower() != 'y':
break
try:
required_sim_time = float(input("Enter required simulated time in hours (default 10.0): ").strip())
except ValueError:
required_sim_time = 10.0
# Run simulation and collect data
data = run_simulation(run_number, required_sim_time)
# Update summary file
update_output_summary(run_number)
# Optional plotting
if input("Plot data? (y/n): ").strip().lower() == 'y':
plot_data(data, run_number)
# Prompt for another run
if input("Another run? (y/n): ").strip().lower() != 'y':
break
run_number += 1
# Cleanup SysCAD connection
sc.Close()
print("🧹 Simulation closed and cleaned up.")
|
Access other Functions
Not all COM methods are directly implemented through the class interface, but you can still access them via member attributes.
For example, the Python scripts used in the examples on the Programmatic Model Generation page call functions to create a new project, add unit models, and establish links between them.
Distributed Example Projects
The SysCAD distributed example projects that uses this Python class are:
- ChemApp Project - Nickel Laterite Smelter Multivariable Optimisation (Requires Separate ChemApp License)