Source Filmmaker

Source Filmmaker

45 ratings
Scripting in SFM - The Ultimate Guide
By Raph!
The guide is unfinished. You can still find useful information. I will finish it. It will help you create your first ever SFM script. For SFM, we will be using Python 2.7. I recommend using a text editor such as Sublime Text or Notepad ++ and not the script editor. Why? Because the script editor does not have much syntax highlighting and other useful features that will considerably save your time. A text editor will highlight stuff, functions, variables and etc. I am using Sublime Text 3. Now, let's get started!
   
Award
Favorite
Favorited
Unfavorite
Source Filmmaker's Documentation! ~ READ IT!
If you don't understand what I am talking about in the guide, there is the SFM documentation!

Almost everything I learned was there (except the Valve's module, but more on that later)

You can find it here!
The Element Viewer
To script, you NEED to know about the element viewer since every script is centered around it:)

The Element Viewer is one of those tools that you can discover by yourself as long as you have a basic idea around it. That's why I won't cover the most advanced functionnalities of it.
Python 2.7 basics
You can't do much if you don't know the python's basic. You can just go ahead and skip this part if you already know them.

Python is one of the easiest programming languages out there. You can almost create anything with Python!

A python script is saved in a .py file. There's also the .pyc files but I don't want to get into that for now.

When I talk about the console, I am talking about the script editor console.

Variables

The first thing you need to know about are variables :

Variables are containers. It will contain the information you decided to give. For example :

variable1 = 2

This is a variable. To assign a value (information) to it, you put an equal sign after the name of the variable and then put the value.

Variables can also stock words and characters.

variablechr = 'My name is Raphael.'

In Python, this type of value is a string. Strings are characters. In the previous example, I added these ' ' at the end and the beginning of the string. This is really important. If you don't put them, the console will think it was another variable.

Now, what if we want our string to appear in the console... We can do it! With the print() function. I will talk about functions a bit later, just understand that it's called print().

print('Hey girl')


I added a string between the brackets. When there is a function, it will ALWAYS have brackets. What you put in the brackets are called arguments. They say to the function how it should do its job. In the previous example, the argument of the function print() is a string. Let's say you have a variable and you wanted it to print it :

variable = 'string'
print(variable)

If you run it into the console, it's supposed to print string.
This is a great example to prove what I was saying a bit earlier about the quotes. If we put quotes in the brackets, the console will print the word variable. In other words, with quotes = sentences or words and without quotes = existing variable. If you forget to put the quotes, you will have an error anyway: -Variable is not defined-.

To test your script, place it in SFMDir/game/usermod/scripts/sfm/animset

You may not have the animset folder, just create it and restart SFM.

Now, right click on an animation set and you should see the name of your script somewhere
if you saved it correctly.

-- FOR THOSE USING SUBLIME TEXT, TO SAVE IT CORRECTLY, YOU HAVE TO SELECT ALL FILES IN THE DROPDOWN THINGY AND IN THE NAME BOX WRITE 'name'.py --

To test what you just learned:

Assign your name to a variable and print it.

Types of values

There is a lot depending on what you are working with, but here's the principal ones.

STRING ~ A string is a character,word or any other text.
INTEGER ~ Number, not decimal.
FLOAT ~ Number, decimal
BOOLEAN ~ True or False.

Functions

Before you start reading, you need to know that an underscore is actually a space. Steam just has problems with normal spacing, so if you see underscores, it's because I meant to write spaces.

A function is an action. You can assign a function to a variable. For example :

printvariable = print('This is a function')

For now, it might look dumb to assign a function to a variable, but when we will get to actual SFM scripting, you will see that most of the pre-made functions return a value. Oh yeah! What are pre-made functions? Print() is one of them. You don't have to create it. I will show you how to get more pre-made functions with modules later. What if you wanted to create your own function for your script? Well here's how you can do it :

def function1():
____print('function1')


def means define. You will only need it for your own functions. You don't want to create something that already exists XD. Function1 is the name of our function here. The brackets are there to add arguments. Let me show you.

def function2(a):
____print(a)


