• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Using regular expressions to find and delete keyframes

Status
Not open for further replies.
Level 29
Joined
Jul 29, 2007
Messages
5,174
I don't know if people here will actually bother with something like this, but here goes.
Hopefully this will help someone.


I have seen multiple times now people asking how to delete all of the keyframes of an animation (since Magos does not do this).

While I was requested in the past to make a tool that deletes animations properly, I never did, oops!

So instead, here's a really short example of automating the process with a text editor, your model in text format, and the power of regular expressions.

NOTE: you will probably need to toggle regular expressions in your text editor, since they tend to be disabled by default.
Pretty much every text editor has this option (among others like case-sensitive searches etc.) in the same window as the Search & Replace.


Here we begin.

Suppose you have a very repetitive text, and you need to make a change to it, and now you need to go line by line and edit the same boring text over and over.
In this case, you'd probably (hopefully) think about the solution - use the Search & Replace command that exists in about every text editor in existence.
So far so good, but what if you have repetitive data, but the data isn't quite the same, just very similar with some known pattern.
For instance, let's say that for each row of your text file, you have the text "Hello1", where 1 is replaced with the number of the row.
Now let's say we want to change that to "Hello1Bye", again with the 1 being the line of the number.
A normal search and replace will not help you - what do you search for, and what do you replace it with?

Welcome to regular expressions.

Regular expressions are a fancy term for pattern searching.
Instead of searching for an exact character, word, sentence, or whatever it is you are doing, you search for patterns.
For the example above, the pattern will be, written in English, "Find the word Hello followed by a number, and replace it with the word Hello, followed by the number that was found, followed by the word Bye".
Notice that there are two operations here, the search, which can also save results that you are interested in (in this case the number!), and then the replace operation, which can use the results saved in the search operation if you desire.

Now let's see how that will look with actual regular expressions.
The search term will be "Hello(\d+)", and the replacement term will be "Hello\\1Bye".
The red text is regular text, nothing special about it.
Brackets have a couple of uses, but the main one, and the one used above, is group capturing.
Group capturing is what I described above as saving results - we save whatever is inside the brackets, and later on, in the replacement term, we can refer to it as \\1, where the 1 here stands for the first captured group.
NOTE: \\1 is used to refer to captured groups in many text editors, however not in all of them. Some use \1, some &1, and still there are more variations.
The \d part is a shortcut that says match any number, while the plus after it means that for a successful match, there MUST be 1 or more numbers.

Now for the actual meat - matching all of the keyframes of an animation in MDL files, and deleting them.

To know what our pattern is, we must obviously first look at what we're trying to match, so let's take a simple keyframe as the first example.

"8700: 0.000000,"

Now, there are all sorts of ways to match this - some more specific, some more generic, it all depends on your needs.
For our needs, we in fact don't care at all about the data (0.000000) or anything else (colon, space, or comma).
We only care about matching the frame number, but we still do need the data, since we want to delete it!
Because of this, in this case it's better to not try to match something perfectly (e.g. matching numbers, vectors, etc.), but rather match the frame number, and just any arbitrary text that follows it.

Following that logic, let's highlight it!

