skimi3d Grid Log in Visit the grid
All scripts
Change how it looks

Texture Changer

Put several textures in a prim and click through them, or pick one from a menu. Adding another is dropping another in.

Goes inThe prim whose face should change
Setup6 to set
Code242 lines
Open toOpen to everyone

What it does

It reads the textures out of the prim's own Contents and puts one on the face you choose. One click moves to the next, or a menu lets people pick by name, and above nine textures the menu pages itself. When you drop a new texture in, it keeps showing the one that was already up rather than jumping, because it remembers the current texture by its name and not by its position in a list that has just been reordered.

What you can build with it

A picture frame with a dozen paintings in it, the wall colour of a house you are selling, a poster board that changes with the season, a shop sign, a demo of a texture set you are selling, a menu board outside a cafe. With WHOLE_LINKSET a wall built from six prims redecorates in one click.

How to use it

1 Drop the script into the prim, then drop your textures into the same prim's Contents.
2 Touch it. Each touch moves to the next texture.
3 If the whole prim changes and you only wanted the front, put that face's number into FACE instead of -1.
4 Name your textures 1 Oak, 2 Pine, 3 Ash if the order matters. Contents is kept alphabetically, so the numbers do the sorting.
5 Set MODE to 1 if people should pick by name from a menu instead of clicking through.

Settings you can change

6

Edit these at the top of the script. Everything works on the defaults · change them only when you need to.

FACE Which face changes. -1 means every face; give a single face number to change the front of a picture frame and leave the wood alone. -1
MODE 0 for one click, next texture. 1 for a menu so people can pick by name. 0
WHOLE_LINKSET Change every prim in the linkset rather than just this one. For a wall built from six prims that should redecorate together. FALSE
START_AT Which texture it starts on, counting from 0. Contents is alphabetical, so naming them 1 Oak, 2 Pine puts them in the order you meant. 0
ANNOUNCE Say the name of each texture as it comes up. Handy while setting up, noisy afterwards. FALSE
WHO_MAY_CHANGE 0 for everyone, 1 for the owner alone, 2 for anyone wearing the same group tag. 0
The code LSL · 242 lines · updated 8 Sep 2026 Download .lsl
// =====================================================================
//  Texture Changer
//  skimi3d script library
//  https://grid.skimi3d.com/scripts/texture-changer
//
//  Put several textures in a prim and click through them. The wall
//  colour of a house you are selling, the poster in a frame, the
//  painting over the fireplace, the sign outside a shop that changes
//  with the season.
//
//  It reads the textures out of the prim's own Contents, so adding one
//  more is dropping one more in. Nothing to edit.
//
//  Plain LSL.
// =====================================================================


// ---------------------------------------------------------------------
//  Settings
// ---------------------------------------------------------------------

// Which face changes. -1 means every face of the prim. Give a single
// face number to change only the front of a picture frame and leave the
// wood alone.
integer FACE = -1;

// How it is chosen:
//   0 = one click, next texture
//   1 = a menu, so people can pick by name
integer MODE = 0;

// Change every prim in the linkset rather than just this one. Useful
// for a wall built from six prims that should redecorate together.
integer WHOLE_LINKSET = FALSE;

// Which texture it starts on, counting from 0. Contents is kept in
// alphabetical order, so naming them 1 Oak, 2 Pine, 3 Ash puts them in
// the order you meant.
integer START_AT = 0;

// Say the name of each texture as it comes up. Handy while you are
// setting up, noisy afterwards.
integer ANNOUNCE = FALSE;

// Who may change it. 0 = everyone, 1 = only the owner, 2 = same group.
integer WHO_MAY_CHANGE = 0;


// ---------------------------------------------------------------------
//  Everything below here is the machinery
// ---------------------------------------------------------------------

float PATIENCE = 60.0;      // how long a menu waits for an answer
integer PER_PAGE = 9;       // a dialog holds nine buttons and no more

list    gTextures = [];
integer gAt       = 0;
integer gChannel  = 0;
integer gListen   = 0;
integer gPage     = 0;