As you can see, a is an argument. It kinda works like a variable. So this function is supposed to print a. But we don't have any a variable, you might say. Well, my friend, this is not a variable, this is an argument, something that will get replaced when we execute the function. The number of arguments you place when you define the function will determine how many arguments you NEED to have when you will execute it. When you want the function to do something, you need to write it under the function and add 4 spaces. Every time you see :, your code will need 4 spaces IF you want this part of your code to be a part of the function, condition or etc. In the next example, only one of the print() is in the function2. Can you guess which one it is?

def function2(a):
____print(a)
print('I like cookies')


Yes. It's the first one, because we added 4 spaces. Now, how do we execute our function? We already executed a pre-made function when we talked about variables. It was print(). It works the same way for custom functions. To execute the function add this to your script:

function2('12')

Now, if you defined the function2 as I showed you earlier, you should see 12 when you run the script. As I said earlier, we need to replace the a when we execute the function. I replaced it with 12. 12 here is a string, not an integer. Why? Because I added quotes. A is like the value you don't know at the beginning. That's pretty much what you need to know about functions for now. You may learn more as you go through this guide.

Comments

Comments are just text that the console won't check. To create a comment:

# This is a comment.

To create a comment with multiples lines:

'''Add three quotes before adding comments and stuff. Alright, IDK what to say I just need this to be two sentences'''


The __init__ function

No, here, the underscore does not count as spaces. Defining this function is smarter when you do Object Oriented Programming (more on that later). This function will automatically execute itself when you run your script.

Modules and Packages

SFM has a lot of modules available. You can add more modules to SFM. I'll probably show you how to do so later on. Modules are a group of pre-made functions OR packages if they have some.


This is a module without packages.


This a module with packages.

A package is a part of a module. The first image contains one script. It's the module itself. When there is an __init__.py file, it automatically means that its a module with packages. The module's name is the folder's name if it has packages. If it does not, it's just the script name.

We can import modules (so we can use their pre-made functions ) to our script this way:

import sfm

This is the easiest way ot do it if you only have a module. Now let's see how it works if it has packages.

from vs import *

from vs import mdlobjects

The one with the * means that it's going to import every package from the module. The other one will only import the precise package. You can find which functions are available by going to their .py files. The vs one is in SourceFilmmaker\game\sdktools\python\2.7\win32\Lib\site-packages\vs.
Every module in SFM is in the python folder (SourceFilmmaker\game\sdktools\python).

Classes

Now, time to get serious! Classes are really important when it comes to Object-Oriented Programming. Don't worry! I will tell you what it means soon! Classes are classes. There are no other ways to put it. To define a class, you need to add:

class class1(759):
____def function1(self):
________print(self)

So the function is a part of the class, so I need to add 4 spaces there as well. You may have noticed that I added an integer in the brackets of the class.


Python 2.7 Basics Part 2
The value between the brackets determines the attribute of the class. It works like functions. I wrote self in the args (arguments) of the function. This means that it will take what the class is and, in this case, print it. The class here has an integer argument, so it will print the integer. When you want to use a function from a module, you might have to write modulenameorpackagename.class.function(). Example:

from vs import *

#movieobjects is a package of the VS module
movieobjects.CDmePresetGroupInfo.Copy(modelPresetGroupsnstuff, Argumentsblabla)


It's possible a function from a module is not in a class.

Object Oriented Programming

YAY! Finally we are here! I will explain to you how you should build your scripts. First off, import your modules. Then, create a class and create the function(s) you want to do in it.

import sfm

class print(animset)
____def __init__(self):
________global animset
________animset = sfm.GetCurrentAnimationSet()
________print(self)


The global word means my variable will be available in every other classes and functions. Really useful if you do something like I am doing (getting the selected animation set) because you do not have to re-assign the variable in each function. EVERYTHING in a class is an OBJECT. That's why they call it Object Oriented Programming or OOP for short. It's an easy way to organize your code and it will look a lot cleaner. Of course, you don't have to use this method, but this is what I am going to use here, in this guide.


Conditions
Ahhh... Conditions. If, else, and elif. Here's a quick example:

IAmACat = True
IAmADog = False

if IAmACat:
____print('You are a cat.')
elif IAmADog:
____print('You are a dog.')
else:
____print('Who the f*ck are you?')


