Python and accoreconsole

Python and accoreconsole

townest
Advocate Advocate
9,013 Views
12 Replies
Message 1 of 13

Python and accoreconsole

townest
Advocate
Advocate

I have successfully created a few batch (.bat) files to cycle through drawings for various tasks calling accoreconsole.  Batch code is kind of foreign to me and not my strength, and I'm thinking Python would be more than capable of replacing my bat scripts.

 

I've played around with subprocess.Popen and os.system but can't quite get over the hump.  Can someone share a simple script that opens and existing dwg in accoreconsole, then draws a line from 0,0 to 500, 1200, then saves and exits.


I know the following python script is wrong but posting this for conceptual purposes, to help the conversation:

 

import subprocess

subprocess.Popen(['C:\somedir\somedwg.dwg', 'l', '0,0', '500,1200', ' ', 'save', 'quit'])

 

I'm not grasping how Python subprocess passes scripting to the console.

 

Any help would be much appreciated.

 

Thank you,

 

Tyler

AutoCAD2022

0 Likes
9,014 Views
12 Replies
Replies (12)
Message 2 of 13

Sea-Haven
Mentor
Mentor

I know nothing about Python, you would need to look into say how Excel can talk to Acad and get it to make objects. So open the link to Autocad. PS Word can do it also.

 

You should rename to .XLSM and have a look at the macros in it. Need the same in python.

 

 

 

'Check if AutoCAD application is open. If is not opened create a new instance and make it visible.
    On Error Resume Next
    Set acadApp = GetObject(, "AutoCAD.Application")
    If acadApp Is Nothing Then
        Set acadApp = CreateObject("AutoCAD.Application")
        acadApp.Visible = True
    End If
 

 

 

 

in particular you may need something like 

 

  
Sub Opendwg()
 
    Dim acadApp As Object
    Dim acadDoc As Object

 'Check if AutoCAD application is open. If is not opened create a new instance and make it visible.
    On Error Resume Next
    Set acadApp = GetObject(, "AutoCAD.Application")
    If acadApp Is Nothing Then
        Set acadApp = CreateObject("AutoCAD.Application")
        acadApp.Visible = True
    End If
 
    'Check (again) if there is an AutoCAD object.
    If acadApp Is Nothing Then
        MsgBox "Sorry, it was impossible to start AutoCAD!", vbCritical, "AutoCAD Error"
        Exit Sub
    End If
    On Error GoTo 0
 
    'If there is no active drawing create a new one.
    On Error Resume Next
    Set acadDoc = acadApp.ActiveDocument
    If acadDoc Is Nothing Then
        Set acadDoc = acadApp.Documents.Add
    End If
    On Error GoTo 0
  
    'Check if the active space is paper space and change it to model space.
    If acadDoc.ActiveSpace = 0 Then '0 = acPaperSpace in early binding
        acadDoc.ActiveSpace = 1     '1 = acModelSpace in early binding
    End If

 End Sub
 
Public Sub addline(x1, y1, z1, x2, y2, z2)
  
 ' Create the line in model space
    Dim acadApp As Object
    Dim acadDoc As Object
    Set acadApp = GetObject(, "AutoCAD.Application")
    Set acadDoc = acadApp.ActiveDocument

    Dim startpoint(0 To 2) As Double
    Dim endpoint(0 To 2) As Double
    Dim lineobj As Object

    startpoint(0) = x1: startpoint(1) = y1: startpoint(2) = z1
    endpoint(0) = x2: endpoint(1) = y2: endpoint(2) = z2

    Set lineobj = acadDoc.ModelSpace.addline(startpoint, endpoint)
    acadApp.ZoomExtents
    
    End Sub
    Public Sub addcirc(x1, y1, z1, rad)
  
 ' Create the circle in model space
    Dim acadApp As Object
    Dim acadDoc As Object
    Set acadApp = GetObject(, "AutoCAD.Application")
    Set acadDoc = acadApp.ActiveDocument

    Dim cenpoint(0 To 2) As Double
   
    Dim circobj As Object

   cenpoint(0) = x1: cenpoint(1) = y1: cenpoint(2) = z1
    Set circobj = acadDoc.ModelSpace.addcircle(cenpoint, rad)
    acadApp.ZoomExtents
    
    End Sub
    
    
    Sub addpoly(cords, col)
    
    Dim acadApp As Object
    Dim acadDoc As Object
    Set acadApp = GetObject(, "AutoCAD.Application")
    Set acadDoc = acadApp.ActiveDocument

    Dim oPline As Object
    
' add pline to Modelspace
Set oPline = acadDoc.ModelSpace.AddLightWeightPolyline(cords)
oPline.Color = col

End Sub
   
    Sub alan1()
    
   
' This example adds a line in model space
' Define the start and end points for the line
   
    px1 = 1
    px2 = 5
    py1 = 1
    py2 = 5
    pz1 = 0
    pz2 = 0
    