list findTextures()
{
    list out = [];
    integer n = llGetInventoryNumber(INVENTORY_TEXTURE);
    integer i;

    for (i = 0; i < n; ++i)
    {
        out += [llGetInventoryName(INVENTORY_TEXTURE, i)];
    }

    return out;
}


show(integer at)
{
    integer n = llGetListLength(gTextures);
    if (n == 0) return;

    // Wrap both ways, so a click past the end comes back to the start.
    while (at < 0)  at += n;
    while (at >= n) at -= n;
    gAt = at;

    string tex = llList2String(gTextures, at);

    integer link = LINK_THIS;
    if (WHOLE_LINKSET) link = LINK_SET;

    llSetLinkPrimitiveParamsFast(link, [PRIM_TEXTURE, FACE, tex,
                                        <1.0, 1.0, 0.0>, ZERO_VECTOR, 0.0]);

    if (ANNOUNCE) llOwnerSay("Now showing: " + tex);
}


integer privateChannel(key who)
{
    return -1 - (integer)("0x" + llGetSubString((string)who, 0, 6));
}


list page(integer n)
{
    integer total = llGetListLength(gTextures);
    if (total <= PER_PAGE) return gTextures;

    integer room  = PER_PAGE - 2;
    integer pages = (total + room - 1) / room;

    if (n < 0) n = pages - 1;
    if (n >= pages) n = 0;
    gPage = n;

    integer from = n * room;
    integer to   = from + room - 1;
    if (to > total - 1) to = total - 1;

    return llList2List(gTextures, from, to) + ["< Back", "Next >"];
}


menu(key who, integer n)
{
    if (gListen) llListenRemove(gListen);

    gChannel = privateChannel(who);
    gListen  = llListen(gChannel, "", who, "");

    llDialog(who, "Which one?", page(n), gChannel);
    llSetTimerEvent(PATIENCE);
}


shut()
{
    if (gListen) llListenRemove(gListen);
    gListen = 0;
    gPage   = 0;
    llSetTimerEvent(0.0);
}


integer mayChange(key who)
{
    if (WHO_MAY_CHANGE == 1) return who == llGetOwner();
    if (WHO_MAY_CHANGE == 2) return llSameGroup(who);
    return TRUE;
}


default
{
    state_entry()
    {
        shut();
        gTextures = findTextures();

        if (llGetListLength(gTextures) == 0)
        {
            llOwnerSay("I have no textures in my Contents. Drop some in and I will start working.");
            return;
        }

        show(START_AT);
    }

    touch_start(integer total)
    {
        key who = llDetectedKey(0);

        if (!mayChange(who))
        {
            llRegionSayTo(who, 0, "This one is not yours to change.");
            return;
        }

        if (llGetListLength(gTextures) == 0)
        {
            llRegionSayTo(who, 0, "There is nothing here to show yet.");
            return;
        }

        if (MODE == 1)
        {
            menu(who, 0);
            return;
        }

        show(gAt + 1);
    }

    listen(integer channel, string name, key who, string message)
    {
        if (message == "Next >")
        {
            menu(who, gPage + 1);
            return;
        }

        if (message == "< Back")
        {
            menu(who, gPage - 1);
            return;
        }

        shut();

        integer at = llListFindList(gTextures, [message]);
        if (at != -1) show(at);
    }

    timer()
    {
        shut();
    }

    changed(integer what)
    {
        // Dropping another texture in should be all it takes. Holding
        // the current one by NAME rather than by number means the
        // picture on the wall does not jump when a new texture sorts
        // itself in ahead of it.
        if (what & CHANGED_INVENTORY)
        {
            string was = llList2String(gTextures, gAt);
            gTextures = findTextures();

            integer at = llListFindList(gTextures, [was]);
            if (at == -1) at = 0;
            gAt = at;
        }
    }

    on_rez(integer start)
    {
        llResetScript();
    }
}

Copy it, make a new script inworld, paste over what is there and save. Nothing here phones home.