So what just happened!? Here, I worked with booleans. Usually, when there are conditions, you would have to write: if something == tothisvalue: , but it's not necessary for boolean. If you don't put any == it means if it's True. Elif is else if and else is, well, else. An if statement doesn't HAVE to have an else statement linked to it. If there are no else statements, the interpreter will just run the conditional code if the condition is respected and then continue on with the rest of the code. I'll give you one last example but with strings.

Str1 = 'T'
Str2 = 'F'

if Str1 != 'F':
print(12)
else:
print(1)


Now you must be like wait wtf is != ?! Well, that's what we are going to talk about now.

Operators

Alright so the basics operators are +, - , * ,/ and = . There is also a %. You are probably familiar with each of them except the %. This is a Modulus. It returns the remainder when first number is divided by the second one. We won't use it that much, but as an example...

7%3 = 1

The conditions ones are : ==, !=, >, <, >= and <=. I already explained the ==. The != means Not equal to. You probably already know < and >. >= is greather than or equal to. <= is less than or equal to.

Logical operators: and, or & not. They only work with Boolean as long as I know. Most of us will use them on conditions. Example:
a = True
b = False

print(a and b) #It will output true if both of the values are true.

print(a or b) #Will print true if one of them is true

print(not a) # Will print true if the value is false

if a is not b or b is not a: #in this case the two conditions are basically the same
____print("a is not b")

Output :

False
True
False
a is not b



"is" is the same as the "==" sign.
"is not" is the same as the "!=" sign.

Bitwise operators: Really useful with floats and integers.

& = AND
| = OR
~ = NOT (so anything except that value)
^ = Combine both


You can find more information on operators on this website![www.geeksforgeeks.org]

Lists
Lists, also known as arrays, are lists of values. The first value of a list is considered as 0 and the second one, 1. To create a list :

list = []

To add something:

list.append('value1')
list.append('value2')
list.append('value3')
list.append('value4')

To add some things:

list.extend([1, 2, 3, 4]) #as you can see, added a temporary list to the list variable. After this function, the temporary list will not exist anymore.

To slice a list:
list= list[1:3] #Only keeps the 1st, 2nd and third element of the list.

To change a list:

list[2] = "correction" #The 2 is the third value in the list. We just replaced the value with the new string.

To combine lists:
list2 = list + [2, 7, 4, 9]

To repeat a value in a list (instead of extending the same value many times.):

list3 = ['Bla'] * 3

To insert :

list2.insert(1,3) #arguements are rank then the value. This one does not replace the value if it's at its destination rank. It will just put it away, so what was the 3rd one before is now the fourth.

delete an item :

del list2[3] #rank = 3 in this situation

delete items:

del list2[3:5]#( this: means to)

delete list:

del list2

Dictionnaries

Dictionaries are keys and values.

dictionary = {
'brand' : 'Valve'
'Program' : 'Source Filmmaker'
'Year' : 2012
}
As you can see, it's similar to variables.
More on dictionaries here[www.w3schools.com]. Overall, it's just assigning a value to another thing. These things are called keys.

For loops

For loops are a time saver. To work with it :

for temporaryvariable in somearray:
something.append(temporaryvariable)

Now, instead of adding everything of the other list one by one, it will just take everything in somearray and append it to the other list called something. You can also use range.

for x in range(0,3):
____print(x)

Output:
0
1
2

As you can see, this is useful for cleaner code. Oh! I may have forgotten to say this about the list : EVERYTHING WITH THE WORD ARRAY IN SFM IS A LIST, NOT A RANDOM ELEMENT OR TYPE OF ELEMENT.

I think we are done with the BASICS. Now let's get scripting!
The SFM Python Modules - Overview
A lot of information can be found here(Credits to Pte Jack). Some of the functions won't be seen in the guide so you can check them out by yourself in the .py files. There are four Built-In modules that you won't find as a .py files. The SFM Module, SFMApp, SFMClipEditor and SFMConsole Module. Just run print ( dir ( nameofbuiltinmodule) ) or print ( help( nameofbuiltinmodule) )to find which functions you can use.

Example with the SFM modules

import sfmUtils
import sfmApp
import sfm
import sfmClipEditor
import sfmConsole

shot = sfm.GetCurrentShot()
animset = sfm.GetCurrentAnimationSet()
print(animset + 'something else')
What you need to know before scripting
Python's basics of course.

You need to know how to use the Element Viewer and the types of elements.