"8700: 0.000000,", and the regular expression that matches it "8700:.*"
The numbers are just plain numbers.
The dot here stands for any character. Without getting too much into it (it's more complicated, but not very important for our use), it will match any letter, number, whitespaces, etc.
The asterisk is closely related to plus, instead requiring 0 or more of the thing you are attaching it too, in this case any letter.
Note that I still add the colon. I do this, because I don't want to accidentally match something entirely different that just happens to have the number "8700" in it (e.g. a vertex position!).

Let's get a little more smart, and select all of the keyframes in some arbitrary range, such as 3300-4300.
Fake data incoming!
"3300: Hello,
3954: Hi,
4000: Bye"
Truly a keyframe conversation on the level of AIs!

The following expression might look a bit complicated at first, but I'll explain it step by step.

"(3[3-9]\d\d|4[0-3]\d\d):.+"

The ending is the usual "grab whatever follows the frame, I don't care", but frame matching got more interesting.
First of all, if you look in the center, you can see the pipe character.
When pipes are used in group capturing (the brackets), they are used as OR statements.
So in this expression, we are saying that either one of the sides of the pipe fulfill a successful match.
Both sides are very similar, for obvious reasons - they simply select a number.
They both use two new concepts - matching groups, and ranges.
If you have a set of things that you want to match against, but others that you don't, you use matching groups.
For instance, if you want to match a character only if it's A or B or C, but not anything else, you'd use [ABC].
This is sort of close to writing (A|B|C)., but it's cleaner, and doesn't force you to capture groups.
The second part of matching groups, is that you can use ranges in them, and only in them.
As you can probably intuitively see, ranges are just what they sound.
For example, the 3-9 part will match any number between (and including) 3 and 9. The same can be done to letters and letter cases (e.g. [a-Z0-9] will match one character of the ABC, regardless of its case, or any digit).

Now, if you think we're done...nope!
Keyframes that use Bezier or Hermite interpolations have also extra tangents, so let's write a new example of a keyframe, now with tangents.

"4000: { 0.854701, 0.854701, 0.854701 },
InTan { 0.854701, 0.854701, 0.854701 },
OutTan { 0.854701, 0.854701, 0.854701 },"

I highlighted InTan and OutTan differently, just to show that they will clearly be our sign if a keyframe has tangents or not!

A thing to note is that we now have multiple lines per search term! I didn't mention this before, but normally, unless specified otherwise, new lines terminate searches (as in, the searches are normally on a line-by-line basis).

Another thing to note is that, ideally, we want one regular expression that handles keyframes both with and without tangents.

So, let's take the expression above, and make it more complicated.

"(3[3-9]\d\d|4[0-3]\d\d):.+((\n.*(InTan|OutTan).*){2})?"

The first part is, of course, all the same, so let's concentrate only on the second one.

"((\n.*(InTan|OutTan).*){2})?"

First of all, all of this mess is inside brackets followed by a question mark.
The question mark means that whatever came before it, in this case everything inside of the brackets, might or might not exist.
If it exists, it will be added to the match, if it doesn't, the match is still successful (assuming the first part matched).

Inside the exterior brackets, are brackets followed by {2}.
The latter part means that anything that came before it, in this case anything in the brackets, must repeat 2 times for a match.
For example, if we had the text "HAHAHA", we could (pointlessly in this case) match it with "(HA){3}"

Next we have \n. This is in fact just a normal way to write new lines in cases where you don't have a multiline text editor.

Now, the search position being in the next line, there's first .*. This is here just to make the regular expression slightly more generic.
We don't necessarily know how exactly the next line starts. Does it start with a tab? 2 tabs? spaces? no whitespaces at all?
So first, we grab any characters we don't care about, but note that I used asterisk, for the case where the tangent starts right at the beginning of the line!

Next we simply look for either InTan or OutTan, followed again by something we don't quite care about.

Since all of this part repeats twice, we actually check two lines, and if the keyframe is indeed bezier or hermite, both tangents will be matched.

So what's next? well, nothing! you click Replace All with an empty replacement term, and all of the keyframes are gone.

Of course, the first part is still specific, and will require you to edit it to get different frame ranges, but that's on you. :)

Regular expressions have MANY more options of how to look for and match data, and are just generally a useful tool for life if you ever handle text of any kind. Learn it.

Good luck.
 
Last edited:

Kyrbi0

Arena Moderator
Level 45
Joined
Jul 29, 2008
Messages
9,502
Posting to Subscroob; I'm actually (re)taking a class that has a section on Regular Expressions (failed that lab, though), and I might actually have some use for it IRL. Now I might have two...
(though to be honest I'll probably just go with MatrixEater once I bug Hermit to show me where)
.

Very cool, either way.
 
Status
Not open for further replies.
Top