Call addline(px1, py1, pz1, px2, py2, pz2)

End Sub

 Sub alan2()
 
    px1 = 1
    py1 = 1
    pz1 = 0
    Radius = 8.5
 
 Call addcirc(px1, py1, pz1, Radius)

 End Sub
 
 Sub alan3()
 'Dim coords(0 To n) As Double
 Dim coords(0 To 5) As Double
 coords(0) = -6: coords(1) = 1:
 coords(2) = 3: coords(3) = 5:
 coords(4) = 7.55: coords(5) = 6.25:
 
 col = 1
    
 Call addpoly(coords, col)

 End Sub

 

0 Likes
Message 3 of 13

townest
Advocate
Advocate

Ok, not a lot of documentation on this but I've made a little progress.  I decided to do some testing on acad.exe first, and got the following code to work:

import subprocess

exe = 'C://somedir//acad.exe'
dwg = 'somedir//somedwg.dwg'
script = 'somedir//somescript.scr'

subprocess.run([exe, dwg, "/b", script])

The "/b" is a switch allowing a call to a script.  

 

Now, back to the original question.  This same python subprocess code does not work with exe = 'C://somedir//accoreconsole.exe.  The subprocess opens accoreconsole in the Python command prompt, but doesn't seem to pass anything past the exe, so the dwg doesn't open, and the scr is not passed. 

 

I would like to learn how Python subprocess works with accoreconsole.  Any ideas would be much appreciated.

 

Tyler

Acad2022  

0 Likes
Message 4 of 13

townest
Advocate
Advocate

Well,

I'm slowly answering my own question.  The following script works with accoreconsule:

 

import subprocess

exe = 'C://somedir//accoreconsole.exe'
dwg = 'somedir//somedwg.dwg'
script = 'somedir//somescript.scr'

subprocess.run([exe, "/i", file, "/s", script])

 

Now, a handy alternative to passing a scr file would be just passing strings as commands.  This would allow short scr's to be written right into the Python code, or even vary the commands if required.  Something like:

subprocess.run([exe, "/i", dwg, "/s", "quit"])

...which would just open the dwg and quit.

 

Let me know if you have any ideas.

 

Thank you,

 

Tyler

Acad2022

0 Likes
Message 5 of 13

Sea-Haven
Mentor
Mentor

"Now, a handy alternative to passing a scr file would be just passing strings as commands."   Accoreconsole does not allow any user input, only those in the script, if you want to talk to Autocad you may need to just use the normal "OPEN" as you have suggested call acad.exe dwgname and a script. As its normal acad you can be asked questions. 

 

Thats why I suggested the excel method this is what you could do with Python but on a open dwg. Editing a closed dwg is a pay level above me. There is a python toolkit for autocad.

0 Likes
Message 6 of 13

martti.halminen
Collaborator
Collaborator

One thing to check: is your Python program trying to use ActiveX to connect to AutoCAD?

 

AcCoreConsole does not support ActiveX.

0 Likes
Message 7 of 13

ghensi
Enthusiast
Enthusiast

Late to the party, I'm facing the same task right now...

 

I intend to use accoreconsole to run some command of my own add-in.