Overall, just be at least an intermediate with the element viewer.

If you don't know it yet, I've made a part of the guide for it, so go check it out!

Basic 3D rigging and modeling knowledge. By that, I mean you have to know what vertices are. Of course, this is not something you NEED to have, but it helps a lot if you want to make rigging scripts.

...and, obviously...
YOU NEED TO KNOW HOW TO DO THE THING YOU WANT TO DO BEFORE SCRIPTING.

Scripting is just a quicker way to do stuff, but I am pretty sure you can do everything manually on SFM without any scripting. Of course, it takes more time, but knowing what you are doing is really good lol.
Learning By Doing
Don't be scared to mess up.

Learn by making scripts you think could be useful even if you are not sure on how to do it correctly.

The only thing you needed was the basics and where you can find information. You will figure it out if you are determined.

So while we do the next little script, try stuff! Don't just copy, you will not learn anything.
Creating a User Interface with QtDesigner and Pyside-Uic (W.I.P)
I have to thank OMG Theres A Bear In My Oatmeal! for telling me it was possible to do this.

We're going to have to work outside of Source Filmmaker to make this, since SFM's Qt Designer is broken and just doesn't work.

First, we need to install Python 2.7 on your computer. Make sure you get the last version of 2.7. I think they're going to discontinue this version of Python, so Python 2.7.17 might be one of the last versions... but if there is a new one, download the new one.

So, you will be greeted with a setup and bla-bla. Everything should be fine if you leave the setup's options to their default values. Make sure you install it for all users. so that our Python installation is directly in C:! If you install Python 2.7 and it's not in C:, that's because...
  • You changed the default path.
  • You only installed for you.

Now, start CMD as an administrator and run this.

set PATH=%PATH%;C:\Python27\;C:\Python27\Scripts\

Perfect! Restart the CMD now.
Now, enter pip install PySide and wait.
Once it's installed, go to C:\Python27\Lib\site-packages\PySide and start-up designer.exe!


I usually always start with a widget. Leave the size to default and hit create!

Now, let's make a script that gets all the animation sets from the shot and shows them in the window. It'll have a duplicate button to duplicate the Animation Sets and one to delete them.

So, for this, we need a list widget to list the Animation Sets! In the toolbox or widget box, you should see an item called a list widget. Drag it in the window.


Drag two push buttons as well.


As you can see, the layout is really ugly. We could just manually place the buttons to our liking, but if we were to resize the window, the widgets wouldn't resize with it.


This is why we can use layouts!
Let's set our widget (or window) to have a grid layout.

Now, you'll see that we can modify our UI, but it's a bit limited. You can also use Size Policy setting to modify a widget's size.


Now, make sure you modify every widget's name, because they're going to be important later.
Scroll down and change the buttons' labels as well.

Alright, save your UI now! We're done with it!
If I were you, I'd save it directly in the path where you want your script to be.
For us, it'll be SFM_PATH/usermod/scripts/sfm/animset
Why AnimSet...? because to actually get a shot, we need an animation set and we need a shot to get all of its animation sets.
--------------------------
Don't forget to save your UI!
W.I.P
Hard-Coded User Interface
The SFM team included PySide with Source Filmmaker. It's really useful. Thanks, guys :D. Anyway,
this module allows us to create UI, also known as User Interface. This is a UI :


Everything you interact with if it's not with actual code is a UI/GUI. If you still don't get what I mean, just watch this old video: https://www.youtube.com/watch?v=ibgbshSPm5A

I will be using OOP for the UI, but keep in mind you can do a UI without it.

Use this method if you really can't use the designer for some reason.

To start off, let's import the module with all of its packages.

from PySide import *

We now need to create a class. Why? For better organization. We are going to use the UI for something else later on. Now, create a class named UI. Its argument is
QtGui.QMainWindow
This means 'self' will mean IN the window. Now let's define our first function : __init__()

The __init__ function
It should have the argument self.
We need to set the window title. You can so by writing
self.setWindowTitle('Title')
. I will call it UI for now. Let's set the size. Add this first
self.setGeometry(400, 300, 1000, 500)
I recommend to set a fixed size, because I tried to do the UI without one and it had a lot of problems. To add a fixed size, write
self.setFixedSize(int, int)
I will set it to 1000 per 500, but you are free to change the values if you want. Now, let's see if our script works! Save it in usermod/scripts/sfm/animset. Now, in SFM, select an animation set and go-to rig. Now click on your script.

