Tuesday, 26 November 2024

Learning Python Part 9

First we went over Pep8 standards, you can find them here: https://peps.python.org/pep-0008/

    Note on the upcoming HW, we need to use PyQt5 but Python 3.12 does not support it. We needed to go down to python 3.9 inside of visual studio and visual studio code. I also had to do 'python pip install PyQt5' for vscode to run it. 

    To check over if you have PyQt5 installed correctly, try importing PyQt5 and run the script to see if it runs without errors. We're going to be importing these modules: 

import sys

from PyQt5.QtGui import QWindow

from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton

    You're going to want to create a class to hold your interface. Start by creating a class that inherits the QWidget class.


    Next we need:
app = QApplication(sys.argv)

    Because we are going to be sending arguments from the command line to our window. We're also going to be creating a seperate thread  thats running for our window.
    To run the script and open the window, you need to include
window = MyHBox()
window.show()

app.exec()
    Running this will give you an empty window with the command prompt open. The window is tied to the command prompt, if you close the command prompt it will also close the window (But if you close the python window the command prompt won't close).

    Now we want to specify the type of layout for our window. For a horizontal layout, we can use QHBoxLayout()
    
QHBoxLayout() is like a container node within Houdini, it's going to hold stuff. Running this again will get you another empty window but smaller. But once you start adding buttons, you can see the layout come into play. 

    The QPushButton needs a name, and a reference to itself, so it knows where to attach the button. Now you should get a window with 3 buttons in it. 
horizontal layout with 3 buttons

    If you want a vertical layout, you can change QHBoxLayout() to QVBoxLayout(). There's also a grid layout. With a grid layout, you need to specify an x and a y value for it. 
   
grid layout with 10 buttons

    The above code says we want to make 10 buttons. The x coordinate is x divided by 5, but the // tells python to divide the integer
, but it will not give us a floating point number. Instead it takes the 'floor' of the answer. Meaning 1 divided by 5 is 0. Think of rounding down. The y coordinate is y modulus 5, meaning it takes the remainder of the division. 
    So far we've made buttons, but they don't do anything. 

Slots to handle signals
Assign slot to handle signal 
self.<Signal Name>.connect(<Slot Function Name>)
    The signal can be many things, from push down to release to hover over, etc. You choose the signal you want the button to have. Then once the button gets that signal, it send that signal to everything in the script, so that anything that is listening to that signal with trigger it's function it's connected to it. This is a much more efficient way that callbacks. 
    Inside of your window class, you can add the connect function right after creating a button to assign a function to it. Here we've chosen the signal 'clicked' so that when we click the button it executes the function. You can look at the documentation to see other signals. 

the function that gets called when we press the button
    Notice how we don't have any parenthesis next to the function eatBacon within the connect function, so we can't send arguments to it. We can use a lambda function to help

lambda

    A lambda is a small anonymous function. It can have multiple arguments but only one expression. It's used to call within arguments. 
variable = lambda <arguments>: <expression> 
the print button name function

  Here we have 2 buttons, one named Johnny and one named Joey. In the connect function. we have a lambda function, with a colon and then the function we want to pass through. 

What if we want to create a for loop to create a bunch of buttons like in the grid layout. We can use partials to help with connecting the functions to the buttons (before you had to do it by hand, because if you did it in a for loop python would only remember the last button in the loop). All you have to do is include partial() inside the connect funciton. Then inside of partial, first include the function you want to have, and then a 
the module for partial
    Make sure you also add:
from functools import partial
    So that we can use partial in our function.

    If we want to add icons, add the tiny pngs to the folder you're building the application in. Then you'll want to make a list with tuples with the name you want the buttons as well as the png names.
make sure you also grab the QIcon module from the PyQt5.QtGui
    Add the png with the setIcon function. 



Thursday, 14 November 2024

VR Project Week 4

 Finishing Vases

   I needed to sim all of the vases in houdini, export them out, and set up all of the respawning capabilities of all vases once they were all destoryed. I used the same anim sequence but reversed for when the vases spawn back. Jason and Noa had helped me come up with the system for spawning back the vases.
Inside of the function in the BP manager

Inside of the event begin play of BP manager to get total of all bp vases in scene

Reset custom event inside of the BP vase

The collision event inside of BP vase.

    Mark helped to paint the skin weights on the final Hercules Mesh, and was able to get it into Unreal. He made an idle and an additional two poses for Hercules. I went in later and added everything into an animation blueprint. I also added in the function of Hercules can track the player's movement with his head. I have had some trouble with this and I fear it is due to the orientation of the joints on his head, but this will have to be a fix for later. 
This is getting the position of the player when inside a sphere collider in Hercules' BP and then setting that to a variable. The ABP listens to a BPI


Also included triggers for a wave animation and a heart pose


In the ABP this is what I used for the head looking at the player



Tuesday, 12 November 2024

Learning Python in Houdini Part 8

 Exception Handling

    Don't try to do it in code (like with if statements), it's awkward, slow, programmer's don't like doing it. But is should be done, exceptions could leave resource in an 'inaccessible state'.
    What happens if your script tries to open a file, but it crashes? Your file has been opened to a script that doesn't exist anymore, and it's possible your file cannot be accessed. It becomes a 'zombie' file. Depending on how bad the crash is, you might have to restart your entire computer in order to open the file.

    Example: say you want the user to enter in a value for scale and then create the object. 
    
    But what if you only wanted the scale to be less than 5? You can raise an error for the user

    We came up with this special situation, and then gave an error message to the user (a pop up box). 

Basic format for handling exceptions:

try:
    code
except ExceptionClass:
    code
except:
    code
else:
    code
finally:
    code
It's either going to: perform the operations in try. If it catches an exception, it will either go specifically to the code bloc where you specified the Exception Class, or go straight to the unspecificed except code bloc. The else statement runs when it does not find any exception. Finally runs no matter what, this statement wille xecute regardless of 'try'. This is an ideal location for resource De-Allocation.  Try is not optional, everything else is optional.
Can have only one empty 'except' at the end before 'else' statement.
Try puts it in a special state where python is geared up to know it might get an exception types (errors).

    Here we're asking for user input again. We use a try statement here (because usually user error is where it's going to break). We want to catch when the user enters zero, because we have a statement that has 360 divided by the user input, and we cannot divide by 0. So when we come accross this, we can use the ZeroDivisionError to tell the user you cannot put in 0.
    We can also put in a ValueError for when users enter things like strings. The final one, the 'Exception' is just a catch all for errors. 
    This will run after you've run the try code, and everything runs smoothly.
   