The commands require a json file path as the only argument (there's a bunch of settings inside it, including the list of drawings to process since they are batch operations).

This is the code snippet I use to create the scr and json file in a temporary directory, hopefully the variables are self explanatory:

 

with TemporaryDirectory() as wd:
    # dump the settings in a json file
    json_fd, json_path = mkstemp(suffix=".json", dir=wd)
    # create .scr that loads the dll and runs the command
    scr_fd, scr_path = mkstemp(suffix=".scr", dir=wd, text=True)
    try:
        with os.fdopen(json_fd, "w") as json_fo:
           json_fo.write(settings.json())
        with os.fdopen(scr_fd, "w", newline="\r\n") as scr:
            scr.write(
                f"SECURELOAD 0\nNETLOAD\n{plugin_path}\n"
                f"SECURELOAD 1\n{cmd_name}\n"
                f"{json_path}\n\n"
            )
        await run_acad_script(scr_path)
    except Exception as ex:
        log.error(f"Something went wrong: {ex}")
    finally:
        # file cleanup
        if os.path.exists(json_path):
            os.remove(json_path)
        if os.path.exists(scr_path):
            os.remove(scr_path)

 

 

"settings" is a Pydantic model, very handy for validation of the information needed, and with the convenience method "json()" to convert the class object to a JSON string.

 

As you can see, I write the entire script in a single line, but it can be split into multiple "write" calls (you still have to add the newline character).

 

"run_acad_script" will be the function that calls subprocess with the right arguments:

 

process = subprocess.Popen(acad_path, "/i", dwg, "/product", "ACAD", "/s", script_path)
process.wait()

 

(for me dwg is an empty dwg file, since the real drawings paths are in the JSON file, but I need to specify it)

 

Right now I'm trying to use the async version of subprocess in order to avoid blocking the app while autocad is running and to forward the accoreconsole messages to my app; sadly my current code is not working, as autocad crashes as soon as it is started.

 

0 Likes
Message 8 of 13

john.kaulB9QW2
Advocate
Advocate

These next questions will be sort of messy so bear with me please.

 

1. I'm a little confused by the last statement; I assume you don't mean blocking as in "threading blocking" you mean blocking as "concurrent" so I don't think I entirely follow what it is you are attempting to do. Your app is doing something and you'd like it to keep doing other things while the subprocess works, right? ...I mean to ask if you are using the proper terms because you know what threading is and you do actually need it.

 

2. Are you a developer or a user? If developer; Why python and not c# or c++ or any other compiled language? And wouldn't this be a lot easier if it weren't done in python? I mean yes, I don't like python (It's right up there with perl to me) but is there even a "front end" for python and are you stuck using it? 🙂

another swamper
0 Likes
Message 9 of 13

Sea-Haven
Mentor
Mentor

I know nothing about python but I am sure it can open a dwg do something and close it. Aecorconsole is designed to run as a standalone program, you could use your python to make a call to the OS maybe that runs the correct syntax for aecoreconsole just replace the BAT file with your sequence of calls to the OS with the correct aec* 1 line.

0 Likes
Message 10 of 13

ghensi
Enthusiast
Enthusiast
Hi Sea-Heven,
I appreciate that you're trying to help, but I don't understand what's your point.

townest (and I) has already figured out how to launch accoreconsole from python using the subrpocess module;
I was offering my solution to build the temporary script file to pass as argument to it.

subprocess.run (or subprocess.Popen) is exaclty what you called "sequence of calls to the OS".
0 Likes
Message 11 of 13

ghensi
Enthusiast
Enthusiast
I'm sorry I added entropy to this discussion, I was just trying to help townest to solve his/her question by showing how you can build a temporary script to pass to accoreconsole.

To answer your questions:
- I'm a developer, and I'm most proficient in python;
- I'm building an API server that listen to request from an Excel add-in I developed (the frontend) and runs a custom command (add-in also developed by me) in accoreconsole;
- Being it an API server, I used concurrency to be able to run multiple commands at the same time;
- The blocking I was talking about is related to the polling/reading of the accoreconsole output to forward the messages to the user (via server sent event, it's like websocket but unidirectional) and be able to receive other requests in the meantime; for this it has to be asyncronous
- I used python also because I can write a REST API server much more easily than, say C# (this is because I already did it before and because there's the wonderful FastAPI library that makes it trivial)
- as I mentioned above, the front end is an excel add-in, so it's HTML+javascript (I tried SvelteKit for this project, and I already love it), but there's plenty of GUI frameworks for python to choose from (mainly bindings to C++ libraries such as Qt).
0 Likes
Message 12 of 13

john.kaulB9QW2
Advocate
Advocate

Things are making a bit more sense now. The concept is interesting.

 

For the record, I'm still a bit fuzzy on your logic (I do threading in POSIX c so I will be of very little actual help to figure out why AutoCAD/accoreconsole does not run properly but I thought I would toss out one or two questions to see if they help) but that fuzzyness could be how python does threading.

 

(guess)
When I pass to my subprocess I can only pass one array argument; if I need to keep track of totalbytesread--for example--I need to pass that as part of my array. From what you show of your "run_acad_script" function it looks as though you will have to pass two arguments. could that be the reason why your thread exists (I am assuming that your thread is EXITING on error).

 

(guess)
why are you waiting() in your subprocess? I mean you have "run_acad_script" as a sub. So as far as I know, it would be a single line: "callacad" -i.e. do it's thing and exit. Although, I can see that you may be gathering up the result(s) from your script for reporting back. Could that process be causing an error (writing to a readonly file)?

 

Again, I don't understand python-threading so I could be speaking gibberish so grain-of-salt to you.

another swamper
0 Likes
Message 13 of 13

ghensi
Enthusiast
Enthusiast

Thanks @john.kaulB9QW2 for your insights.

 

I figured out that the crash has nothing to do with the subprocess part, but it is an unhandled exception in my c# code at the very beginning of the command.

 

To answer to your guesses: the subprocess.Popen() and .wait() were pseudocode that I don't use.

In fact, Popen or run need the command and its arguments line as a single parameter (as list of string or a single string).

I'm now using the asyncio.create_subprocess_exec, that accepts/wants the command and the argument as separate parameters (positional arguments or *args in python slang); I mixed up the two things and wrote completely wrong code 😅

 

You guessed right about wait(), it can be used to collect the return code, stdout and stderr of the process;

However I don't use it because it blocks the python thread; instead I run an asyncronous loop to (asyncronously) read a single line of stdout and forward it to the client.

This is still messy and error-prone code, so I refrain from sharing it.

0 Likes