Can someone code a Python shaky-cam script?


https://forum.reallusion.com/Topic400015.aspx
Print Topic | Close Window

By gordryd - 7 Years Ago
If any of you Python programmers are looking for a project to code, might I suggest a "Shaky-Cam" app?  I guess one way to do this would be to sample the Camera Transform at a given key interval (the program might have to create those keys).  This would allow the shake to be added/subtracted from existing Transform camera movement.  Not sure if it would work with Paths or not?

Just a few thoughts about what I personally think would be helpful to have:

1. Camera Shake Presets, like:
   a. Walking (random + emphasized vertical shake every 0.5 second?  I think that matches standard animation walk cycle...)
   b. Running (random + empahsized vertical shake every 0.25 seconds?)
   c. Vehicle - Wheeled/Aircraft (random short interval shaking in all directions)
   d. Boat (low frequency pitch/roll/yaw)
   The Presets could have a 'multiplier' to control the amount
   
2. A pre-visualizer (maybe a red ball moving inside a square w/crosshairs, to see how much shake will be applied before committing to Timeline)

I'm sure lots of other things could be added, such as more advanced control of Preset settings, but this would be a great start.

Any takers?
By thedirector1974 - 7 Years Ago
You don't need to have a phyton script to get a shaky cam effect. Just link the camera to the head of an avatar and give that avatar a idle/running/walking animation. That's it ...
By 4u2ges - 7 Years Ago
I've done that thing with the character in the past as Director suggested. But today I needed an all directions subtle camera shake.
So I figured I finally would get my hands on Python. Had to go through the crash course (thanks all for the suggestions and discussions in this sub-forum).

Anyhow, came up with this basic script. Nothing fancy. But it can be tweaked to accommodate most situations where camera needs a shake.
Pretty much self explanatory with some comments. Make sure to run the script after you settle your camera with transformation points, smoothing, transitions... etc.
Could also be suited for the dummy driven cameras. Just need to change "Camera" to the prop name and object type. Have not tested for the Cameras on the Path.

Test the script on some demo project first. If after running a script you are not happy and need to change some parameters, perform an undo in iClone and load amended script over again.

import RLPy, math
from random import *

# Created for the camera named "Camera"
cameraMan = RLPy.RScene.FindObject(RLPy.EObjectType_Camera, "Camera")

# Shake angles limit. Represents variation from -0.0035 to 0.0035 radians (-0.2 to 0.2 degrees)
# Random number is generated within those limits and added to each axis for designated frames
minDelta = -35
maxDelta = 35


# For the first 800 frames adds a shake by randomly tweaking an XYZ rotation for every 10th frame

for camFrame in range(0,800,10):
    shakeTime = RLPy.RTime.IndexedFrameTime(camFrame, RLPy.RGlobal.GetFps())
    cameraMan_control = cameraMan.GetControl("Transform")
    cameraMan_data = cameraMan_control.GetDataBlock()
   
    getXRotation = cameraMan_data.GetData("Rotation/RotationX", shakeTime).ToFloat() + randint(minDelta, maxDelta)/10000
    getYRotation = cameraMan_data.GetData("Rotation/RotationY", shakeTime).ToFloat() + randint(minDelta, maxDelta)/10000
    getZRotation = cameraMan_data.GetData("Rotation/RotationZ", shakeTime).ToFloat() + randint(minDelta, maxDelta)/10000
   
    cameraMan_data.SetData("Rotation/RotationX", shakeTime, RLPy.RVariant(getXRotation))
    cameraMan_data.SetData("Rotation/RotationY", shakeTime, RLPy.RVariant(getYRotation))
    cameraMan_data.SetData("Rotation/RotationZ", shakeTime, RLPy.RVariant(getZRotation))
    cameraMan_control.SetKeyTransition(shakeTime, RLPy.ETransitionType_Ease_In_Out, 1.0)


And a sample demo:



