Calling a Python Script from Rust

Fellow Rust enthusiasts, I am a structural engineer and also a new Rust user. I have a program I wrote in python to connect with one of the engineering programs (SAP2000) using its Open API. The API works perfectly using Python but I am not sure it works with Rust. However, I am slowly trying to use more Rust and give up Python for connecting with this API for the numerous benefits Rust offers and also to produce better distributable files. I have put the code (in Python) below for reference. Regarding this, I request help with two questions:
(1) How can I run this python script from within Rust, keeping in mind the API compatibility issue?
(2) For future projects, written completely in Rust, how can I connect with this API? Once I can know how to run one function (example is everything starting with Sap in the code below), I can figure out how to build an entire project.
I will sincerely appreciate help on this.

import comtypes.client
import math
SapObject=comtypes.client.GetActiveObject("CSI.SAP2000.API.SapObject")
SapModel = SapObject.SapModel
ret=SapModel.SetModelIsLocked(False)

lb_ft_F = 2
ret=SapModel.SetPresentUnits(lb_ft_F)
h = float(input('What is the eeve Height?\n'))
B = float(input('What is the building width?\n'))
L = float(input('What is the building length?\n'))

avail=input('Is building height available (Y/N)?\n')
if avail=="Y":
    H = float(input('What is the ridge Height?\n'))
    theta=math.atan((H-h)/(B/2))
else:
    theta = float(input('What is the roof angle in degrees?\n'))
    theta = theta*math.pi/180
    
x=float(input('What is the bay spacing?\n'))
y=float(input('What is the purlin spacing?\n'))
ceil_1=math.ceil((B/2)/y)
ceil_2=math.ceil(L/x)
new_y=(B/2)/ceil_1
new_x=L/ceil_2

X_1=[]
X_1.append(0)
X_2=[]
X_2.append(B)
Y=[]
Y.append(h)

for n in range(1,ceil_1):
    X_1.append(n*new_y)
    X_2.append(B-(n*new_y))
    Y.append(h+n*(new_y*math.tan(theta)))

X_1.append(B/2)
X_2.append(B/2)
Y.append(h+((B/2)*math.tan(theta)))
#print(X_1)
#print(X_2)
#print(Y)

MATERIAL_CONCRETE = 2
ret = SapModel.PropMaterial.SetMaterial('CONC', MATERIAL_CONCRETE)
ret = SapModel.PropMaterial.SetMPIsotropic('CONC', 3600, 0.2, 0.0000055)
ModValue = [1000, 0, 0, 1, 1, 1, 1, 1]
ret = SapModel.PropFrame.SetModifiers('R1', ModValue)


SapModel.FrameObj.AddByCoord(0,0,0,0,0,h,Name="Frame1",PropName="Default")
SapModel.View.RefreshView(Zoom=False)

Something like the pyo3 crate might be useful here. See this page for their documentation on how to use it to call Python APIs from Rust: Calling Python from Rust - PyO3 user guide

If you just want to run a python program/script and communicate with it, for instance, only via stdin/stdout, you might need nothing more than to use standard library APIs for spawning processes, in order to start your python interpreter to run the script.

3 Likes

Thanks @steffahn for the response - I think the PyO3 might be the right tool for me. I was also looking into RustPython - is that also for the same purpose as PyO3. I was confused with the utility of both of these crates.

I don't know RustPython, but from the documentation I can glance that it is (some day supposed to be) a full-fledged python interpreter (that you can use to embed python scripting in Rust). On the other hand, pyo3 is a library for integrating Rust with the official CPython interpreter and PyPy (another Python interpreter). I have no idea how mature RustPython is but from the lack of documentation I'd say not very. So you probably want to stick with pyo3 which is well used and has a healthy amount of documentation and questions asked here and on other sites.

1 Like

RustPython is a cool project, but I don't think you can use it here

Full Python 3 environment entirely in Rust (not CPython bindings), with a clean implementation and no compatiblity hacks.

The comtypes module is using ctypes - python's foregin function module - you could implement a replacement for comtypes in rust, but looks like a bigger project.

2 Likes

Thank you @jofas and @Jesper - I will stick to PyO3 then for my project. Regarding comtypes/ctypes, do you know of any crates that I can use to use Rust for the same purpose?

Sorry, I don't - not familiar with SAP2000 - but googling suggests that software has other APIs than the python comtypes module you are using.

​ ​​​​​​​​Multiple Programming Languages ​​API supports most programming languages, including Visual Basic for Applications (VBA), VB.NET, C#, C++, Visual Fortran, Python and Matlab .

Rust has its own foreign function interface - for calling c libraries

1 Like

No problem :slight_smile: Appreciate the help. So, to clarify @Jesper , are you suggesting that I rewrite my program in C and then use Rust's FFI to call C libraries?

Seems like you are looking for an excuse NOT to use python when that probably is the best choice.

If you are interested in calling c from rust I would set up a simple hello world example to experiment with.

However, since you know how comtypes works as a user, you can of course trace your way through it and translate to rust - use the rust FFI where python uses ctypes...

1 Like

@Jesper You are right, I can surely use Python because all the programs I have surely function as they should. But I am trying to shift to Rust because of (1) the ease of generating a distributable .exe file instead of trying to install Python and other libraries and most importantly sharing the source code files on Python. I plan on distributing this software but do not want to share the source code. (2) Speed - so far my programs are small and take <30 seconds to run completely, but I know that in future, speed will be a big factor for me, and from what I have heard Rust is much faster than Python. (3) I am trying to use my motivation to develop this software as a way to learn Rust.

These are my biggest reasons for shifting to Rust. Translating to C and then calling it from Rust, might be overkill. Instead I will simply stick with Python because it gets the job done for now. And then think about how to mitigate the problems in Python itself (speed, licensing, etc.) and then once the whole project is built, think about converting to other languages in future updates.

Thanks for the responses.

This tells me that what you're doing is consuming a COM API for SAP2000; as a result, I'd look at com-rs as the Rust equivalent of comtypes, noting that you'll need to define COM interfaces for the objects in Rust, based on the SAP2000 COM API documentation.

Python avoids the need to define COM interfaces, since it can use runtime introspection to find out what the APIs actually are.

3 Likes

@farnz Thank you for suggesting this - I will definitely look into this - looks promising, though the readme says the crate is under heavy development. You are correct, I am using a COM API for SAP2000 and Python makes it easy to use them directly. I will try to define COM interfaces for objects in Rust - hope that turns out to be easy.

1 Like