Exception Classes

 There is an exception class that you can use to make child classes to create new errors to catch. (You  can see more here)
    Here we want to create an error that catches when we input a negative integer. It's a child of the value Error, because it is the most similar to that parent class. We don't actually need to put anything in the class for this one.
    You can test if the number is less than 0, and then raise a SubZeroPigs class. Then in your exceptions:

    This will create a squab instead of pigs if you get a negative user inputted number. Note it needs to be before ValueError, because it is a child class of Value Error, and if you raise it it can also trigger the ValueError exception. 

    There is a way of using a library called traceback and print out the actual data that caused the error in your code, as well as all the steps that led up to it. We've been dealing with this before in Houdini when we get errors in our code.  

File Processing

Four fundamental operations: 
1. Open a file
2. Read file contents to a string OR
3. Write a string to a file
4. Close the file

    Here's the syntax for opening and closing a file. I give the exact file path but had to put an 'r' infront of the path string to let python know it's a raw string (Don't use \ as an escape character)

    Now since we're going to be dealing with user error when they input a wrong file, we can use 'try' to check for errors. 

    For open() there are different types of 'open modes'. 
  • r : opens a file for reading, raises IOError if file doe not exist
  • w : opens a file for writing. truncates if file exists. creates if does not exist (WILL NOT THROW AN ERROR)
  • a : appends to the end of the file. creates if does not exist
  • r+ : opens file for reading or writing. raises IOError if file does not exist (be careful for this, because if you change the file, whatever you read is not what's in the file currently)
  • w+ : opens for reading or writing. truncates if file exists. creates if does not exists
    We can take the contents of the file and give it to a variable.
    inFile.read() will take the entirety of the file, and put it as a string. We then give this to a variable named filecontents. When we run this we get this:

Trying the write open mode

    If you wanted to try to create a file, use the write open mode ('w'). 

    Here we're creating a file named crag.py and opening it for write. Then we're going to assign the rocky variable the houdini node crag test geometry that's already in our scene. Next we're going to write: 
    This writes out the content of any houdini node object entirely as a python script. Then we just close the file, along with testing for any exceptions. 
We shouldn't necessarily need it since usually the write open mode doesn't usually throw an error, unless you're trying to write a place you don't have permission to.
    
Responsible Close
    You must guarantee any file access will not abandon an 'open' resource!. Use 'finally' to guarantee resource closure.
myFilePointer = open(myFile, mode)
try:
    performOperations(myFIlePointer)
finally:
    myFilePointer.close()
Even when the computer is about to crash, the finally node will always run before it crashes.

Context Managers

    Allow groups of statements to be run under control of single objects
with contextManager.Expression as targetVariable:
    doSomething(targetVariable)

Python gives us nice context managers for 'open', instead of try-finally
with open(fileName, mode='r') as fileVariable:
    inputString = fileVariable.read()
with open(fileName, mode'w') as fileVariable:
    fileVariable.write(outputString)
    It handles all the exceptions and trys for you within the Context Manager. It handles the close and everything. Here's an example of how to use it to write out the contents of Roberto into a python file:
And then we can continue this, destroying the rubbertoy and then reading back the contents of the file.
    This method only raises the exception, it does not catch it (meaning it would not print out whatever data it has at the moment of crash).

JSON

   A Json file is like an infinitely recursive python dictionary. It is a dictionary where the 'value' can be a list of directionaries. 
Example of using Json files with python

    First we get the json file and read it. It starts off as a string, and then using 'json.loads' we convert it to a python dictionary. Then we can manipulate the data (maybe append a new key+value), we use json.dumps
    What this does is it converts a dictionary to a bigass string. But dictionaries don't know what new lines or formatting is; so you have to tell it to format a certain way. The indent = 4 with the separators = , ; means whenever you encounter a comma or colon, stick in an indent with 4 spaces. Now you can write this string out as a new json file.

XML

    An XML file has a parent element, and then the parent has child elements, and then those child elements have further child elements underneath it. It has a branching tree structure. HTML is an XML format. 
    Every Element requires an opening tag and a closing tag.
    You can use ElementTree module to parse through an XML file.


Difference between JSON and XML:

    If your data is inherently recursive, use XML (if you're rigging and you want to output all of your bones, use XML to recursive walk through the bone hierarchy). 
    JSON seems to look better formatted when printed out.

Friday, 8 November 2024

Unreal - Blueprinting Interface

     Today we'll be talking about blueprint interfaces. Blueprints don't necessarily talk to each other, you have to tell them to do so. So far we've been casting blueprints to each other to get and set variables, but it can be heavy on performance. It's good for experimenting and if you need something quick, but blueprint interfacing is recommended for better performance. 

    We'll be using our HW7 project that had us practicing animation state machines. We need the animation blueprint and character blueprint to talk with each other.

Inside of ABP_Robo, we've got this in the event graph. Instead of casting to BP_Quinn we're going to make a blueprint interface. Right click on your content browser, blueprints>blueprint interface


This is what it looks like
    First Thing we're going to do is rename the function.

    Next, some of the variables we want to keep track of are Velocity and isFiring. So we can click on the QuinnData functions and add two variables to the outputs. It's going to store all the data that is transpiring, and then transmit this data to our other blueprints.
    Inside of our BP_Quin, we need to get the firing variables into our blueprint interface. First we need to tell BP_Quinn to listen to our BPI. Select class settings up at the top, go into the details tab, and under Interfaces>Implemented Interfaces
Make sure you hit class settings

Inside of BP_Quinn
    Now Quinn Data should show up under interfaces in your my Blueprint tab.
Inside of BP_Quinn
    Double clicking on it will pop up another window that includes all the nodes from the BPI inside of your BP_Quinn. You can drag variables from the 'my Blueprint' tab into the Quinn Data tab. 
Velocity is an inherent attribute of a pawn class when it is created, so we just need to call a get velocity node. 

    Now we need to tell our animation blueprint to listen in to any event associated with BPI Quinn. We do the same thing we did with the BP_Quinn by going into the class settings. Interfaces>Implemented Interfaces.
Shows up here now
    Now drag off from the  'Try Get Pawn Owner' node and look for Quinn Data. Make sure that it is the one with (Message) next to the name. 


    You need the one with message because the message node is the one reading the data from the BPI, which we can then set to our other variables. 
    We're completely disconnected the cast node now, but if we compile this, it should work and our character should run and fire. 
    The variables inputted into the BPI sends a signal into the world, The BPI listens to it, and then sends that signal to all of the blueprints we told them to listen to the BPI. The BPI doesn't do anything really, it just holds data and methods, and the other blueprints will set and get the variables from it. 

Illumination Models

 Basic Lighting Models The Three Illumination Models: ambient: color of objects in the scene with no direct light on it (When it is in shado...