UPDATE: It is possible to add shaker to the camera on a Path. Not directly though.
Link a Dummy object to the Path. Align camera with Dummy. Link Camera to Dummy. Apply script to Camera.
It also works with Look At applied to a Dummy (when circling around the object for instance).
By Data Juggler - 7 Years Ago
I just completed my first Python script this morning, let me see if I can get a couple of questions answered and I could probably write out the Python in C# pretty easily to generate a script.

Would you want this to apply to the selected camera in a project? I guess the more robust way is to show a UI element and let you pick, but that would probably be a version 2 thing.

One of my open source projects located in my massive C# code repo I just released on GitHub last week (that no one has even found yet until I finish my animated video I am making and start promoting it) called Random Shuffler, which is used to make Dice and Card game shuffling easy, but it also works for integers. This class library shuffles like a real deck of cards shuffles (even distribution) where as random number generators use Math and time seeds and are not that random at all.

If any C# / SQL developers happen to find this and want it is, it is free:
https://github.com/DataJuggler/SharedRepo/tree/master/Core/RandomShuffler
Full Repo is here:
https://github.com/DataJuggler/SharedRepo 

https://forum.reallusion.com/uploads/images/3f24efe6-7c05-41e3-8065-abce.png


By gordryd - 7 Years Ago
4u2gues - Thanks, I'll give this a try tonight.  On a related topic, if you wanted an object to 'shake' (say a car on a bumpy road) while the camera stayed 'still', is this the only line that would need to be changed (subsitute chosen item for ObjectType_Camera and "Camera")?

cameraMan = RLPy.RScene.FindObject(RLPy.EObjectType_Camera, "Camera")
By 4u2ges - 7 Years Ago
@gordryd
Yes, exactly. Should work fine.
cameraMan = RLPy.RScene.FindObject(RLPy.EObjectType_Prop, "PropName")
By gordryd - 7 Years Ago
@4u2gues - I tried your 'ShakyCam' script and it works well -- thanks for that.  I've actually created half a dozen sub-menu items for different motions (Cam-Walking, Cam-Running, Prop-Car, Prop-Bumpy, Prop-Boat, Prop-Airplane).  The main issue I am having is that the effect is cumulative, which in theory shouldn't be a problem.  Probability says it should all average out, since the rotation angles are supposed to be random.  In actuality though, they seem to be biased in a certain direction, and the longer the scene plays the farther away from 'center' the camera or object will drift (as opposed to random rotations around a fixed point).
I also have a prop which doesn't seem to respond to the script -- no rotation is visible and no keyframes are added.  If I import a cube and rename to the same prop name, the cube will react to the script.  The original prop has no constraints, doesn't have physics enabled, isn't on a path, etc.  Not sure what is going on with this one, but I can live with that.
Now if only I can figure out how to create a UI so I can input/change the values on the fly (instead of changing the script and re-starting iClone)...
By 4u2ges - 7 Years Ago
Yes, you are right, it probably IS cumulative. I should have looped through the frames, gathered values into array and then applied values with another loop parsing resulted array.
As to UI, oh well, it is another story. I have not gotten to that part yet. Probably will at some point :)
By videodv - 7 Years Ago
One way to create a ui is to use the qt designer follow this Link
And the manual from here Link

Hope this helps

Chris.
By videodv - 7 Years Ago
Here's a very quick and simple way to load and run a script with the ui design this has one slider as an example.

import PySide2, RLPy, os, math
from PySide2 import *
from PySide2.QtWidgets import *
from PySide2.shiboken2 import wrapInstance

#-- Make a global variable contain Dialog --#
sample_dialog = None
qtui_widget = None

def run_script():
    global sample_dialog

    #-- Make the RL Dialog --#
    sample_dialog = RLPy.RUi.CreateRDialog()
    sample_dialog.SetWindowTitle("Prop Options")

    #-- Wrap to PySide Dialog --#
    pyside_dialog = wrapInstance(int(sample_dialog.GetWindow()), QtWidgets.QDialog)
    sample_layout = pyside_dialog.layout()

    #-- Load Ui file --#
    ui_file = QtCore.QFile(os.path.dirname(__file__) + "/youruiname.ui") # Change Name to your UI here
    ui_file.open(QtCore.QFile.ReadOnly)
    qtui_widget = QtUiTools.QUiLoader().load(ui_file)
    ui_file.close()
   
    # Get Controls from youruiname.ui
    ui_some_slider_name = qtui_widget.findChild(PySide2.QtWidgets.QSlider, "someSliderName")


    # OK lets go and do something
    ui_some_slider_name.valueChanged[int].connect(run_something)
        
    # #-- Assign the Ui file to PySide dialog --#
    sample_layout.addWidget(qtui_widget)

    sample_dialog.Show()