What happened?
Nothing.
That's because we need to add three things
Let's add
self.show()
super('nameofclass', self).__init__() #At the beginning of our function
UI = 'nameofclass'() #OUTSIDE of our class (standalone)

Now, save it and try again.


Awesome. Our main window has been created. That's the only thing you should do in this function for the UI, create the frame.

The Home Function
Before doing anything let's add
self.home()
to the __init__(). If we don't, the home function will never run. Let's define the home function in the Window/UI class (whatever you named it). Arguments = self.

Let's create a label. A label is a text. We need to assign the label to a variable so here's what we need to do
l1 = QtGui.QLabel(self)
You can change the variable's name. To set the text,
l1.setText('Welcome To My SFM Script')
We have to place the label somewhere!
l1.setGeometry(500, 10, 300, 100)
and of course, we need to show it
l1.show()
Perfect! You now know how to make basic labels, but what if we want to change the font?

To do so, we'll need to create a new variable. I'll just call it font1.
font1 = QtGui.QFont("Times", 9, QtGui.QFont.Bold)

Let's set the font as well
l1.setFont(font1)

and the result isssssss

Yay! We did it. You can change some arguments of the QFont function to try out new fonts. To set the color :
l1.setStyleSheet('color: yellow')
Again, play with the attributes, you will discover things.

Buttons are important, so let's make some. A button can be created with
QtGui.QPushButton('text', whereitssupposedtogososelfforus)
Like the label, we need to assign it to a variable if we want to modify its look. For the buttons, you can resize and move with these :

btn1.resize(90, 25) btn1.move(0, 455) btn1.show()

If you run your script, the button won't do anything. That's because it needs to call a function. You can make it call one by writing :

btn1.clicked.connect(self.calledfunction())

Now, we don't have any function to call, so let's create one in the class.

The Button1 Function
def btn1(self): print('YOU JUST PRESSED ME! AHHH!')
Back to the home function
Now we have a function to call!

btn1.clicked.connect(self.btn1)

Run your script and look at what happens when you click on the button.


That's the only thing you need to know about buttons: they're to execute a function. It's going to be really useful when we will use other windows and etc.

Now, let's add a use to the button. In the function, write :
import sfm global animset animset = sfm.GetCurrentAnimationSet() print(animset)

Now, run your script again. You should see information about the animation set you selected. Now, since this part of the guide is about the UI, you don't need to know what I just did. Just remember, buttons are function triggers.

We can also create a pop-up message!

Variable = QtGui.QMessageBox()

I'll call my variable pop. Now let's set the text and the window title.
pop.setText('I am a pop-up window.') pop.setWindowTitle('Wait')

and, of course, we need to EXECUTE it, not show it :

pop.exec_()

If we were to show it, it would automatically appear when we run our script.

Want to know more about message boxes? Click here.[www.tutorialspoint.com]

Speaking of documentation, there's documentation for this module : here[pyqt.sourceforge.net]

You can also find other tutorials about the QT interface here

This was the fundamentals of an SFM UI. You can obviously listen to the videos I mentioned before. In some script examples, I other functions of the module that I did not show. That's why I recommend watching the videos.

Now, let's see what we can make with the other modules.
Scripts without Object Oriented Programming
The problem with Object Oriented Programming

For beginners, OOP may look complicated. Since it's not necessary, we can make scripts faster. Faster doesn't mean better. I just find OOP easier to read. Alright so let's see how we can make everything easier.

So in the last part of this guide, I showed you how to make a UI with OOP. You could make one without it.


See? It's less complicated than this :


Lest lines for the same thing.

What about classes?

Classes are still a thing in ''normal'' python, but I doubt we'll use them. We will take functions within classes from modules, but we won't create some.

[]h1]In summary...[/h1]

Everything I showed you still apply to Python scripting, even without OOP. In the next part, we won't use OOP... I just want you to know it's possible to make a script faster without making it harder to read.
The Valve's Python Module (W.I.P)
VS, also known as the Valve's python module, is a HUGE module. It has a lot of useful functions. We won't go trough all of them, so I recommend checking out the .py files by yourself. We will also use the SFM modules.


