Author Topic: IronPython for Bricscad  (Read 4570 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
IronPython for Bricscad
« on: December 22, 2017, 06:50:22 PM »
Tested on Bricscad V16 to V18, I will build a version for AutoCAD as well if there's enough interest.

For those interested, I've attached a zip folder containing setup files so you can load the interpreter (PyCAD.dll) via NETLOAD and then run some scripts created in the 'plugins' folder.

So far it only has a simple prompt (command = IPY_PROMPT) where you can call functions from your scripts. Any scripts that get loaded (automatically from the plugins folder) will have their functions exposed to the CAD system via this prompt. In the near future I will be able to create proper "commands" to make it more useful as a scripting tool.

If you want to make changes to a script you will need to stop the prompt (esc or enter blank line) you can then reload the script using IPY_RELOAD then start the prompt again for testing.

There's a few test scripts in the plugins folder, one has an error built in on purpose for testing, it requires very little to fix to remove the error message so don't be worried about that.

I just put it here to see what sort of interest there is in this, I'm building it for a scripting interface for one of my current projects but I will make it more generic and open source if people want to use it.

Future releases will have a small text editor for live coding and testing.

As you have access to the whole .net framework you can use any .net library you like. Most of the standard Python library is available as well so if you're used to Python std functions you'll feel right at home, they work together well. Using Python external libraries is not available at this stage and would require the installation of both Python and IronPython, I chose to keep it simple for now.
As the code is IronPython, if you build a large module that's well established you can easily compile it to a dll proper in Visual Studio but for now you can write script files in your editor of choice.

Installation:
Unzip the contents to a folder of your choice, it contains dlls and a plugins folder with a few script files. Put any new scripts here.
In Bricscad run NETLOAD and browse to this folder and choose the PyCAD.dll and load it.
Commands available: IPY_PROMPT, IPY_RELOAD

Let me know if you have any questions or issues, thanks.

"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
Re: IronPython for Bricscad
« Reply #1 on: December 22, 2017, 08:52:04 PM »
Here's a sample script to show a dialog with WPF.
Just hard coded but using XAML wouldn't be much more of a stretch. This sample also shows how to add other .net framework references, I'm sure this boilerplate could be filed away into a class module to derive from to provide a simple API for the novice programmer.

If I get time I'll add some controls to interact with the CAD app.

Place this file into the plugins folder and at the IPY_PROMPT enter 'wpfdlg' to run it.

Code - Python: [Select]
  1. # Reference the WPF assemblies
  2. import clr
  3. clr.AddReferenceByName("PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
  4. clr.AddReferenceByName("PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
  5. import System.Windows
  6.  
  7. # Initialization Constants
  8. Window = System.Windows.Window
  9. Application = System.Windows.Application
  10. Button = System.Windows.Controls.Button
  11. StackPanel = System.Windows.Controls.StackPanel
  12. Label = System.Windows.Controls.Label
  13. Thickness = System.Windows.Thickness
  14. DropShadowBitmapEffect = System.Windows.Media.Effects.DropShadowBitmapEffect
  15.  
  16. def _createWpfDlg():
  17.  
  18.     # Create window
  19.     my_window = Window()
  20.     my_window.Title = 'Welcome to IronPython'
  21.     my_window.Width = 600
  22.     my_window.Height = 450
  23.  
  24.     # Create StackPanel to Layout UI elements
  25.     my_stack = StackPanel()
  26.     my_stack.Margin = Thickness(15)
  27.     my_window.Content = my_stack
  28.  
  29.     # Create Button and add a Button Click event handler
  30.     my_button = Button()
  31.     my_button.Content = 'Push Me'
  32.     my_button.FontSize = 24
  33.     my_button.BitmapEffect = DropShadowBitmapEffect()
  34.  
  35.     def clicker(sender, args):
  36.         # Create new label
  37.         my_message = Label()
  38.         my_message.FontSize = 48
  39.         my_message.Content = 'Welcome to IronPython!'
  40.  
  41.         # Add label into stack panel of controls
  42.         my_stack.Children.Add (my_message)
  43.  
  44.     # add the clicker event handler
  45.     my_button.Click += clicker
  46.     my_stack.Children.Add (my_button)
  47.  
  48.     return my_window
  49.  
  50.  
  51. def wpfdlg():
  52.     # show the dialog
  53.     dlg = _createWpfDlg()
  54.     dlg.Show()
  55.  
"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
Re: IronPython for Bricscad
« Reply #2 on: December 23, 2017, 07:29:36 PM »
Add a simple line entity:
Demonstrates basic use of database and transactions in Python. Note the 'with' keyword that is the equivalent of 'using' in C#.

I must admit, Python code is very easy on the eyes (and the keyboard ;) )

Code - Python: [Select]
  1. import Bricscad.ApplicationServices as AcAp
  2. from Teigha.DatabaseServices import *
  3. from Teigha.Geometry import *
  4.  
  5. def addLine():
  6.     doc = AcAp.Application.DocumentManager.MdiActiveDocument
  7.     db = doc.Database
  8.  
  9.     # create the line entity
  10.     p1 = Point3d(300, 100, 0)
  11.     p2 = Point3d(500, 200, 0)
  12.     line = Line(p1, p2)
  13.  
  14.     with db.TransactionManager.StartTransaction() as tr:
  15.         # add it to modelspace
  16.         bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
  17.         btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite)
  18.  
  19.         btr.AppendEntity(line)
  20.         tr.AddNewlyCreatedDBObject(line, True)
  21.  
  22.         tr.Commit()
  23.  
"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
Re: IronPython for Bricscad
« Reply #3 on: December 27, 2017, 09:31:39 PM »
Here's a simple wrapper script to make it easy to write clean Python code.

Code - Python: [Select]
  1. # dwgtools.py
  2. ''' This module contains basic interaction helper functions for working
  3. with the currently active dwg file open in the editor.
  4. It is basically a simple wrapper to make writing idiomatic Python code easy
  5. and removes a lot of boiler plate.
  6. '''
  7. import Bricscad.ApplicationServices as AcAp
  8. import Bricscad.EditorInput as AcEd
  9. from Teigha.DatabaseServices import *
  10.  
  11. class Drawing:
  12.     def __init__(self):
  13.         self.doc = AcAp.Application.DocumentManager.MdiActiveDocument
  14.         self.db = self.doc.Database
  15.         self.ed = self.doc.Editor
  16.  
  17.     def add_entity(self, entity):
  18.         ''' Adds an Entity object to the current ModelSpace layout. '''
  19.         with self.db.TransactionManager.StartTransaction() as tr:
  20.             # add it to modelspace
  21.             bt = tr.GetObject(self.db.BlockTableId, OpenMode.ForRead)
  22.             btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite)
  23.  
  24.             btr.AppendEntity(entity)
  25.             tr.AddNewlyCreatedDBObject(entity, True)
  26.  
  27.             tr.Commit()
  28.  
  29.     def write_message(self, message_string):
  30.         ''' Sends a textual message to the Command Line of the Editor. '''
  31.         self.ed.WriteMessage(message_string)
  32.  