def run_something(value)
    print("Slider Value: {}".format(value))

Make shure the ui is in the folder you are running the script from.
Chris.
By Data Juggler - 7 Years Ago
@Chris Many thanks! As soon as I get a chance I will see what I can do with this.



By videodv - 7 Years Ago
I forgot to say when you open the designer select create dialog without buttons and also make sure you set the min and max size of your dialog or you only see a title bar when running the script.

Chris.
By Delerna - 7 Years Ago
4u2ges/ videodv. 
More good efforts. I started working on this while still living in Sydney before I moved back to my home town. Had so many things to do since I moved I got lost on working with python plugin.
Going to get back into it this though and I will be using your codes and the many that have been posted to assist me to get a good understanding of coding iClone.
By videodv - 7 Years Ago
Data Juggler (4/21/2019)
@Chris Many thanks! As soon as I get a chance I will see what I can do with this.



Your welcome

Chris.
By videodv - 7 Years Ago
Delerna (4/21/2019)
4u2ges/ videodv. 
More good efforts. I started working on this while still living in Sydney before I moved back to my home town. Had so many things to do since I moved I got lost on working with python plugin.
Going to get back into it this though and I will be using your codes and the many that have been posted to assist me to get a good understanding of coding iClone.


Hi
Took me a while to get my head around QT and Python as well as the Iclone API but Im getting there now.
Wish you all the best as I learned a lot from the scripts put up by others and of course a lot of head scratching :) and experimenting :ermm:

Chris.




By 4u2ges - 7 Years Ago
@ Delerna Thank you and good luck!

@Chris Awesome! Thank you for the links and a demo. Will get to the bottom of it at some point.
By videodv - 7 Years Ago
4u2ges (4/21/2019)
@ Delerna Thank you and good luck!

@Chris Awesome! Thank you for the links and a demo. Will get to the bottom of it at some point.


Your welcome
Chris.

By gordryd - 7 Years Ago
@Chris - looks like you know what you are doing (as opposed to me just cobbling together pieces that others have contributed).  So I created a form in QT Designer, but have questions about a few things.  Here is what the form looks like in QTD:
https://forum.reallusion.com/uploads/images/e8e50f3b-06c4-468d-ba8c-dbfe.jpg
How would I do the following?
1) Populate the Preset ComboBox from a list inside my main.py script when the form opens
2) Populate the Object ComboBox from current iClone project (with either all Cameras or all Props - Camera/Prop selection will be made before box is displayed) when the form opens
3) Pass values from SpinBoxes to main.py variables when 'OK' is clicked?
Thanks - when I get this to a stage that I am satisfied with I will make it available to all that want it.

By videodv - 7 Years Ago
gordryd (4/22/2019)
@Chris - looks like you know what you are doing (as opposed to me just cobbling together pieces that others have contributed).  So I created a form in QT Designer, but have questions about a few things.  Here is what the form looks like in QTD:
https://forum.reallusion.com/uploads/images/e8e50f3b-06c4-468d-ba8c-dbfe.jpg
How would I do the following?
1) Populate the Preset ComboBox from a list inside my main.py script when the form opens
2) Populate the Object ComboBox from current iClone project (with either all Cameras or all Props - Camera/Prop selection will be made before box is displayed) when the form opens
3) Pass values from SpinBoxes to main.py variables when 'OK' is clicked?
Thanks - when I get this to a stage that I am satisfied with I will make it available to all that want it.



Hi gordryd

Not sure if I would say I know what I am doing as yourself I have had a lot of experimenting and head scratching over the last couple of months but I seem to have got the basic jist at least, anyway to answer you questions see the code below.


