#!/usr/bin/python
import os
def scanForFiles(_dir) :
# grab all the files in the directory
files = os.listdir(_dir)
# create an empty dictionary for the results
result = {}
# this will hold our sorted list to be returned
sortedList = []
# these are the valid file extensions as a tuple as ends with needs it
validFiles=("tiff","tif","exr","jpg","jpeg")
# process each of the files found and see if it's in the format
# name.#####.ext
for file in files:
# so first we try and split on the . and see if we have a valid suffix
try:
prefix, frame, suffix = file.split('.')
if suffix in validFiles :
try:
# if we do we add it to the dictionary (using the frame value)
result[prefix][0].append(frame)
except KeyError:
# we have a new file sequence, so create a new key:value pair
result[prefix] = [[frame],suffix]
except ValueError:
print "ignoring file in directory %s" %(file)
# now we have a list of data I want to return in the form that nuke needs
# in this case we need name.%xd.ext so we grab the data and format the string
for file in result :
# get the filename
fileName=file
# get the extention
extention=result[file][1]
# grab the numeric start and end values
minFrameValue=int(min(result[file][0]))
maxFrameValue=int(max(result[file][0]))
# this is a bit of a hack as the values could be different
# but we basically form the frame pad values
framePadSize=len(result[file][0][0])
padString="%%0%dd" %(framePadSize)
fileNameString="%s.%s.%s" %(fileName,padString,extention)
# add the data to the list
sortedList.append([fileNameString,minFrameValue,maxFrameValue])
# sort it and return
sortedList.sort()
return sortedList
path = nuke.getFilename("Select Directory")
files=scanForFiles(path)
print files
for fileData in files :
fileName=fileData[0].split(".")
fileName=fileName[0]
try :
fileNode = nuke.nodes.Read(file="%s/%s" %(path,fileData[0]),name=fileName)
fileNode.knob("first").setValue(fileData[1])
fileNode.knob("last").setValue(fileData[2])
fileNode.knob("on_error").setValue(3)
Shuffle = nuke.nodes.Shuffle(inputs=[fileNode],name=fileName)
except :
print "error on ",fileName
Wednesday, 18 May 2011
A Simple Nuke file loader
Wednesday, 6 April 2011
Using the Maya MScriptUtil class in Python
Been using some maya and python and discovered there was no way to access some of the MImage methods as they use pass by reference to return values. This after some investigation lead me to the MScriptUtil class.
Now a couple of things struck me when reading the documents, the first being
On further reading you get even more of a sense of that. What I wanted to do was to access the width and height of an MImage and access the RGB(A) data of the MImage and the only way of really doing this within python is to use the MScriptUtil Class.
MImage Class Design
I decided to wrap up all the functionality I needed into a simple python class which could be re-used within maya, the basic class design is as follows
The class will be constructed with a filename and will automatically read the image file and grab the image dimensions as well as a pointer to the data.
Initially the class was designed to check to see if alpha was present and determine if the data was packed as RGB or RGBA and step through the packed data accordingly, however on further reading of the documents I discovered
Using MScriptUtil
There are several ways to use MScriptUtil, the various constructors allow us to generate an object by passing in an object as a reference value, or we can create an instance of the class and then associate an object as a reference.
Initially I generated one MScriptUtil class, and associated the pointers from an already instantiated object. However this didn't work correctly. After reading the help I found the following note
Next a call to the MImage getSize method is called
As you can see in this method both the width and the height values are passed by reference. The code below passes the new pointers we have created using the script util class into the getSize method and these will be associated.
See all this code could be avoided if MImage had getWidth and getHeigh accessor methods!
Now for some speedups
To access the pixel data we need to grab the array of data from the MImage class, this is done with the following method call
The help says that
So we also have a pointer that we need to convert into a python data type. This can be done with the getUcharArrayItem however this would need to be created each time we try to access the data.
In python however it is possible to create a reference to a function / method in the actual code. This is done by assigning a variable name to a function and then using this instead of the actual function call. This can significantly speed up methods as the python interpretor doesn't have to lookup the method each time.
The following code shows this and the pointer to the method is stored as part of the class
To get the pixel data we need to calculate the index into the pointer array (1D) as a 2D x,y co-ordinate. This is a simple calculation as follows
The complete method is as follows
As you can see the index is calculated then the method saved earlier is called to grab the actual value at the index location (Red) then index+1 (green) index+2 (blue) and index+3 (alpha). For convenience I also wrote a getRGB method as shown
Other methods shown below are also added to the class to allow access to the attributes, whilst python allows access to these class attributes directly, when porting C++ code usually we will have getWidth / getHight style methods so I just added them.
Using the Class
The following example prompts the used for a file name then loads the image, anything in the red channel of the image with a pixel value greater than 10 is used to generate a cube of height r/10
Using the following image
Now a couple of things struck me when reading the documents, the first being
"This class is cumbersome to use but it provides a way of building parameters and accessing return values for methods which would normally not be scriptable".To my mind this reads as
"This is a hack but we couldn't be arsed to re-factor loads of our code to add proper accesors and mutators"
On further reading you get even more of a sense of that. What I wanted to do was to access the width and height of an MImage and access the RGB(A) data of the MImage and the only way of really doing this within python is to use the MScriptUtil Class.
MImage Class Design
I decided to wrap up all the functionality I needed into a simple python class which could be re-used within maya, the basic class design is as follows
The class will be constructed with a filename and will automatically read the image file and grab the image dimensions as well as a pointer to the data.
import maya.OpenMaya as om import sys class MayaImage : """ The main class, needs to be constructed with a filename """ def __init__(self,filename) : """ constructor pass in the name of the file to load (absolute file name with path) """ # create an MImage object self.image=om.MImage() # read from file MImage should handle errors for us so no need to check self.image.readFromFile(filename) # as the MImage class is a wrapper to the C++ module we need to access data # as pointers, to do this use the MScritUtil helpers self.scriptUtilWidth = om.MScriptUtil() self.scriptUtilHeight = om.MScriptUtil() # first we create a pointer to an unsigned in for width and height widthPtr = self.scriptUtilWidth.asUintPtr() heightPtr = self.scriptUtilHeight.asUintPtr() # now we set the values to 0 for each self.scriptUtilWidth.setUint( widthPtr, 0 ) self.scriptUtilHeight.setUint( heightPtr, 0 ) # now we call the MImage getSize method which needs the params passed as pointers # as it uses a pass by reference self.image.getSize( widthPtr, heightPtr ) # once we get these values we need to convert them to int so use the helpers self.m_width = self.scriptUtilWidth.getUint(widthPtr) self.m_height = self.scriptUtilHeight.getUint(heightPtr) # now we grab the pixel data and store self.charPixelPtr = self.image.pixels() # query to see if it's an RGB or RGBA image, this will be True or False self.m_hasAlpha=self.image.isRGBA() # if we are doing RGB we step into the image array in 3's # data is always packed as RGBA even if no alpha present self.imgStep=4 # finally create an empty script util and a pointer to the function # getUcharArrayItem function for speed scriptUtil = om.MScriptUtil() self.getUcharArrayItem=scriptUtil.getUcharArrayItem
Initially the class was designed to check to see if alpha was present and determine if the data was packed as RGB or RGBA and step through the packed data accordingly, however on further reading of the documents I discovered
"The image is stored as an uncompressed array of pixels, that can be read and manipulated directly. For simplicity, the pixels are stored in a RGBA format (4 bytes per pixel)".So this was not required and was removed.
Using MScriptUtil
There are several ways to use MScriptUtil, the various constructors allow us to generate an object by passing in an object as a reference value, or we can create an instance of the class and then associate an object as a reference.
Initially I generated one MScriptUtil class, and associated the pointers from an already instantiated object. However this didn't work correctly. After reading the help I found the following note
So if you need to use two pointers at the same time you need to create two MScriptUtil objects.
self.scriptUtilWidth = om.MScriptUtil() self.scriptUtilHeight = om.MScriptUtil() # first we create a pointer to an unsigned in for width and height widthPtr = self.scriptUtilWidth.asUintPtr() heightPtr = self.scriptUtilHeight.asUintPtr()Next we set the values to 0 for both the pointers, whilst this is not required, it make sure when we load the actual values if nothing is returned we have a null value.
Next a call to the MImage getSize method is called
self.scriptUtilWidth.setUint( widthPtr, 0 ) self.scriptUtilHeight.setUint( heightPtr, 0 ) self.image.getSize( widthPtr, heightPtr )Finally we need to extract the values that the pointers are pointing to, and load them into our python class which is done in the following code
self.m_width = self.scriptUtilWidth.getUint(widthPtr) self.m_height = self.scriptUtilHeight.getUint(heightPtr)
See all this code could be avoided if MImage had getWidth and getHeigh accessor methods!
Now for some speedups
To access the pixel data we need to grab the array of data from the MImage class, this is done with the following method call
self.charPixelPtr = self.image.pixels()
The help says that
"Returns a pointer to the first pixel of the uncompressed pixels array. This array is tightly packed, of size (width * height * depth * sizeof( float)) bytes".
So we also have a pointer that we need to convert into a python data type. This can be done with the getUcharArrayItem however this would need to be created each time we try to access the data.
In python however it is possible to create a reference to a function / method in the actual code. This is done by assigning a variable name to a function and then using this instead of the actual function call. This can significantly speed up methods as the python interpretor doesn't have to lookup the method each time.
The following code shows this and the pointer to the method is stored as part of the class
scriptUtil = om.MScriptUtil() self.getUcharArrayItem=scriptUtil.getUcharArrayItemgetPixels
To get the pixel data we need to calculate the index into the pointer array (1D) as a 2D x,y co-ordinate. This is a simple calculation as follows
index=(y*self.m_width*4)+x*4In this case we hard code the 4 as the MImage help states that the data is always stored as RGBA if this were not the case we would have to query if the data contained an alpha channel and make the step 3 or 4 depending upon this.
The complete method is as follows
def getPixel(self,x,y) : """ get the pixel data at x,y and return a 3/4 tuple depending upon type """ # check the bounds to make sure we are in the correct area if x<0 or x>self.m_width : print "error x out of bounds\n" return if y<0 or y>self.m_height : print "error y our of bounds\n" return # now calculate the index into the 1D array of data index=(y*self.m_width*4)+x*4 # grab the pixels red = self.getUcharArrayItem(self.charPixelPtr,index) green = self.getUcharArrayItem(self.charPixelPtr,index+1) blue = self.getUcharArrayItem(self.charPixelPtr,index+2) alpha=self.getUcharArrayItem(self.charPixelPtr,index+3) return (red,green,blue,alpha)
As you can see the index is calculated then the method saved earlier is called to grab the actual value at the index location (Red) then index+1 (green) index+2 (blue) and index+3 (alpha). For convenience I also wrote a getRGB method as shown
def getRGB(self,x,y) :
r,g,b,a=getPixel(x,y)
return (r,g,b)
Other methods shown below are also added to the class to allow access to the attributes, whilst python allows access to these class attributes directly, when porting C++ code usually we will have getWidth / getHight style methods so I just added them.
def width(self) :
""" return the width of the image """
return self.m_width
def height(self) :
""" return the height of the image """
return self.m_height
def hasAlpha(self) :
""" return True is the image has an Alpha channel """
return self.m_hasAlpha
Using the Class
The following example prompts the used for a file name then loads the image, anything in the red channel of the image with a pixel value greater than 10 is used to generate a cube of height r/10
import maya.OpenMaya as om
import maya.cmds as cmds
basicFilter = "*.*"
imageFile=cmds.fileDialog2(caption="Please select imagefile",
fileFilter=basicFilter, fm=1)
img=MayaImage(str(imageFile[0]))
print img.width()
print img.height()a
xoffset=-img.width()/2
yoffset=-img.height()/2
for y in range (0,img.height()) :
for x in range(0,img.width()) :
r,g,b,a=img.getPixel(x,y)
if r > 10 :
cmds.polyCube(h=float(r/10))
cmds.move(xoffset+x,float(r/10)/2,yoffset+y)
Using the following image
We produce the following
Friday, 25 March 2011
Staring out the Windoze
My usual day to day development environment is a mac, and I usually write code using Qt / g++ so it's easy to port it to Linux which is the main development system at the University. However most of the students seem to use Windows as their main computer system so I had to really support windows with my NGL library.
Luckily the Windows version of the QtSDK comes with a g++ compatible compiler called mingw and once the other libraries were installed in the correct places the library seems to compile and link into a DLL.
Problems with GLEW
The first problem I encountered was with the GLEW library. The versions available for download are compiled with the visual studio compiler and are not compatible with the mingw compiler / linker. To make this easier to use and integrate into the ngl library I added the source of the latest GLEW tree to the ngl project and this is included into the windows branch of ngl automatically in the Qt project file. This is done using the following code
Cross Platform Development
Some other conditional code was also required to make things compile correctly on all platforms. NGL uses 3 flags to set specific platform dependent includes and flags. These are found in the .pro file as part of the qmake process and the following shows these
So not the compiler will pass on the command line -D[DARWIN][LINUX][WIN32] dependent upon the platform being used. This is then used to switch in the main header files for the libs, and to place this all in one include file the <ngl/Types.h> is used and has the following conditional compilation code
With all this in place the library built with no problems and a .dll / .a file was generated under windows and a simple demo application would run. I left the library under windows at this stage and continued the development under mac, however I began to get reports of things not working under windows. Initially I thought it was a driver / gpu problem as it seem to occur in the shader manager class.
When things go wrong
The first indications of things not working was when a lookup to a shader returned the null program which is a safety mechanism. The following code shows the method and the call to the method.
It appeared at first that the map find method was at fault as it was not finding a shader that I knew was in the class. However I started to print out the size and contents of the map I noticed that in some cases the map contained the correct values and in other cases it was empty. Why would this be?
This shouldn't happen!
It seemed weird that this was happening as the class was based on a singleton pattern so there should only be one instance of the class, also this bug only appeared in the windows version so I was unsure what was going on.
I placed a number of debug statements in the singleton template class used for the shader manager and discovered that the constructor was being called twice WTF!
My initial response was that I was having a weird threading issue and my singleton wasn't thread safe, so I added a QMutex / QMutexLocker construct around the singleton and also made it inherit from boost::noncopyable so that it could not be copied.
This however was not the solution to the problem as it still continued.
Digging Deeper
After digging deeper into the code I traced exactly where the rouge call to the singleton ctor came from. In my TransformStack there is a convenience method to load the current Transform into a parameter of the shader, the code is actually in the ngl::Transform class and looks like this.
Separate compilation units
It seems in the windows DLL the library data is in a different scope when constructed unlike the unix / mac shared libraries. In this case when the dll invokes the shader manager singleton, it doesn't exist in the DLL memory space even though it has been called in the main application linking to the DLL. In other words the singleton is not shared by default. There is a good post here about it.
To test this was the case I quickly created a static global variable, within the transform class which could be set outside the DLL module by a quick method call. This basically passed in the application side instance of the Shader manager class, and used that in the call, and it fixed the problem. However Rob the Bloke pointed out the error of my ways by saying
It was now time to bite the bullet and do a major re-factor of the code to make all the classes export from the DLL properly, I've never done this before and the initial attempts were somewhat problematic, however the basic approach is as Rob said
So we need some pre-compilation glue to make this work, first under windows only then dependent upon if we are building the dll or using the dll. The following code was added to the Types.h class
Once this has been added to the types file we need to add the BUILDING_DLL define to each of the classes. This is shown below.
Once this was added it seemed to work for all the classes in the basic demo. However some of the more advance demos still failed to compile. I have several functions in the library which also needed to be exported, this was done as follows
And finally any extraction / insertion operators as they are friend classes will also need to be exported
Finally it's working !
Or so I thought, some of the simple programs that only used the ngl library compiled and ran correctly, however one of the programs which made a call to glUseProgram(0) in the main application failed to compile, due to a linker error, this error said that the method was not found and gave an imp__glew.... style error which means that the actual function call could not be found.
I thought that I had statically bound GLEW into my lib so it would work, however GLEW basically binds method calls to the driver once the glewInit function is called and in this case this is called in the DLL but no my application, to overcome this problem I need to put the glew.c source code into my applications as well the library.
To do this the following is added to the application .pro file (as the glew.c file is shipped with ngl)
And finally we need to initialise glew in both the DLL and Application
This is not an ideal solution but is quite easy to do and saves the hassle of building the glew library using Qt / mingw. I will at some stage do this but for now this quick hack will suffice.
And for my next trick
Now that I've sullied my mac by installing windows I think I need to do a bit more windows support, the next stage is going to be building a Visual Studio version of ngl. Once this is done I think I may also add a DirectX back end to it as well however for now I will stick to the mac ;-)
Luckily the Windows version of the QtSDK comes with a g++ compatible compiler called mingw and once the other libraries were installed in the correct places the library seems to compile and link into a DLL.
Problems with GLEW
The first problem I encountered was with the GLEW library. The versions available for download are compiled with the visual studio compiler and are not compatible with the mingw compiler / linker. To make this easier to use and integrate into the ngl library I added the source of the latest GLEW tree to the ngl project and this is included into the windows branch of ngl automatically in the Qt project file. This is done using the following code
win32: {
LIBS += -lmingw32
LIBS += -lstdc++
DEFINES += WIN32
DEFINES += USING_GLEW
DEFINES +=GLEW_STATIC
INCLUDEPATH += C:/boost_1_44_0
INCLUDEPATH += C:/boost
LIBS += -L/lib/w32api
DESTDIR = c:/
SOURCES+=$$BASE_DIR/Support/glew/glew.c
INCLUDEPATH+=$$BASE_DIR/Support/glew/
DEFINES+=_WIN32
CONFIG+=dll
DEFINES+=BUILDING_DLL
}
Once this code is in place, the windows version of Qt will set the win32 specific flags and compile correctly and also include the glew elements statically into my library and hence reducing a dll dependancy.Cross Platform Development
Some other conditional code was also required to make things compile correctly on all platforms. NGL uses 3 flags to set specific platform dependent includes and flags. These are found in the .pro file as part of the qmake process and the following shows these
linux-g++:{
DEFINES += LINUX
LIBS+=-lGLEW
}
linux-g++-64:{
DEFINES += LINUX
LIBS+=-lGLEW
}
macx:DEFINES += DARWIN
So not the compiler will pass on the command line -D[DARWIN][LINUX][WIN32] dependent upon the platform being used. This is then used to switch in the main header files for the libs, and to place this all in one include file the <ngl/Types.h> is used and has the following conditional compilation code
#if defined (LINUX) || defined (WIN32) #include <gl/glew.h> #include <gl/gl.h> #include <gl/glu.h> #endif #ifdef DARWIN #include <unistd.h> #include <opengl/gl.h> #include <opengl/glu.h> #endif
With all this in place the library built with no problems and a .dll / .a file was generated under windows and a simple demo application would run. I left the library under windows at this stage and continued the development under mac, however I began to get reports of things not working under windows. Initially I thought it was a driver / gpu problem as it seem to occur in the shader manager class.
When things go wrong
The first indications of things not working was when a lookup to a shader returned the null program which is a safety mechanism. The following code shows the method and the call to the method.
ShaderProgram * ShaderManager::operator[](
const std::string &_programName
)
{
std::map <std::string, ShaderProgram * >::const_iterator program=m_shaderPrograms.find(_programName);
// make sure we have a valid program
if(program!=m_shaderPrograms.end() )
{
return program->second;
}
else
{
std::cerr<<"Warning Program not know in [] "<<_programName.c_str();
std::cerr<<"returning a null program and hoping for the best\n";
return m_nullProgram;
}
}
// in application
ngl::ShaderManager *shader=ngl::ShaderManager::instance();
(*shader)["Blinn"]->use();
It appeared at first that the map find method was at fault as it was not finding a shader that I knew was in the class. However I started to print out the size and contents of the map I noticed that in some cases the map contained the correct values and in other cases it was empty. Why would this be?
This shouldn't happen!
It seemed weird that this was happening as the class was based on a singleton pattern so there should only be one instance of the class, also this bug only appeared in the windows version so I was unsure what was going on.
I placed a number of debug statements in the singleton template class used for the shader manager and discovered that the constructor was being called twice WTF!
My initial response was that I was having a weird threading issue and my singleton wasn't thread safe, so I added a QMutex / QMutexLocker construct around the singleton and also made it inherit from boost::noncopyable so that it could not be copied.
This however was not the solution to the problem as it still continued.
Digging Deeper
After digging deeper into the code I traced exactly where the rouge call to the singleton ctor came from. In my TransformStack there is a convenience method to load the current Transform into a parameter of the shader, the code is actually in the ngl::Transform class and looks like this.
void Transformation::loadMatrixToShader(
const std::string &_shader,
const std::string &_param,
const ACTIVEMATRIX &_which
)
{
computeMatrices();
ShaderManager *shader=ngl::ShaderManager::instance();
switch (_which)
{
case NORMAL :
{
shader->setShaderParamFromMatrix(_shader,_param,m_matrix);
}
break;
case TRANSPOSE :
{
shader->setShaderParamFromMatrix(_shader,_param,m_transposeMatrix);
}
break;
case INVERSE :
{
shader->setShaderParamFromMatrix(_shader,_param,m_inverseMatrix);
}
break;
}
}
This method actually lives in the library, and hence under windows in the DLL, and this is where the problem begins.Separate compilation units
It seems in the windows DLL the library data is in a different scope when constructed unlike the unix / mac shared libraries. In this case when the dll invokes the shader manager singleton, it doesn't exist in the DLL memory space even though it has been called in the main application linking to the DLL. In other words the singleton is not shared by default. There is a good post here about it.
To test this was the case I quickly created a static global variable, within the transform class which could be set outside the DLL module by a quick method call. This basically passed in the application side instance of the Shader manager class, and used that in the call, and it fixed the problem. However Rob the Bloke pointed out the error of my ways by saying
"That's a critical bug and refactor waiting to happen. If you need to share something across DLL boundaries, there is exactly one way to achieve that. Your solution fails the second anyone adds another DLL that links to ngl (i.e. you've just offset the problem for your students to deal with later). s_globalShaderInstance *needs* to be DLL exported."Making it a "proper" dll
It was now time to bite the bullet and do a major re-factor of the code to make all the classes export from the DLL properly, I've never done this before and the initial attempts were somewhat problematic, however the basic approach is as Rob said
"if you need to access something from a DLL, it needs to be DLL_EXPORTED. End of story"This code would have to be in the windows version, and depending upon wether we are building the library (DLL) or using the library we would need to either __dllimport or __dllexport our classes.
So we need some pre-compilation glue to make this work, first under windows only then dependent upon if we are building the dll or using the dll. The following code was added to the Types.h class
#ifdef WIN32
#ifdef BUILDING_DLL
#define NGL_DLLEXPORT __declspec(dllexport)
#else
#define NGL_DLLEXPORT __declspec(dllimport)
#endif
#else
#define NGL_DLLEXPORT
#endif
Once this has been added to the types file we need to add the BUILDING_DLL define to each of the classes. This is shown below.
class NGL_DLLEXPORT Vector
{
...
};
Once this was added it seemed to work for all the classes in the basic demo. However some of the more advance demos still failed to compile. I have several functions in the library which also needed to be exported, this was done as follows
// Util.h
extern NGL_DLLEXPORT ngl::Real radians(
const Real _deg
);
// Util.cpp
NGL_DLLEXPORT Real radians(
const ngl::Real _deg
)
{
return (_deg/180.0f) * M_PI;
}
And finally any extraction / insertion operators as they are friend classes will also need to be exported
friend NGL_DLLEXPORT std::istream& operator>>(std::istream& _input, ngl::Vector &_s);
NGL_DLLEXPORT std::istream& operator>>(
std::istream& _input,
ngl::Vector& _s
)
{
return _input >> _s.m_x >> _s.m_y >> _s.m_z;
}
Finally it's working !
Or so I thought, some of the simple programs that only used the ngl library compiled and ran correctly, however one of the programs which made a call to glUseProgram(0) in the main application failed to compile, due to a linker error, this error said that the method was not found and gave an imp__glew.... style error which means that the actual function call could not be found.
I thought that I had statically bound GLEW into my lib so it would work, however GLEW basically binds method calls to the driver once the glewInit function is called and in this case this is called in the DLL but no my application, to overcome this problem I need to put the glew.c source code into my applications as well the library.
To do this the following is added to the application .pro file (as the glew.c file is shipped with ngl)
win32: {
DEFINES+=USING_GLEW
INCLUDEPATH+=-I c:/boost_1_44_0
INCLUDEPATH+=-I c:/boost
INCLUDEPATH+= -I C:/NGL/Support/glew
LIBS+= -L C:/NGL/lib
LIBS+= -lmingw32
DEFINES += WIN32
DEFINES += USING_GLEW
DEFINES +=GLEW_STATIC
DEFINES+=_WIN32
SOURCES+=C:/NGL/Support/glew/glew.c
INCLUDEPATH+=C:/NGL/Support/glew/
}
And finally we need to initialise glew in both the DLL and Application
ngl::NGLInit *Init = ngl::NGLInit::instance(); #ifdef WIN32 glewInit(); #endif Init->initGlew();
This is not an ideal solution but is quite easy to do and saves the hassle of building the glew library using Qt / mingw. I will at some stage do this but for now this quick hack will suffice.
And for my next trick
Now that I've sullied my mac by installing windows I think I need to do a bit more windows support, the next stage is going to be building a Visual Studio version of ngl. Once this is done I think I may also add a DirectX back end to it as well however for now I will stick to the mac ;-)
Tuesday, 22 March 2011
Using Qt GUI components and NGL
This blog post is going to be my first Video blog post, it show how to start from scratch and create a gui program using Qt and NGL, the videos have been posted to youtube as the quality is much better than on blogspot, and you can grab the code using bzr from here
bzr branch http://nccastaff.bmth.ac.uk/jmacey/Code/QtNGL
For my students I've also copied the original movie files onto the public directory here /public/mapublic/jmacey/QtNGL/
Part 1 Setup
This video show how to setup the initial project and directories, it uses the ncca coding standard for project setup
Part 2 Adding source files to the project
This video shows how to add our source files as well as an initial QMainWindow class and a main.cpp file
Part 3 Setting up the .pro project file
This move shows how to setup the .pro file for qmake and we write a simple QApplication within the main.cpp file
Part 4 Creating a GUI using the Designer
In this Video I create the inital part of the GUI using the QDesigner module of QT Creator, I also go into quite a lot of detail about using the QGridLayout and the use of Layouts in general
Part 5 Adding the rest of the GUI components
In this video I add the rest of the UI components and finish off the rest of the GUI.
Part 6 Creating the OpenGL Window
In this video we extend the QGLWidget class to build our own GLWindow which we will put in the NGL classes to draw a teapot / sphere and cube and adjust the properties of the ngl::Transform Class. In this initial part of this video we add the GLWindow to the grid layout. I've added an empty GLWindow.cpp/h file here
Part 7 Adding NGL to the .pro file
Next we add lots of flags to the .pro qmake file to configure the paths used for the ngl library.
Part 8 Adding a Camera
Now we add an ngl::Camera class and a simple ngl::Primitive drawing routine
Part 9 Adding glsl Shaders
Using the ngl::ShaderManager class we load the vertex and fragment shaders and give them a quick test
Part 10 Adding Signals and Slots
We now use the Qt Signals and Slots mechanism to wire up the GUI components to our GLWindow class
Part 11 connecting the rest of the GUI components
Using the signals and slots mechanism I wire up the rest of the components
Part 12 Connecting the combo box
Using signals and slots I connect the QComboBox to select the different drawing modes
Part 13 Lights and Colour
In the final instalment I add an ngl::Light and Material to the program to light and set the initial material values, next I use the QColorDialog to select the colour I wish to use for the object
bzr branch http://nccastaff.bmth.ac.uk/jmacey/Code/QtNGL
For my students I've also copied the original movie files onto the public directory here /public/mapublic/jmacey/QtNGL/
Part 1 Setup
This video show how to setup the initial project and directories, it uses the ncca coding standard for project setup
Part 2 Adding source files to the project
This video shows how to add our source files as well as an initial QMainWindow class and a main.cpp file
Part 3 Setting up the .pro project file
This move shows how to setup the .pro file for qmake and we write a simple QApplication within the main.cpp file
Part 4 Creating a GUI using the Designer
In this Video I create the inital part of the GUI using the QDesigner module of QT Creator, I also go into quite a lot of detail about using the QGridLayout and the use of Layouts in general
Part 5 Adding the rest of the GUI components
In this video I add the rest of the UI components and finish off the rest of the GUI.
Part 6 Creating the OpenGL Window
In this video we extend the QGLWidget class to build our own GLWindow which we will put in the NGL classes to draw a teapot / sphere and cube and adjust the properties of the ngl::Transform Class. In this initial part of this video we add the GLWindow to the grid layout. I've added an empty GLWindow.cpp/h file here
Part 7 Adding NGL to the .pro file
Next we add lots of flags to the .pro qmake file to configure the paths used for the ngl library.
Part 8 Adding a Camera
Now we add an ngl::Camera class and a simple ngl::Primitive drawing routine
Part 9 Adding glsl Shaders
Using the ngl::ShaderManager class we load the vertex and fragment shaders and give them a quick test
Part 10 Adding Signals and Slots
We now use the Qt Signals and Slots mechanism to wire up the GUI components to our GLWindow class
Part 11 connecting the rest of the GUI components
Using the signals and slots mechanism I wire up the rest of the components
Part 12 Connecting the combo box
Using signals and slots I connect the QComboBox to select the different drawing modes
Part 13 Lights and Colour
In the final instalment I add an ngl::Light and Material to the program to light and set the initial material values, next I use the QColorDialog to select the colour I wish to use for the object
Monday, 14 March 2011
Game Style Object control in Qt
It is quite typical in a game to need to control an object by keeping a key pressed and for it to respond to these pressed key continuously. The following example demonstrates this in action as shown in the following video
Space Ship class
The following class diagram shows the basic space ship class we are going to move
The constructor of the class is very simple, it loads in the mesh and sets the member variables to default values as shown
To draw the ship we move our transform stack position to the correct location and rotation and draw the VBO from the mesh
For the rotation the value is just added.
Processing Key presses
Each subclass of the QWidget class has access to the following virtual protected methods
GLWindow key processing
To process the keys in the GLWindow class we are going to create a QSet to contain the active key strokes, for each keyPress event (key down) we will add the key to the QSet using the += operator. Every time is released the key will be removed from the QSet using the -= operator. This is shown in the following code
Now we will create an method to process the key values stored in the set and update the ship.
First we set the x,y and rotation values to 0 to indicate there is no movement to the ship, next we loop through all the keys in the QSet using the foreach iterator provided by Qt. We then check to see if a key is active and update the direction variable based on the static variable s_shipUpdate. Depending upon the direction we set this to +/-s_shipUpdate.
Finally we check to see if there are any keys active and if so update the ship direction by calling the move function.
Tying it all together
Finally we want to separate the movement and draw commands so we can get a fixed update for drawing. To do this we create two timers one which will trigger the ship move update and one for the re-draw. This is done with the following code, first in the constructor we start the timer.
Next in the timerEvent in GLWindow we process and dispatch if we get the correct timer event as follows
The full code for this demo can be downloaded from here using the command bzr branch http://nccastaff.bmth.ac.uk/jmacey/Code/GameKeyControl
Space Ship class
The following class diagram shows the basic space ship class we are going to move
The constructor of the class is very simple, it loads in the mesh and sets the member variables to default values as shown
SpaceShip::SpaceShip(
ngl::Vector _pos,
std::string _fname
)
{
m_pos=_pos;
m_mesh = new ngl::Obj(_fname);
m_mesh->createVBO(GL_STATIC_DRAW);
m_rotation=0;
}
To draw the ship we move our transform stack position to the correct location and rotation and draw the VBO from the mesh
void SpaceShip::draw()
{
ngl::ShaderManager *shader=ngl::ShaderManager::instance();
(*shader)["Blinn"]->use();
m_transform.getCurrentTransform().setPosition(m_pos);
m_transform.setRotation(0,m_rotation,0);
m_transform.loadGlobalAndCurrentMatrixToShader("Blinn","ModelMatrix");
m_mesh->draw();
}
To update the ships position m_pos and rotation we use the following methodsconst static float s_xExtents=40;
const static float s_yExtents=30;
void SpaceShip::move(
float _x,
float _y
)
{
float currentX=m_pos.m_x;
float currentY=m_pos.m_y;
m_pos.m_x+=_x;
m_pos.m_y+=_y;
if(m_pos.m_x<=-s_xExtents || m_pos.m_x>=s_xExtents)
{
m_pos.m_x=currentX;
}
if(m_pos.m_y<=-s_yExtents || m_pos.m_y>=s_yExtents)
{
m_pos.m_y=currentY;
}
}
void SpaceShip::rotate(
float _rot
)
{
m_rotation+=_rot;
}
The move method, first stores the current x and y values of m_pos, we then increment the values based on the parameter passed in and check agains the extents of the screen. If we are not at the edge of the screen as set in these extents we add the values passed in to the value of the m_pos attribute.For the rotation the value is just added.
Processing Key presses
Each subclass of the QWidget class has access to the following virtual protected methods
void QWidget::keyPressEvent ( QKeyEvent * event ) ; void QWidget::keyReleaseEvent ( QKeyEvent * event );Which ever QtWindow has focus will receive these events and the current key can be queried via the event parameter passed in. In this example the MainWindow class processes the key events as we wish to allow the window to become fullscreen and this method can only be called from a class that inherits from QMainWindow The following code shows how the key press and release methods are implemented
void MainWindow::keyPressEvent(
QKeyEvent *_event
)
{
// this method is called every time the main window recives a key event.
switch (_event->key())
{
case Qt::Key_Escape : QApplication::exit(EXIT_SUCCESS); break;
case Qt::Key_W : glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); break;
case Qt::Key_S : glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); break;
case Qt::Key_F : showFullScreen(); break;
case Qt::Key_N : showNormal(); break;
default : break;
}
// once we have processed any key here pass the event
// onto the GLWindow class to do more processing
m_gl->processKeyDown(_event);
}
void MainWindow::keyReleaseEvent(
QKeyEvent *_event
)
{
// once we have processed any key here pass the event
// onto the GLWindow class to do more processing
m_gl->processKeyUp(_event);
}
Once any keys required in the MainWindow class are processed the key events are then passed onto the GLWindow class for more processing.GLWindow key processing
To process the keys in the GLWindow class we are going to create a QSet to contain the active key strokes, for each keyPress event (key down) we will add the key to the QSet using the += operator. Every time is released the key will be removed from the QSet using the -= operator. This is shown in the following code
// in GLWindow.h
/// @brief the keys being pressed
QSet< Qt::Key> m_keysPressed;
// in GLWindow.cpp
void GLWindow::processKeyDown(
QKeyEvent *_event
)
{
// add to our keypress set the values of any keys pressed
m_keysPressed += (Qt::Key)_event->key();
}
void GLWindow::processKeyUp(
QKeyEvent *_event
)
{
// remove from our key set any keys that have been released
m_keysPressed -= (Qt::Key)_event->key();
}
Now we will create an method to process the key values stored in the set and update the ship.
void GLWindow::moveShip()
{
/// first we reset the movement values
float xDirection=0.0;
float yDirection=0.0;
float rotation=0.0;
// now we loop for each of the pressed keys in the the set
// and see which ones have been pressed. If they have been pressed
// we set the movement value to be an incremental value
foreach(Qt::Key key, m_keysPressed)
{
switch (key)
{
case Qt::Key_Left : { xDirection=s_shipUpdate; break;}
case Qt::Key_Right : { xDirection=-s_shipUpdate; break;}
case Qt::Key_Up : { yDirection=s_shipUpdate; break;}
case Qt::Key_Down : { yDirection=-s_shipUpdate; break;}
case Qt::Key_R : { rotation=1.0; break;}
default : break;
}
}
// if the set is non zero size we can update the ship movement
// then tell openGL to re-draw
if(m_keysPressed.size() !=0)
{
m_ship->move(xDirection,yDirection);
m_ship->rotate(rotation);
}
}
First we set the x,y and rotation values to 0 to indicate there is no movement to the ship, next we loop through all the keys in the QSet using the foreach iterator provided by Qt. We then check to see if a key is active and update the direction variable based on the static variable s_shipUpdate. Depending upon the direction we set this to +/-s_shipUpdate.
Finally we check to see if there are any keys active and if so update the ship direction by calling the move function.
Tying it all together
Finally we want to separate the movement and draw commands so we can get a fixed update for drawing. To do this we create two timers one which will trigger the ship move update and one for the re-draw. This is done with the following code, first in the constructor we start the timer.
m_updateShipTimer=startTimer(2); m_redrawTimer=startTimer(10);
Next in the timerEvent in GLWindow we process and dispatch if we get the correct timer event as follows
void GLWindow::timerEvent(
QTimerEvent *_event
)
{
// the usual process is to check the event timerID and compare it to
// any timers we have started with startTimer
if (_event->timerId() == m_updateShipTimer)
{
moveShip();
}
if (_event->timerId() == m_redrawTimer)
{
updateGL();
}
}
The full code for this demo can be downloaded from here using the command bzr branch http://nccastaff.bmth.ac.uk/jmacey/Code/GameKeyControl
Friday, 11 March 2011
Anonymous Union / Struct Weirdness
I've been having a look at clang++ / llvm as this will be the new default compiler for Mac, when I came across the following error in some of my code.
Well i'm not really sure, according to clang++ "anonymous structs are a GNU extension" so are allowed, however this doesn't account for the breaking of encapsulation.
After further tests I moved the union into the protected area and still it works and I can access the member directly. So it would appear that anonymous struct / unions are always public.
Testing this with Visual Studio 2005 I get the same results, however with the new version of clang++ that ships with Xcode 4 (Apple clang version 2.0 (tags/Apple/clang-137) (based on LLVM 2.9svn)) I get the following
clang++ -pedantic -fdiagnostics-fixit-info VecTest.cpp -o Test
VecTest.cpp:30:5: warning: anonymous structs are a GNU extension [-Wgnu]
struct
^
VecTest.cpp:46:4: error: 'm_x' is a protected member of 'Vector'
v.m_x=2.0;
^
VecTest.cpp:32:13: note: declared protected here
float m_x;
^
VecTest.cpp:47:15: error: 'm_x' is a protected member of 'Vector'
std::cout<<v.m_x<<"\n";
^
VecTest.cpp:32:13: note: declared protected here
float m_x;
^
1 warning and 2 errors generated.
So it seems clang++ deals with the visibility / scope correctly unlike the other two compilers, for now I think I'm going to move the structs in my library to public anyway as this will give me more speed / access for the basic math classes.
In file included from src/Vector.cpp:17:
include/ngl/Vector.h(476) : error: 'm_x' is a protected member of 'ngl::Vector'
return Vector(_k*_v.m_x, _k*_v.m_y, _k*_v.m_z,_v.m_w);
^
include/ngl/Vector.h(423) : note: declared protected here
Real m_x;
^
This is fair enough however this compiled fine with g++, after turning on the pedantic flag in g++ I got the following error
In file included from src/Vector.cpp:17:
include/ngl/Vector.h:437: error: ISO C++ prohibits anonymous structs
Now this is where the plot thickens and I learnt something new.
So clang++ says I have a visibility problem, whereas g++ running normally says this is fine, and g++ in full pedantic mode say no way, who is correct and what is going on?
A Simple Test Class
To test the problems I was encountering I decided to write a simple class as shown below
class Vector
{
public :
Vector(
const Vector& _v
) :
m_x(_v.m_x),
m_y(_v.m_y),
m_z(_v.m_z),
m_w(_v.m_w){;}
Vector(
const float& _x=0.0,
const float& _y=0.0,
const float& _z=0.0,
const float& _w=1.0f
):
m_x(_x),
m_y(_y),
m_z(_z),
m_w(_w){;}
private :
union
{
struct
{
float m_x;
float m_y;
float m_z;
float m_w;
};
float m_openGL[4];
};
};
So here we have a simple union of 4 floats and an array of 4 floats which if we look at the memory layout would look like thisI then wrote a simple test program which looked like this
int main()
{
Vector v(1.0,2.0,3.0,1.0);
v.m_x=2.0;
std::cout<< v.m_x<<"\n";
}
You will notice in the above program, that I access the member m_x directly. However in the class this is declared in the private section. How does this work?Well i'm not really sure, according to clang++ "anonymous structs are a GNU extension" so are allowed, however this doesn't account for the breaking of encapsulation.
After further tests I moved the union into the protected area and still it works and I can access the member directly. So it would appear that anonymous struct / unions are always public.
Testing this with Visual Studio 2005 I get the same results, however with the new version of clang++ that ships with Xcode 4 (Apple clang version 2.0 (tags/Apple/clang-137) (based on LLVM 2.9svn)) I get the following
clang++ -pedantic -fdiagnostics-fixit-info VecTest.cpp -o Test
VecTest.cpp:30:5: warning: anonymous structs are a GNU extension [-Wgnu]
struct
^
VecTest.cpp:46:4: error: 'm_x' is a protected member of 'Vector'
v.m_x=2.0;
^
VecTest.cpp:32:13: note: declared protected here
float m_x;
^
VecTest.cpp:47:15: error: 'm_x' is a protected member of 'Vector'
std::cout<<v.m_x<<"\n";
^
VecTest.cpp:32:13: note: declared protected here
float m_x;
^
1 warning and 2 errors generated.
So it seems clang++ deals with the visibility / scope correctly unlike the other two compilers, for now I think I'm going to move the structs in my library to public anyway as this will give me more speed / access for the basic math classes.
Wednesday, 19 January 2011
Houdini Python .asCode()
Been writing some Houdini python code recently to help with the import and export of animation data from our 3 main animation packages (Maya, XSI, Houdini) as well as to my C++ / Python engines.
Houdini has a good python model and I discovered a really useful feature in most of the nodes which is a method called .asCode(). What this does it returns as a string the code used to create the current node.
This is really good as you can get output and inspect for you own code. I decided to write a simple shelf tool for automating this process and dumping the data out to a file.
GetAbsoluteFileName
The first issue I had was with the Houdini file manager shown below
HouAsCode.py
The rest of the function is simple. We see which nodes are selected in houdini iterate through all of them calling the asCode() method
You can download the full script here
Installation
The easiest way to run this script is to place a shelf button into Houdini with the script embedded into it. First right click onto the shelf you wish to place the button on as shown
Make sure the script language combo box is set to python and then press the accept button.
Output
The following is a sample output from the tool
Houdini has a good python model and I discovered a really useful feature in most of the nodes which is a method called .asCode(). What this does it returns as a string the code used to create the current node.
This is really good as you can get output and inspect for you own code. I decided to write a simple shelf tool for automating this process and dumping the data out to a file.
GetAbsoluteFileName
The first issue I had was with the Houdini file manager shown below
If a file is chosen from the normal file system we get an absolute file name, however Houdini has the ability to set up certain local environment variables which it uses internally. These however are not translated into the python scripts. These variables are $HIP, $JOP, $HOME which will be returned with any filename selected for example $HIP/myGeo.bgeo.
This needs to be translated into an absolute filename before python can access it, so a simple function was written to pop up the dialog and then strip any of the environment variables and replace them with the actual values. This is shown in the function below (which I use in a lot of other code)
########################################################################################################################
## @brief a basic function to return a file name / absolute path stripping off $HIP etc
## @param[in] _title the title to be displayed in the file box
## @param[in] _wildCard the file selection wildcard i.e. *.obj etc
## @param[in] _fileType the houdini file type option e.g. hou.fileType.Any
## @returns a fully qualified file path or None
########################################################################################################################
def GetAbsoluteFileName(_title,_wildCard,_fileType) :
# launch a file select and get the data
file=hou.ui.selectFile(None,"Select File To Save",False,_fileType,"*.py","",False,False,hou.fileChooserMode.Write)
# if it was empty bomb out and return none
if file =="" :
return None
else :
# so we got some data we need to split it as we could have $JOB $HIP or $HOME prepended
# to it if we partition based on the / we get a tuple with "", "/","/....." where the
# first element is going to be an environment var etc.
file=file.partition("/")
# we have $HOME so extract the full $HOME path and use it
if file[0]=="$HOME" :
prefix=str(hou.getenv("HOME"))
elif file[0] == "$HIP" :
#we have $HIP so extract the full $HIP path
prefix=str(hou.getenv("HIP"))
# we have a $JOB so extract the full $JOB path
elif file[0] == "$JOB" :
prefix=str(hou.getenv("JOB"))
# nothing so just blank the string
else :
prefix=str("")
#now construct our new file name from the elements we've found
return "%s/%s" %(prefix,file[2])
Now this has been written we can use it to popup a dialog and return the filenameHouAsCode.py
The rest of the function is simple. We see which nodes are selected in houdini iterate through all of them calling the asCode() method
file=GetAbsoluteFileName("Enter File to Save ","*.py",hou.fileType.Any)
if file != None :
fileOut=open(file,'w')
fileOut.write("#############################################\n")
fileOut.write("# generated by Hou as Code script \n")
fileOut.write("#############################################\n")
for objects in hou.selectedNodes() :
fileOut.write("#############################################\n")
fileOut.write("# from object %s \n" %(objects.name()))
fileOut.write("#############################################\n")
fileOut.write("%s \n" %(objects.asCode()))
fileOut.write("#############################################\n\n")
fileOut.close()
You can download the full script here
Installation
The easiest way to run this script is to place a shelf button into Houdini with the script embedded into it. First right click onto the shelf you wish to place the button on as shown
Next we edit the tool and add the name etc as shown
Finally we can paste the script into the tool as shown in the next dialog
Output
The following is a sample output from the tool
#############################################
# generated by Hou as Code script
#############################################
#############################################
# from object helixRename
#############################################
# Initialize parent node variable.
if locals().get("hou_parent") is None:
hou_parent = hou.node("/obj/helixObjectImport/helixBakeChannel")
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename
hou_node = hou_parent.createNode("rename", "helixRename", run_init_scripts=False, load_contents=True)
hou_node.move(hou.Vector2(0, 0))
hou_node.setAudioFlag(False)
hou_node.setExportFlag(False)
hou_node.bypass(False)
hou_node.setDisplayFlag(False)
hou_node.setSelected(True)
hou_node.setUnloadFlag(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/stdswitcher1 parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("stdswitcher1")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/renamefrom parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("renamefrom")
hou_parm.set("?[xyz]")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/renameto parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("renameto")
hou_parm.set("t[xyz]0")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/scope parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("scope")
hou_parm.set("*")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/srselect parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("srselect")
hou_parm.set("max")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/units parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("units")
hou_parm.set("seconds")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/timeslice parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("timeslice")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/unload parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("unload")
hou_parm.set(0)
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/export parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("export")
hou_parm.set("/obj")
hou_parm.setAutoscope(False)
hou_parm.lock(False)
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/gcolor parm tuple
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm_tuple = hou_node.parmTuple("gcolor")
hou_parm_tuple.set((0.9, 0.9, 0))
hou_parm_tuple.setAutoscope((False, False, False))
hou_parm_tuple.lock((False, False, False))
# Code for /obj/helixObjectImport/helixBakeChannel/helixRename/gcolorstep parm
if locals().get("hou_node") is None:
hou_node = hou.node("/obj/helixObjectImport/helixBakeChannel/helixRename")
hou_parm = hou_node.parm("gcolorstep")
hou_parm.set(0.05)
hou_parm.setAutoscope(False)
hou_parm.lock(False)
hou_node.setColor(hou.Color([0.8, 0.8, 0.8]))
hou_node.setExpressionLanguage(hou.exprLanguage.Python)
# Code to establish connections for /obj/helixObjectImport/helixBakeChannel/helixRename
hou_node = hou_parent.node("helixRename")
if hou_parent.node("helixImportData") is not None:
hou_node.setInput(0, hou_parent.node("helixImportData"), 0)
hou_node.setUserData("___toolcount___", "1")
hou_node.setUserData("___toolid___", "NCCAPointBakeImport")
#############################################
Subscribe to:
Posts (Atom)