and to use it, here is the refactored addLine code:
Code - Python: [Select]
  1. from dwgtools import *
  2.  
  3. def addLine():
  4.     # create the line entity
  5.     line = Line(Point3d(300, 100, 0), Point3d(500, 200, 0))
  6.    
  7.     # add it to the drawing
  8.     dwg = Drawing()
  9.     dwg.add_entity(line)
  10.     dwg.write_message("Added your new Line to the drawing database!")
  11.  
« Last Edit: December 27, 2017, 10:13:16 PM by MickD »
"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien

Danipon

  • Mosquito
  • Posts: 5
Re: IronPython for Bricscad
« Reply #4 on: January 01, 2018, 12:11:05 PM »
Well done MickD! I recently embedded IronPython in a large project and it is good enough as scripting language. Just few remarks I noticed in my work (not tested your code):
  • The IronPython engine is bloody slow to initialize.
  • Maybe not the best scripting language for a CAD in terms of speed.
  • I could not find a way to hot-reload python scripts without restarting the application. This is a major drawback since you normally use a script for prototyping and the write-test-change-reload pattern is extrememly convenient. It seems that is not so easy to clean symbols once you reload the script. This is perfectly reasonable but I did the same thing for LUA language and had no problems reloading scripts.
Anyway that is my (limited) experience. It is an interesting project!

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
Re: IronPython for Bricscad
« Reply #5 on: January 01, 2018, 06:01:47 PM »
The IronPython engine is bloody slow to initialize.
Maybe not the best scripting language for a CAD in terms of speed.
Initial start up is a bit slow but once the scripts are loaded and jitted, IronPython is as fast as C#. (I compile the scripts on load rather than use 'eval'). IronPython is reported to be 10x-100x faster than CPython which is tolerable for a lot of projects :)