1. Not sure how to do this as yet (see below) but You can set the options for the Combobox from within the QT Designer just right click and select "edit Items"

2. Object Combo Box

I use this function to populate a TreeWidget with all the relevant objects I wish from the scene
not sure how to set a combo box but it should be simular.

Im not at my home computer at the moment so cannot check this out but i will check this out tomorrow when I finish work and at home.
Perhaps someone with more knowlege with Combo Boxes could chime in here and put both of us right?

def get_scene_objects(ui_treeWidget):
    #-----Get all avatar / prop / camera / light-----
    avatar_objects = RLPy.RScene.FindObjects( RLPy.EObjectType_Avatar ) # Get all avatars in scene
    prop_objects = RLPy.RScene.FindObjects( RLPy.EObjectType_Prop ) # Get All Props in Scene
    light_objects = RLPy.RScene.FindObjects( RLPy.EObjectType_Light ) # Get All Lights in Scene
    camera_objects = RLPy.RScene.FindObjects( RLPy.EObjectType_Camera ) # Get A Cameras in scene

    ui_treeWidget.setColumnCount(1)
    ui_treeWidget.setHeaderLabels(['Scene Objects'])

    #Avatar level item and its kids
    item0 = QTreeWidgetItem(ui_treeWidget, ['Avatar'])
    #-----Create item for all scene objects-----
    for item in avatar_objects:
        _name = item.GetName()
        QTreeWidgetItem(item0, [_name])
    #Prop level item and its kids
    item0 = QTreeWidgetItem(ui_treeWidget, ['Prop'])
    #-----Create item for all scene objects-----
    for item in prop_objects:
        _name = item.GetName()
        QTreeWidgetItem(item0, [_name])
    #Light level item and its kids
    item0 = QTreeWidgetItem(ui_treeWidget, ['Light'])
    #-----Create item for all scene objects-----
    for item in light_objects:
        _name = item.GetName()
        QTreeWidgetItem(item0, [_name])
    #Camera level item and its kids
    item0 = QTreeWidgetItem(ui_treeWidget, ['camera'])
    #-----Create item for all scene objects-----
    for item in camera_objects:
        _name = item.GetName()
        QTreeWidgetItem(item0, [_name])

ui_tree_view = qtui_widget.findChild(PySide2.QtWidgets.QTreeWidget, "treeWidget") # Find the tree widget

ui_tree_view.itemClicked.connect(run_tree_view) # Go and do something


def run_tree_view(it, col):
    print(it.text(col)) # Print the tree widget value selected



3. Spin Boxes

some_spin_box_value = 0 # This is global for the spinbox value place under your imports at top of script

    ui_spin_box = qtui_widget.findChild(PySide2.QtWidgets.QSpinBox, "spinBox") # Find the control Spin Box with name "spinBox" or your own name

    ui_spin_box.valueChanged.connect(run_spin_box) # Run when spinbox updated

def run_spin_box(value):
    Global some_spin_box_value
    some_spin_box_value = value # Save the spinbox value to use later in the script

Hope this helps in some way.
Chris.


By gordryd - 7 Years Ago
Thanks Chris - actually, I forgot to mention something that makes it even more complicated. -- I would like to populate (change) all the other boxes depending on which 'Preset' is selected.  As they say at work, continuous process improvement...
By videodv - 7 Years Ago
Just a quick update here is the code to populate a combo box

ui_combo_box = qtui_widget.findChild(PySide2.QtWidgets.QComboBox, "comboBox")

get_screen_objects_combo_box(ui_combo_box)

def get_screen_objects_combo_box():
    #-----Get all props -----
    prop_objects = RLPy.RScene.FindObjects( RLPy.EObjectType_Prop )      
    for item in prop_objects:
        ui_combo_box.addItem(item.GetName())

And to get the selected item
ui_combo_box.activated[str].connect(run_combo_box)

def run_combo_box(text):
    print("Combo Box Text: {}".format(text.GetName())

Hope this helps
Chris.