Animating with Python
As a first example, I will animate an animation set. I will chose the engineer model for this example. You may wonder why we would want to work with Python if we can just animate directly on the UI. The thing with Python and the VS module is that you can get certain types of values, sometimes the selected ones. We could use the animation on every model, as long as the user PRECISES which bones to use in the script's UI. That was the long answer. The short answer is : for combability with other animation sets or other elements. Alright, let's start!

First off, we need to get our modules.

import 'nameofmodule'

The VS module is special, because it contains packages. To correctly import the VS module, please insert the following line.

from vs import *

I explained a bit about packages in the python's basic part.

We also need to GET our animation set and return it to a variable. I recommend assigning the BASICS variable at the beginning of the script, not in a function/class.

Here's how it looks so far

import sfm import sfmApp import sfmUtils import sfmClipEditor #import sfmConsole #------------------- from PySide import * from vs import * import vsUtils animset = sfm.GetCurrentAnimationSet() animset.name = str(animset.name)

So... here, I won't use OOP because I don't think it's necessary. For beginners, Object Oriented Programming can be really hard and I want to make this process as easy as possible.

Create your window without OOP by assigning QtGui.QMainWindow() to a variable. Now, instead of self everywhere, it's going to be the name of your variable everywhere. You will also have to add variable.show()

Now, I will create a Combo Box (ahah, thats me). If you don't know what it is, watch this if you haven't yet

BUT WAIT! I SAID I WILL! It does not mean I need to do it now. We don't have anything to put into the box anyway. We will have to get the controls! Before we do, here's how it should look like so far :

The VS module has actually something for us! As I said, it has multiples packages so we'll have to precise which one. There's a GetControls() function in the movieobjects package!

To use this function, we will need to precise the package and class it's in, so it's package.class.function(). For this function, we need an animation set and, of course, we have it!



If we try to print it, you will see a number and then this: p_CDmaElementArrayT_CDmElement_t.

An array is a list.

We know this object has elements, but how can we get them? I won't show it in this part. It's too complicated and we need to cover other things before actually doing it. HOWEVER, we have this sweet array called rootControlGroup. It allows us to have more control over our bones and flexes easily. Now, you might ask: why would you want to get the controls array if this is way easier? The answer is: it has more useful functions. You see, rootControlGroup has a lot of functions, but they do the job for you. With the controls array, you can really customize it. They, Valve's employees, did this because I think it's possible to NOT have a rootControlGroup. rootControlGroup is just more User-Friendly control.

Alright so let's get the Pelvis bone.

Now, you may know the name of the bone. You can add it to a variable like thar :



Sometimes when you look for documentation in the .py file you will see this : char const *. Just ignore it. I was confused at first, so just saying it now.

What if you don't know the name of the bone? You could make the user WRITE the name of it or maybe make a UI like the auto-rigger script does. As you can see, there is many ways to create one script.

If you are planning on making Pose-To-Pose animation, you may want to get every control instead. Alright... I said I wasn't going to explain it, but here we are. Let's talk about loops with controls.

controls = Animset.GetControls()

Now, I didn't even precise any parameter... Well, this function needs an animation set, right...? and it's part of the CDmeAnimationSet class. So, it needs itself. If you go in movieobjects.py, you'll see that the only argument it asks for is itself, an animation set. So, you could precise an animation set, but since we already have on and we know this function is a part of it, we can use the function like so... because, yes, if you haven't figured it out yet, the CDmeAnimationSet class is the animation set class. Every animation set is an instance of it.

for i in controls: ____controls.append(i)

OR

for i in controls: ____controlslist = i ____controls= controlslist

So now we have our controls's list.

----------------------
WIP


4 Comments
A Metal Penguin 19 Oct, 2024 @ 12:29pm 
Easiest way to get QT Designer (on Windows) is to open up the terminal and type
winget install Qt.QtDesigner
Anyar 8 Jun, 2022 @ 4:46pm 
Thanks for making this guide!
valance 15 Oct, 2020 @ 10:44pm 
broo..... when this getting an update :(
OMG Theres A Bear In My Oatmeal! 10 Mar, 2019 @ 6:49pm 
For when it comes to creating the ui I recommend using the QT Designer that comes with pyside.