Quote
I could not find a way to hot-reload python scripts without restarting the application. This is a major drawback since you normally use a script for prototyping and the write-test-change-reload pattern is extrememly convenient. It seems that is not so easy to clean symbols once you reload the script. This is perfectly reasonable but I did the same thing for LUA language and had no problems reloading scripts.
The IPY_RELOAD does indeed do a hot reload without restart, as you say, this is key!
I scrap the old scope and create a new one and it seems to work well so far. I will setup a GitHub repo soon with all source as soon as I get it sorted a bit more.

Quote
Anyway that is my (limited) experience. It is an interesting project!
Thanks for your interest Danipon!

I actually built a scripting engine in Clojure which works the same but is probably a bit quicker on start-up. I like Lisp but Python is a lot easier on the brain than Clojure for newbies who just want to knock out a quick script :)

The end goal is to have a plugins directory where tested/finalised scripts are kept and loaded once which should have good performance. I'm working on a simple editor for testing new scripts that will use the reload feature, you can create as many scripting engines and scopes as you like so any new code won't tread on any preloaded code.
I now have a lot better error reporting output with line numbers etc for errors that works well enough for debugging.

If you end up with a lot of scripts it's very easy to create an IronPython class lib project and compile them into a dll proper. the main goal though is to make it easy for newbies to use without having to install a full blown dev environment and I can just write scripts for clients without having to do a full rebuild and release of the main app.

It still has a ways to go but it's coming along well and I'm very happy with it and I doubt I'll write too much more C# until I need too :)
"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien

Jerry J

  • Newt
  • Posts: 48
Re: IronPython for Bricscad
« Reply #6 on: May 10, 2018, 04:53:21 AM »
I was wondering if you have set anything up on GitHub?  This looks very interesting, I've struggled a little with C# and frustrated with the amount of code for simple tasks.  I'm sure I'll get-it someday...

If you have any suggestions or samples of python, I would enjoy seeing some more.

thanks and the best to ya

MickD

  • King Gator
  • Posts: 3619
  • (x-in)->[process]->(y-out) ... simples!
Re: IronPython for Bricscad
« Reply #7 on: May 10, 2018, 05:46:03 AM »
Hi Jerry,

The repo is not set up just yet, I need to refactor it a bit and separate out the scripting engine from the CAD-specific section to make it more portable for other applications. I also want to work out some more debugging features as well, a lot of things get through and don't get caught until you use the function but the stack trace does give you the clues you need to fix the problem.

I've also implemented proper CAD command initialisation (C# uses attributes, Python uses decorators/annotations) so there is no need for the IPY prompt, just call the commands.
The IPY_RELOAD still works when you have updated 'commands' so all is good :)

cheers.
"Short cuts make long delays,' argued Pippin.”
J.R.R. Tolkien