tclvmd

Use proc from inside another proc on TCL


I'm trying to write a script in tcl to execute some analysis on VMD. First I'm creating an atom selection inside a procedure, then I'm trying to use it on another procedure.

The atom selection from VMD is implemented as a Tcl function. The data returned from atomselect is the name of the function to use. You can read more here.

When I create the atom selection outside a proc I can use it inside one just passing it as global. But now that I'm creating it inside one, when I try to use it it's just a string with the procedure name.

Resuming,

This works:

set all [atomselect top all]

proc test {} {
    global all
    $all num
}

test # Outuputs 7111

But this don't:

proc create {} {
    global all
    set all [atomselect top all]
}

proc test {} {
    global all
    $all num
}

create
test # Outputs -> invalid command name "atomselect1"

Solution

  • The problem is that the command created by the atomselect command is being deleted when the procedure returns. I strongly suspect that this is being done using a local variable deletion trace (on some variable that is otherwise unused). When atomselect is run in the global scope, there's no such natural deletion event (unless the global namespace is deleted, and that's an interpreter-ending event) so the trace never gets called and the created command persists indefinitely.

    If we rewrite your create like this:

    proc create {} {
        global all
        # Double quotes just because of the highlighting here
        set all [uplevel "#0" atomselect top all]
    }
    

    then the atomselect runs in the global scope (and the created command should persist) but all the rest of your code is in your procedure.

    More properly, it should be done like this:

    proc create {} {
        global all
        # Double quotes just because of the highlighting here
        set all [uplevel "#0" [list atomselect top all]]
        # Or this, to better show that we're talking about running a command:
        #   set all [uplevel "#0" [list \
        #       atomselect top all]]
    }
    

    This becomes far more important when you pass arguments with spaces (or other metasyntax characters) in, as list not only produces lists, but also substitution-free commands. (In fact, building commands and command fragments is absolutely one of the main uses of the list command; actual lists for ordinary data storage and manipulation tend to be made with some other command.)