This script was written by Todd Borst, the creator of Second Life's puppeteer:
// Open Prim Animator - by Todd Borst // Extensive Modifications by SignpostMarv Martin // Note from Todd to other editors: Please document changes you have made to get proper credit. // Note from Todd to users: People may have edited the script from since I've posted it originally. // You can always view the original script by clicking the history tab. // This is provided AS IS without support. Please don't bug me demanding // help or custom work for this free script. // Summary: This is a simple prim animation script. Just add this script // to your object and a dialog will automatically pop up for you to use. // Features: // -Single script "Prim Puppeteer" like animation tool // -Playback controllable through external scripts // -Animation is scalable and resizeable // -On-touch trigger built-in // -Completely free and open sourced // License: // You are welcome to use this anyway you like, even bring to other grids // outside of Second Life. You are welcomed to sell it if you've made your // own improvements to it. This is effectively public domain. Have fun. integer COMMAND_CHANNEL = 32; integer primCount; integer commandListenerHandle = ERR_GENERIC; list posList; list rotList; list scaleList; integer currentSnapshot; integer recordedSnapshots; vector rootScale; vector scaleChange = <1.0, 1.0, 1.0>; integer maxMemory; integer freeMemory; // The values for playAnimationStyle means // 0 := no animation playing // 1 := play animation once // 2 := play animation looping integer playAnimationStyle; key op_import = "6b78fcc8-e147-4105-99a6-ff19b4bf559d"; key op_export = "7c2ca168-2b64-4836-8727-8e62b78dbd44"; key op_alter_rootScale = "f9d3389e-a78c-43f8-9e35-c11adec112a5"; // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ show_snapshot(integer snapNumber) { if (!snapNumber || recordedSnapshots < snapNumber) return; vector rootPos = llGetPos(); rotation rootRot = llGetRot(); vector pos; rotation rot; vector scale; list params; integer i = 2; do { pos = llList2Vector(posList, ((snapNumber - 1)*(primCount - 1)) + (i - 2)); rot = llList2Rot(rotList, ((snapNumber - 1)*(primCount - 1)) + (i - 2)); scale = llList2Vector(scaleList, ((snapNumber - 1)*(primCount - 1)) + (i - 2)); if ( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 ) { pos.x *= scaleChange.x; pos.y *= scaleChange.y; pos.z *= scaleChange.z; scale.x *= scaleChange.x; scale.y *= scaleChange.y; scale.z *= scaleChange.z; } params += [PRIM_LINK_TARGET, i, PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ]; if (64 < llGetListLength(params)) { llSetLinkPrimitiveParamsFast(LINK_THIS, params); params = []; } } while (++i <= primCount); if (llGetListLength(params)) { llSetLinkPrimitiveParamsFast(LINK_THIS, params); params = []; } } playAnimation(float delay, integer loop) { if (delay < 0.1) delay = 1.0; if (loop == FALSE) playAnimationStyle = 1; else playAnimationStyle = 2; if (1 <= recordedSnapshots) llSetTimerEvent(delay); } showMenuDialog() { // return; string temp = (string)((float)freeMemory/(float)maxMemory * 100.0); string menuText = "Free Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" + "\nSnapshot " + (string)currentSnapshot +" of " + (string)recordedSnapshots + "\n\n[ Record ] - Record a snapshot of prim positions" + "\n[ Play ] - Play back all the recorded snapshots" + "\n[ Publish ] - Finish the recording process" + "\n[ Show Next ] - Show the next snapshot" + "\n[ Show Prev ] - Show the previous snapshot"; llDialog(llGetOwner(), menuText, ["Record","Play","Publish","Show Prev","Show Next","Loop","Stop","Export"], COMMAND_CHANNEL); } string truncate_float(float foo) { if (foo == 0.0) return "0"; else if (foo == (float)((integer)foo)) return (string)((integer)foo); string bar = (string)foo; while (llGetSubString(bar, -1, -1) == "0") bar = llGetSubString(bar, 0, -2); if (llGetSubString(bar, -1, -1) == ".") bar = llGetSubString(bar, 0, -2); return bar; } calc_scaleChange() { if (rootScale != ZERO_VECTOR) { vector newScale = llGetScale(); if ( (newScale.x / rootScale.x) != scaleChange.x || (newScale.y / rootScale.y) != scaleChange.y || (newScale.z / rootScale.z) != scaleChange.z) { scaleChange.x = newScale.x / rootScale.x; scaleChange.y = newScale.y / rootScale.y; scaleChange.z = newScale.z / rootScale.z; } } } default { state_entry() { maxMemory = llGetFreeMemory(); freeMemory = llGetFreeMemory(); primCount = llGetNumberOfPrims(); commandListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), ""); showMenuDialog(); rootScale = llGetScale(); if (llGetInventoryType("OPA Notecard Import - 2011-11-03") == INVENTORY_SCRIPT){ llResetOtherScript("OPA Notecard Import - 2011-11-03"); } } // Feel free to remove this on-touch trigger if you are using your own script to control playback // touch_start(integer num_detected) // { // if (commandListenerHandle == ERR_GENERIC) // { // if (playAnimationStyle == 0) // playAnimation(1.0,TRUE); // else // { // playAnimationStyle = 0; // llSetTimerEvent((float)FALSE); // } // } // } changed(integer change) { if (change & CHANGED_SCALE) calc_scaleChange(); if (change & CHANGED_LINK) { if ( primCount != llGetNumberOfPrims() ) { llOwnerSay("Link change detected, reseting script."); llResetScript(); } } } //The message link function is to allow other scripts to control the snapshot playback //This command will display snapshot #2: // llMessageLinked(LINK_ROOT, 2, "XDshow", NULL_KEY); llSleep(1.0); // //This command will play through all the recorded snapshots in ascending order. The number "1.0" is the delay speed and can be changed. // llMessageLinked(LINK_ROOT, 0, "XDplay", "1.0"); // //This command will loop through all the recorded snapshots in ascending order. The number "1.0" is the delay speed and can be changed. // llMessageLinked(LINK_ROOT, 0, "XDplayLoop", "1.0"); // //To stop any playing animation use // llMessageLinked(LINK_ROOT, 0, "XDstop", NULL_KEY); link_message(integer sender_num, integer num, string str, key id) { if ("XDshow" == str && 1 <= num && num <= recordedSnapshots) show_snapshot(num); else if ("XDplay" == str) { currentSnapshot = 1; float delay = (float)((string)id); playAnimation(delay, FALSE); } else if ("XDplayLoop" == str) { float delay = (float)((string)id); playAnimation(delay, TRUE); } else if ("XDstop" == str) { playAnimationStyle = 0; llSetTimerEvent((float)FALSE); } else if ("XDexport" == str && !num) { list export = []; string foo; vector bar; rotation baa; string baz; integer i = 2; integer j = primCount; do export += [llGetLinkName(i)]; while (++i <= j); llMessageLinked(sender_num, 1, llDumpList2String(export,"|") , op_export); export = []; i = 0; j = llGetListLength(posList); do { bar = llList2Vector(posList,i); export += ["<" + truncate_float(bar.x) + "," + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"]; } while (++i < j); llMessageLinked(sender_num, 2, llDumpList2String(export,"|") , op_export); export = []; i = 0; j = llGetListLength(rotList); do { baa = llList2Rot(rotList,i); export += ["<" + truncate_float(baa.x) + "," + truncate_float(baa.y) + "," + truncate_float(baa.z) + "," + truncate_float(baa.s) + ">"]; } while (++i < j); llMessageLinked(sender_num, 3, llDumpList2String(export,"|") , op_export); export = []; i = 0; j = llGetListLength(scaleList); do { bar = llList2Vector(scaleList,i); export += ["<" + truncate_float(bar.x) + "," + truncate_float(bar.y) + "," + truncate_float(bar.z) + ">"]; } while (++i < j); llMessageLinked(sender_num, 4, llDumpList2String(export,"|") , op_export); } else if ("XDmenu" == str) { showMenuDialog(); } else if ("XDimportLength" == str && 0 < num) { list foo; list bar; integer i; do { foo += [ZERO_VECTOR]; bar += [ZERO_ROTATION]; } while (++i < num); posList = foo; scaleList = foo; rotList = bar; llMessageLinked(sender_num,-1,str,op_import); recordedSnapshots = num / (llGetNumberOfPrims() - 1); llMessageLinked(LINK_SET, recordedSnapshots, "XDrecordedSnapshots", NULL_KEY); currentSnapshot = 1; } else if ("XDrecordedSnapshots" == str && num == -1) { llMessageLinked(sender_num,recordedSnapshots,str,NULL_KEY); } else if (id == op_import && 0 <= num) { list params = llParseString2List(str, ["|"], []); vector impPos = (vector)llList2String(params, 0); rotation impRot = (rotation)llList2String(params, 1); vector impSize = (vector)llList2String(params, 2); posList = llListReplaceList(posList, [impPos], num, num); rotList = llListReplaceList(rotList, [impRot], num, num); scaleList = llListReplaceList(scaleList, [impSize], num, num); } else if (id == op_alter_rootScale) { rootScale = (vector)str; calc_scaleChange(); } } listen(integer channel, string name, key id, string message) { list parsedMessage = llParseString2List(message, [" "], []); string firstWord = llToLower(llList2String(parsedMessage, 0)); string secondWord = llToLower(llList2String(parsedMessage, 1)); if ("show" == firstWord && recordedSnapshots > 0) { llSetTimerEvent((float)FALSE); if (secondWord == "next") { ++currentSnapshot; if (recordedSnapshots < currentSnapshot) currentSnapshot = 1; show_snapshot(currentSnapshot); } else if (secondWord == "prev") { --currentSnapshot; if (currentSnapshot < 1) currentSnapshot = recordedSnapshots; show_snapshot(currentSnapshot); } else { currentSnapshot = (integer)secondWord; if (currentSnapshot && currentSnapshot <= recordedSnapshots) { show_snapshot(currentSnapshot); llOwnerSay("Showing snapshot: " + (string)currentSnapshot); } else { llOwnerSay("Invalid snapshot number given: " + (string) currentSnapshot + "\nA valid snapshot number is between 1 and " + (string) recordedSnapshots); currentSnapshot = 1; } } } else if (firstWord == "record") { vector rootPos = llGetPos(); integer i = 2; do { vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]), 0); pos.x -= rootPos.x; pos.z -= rootPos.z; pos.y -= rootPos.y; pos = pos / llGetRot(); posList += pos; rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]), 0); rot = rot / llGetRot(); rotList += rot; scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]), 0); } while (++i <= primCount); ++recordedSnapshots; llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots); freeMemory = llGetFreeMemory(); } else if (firstWord == "play") { float delay = (float)secondWord; currentSnapshot = 1; playAnimation(delay, FALSE); } else if ("publish" == firstWord) { llSetTimerEvent((float)FALSE); playAnimationStyle = 0; currentSnapshot = 1; llListenRemove(commandListenerHandle); commandListenerHandle = -1; llOwnerSay("Recording disabled. Publish complete.\nClick me to toggle animation on/off."); } else if ("loop" == firstWord) { llMessageLinked(LINK_THIS, 0, "XDplayLoop", NULL_KEY); } else if ("stop" == firstWord) { llMessageLinked(LINK_THIS, 0, "XDstop", NULL_KEY); } else if ("export" == firstWord) { llOwnerSay("Should be exporting"); llMessageLinked(LINK_THIS, 0, "XDexport", NULL_KEY); } if (commandListenerHandle != ERR_GENERIC) showMenuDialog(); } timer() { show_snapshot(currentSnapshot); if (currentSnapshot < recordedSnapshots) ++currentSnapshot; else { if (playAnimationStyle == 2) currentSnapshot = 1; else { llSetTimerEvent((float)FALSE); if (commandListenerHandle != ERR_GENERIC) showMenuDialog(); } } } }