Movies have scripts, videogames have scripts, so why not a Python script can have its own?

* Note: this post may be too nerdy technical for your taste. If you simply want to check the PasteAll.org add-on you can get it here *

Saturday night, I had a really good time and wasn’t planning to sleep any soon. After the long afternoon enjoying the scenarios of Tuscany and Florence to play Assassin’s Creed II was also out of question.

So why not “geek” out and start some personal project?

Have you ever been to PasteAll.org? It’s a very nice website used to quickly share a text, an image or a .blend file. It’s used by a lot of Blender developers and bug reporters to avoid flooding emails and IRC channels with tons of pasted lines of code. I personally use it often to share patches or python scripts and being a lazy person an exigent user never got used to all the necessary steps I need to go through to have my text shared there (it’s actually pretty simple, but well, I’m a hard to please user ;))

PasteAll.org screenshot

With no big pretensions but to amuse myself, I decided to create an add-on (a.k.a. plugin) to integrate PasteAll within Blender 2.5. This was also a good way to learn the recent changes in the API.

So how do you start with that?

First things first, let’s take a look at the page source code. A quick look for “form” shows me only a table id=”form”. I was confused. I studied HTML a lo(oooo)ng time ago. But still, I know that there must be a form element. A few lines later and we have the following enigmatic line:

<script type="text/javascript">
    document.write(scramble('cQNmr,r>=uNwt-)NS=-,i<=aN8t-qa8w>O7)u)-,awt-<Nw>QNmr-ecwaM,<PiSSt-uaw>-eca8)h=,=G)>t-uaww>8-,8ir>t-i<=aN8-,
    awt-i<=aN8>8=mG-,MiPh>t-SiM>)iS=>-,qeca8)h=,=G)>t-uaww>8-,8ir>t-)im>8=_aw-,awt-)im>8=>8=mG-,MiPh>t-0-,qecqwaMecwaM,awt->8=mG-ec),awt
    -GNhm<Nw>-ecar\,Sm<t-qar\qGNhm<Nw>7\aQ-,iP=t-RNhm,ANw>U-ecq)ec=>O=im>i,8ir>t-<Nw>-,awt-<Nw>>8=mG-,mNdSt-!X-,<NPSt-!!0-ecq=>O=im>i
    ecwaM,awt-N)=aN8S-ecwaM,awt-SiM>iS-eci,um>Qt-JiMiS<ma)=UMNawB0p-,awt-ShIra=>8=mG-,N8<Pa<Kt-ShIra=ria8Bp-,=a=P>t-ShIra=,<Nw>-ecqiecS>
    P><=,8ir>t-Pi8\hi\>_aw-,awt-Pi8\hi\>>8=mG-ecN)=aN8,MiPh>t-0-,S>P><=>wt-S>P><=>w-e"cqN)=aN8ecN)=aN8,MiPh>t-!-eiIi)cqN)=aN8ecN)=aN
    8,MiPh>t-V-ei<=aN8S<ma)=cqN)=aN8ecN)=aN8,MiPh>t-W-eiwicqN)=aN8ecN)=aN8,MiPh>t-?-ei)i<u>cqN)=aN8ecN)=aN8,MiPh>t-X-ei))P>S<ma)=cqN)=aN
    8ecN)=aN8,MiPh>t-L-eiSrcqN)=aN8ecN)=aN8,MiPh>t-.-eiS)cqN)=aN8ecN)=aN8,MiPh>t-n-eih=Na=cqN)=aN8ecN)=aN8,MiPh>t-@-eIiSucqN)=aN8ecN)=aN
    (...)

Can you see the woman in red here? </matrix jokes>. It’s a nice finding actually. The next step is not to give up followed by visiting the Javascript files used by the page to see if the scramble function is hidden somewhere … and Bingo !

Scramble

