Acorn scripting using Python
September 24, 2007
Gus Mueller, of VoodooPad fame, introduced Acorn last week. Amongst its features is Python scriptability. As the author of NodeBox, this sounds like something to have fun with. Check it out:
If you want to play with it yourself, here's the script:
Place it in "/Users/username/Library/Application Support/Acorn/Plug-Ins/" to make it appear under File>Actions.
Following is a detailed rundown of what I did in order to crack open the program and play around with it.
How to extend Acorn using Python
I became interested in the scripting possibilities by reading the official plugin documentation. Acorn's scripting support was meant to make new filters and such, but since it includes the PyObjC classes, you can basically do everything you can in Cocoa through Python. It's a really fast and easy way to make native Mac OS X applications, or, as in our case, extend existing ones!
Generating documentation
If I was going to achieve anything, I needed documentation for Acorn's internals. I wrote a script that parsed the class attributes and generated HTML documentation for them. Finding out the classes was a matter of traversing the object space, and looking for names (with obj.class). The script filters out the superclass's methods, and only keeps methods defined in this class.
This gave me a nice HTML page with all method names. I didn't have the parameters, but I could guess based on the method name.
Download docfinder.py.
The scripting drawer
To get something going fast, the first thing I wanted to see was a scripting drawer. I ended up extending NSDrawer because of refcount issues.
class ScriptingController(NSDrawer):
def initWithDocument_(self, doc):
rootView = NSView.alloc().initWithFrame_( ((0, 0), (300, 300)) )
self.setContentView_(rootView)
win = doc.windowController().window()
self.setParentWindow(win)
self.open()
def main(image):
doc = NSDocumentController.sharedDocumentController().currentDocument()
sc = ScriptingController.alloc().initWithDocument_(doc)
I then added the text view and the run button (without using a NIB file, to minimize dependencies). Attaching a button in PyObjC is a bit weird:
runButton.setAction_(objc.selector(self.runScript_, signature="v@:@@"))
This is where the refcount bug hit me. You see, my "main" method creates the interface and then terminates. Once the interface is on screen, the "run" button refers to a method that is now out of scope. The only way to keep it in scope is by making sure Cocoa keeps a reference to it through its windowing system: hence the NSDrawer subclassing.
Running the script
Running a Python script is as simple as:
source = self.textView.string()
code = compile(source + '\n\n')
exec self.code
While this works, you really need to setup a namespace with some useful commands. My custom AcornContext can access the document's layers, and add shape layers (which you saw in the demo) and bitmap layers. Example:
for i in range(10):
add_layer('/Users/enigmeta/Desktop/hdr/DSCN%04i.JPG' % i)
Wrapping the Acorn classes
Acorn's TSShapeLayer provides all the right methods, but I wanted something NodeBox-esque for drawing shapes. Therefore, I wrapped it in a custom class, to which I added "rect" and "circle" methods. Here's the rect method:
def rect(self, x, y, width, height, **kwargs):
r = TSRectangle.alloc().init()
r.setDrawsFill_(True)
r.setBounds_(((x, y), (width, height)))
self._colorize(r, **kwargs)
self._layer.addInRectangles_(r)
The dangers of running code
Running other people's code is fraught with danger: the script could crash, or do an expensive operation (such as an infinite loop) and cause the system to lock up. Using threads doesn't help much, but I did found a way to make it work. The trick is to start a new Thread with the method userCancelledMonitor, that looks like this:
def runCode(self, code):
self._scriptDone = False
try:
t = Thread(target=self._userCancelledMonitor, name="UserCancelledMonitor")
t.start()
exec code in self.ns
finally:
self._scriptDone = True
def _userCancelledMonitor(self):
import time
from signal import SIGINT
from Carbon import Evt
while not self._scriptDone:
if Evt.CheckEventQueueForUserCancel():
# Send a SIGINT signal to ourselves.
# This gets delivered to the main thread,
# cancelling the running script.
os.kill(os.getpid(), SIGINT)
break
time.sleep(0.25)
This code is from PyEdit, which is the default editor for Mac Python installations. Some custom code (from NodeBox) wraps exceptions thrown inside of the user's code.
What's next
The upcoming version adds an output panel that displays errors and print statements. Right now, these are forwarded to the Console (/Applications/Utilities/Console.app), which is cumbersome.
In addition, the API needs some work, of course, and I really want to add some niceties to the interface.
Acorn hits the right note: it's obviously a simpler, faster version of Photoshop. Gus did the right thing by staying close to the original (the keyboard shortcuts are mostly the same) while improving on some key points: simplicity, speed and "nativeness". The Python scripting makes it my absolute favourite hackable image editor.

