[Closed] Real time stepped mode script?
There is a script for maya that does stepped mode animation without having to chage your f-curves tangents and I would like to use the same kind of script for 3dsmax.
http://www.creativecrash.com/maya/downloads/scripts-plugins/animation/c/abxplaykeys
Is there such a thing? If not, I’d like to make one and I’d like to know where to start. I’ve done a bit of maxscript previously but I’m not sure where I would start with this one.
thanks a lot.
EDIT:
My attempt at it is available here: http://www.scriptspot.com/3ds-max/stepped-playback
check MXS Help:
Trackbar – trackbar.getPreviousKeyTime() – trackbar.getNextKeyTime()
and
Trackbar Filter Callback Function Examples
and
Timer UI Control
Scenario:
- filter keys in trackbar to jump through
- activate timer control to jump through keys forward (or backward) and control playback speed (or delays)
- check state of your “play animation” mode (or esc pressed) to cancel the mode.
I’ve done something tonight which is a start, I guess. But I’m having problems with the timing. the timer requires an interval in milliseconds, but it seems to be very imprecise. Or maybe I’m not understanding something.
If someone wants to look at it and let me know why the timing is wrong, I’d appreciate. The script does what it’s supposed to do, albeit it’s very basic. You select an object and it scroll through the keys. Playing it ‘stepped’ style.
I’m using the simple 1000 / frameRate equation to calculate the interval. So with a frameRate of 30fps, 1000 milliseconds / 30 = 33.3334 milliseconds intervals, but the animation always ends up slower. Plus the timer requires an integer only, so it rounds it to 33 milliseconds intervals.
the script:
aSpeed = timeConfiguration.PlaybackSpeed;
aStart = animationRange.start;
aEnd = animationRange.end;
tInterval = 1000 / frameRate as integer;
-- fr = frameRate;
-- tf = ticksPerFrame;
tBar = maxOps.trackbar;
nextK = at time sliderTime tBar.getNextKeyTime();
firstK = at time aStart tBar.getNextKeyTime();
-- get wait time between keys (as frames)
function getWait =
(
if ((nextK.frame > aEnd.frame) OR (nextK.frame - sliderTime < 0)) then
(
waitTime = aEnd - sliderTime;
waitTime = waitTime + (firstK - aStart);
)
else waitTime = nextK - sliderTime;
return waitTime;
)
UI
rollout steppingUI "Stepping"
(
timer clock "Clock" interval:tInterval;
label label "0";
label frame "";
local temp = 0;
on clock tick do
(
nextK = at time sliderTime tBar.getNextKeyTime();
firstK = at time aStart tBar.getNextKeyTime();
timeToWait = getWait();
temp = temp+1;
label.text = temp as string;
if (temp == timeToWait) then
(
if (nextK.frame > aEnd.frame) then ( sliderTime = firstK; frame.text = firstK as string; temp=0;)
else
(
frame.text = nextK as string;
sliderTime = nextK;
temp=0;
)
)
)
)
createDialog steppingUI;
Well, I updated it, it does work, but as I previously said the timer is rather imprecise in maxscript. The timer function in MEL seems 10 times more powerful.
If anyone has experience using the timer in maxscript and would like to give me some tips to use it with more precision, I’d love that
here it is, finally stepped keys with ze Biped! It’s still very crude. Just select one or more animated object and as long as you see keys on your trackbar you’re good.
tBar = maxOps.trackbar;
aStart = animationRange.start;
aEnd = animationRange.end;
nextK = at time sliderTime tBar.getNextKeyTime();
firstK = at time (aStart - 1f) tBar.getNextKeyTime();
lastK = at time aEnd tbar.getPreviousKeyTime();
tInterval = 1000 / frameRate as float;
-- get wait time between keys (as frames)
function getWait =
(
if ((nextK.frame >= aEnd.frame) OR (nextK.frame - sliderTime < 0)) then
(
waitTime = aEnd - sliderTime;
if (firstK != aStart) then waitTime = waitTime + (firstK - aStart);
)
else waitTime = nextK - sliderTime;
return waitTime;
)
-- UI
rollout steppingUI "Stepping"
(
timer clock "Clock" interval:(floor(tInterval * 0.95) as integer); -- the higher this number, the SLOWER it'll playback. 0.94 seems right for 15 FPS and 0.95 for 30 FPS.
label label "0";
label frame "";
on clock tick do
(
nextK = at time sliderTime tBar.getNextKeyTime();
timeToWait = getWait();
-- print clock.ticks;
label.text = clock.ticks as string;
if (clock.ticks == timeToWait) then
(
if (nextK.frame >= aEnd.frame) then ( sliderTime = firstK; frame.text = firstK as string; clock.ticks = 0;)
else
(
frame.text = nextK as string;
sliderTime = nextK;
clock.ticks = 0;
)
)
)
)
createDialog steppingUI;
-- (floor(tInterval * 0.95) as integer);
your delay calculation is wrong.
interval for the timer ticking must be <key_distance_in_frames>*1000./<frame_rate>
also you don’t need to wait inside timer tick event.
scenario should be:
0. use your own checkbutton <play> (or macroscript button)
- on button <play> changed to ON calculate interval for timer and store next frame
- set timer.active ON and its interval for delay
- on timer tick set active OFF, jump on precalculeted next frame, calculate next frame and delay, set new interval, set active ON
- on button <play> changed to OFF set timer.active OFF
honestly i’ve already wrote this script.
also you have to take into account “view redraw” time. You can use Viewport Redraw Callback Mechanism(see MXS help for details)
I kind of guessed you did something similar, considering you told me exactly what to look for and how to approach it
I can see how I was using the timer wrongly. Funny, I found out about registerTimeCallback (that’s the one you’re talking about, right?) an hour ago and was testing it out.
That said, I’d love to see your version…
here is a light version (without Viewport Redraw Callbacks):
try(destroydialog keyPlayRol) catch()
global spSettings = if spSettings == undefined then #(off, 60, on, 1) else spSettings
rollout keyPlayRol "Key Playback by denisT"
(
group "Frame Rate:"
(
label curr_lb "Current: " across:2 align:#right offset:[21,0] enabled:(not spSettings[1])
spinner curr_sp "" enabled:off range:[1,1000,frameRate] type:#integer fieldwidth:56 offset:[1,0]
checkbox cust_ch "" across:2 checked:spSettings[1] width:15 offset:[0,-1]
spinner cust_sp "Use Custom Rate: " range:[1,1000,spSettings[2]] type:#integer fieldwidth:56 enabled:spSettings[1] offset:[1,0]
)
group "Playback:"
(
checkbox loop_ch "Loop" across:2 checked:spSettings[3] enabled:off
radiobuttons dir "Direction:" labels:#("Forward", "Reverse") columns:2 default:spSettings[4] align:#left offset:[-36,1]
checkbutton play "Play Animation" width:180 offset:[0,4]
)
local redraw = on
local next
timer play_tm active:off
on cust_ch changed state do curr_lb.enabled = not (cust_sp.enabled = spSettings[1] = state)
on cust_sp changed val do spSettings[2] = val
on loop_ch changed state do spSettings[3] = state
on dir changed state do spSettings[4] = state
fn getDelay =
(
next = case spSettings[4] of
(
1: if (f = trackbar.getNextKeyTime()) != undefined and f != currenttime do
(
delay = if f < currenttime then (animationrange.end - currenttime) + (f - animationrange.start)
else (f - currenttime)
#(delay, f)
)
2: if (f = trackbar.getPreviousKeyTime()) != undefined and f != currenttime do
(
delay = if f > currenttime then (currenttime - animationrange.start) + (animationrange.end - f)
else (currenttime - f)
#(delay, f)
)
)
if next != undefined then
(
rate = if spSettings[1] then spSettings[2] else frameRate
next[1] = next[1]*1000./rate
next
)
else undefined
)
fn startPlay =
(
play_tm.interval = next[1]
play_tm.active = on
)
fn stopPlay =
(
play.state = play_tm.active = off
next = undefined
)
on play changed state do
(
if state and not isAnimPlaying() and (next = getDelay()) != undefined then startPlay()
else stopPlay()
)
on play_tm tick do
(
-- format "next: %
" next
if play.state and next != undefined then
(
play_tm.active = off
slidertime = next[2]
if (next = getDelay()) != undefined then startPlay() else stopPlay()
)
else stopPlay()
)
on keyPlayRol close do play_tm.active = off
on keyPlayRol open do ()
)
createdialog keyPlayRol width:200 height:166
Thanks a lot Denis, I’ll learn from it. Callbacks are a tad obscure to me right now, I have some reading to do.
Welllllllllllll…
I completed it and managed to get everything working. But I’m finding out the timer method isn’t working well enough. It’s not a fast enough solution when used with getNextKeyTime(). Even with a timer of 1 millisecond the playback lags.
I need to find a better method.
Btw, I still don’t understand how viewport redraw callbacks would help in this situation. As I understand they’re called whenever the viewports are refreshed. It doesn’t really help with making things faster. I used timeStamp() to calculate viewports redraw.
Here is it:
-- Jean-Sebastien Nicaise
-- with the help of denisT of [cgsociety.org]( http://cgsociety.org/ )'s forums
-- [email]jsnicaise@gmail.com[/email]
-- Dec. 16, 2009 23:00
---------------------------------------------------------------------------------
-- This script plays back the animation in stepped mode
--
-- something to remember: when playing, the last frame is not
-- played, as it is when playing an animation with
-- 3ds max.
--
-- also: it is not as precise as normal stepped mode playback.
-- It will slow down the more keys you have and the larger
-- the animation range is.
---------------------------------------------------------------------------------
-- display for debug: display all keys inside trackbar
-- display delay (in frames) for each keys
try (destroyDialog steppingUI) catch()
-- spSettings[1] = Current/Custom FPS on/off
-- spSettings[2] = Custom FPS integer
-- spSettings[3] = Loop on/off
-- spSettings[4] = 1 = forward, 2 = backward
global spSettings = if spSettings == undefined then #(off, 60, on, 1) else spSettings
-- UI
rollout steppingUI "Stepped Keys"
(
group "Frame Rate:"
(
label curr_lb "Current: " across:2 align:#right offset:[21,0] enabled:(not spSettings[1]);
spinner curr_sp "" enabled:off range:[1,1000,frameRate] type:#integer fieldwidth:56 offset:[1,0];
checkbox cust_ch "" across:2 checked:spSettings[1] width:15 offset:[0,-1];
spinner cust_sp "Use Custom Rate: " range:[1,1000,spSettings[2]] type:#integer fieldwidth:56 enabled:spSettings[1] offset:[1,0];
)
group "Playback:"
(
checkbox loop_ch "Loop" across:2 checked:spSettings[3] enabled:true;
radiobuttons dir "Direction:" labels:#("Forward", "Reverse") columns:2 default:spSettings[4] align:#left offset:[-36,1];
checkbutton play "Play Animation" width:180 offset:[0,4];
)
timer playTm active:false;
------------------------------
-- functions set
------------------------------
-- REWORKED
-- returns an array of keys for the selected object(s). The values inside the array are Time classes. (-12f, 8f, etc.)
function getKeys dir =
(
if $ != undefined then
(
a = #();
case dir of
(
1:
if (nextK = at time currentTime trackbar.getNextKeyTime()) == undefined and (at time (currentTime - 1f) trackbar.getNextKeyTime()) != undefined then
(
append a (at time (currentTime - 1f) trackbar.getNextKeyTime());
)
else
(
while (findItem a nextK) == 0 and nextK != undefined do
(
append a nextK;
nextK = at time nextK trackbar.getNextKeyTime();
)
if a.count == 0 then return undefined;
else return a;
)
2:
if (nextK = at time currentTime trackbar.getPreviousKeyTime()) == undefined and (at time (currentTime + 1f) trackbar.getPreviousKeyTime()) != undefined then
(
append a (at time (currentTime + 1f) trackbar.getPreviousKeyTime());
)
else
(
while (findItem a nextK) == 0 and nextK != undefined do
(
append a nextK;
nextK = at time nextK trackbar.getPreviousKeyTime();
)
if a.count == 0 then return undefined;
else return a;
)
)
)
)
-- REWORKED
-- filter an array of keys depending if keys are inside given range (inclusively)
function filterKeys arr start end =
(
a = #();
if arr != undefined then
(
for i in 1 to arr.count by 1 do
(
if arr[i] < start or arr[i] > end then ()
else append a arr[i];
)
)
if a.count == 0 then return undefined;
else return a;
)
-- REWORKED
-- returns true if it finds val value inside arr array, false if not and undefined if arr is undefined
function isInside arr val =
(
if arr != undefined then
(
if (r =(findItem arr val)) != 0 then return true;
else return false;
)
)
-- REWORKED
-- returns the previous key of given key within array
function returnPrev arr key =
(
if key != undefined and arr != undefined and arr.count > 1 and (isInside arr key) then
(
if ((findItem arr key) - 1) < 1 then return arr[arr.count];
else return arr[((findItem arr key) - 1)];
)
)
-- REWORKED
-- returns the next key of given key within array
function returnNext arr key =
(
if key != undefined and arr != undefined and arr.count > 1 and (isInside arr key) then
(
if ((findItem arr key) + 1) > arr.count then return arr[1];
else return arr[((findItem arr key) + 1)];
)
)
-- REWORKED
-- returns wait time in frames between two frames, depending of the given animation range and the direction the animation is going
-- dir = 1 = forward, 2 = backward
function getDelay arr first second start end dir =
(
if first != undefined and second != undefined and arr != undefined then
(
case dir of
(
1: if (isInside arr first) and (isInside arr second) then
(
if first > second then
(
wait = (end - first) + (second - start);
)
else if first < second then
(
wait = second - first;
)
else wait = undefined;
)
2: if (isInside arr first) and (isInside arr second) then
(
if first > second then
(
wait = first - second;
)
else if first < second then
(
wait = (first - start) + (end - second);
)
else wait = undefined;
)
)
return wait;
)
)
------------------------------
-- END functions set
------------------------------
-- format "next: %
" next
local playArray = #();
local nextDelay;
local nextK;
local fps;
local steps;
local t2;
local t;
-- UI checkboxes, sliders and radio buttons events
on cust_ch changed state do
(
curr_lb.enabled = not (cust_sp.enabled = spSettings[1] = state);
if spSettings[1] then fps = spSettings[2]; else fps = frameRate;
)
on dir changed state do
(
case (spSettings[4] = state) of
(
1: playArray = filterKeys (getKeys 1) animationRange.start (animationRange.end - 1);
2: playArray = filterKeys (getKeys 2) (animationRange.start + 1) (animationRange.end);
)
)
on cust_sp changed val do fps = spSettings[2] = val;
on loop_ch changed state do spSettings[3] = state;
-- main
function startPlay =
(
d = (nextDelay.frame) * 1000 / fps
if t != undefined and t2 != undefined then
(
if (d - (t2 - t)) <= 0 then playTm.interval = 1;
else playTm.interval = d - (t2 - t);
)
else playTm.interval = d;
playTm.active = true;
print (format "wait time for next key: %
" PlayTm.interval);
)
fn stopPlay =
(
play.state = playTm.active = false;
playTm.ticks = 0;
nextK = undefined;
nextDelay = undefined;
)
-- to do: optimize
on play changed state do
(
case spSettings[4] of
(
1: if state and not isAnimPlaying() and (playArray = filterKeys (getKeys 1) animationRange.start (animationRange.end - 1)) != undefined then
(
if spSettings[1] then fps = spSettings[2]; else fps = frameRate;
sliderTime = playArray[1];
if (nextK = returnNext playArray currentTime) != undifined and (nextDelay = getDelay playArray currentTime nextK animationRange.start animationRange.end 1) != undefined then startPlay(); else stopPlay();
steps = playArray.count;
)
else stopPlay();
2: if state and not isAnimPlaying() and (playArray = filterKeys (getKeys 2) (animationRange.start + 1) (animationRange.end)) != undefined then
(
if spSettings[1] then fps = spSettings[2]; else fps = frameRate;
sliderTime = playArray[1];
if (nextK = returnNext playArray currentTime) != undifined and (nextDelay = getDelay playArray currentTime nextK animationRange.start animationRange.end 2) != undefined then startPlay(); else stopPlay();
steps = playArray.count;
)
else stopPlay();
)
)
on playTm tick do
(
if play.state then
(
if not(spSettings[3]) and playTm.ticks >= steps then stopPlay();
else
(
playTm.active = false;
t = timeStamp();
slidertime = nextK;
t2 = timeStamp();
if (nextK = returnNext playArray currentTime) != undefined and (nextDelay = getDelay playArray currentTime nextK animationRange.start animationRange.end spSettings[4]) != undefined then startPlay(); else stopPlay();
print (format "It took this time for render: %
" (t2 - t));
)
)
else stopPlay();
)
on steppingUI open do ()
on steppingUI close do ( playTm.active = false; )
)
createDialog steppingUI width:200 height:166;