function scramble(t) {
    var s = new Array
    ('Z','U','2','h','s','X','t',')','O','w','R','l',''','J','#','I','z','6','7','!','o','K','<','q','g','3 ','a','8','G',' ',
    'A','?','Q','','M','T','(','m','9','>','H',':','V','u','S','5','=','p','x','d','Y','P' ,';','j','F','b','D','L','.','1','N',
    'k','c','/','','W','i','n','y','"','C','4','f',',','v','E','B','r','@','e');

    var n = '';
    var x = t.split('');

    $.each(x, function(i, c){
        var p = $.inArray(c, s);
        if(p < 0) {
            n = n + c;
        } else if(p == 24) {
            n = n + '\';
        } else {
            p = p + 40;
            if(p >= 80){ p = p - 80;
        }
        n = n + s[p];
    } ;
    return n;
}

To fight fire with fire

“Do snakes drink coffee?”

with some trial and error I quickly redid this code in Python  – unscramble.py. The biggest problem here was to replace the javascript function $.inArray(c,s) with the equivalent in Python – s.index(c). While javascript returns a negative number when the element is not found, Python crashes (it raises a ValueError exception). One more overcome step. The night starts to welcome the day.

<form method="post" action="/index.php" id="codeform">
    <div class="hide"><input type="hidden" name="action" id="actionentry" value="savepaste" />
        <input type="hidden" name="parent_id" id="parententry" value="0" />
    </div><div id="entry"><p id="yourcode"><img src="/img/yourcode.gif" alt="Your Code:"></p>
    <textarea name="code" id="codeentry" rows="15" cols="110"></textarea>
    <div id="options"><div id="saveas"><a href="javascript:void(0)" id="submitentry" onclick="submitmain()" title="submit code"></a>

    <select name="language_id" id="languageentry">
        <option value="0" selected="selected">-</option>
        <option value="1">abap</option><option value="2">actionscript</option><option value="3">ada</option>
        <option value="4">apache</option><option value="5">applescript</option><option value="6">asm</option>
        <option value="7">
        ( . . . )

Now let’s speed things up a little bit. I knew already how to send data to a webserver. Actually I did it last year for my first script for Blender 2.5 by the way. With the form action and the input names and parameters I was able to send a text to the server. Work done? ha ha I wish.

Planning ahead

At this point I had something working. I thought it would be nice to plan out what else I would need to do. Keep in mind that the work was not done in this order:

  1. get the selected text from Blender
  2. determines the file format/extension
  3. send it to pasteall.org
  4. gets the returned page and find out what is the addressed of the submitted text
  5. copy the address to the clipboard
  6. optionally open the page in the webbrowser
  7. user interface (U.I.)

3 was done. 4 and 6 should be pretty straightforward (it was indeed). 7 was only done after gathering some user feedback, but again, no big surprises. 2 was easy too, but I didn’t go as fancy as I wanted to (I’m only reading the file extension, instead of reading it and trying to guess). 1 and 5 would require some investigation and testing to see how to do that. For let’s talk about them.

Up or Down?

Do you select your text from top to bottom or from bottom to top? This subtle difference makes scripting a bit cumbersome. There are probably more elegant way to do that, but this is what I came up with:

def get_selected_text(self, text):
    ''''''
    current_line = text.current_line select_end_line = text.select_end_line
    current_character = text.current_character
    select_end_character = text.select_end_character
    # if there is no selected text return None
    if current_line == select_end_line:
        if current_character == select_end_character:
            return None
        else:
            return current_line.body[min(current_character,select_end_character):max(current_character,select_end_character)]
    text_return = None
    writing = False
    normal_order = True # selection from top to bottom
    for line in text.lines:
        if not writing:
            if line == current_line:
                text_return = current_line.body[current_character:] + "n"
                writing = True
                continue
            elif line == select_end_line:
                text_return =  select_end_line.body[select_end_character:] + "n"
                writing = True
                normal_order = False
                continue
        else:
            if normal_order:
                if line == select_end_line:
                    text_return += select_end_line.body[:select_end_character]
                    break
                 else:
                    text_return += line.body + "n"
                    continue
             else:
                 if line == current_line:
                     text_return += current_line.body[:current_character]
                     break
                 else:
                     text_return += line.body + "n"
                     continue
    return text_return

That function made me realize how the API of the Blender 2.5 can still use some love. Although “current” versus “select_end” makes sense, it looks way too misleading in my opinion. I will give it more thoughts, I will likely propose some change here. Also, shouldn’t this functionality be incorporated in the API? Anyways, it’s always fun to implement those things. What leads us to the last step of this work:

Script Cheating

When you can’t do something in Python how do you do? You implement it in the Blender code directly 🙂

This is how I solved the clipboard problem. For this I had a big help from Campbell Barton (fulltime developer that works for Blender Foundation and is always kind to help other coders). C memory management (pointers, malloc, free, …) still beats me once in a while.

For the curious the patch can be seen here, and it’s already in the Blender source code.

Après la Pluie . . .

If you read it until here you will be glad to know that the add-on is working perfectly and may become part of a downloadable package of scripts available in the next Blender release (2.6?). Give it a try !

PasteAll.org Add-On Blender 2.5.4svn

After going through three programming languages (Javascript/Python/C) we got to our happy end. I hope you enjoyed this inside perspective in the creation process of the script. Don’t be shy to comment on that.

Have a hacky day !
Dalai

* Note: after I finished the script I contacted the team from PasteAll.org to see if they had any problems with me sharing the script. They not only saw no problems with that but also shared my excitement with this new add-on. Thank you guys !

Related links:

3 Thoughts on “A script with a script

  1. Wow, nicely done! I’m sure this script will save some people a lot of time. =) This also really makes me want to learn Python. =P

  2. Very interesting story Dalai. I really need to dive into the 2.5 API too…

  3. Solano on May 26, 2011 at 7:17 pm said:

    Very nice! 🙂

Post Navigation