Comments
Dan — 26 September 2007 on 16:19
This is very cool Frederik! Thanks for sharing it, I can't wait have a little more time to mess with Acorn and Python.
Kelan — 27 September 2007 on 3:37
I'm super excited to play with this, but just tried it, and got the error "No module named path" (in Console) when Acorn launched, and the plugin didn't load. Do I need a special/newer version of Python or anything? (I just have the stock version from 10.4).
Thanks!
Chris — 27 September 2007 on 14:31
Looking again at the screenshot above, it's obvious that Frederik is running this in Leopard, which comes with Python 2.5 and an ObjC/Cocoa bridge. It's possible to update Python and PyObjC under Tiger, I've downloaded both (2.4, 2.5 also available).
It took a little bit of figuring out (never played with Python before), but running the "import" lines from the top of the Python file - "import objc, os" etc. work without fail in python2.4 - but it still won't work in Acorn, bringing up "No module name path". I even hacked the Acorn/Contents/Plug-Ins/PythonPlugin.acplugin/Contents/Info.plist file to point to the new python binaries...
Have done a load of searching and haven't found anything of help... help?
Brian — 28 September 2007 on 2:24
Rename the file from appscript-01.py to appscript.py and it will load correctly.
You should not need to install a particular version of Python. Acorn's Python plugin uses 2.3 to execute plugin scripts.
Brian — 28 September 2007 on 2:28
Uhm, acornscript, not appscript.
Frederik — 28 September 2007 on 2:49
Brian's right. Just rename the script.
The objc and AppKit modules are inside of Acorn, so you don't need to install them.
Noah — 28 September 2007 on 17:42
This totally rocks. I've only just looked at Acorn a little, but if it does Python, I'm going to have to give it another shot.
Kelan — 29 September 2007 on 2:11
Sweet. Renaming the file worked. Thanks Brian (and doubly-so to Frederik, of course). Fun times are at hand.
But, out of curiosity, why didn't it work with the other name?
Frederik — 29 September 2007 on 8:52
I renamed the download script -- that should take care of all the problems.
The reason why the script won't work is due to a Python module loading problem, I guess.
Brian — 29 September 2007 on 22:28
Both the dot and the hyphen in acornscript-0.1 are illegal in Python identifiers.
I think the misleading error message is caused either by a bug in Acorn's Python plugin or possibly a bug in PyObjc. The imp module used internally by Acorn will normally raise an ImportError or even a SystemError when importing a module with an invalid name and also display the name of the offending module.
Garth Roxburgh-Kidd — 13 October 2007 on 22:28
acornscript fails if my current window has mutiple layers, one of them a shape layer. Acorn asks me "Rasterize the shape layer?". If I let it, the drawer opens but I can't type. If I cancel, the drawer doesn't even open.
Tom Fuerstner — 21 October 2007 on 10:31
Are you actively working on a real integration of Acorn and a "GUI-less" NodeBox ?
Puja — 14 April 2008 on 3:59
Dear Professional,
High Places International is a Global Executive Search Firm specializing in Senior and International placements for a niche clientele. We have been retained by an IT giant for talent search of Test Manager.This position will be based in Netherlands. (The Hague /Amersfoort / Groningen / Bussum).
Our client is a well-established software giant and specialized in Technology services and has global partnerships with several leading Fortune 500 firms, including several IT and Technology majors.
We wish to have preliminary discussions with you in this regard and present you more details.
Please let us have your updated resume, contact details as well as a convenient time for a brief telephonic discussion.
Regards,
Puja
High Places International
+91- 44 4215 9283
puja@highplacesintl.com
Puja Jain — 14 April 2008 on 4:00
Dear Professional,
High Places International is a Global Executive Search Firm specializing in Senior and International placements for a niche clientele. We have been retained by an IT giant for talent search of Test Manager.This position will be based in Netherlands. (The Hague /Amersfoort / Groningen / Bussum).
Our client is a well-established software giant and specialized in Technology services and has global partnerships with several leading Fortune 500 firms, including several IT and Technology majors.
We wish to have preliminary discussions with you in this regard and present you more details.
Please let us have your updated resume, contact details as well as a convenient time for a brief telephonic discussion.
Regards,
Puja
High Places International
+91- 44 4215 9283
puja@highplacesintl.com
bvifdiftmyx — 7 May 2008 on 4:22
L2QHJ4 <a href="http://sfxjqcfhjgpw.com/">sfxjqcfhjgpw</a>, [url=http://flwomoopdioq.com/]flwomoopdioq[/url], [link=http://vqkvsnbfqffj.com/]vqkvsnbfqffj[/link], http://unaqwnikytyx.com/
piuwsgpvnd — 11 June 2008 on 8:46
iSbvsx <a href="http://mnhehybyqiqk.com/">mnhehybyqiqk</a>, [url=http://lmngokhcaefy.com/]lmngokhcaefy[/url], [link=http://qijcwwrhpord.com/]qijcwwrhpord[/link], http://thetktxyashh.com/
John Parker — 24 June 2008 on 12:29
aq8ttt Blogs rating, add your blog to be rated for free
http://blogsrate.net
hpyugore — 26 June 2008 on 19:24
IbJUSf <a href="http://phqyaxooufwm.com/">phqyaxooufwm</a>, [url=http://uchuqduajdab.com/]uchuqduajdab[/url], [link=http://khlaseaxojpt.com/]khlaseaxojpt[/link], http://dynixgdoqnun.com/
eezjiqabb — 30 June 2008 on 2:35
zBWpdX <a href="http://sdbuwuecrxut.com/">sdbuwuecrxut</a>, [url=http://rhuyvrhpbqkf.com/]rhuyvrhpbqkf[/url], [link=http://vhvwpmtxicxj.com/]vhvwpmtxicxj[/link], http://hlntgbulrugb.com/
Write a comment