SOP, DOP, Wrangle seems to be covered with material: dancer_tone5: So I would like to write Python material. The version is as of Houdini 15.5. Since Houdini is a node-based system, in other words, it allows visual programming, I think there are fewer opportunities for scripting with Python than with other 3D CG software. So where do you use Python! : punch_tone1: I'm talking about it, but after all pipeline development is the main thing. For example, if Houdini's standard dispatcher HQueue is used as standard, the rendering job will be passed to another client after the simulation of all frames is completed, but with Python, ** 1 frame simulation When you're done, you can say ** throw the render job to another client. You can also specify a Python script file with the -P option of the Mantra command, so you can render by overwriting the ** IFD rendering properties ** (for example, subpixels) that you have already exported, so subpixels. You can save the trouble of changing and outputting the IFD again. : wink: You can also use Python to read the ** object transform ** information in the Alembic file when you read it. HQueue itself is free, but it checks for licenses of Houdini Indie or higher (I don't get a license), and IFD export is only possible with the commercial version of Houdini / Engine, so it is useful information for both Apprentice users and Indie users ** Python I would like to introduce how to get information on Alembic **. : relaxed:
Houdini's Python provides a module called ** _alembic_hom_extensions **. Normally, when controlling Houdini with Python, you start with the ** hou ** module, but this ** _alembic_hom_extensions ** is independent of the ** hou ** module. In other words, no license is required if you just use this module. But it doesn't make much sense because most of the time you pass Alembic information to Houdini for processing: scream_cat: By using this module, you can mainly get the object hierarchy, object transform, and visibility of the specified Almembic file. You can also get the value of the object's user properties (in Alembic from Houdini, that's the resolution added to the camera object) and the time range of the object animation. The functions that this module has are: Read through: zzz: Let's move on to the next explanation.
python
alembicArbGeometry(abcPath, objectPath, name, sampleTime) → (value, isConstant, scope)
Returns a tuple of None or (value, isConstant, scope). The contents of this tuple are the value of the attribute, a Boolean value indicating whether the attribute is constant on the time axis, and a scope ('varying','vertex','facevarying','uniform','constant','unknown'. ')is. </ sup>
python
alembicClearArchiveCache()
Clears the internal cache of Alembic files. </ sup>
python
alembicGetArchiveMaxCacheSize()
Returns the Alembic file cache size. </ sup>
python
alembicGetCameraDict(abcPath, objectPath, sampleTime)
Returns a dictionary of camera parameters for the specified object. </ sup>
python
alembicGetCameraResolution(abcPath, objectPath, sampleTime)
Returns a tuple containing None or two floats. The first value is the Houdini Camera's X resolution. The second value is the Houdini Camera's Y resolution. Some cameras (eg Maya Camera) have no resolution, so None is returned in this case. </ sup>
python
alembicGetObjectPathListForMenu(abcPath)
Returns a tuple of strings in the format required for menu callbacks. </ sup>
python
alembicGetSceneHierarchy(abcPath, objectPath) → (object_name, object_type, (children))
Returns 3 tuples. Each tuple has the following structure: (object_name, object_type, (children)) (children) is a tuple containing child nodes. object_type contains one of the following (may include other types): --cxform constant transform node --xform animation transform node --camera camera node --Polymesh Polygon Mesh Shape Node --subdmesh Subdivision Surface Mesh Shape Node --faceset Face Set Shape Node -curves Curves shape nodes --points Points Shape Node --nupatch NuPatch shape node --unknown unknown node
python
alembicHasUserProperties(abcPath, objectPath)
Returns None if the object has no user properties. If so, returns whether the user property is constant over time. </ sup>
python
alembicSetArchiveMaxCacheSize(size)
Sets the maximum number of Alembic files that will be cached at one time. </ sup>
python
alembicTimeRange(abcPath, [objectPath=None]) → (start_time, end_time)
Returns a tuple of None or (start_time, end_time). This tuple contains the global start / end times of the archive based on the FPS information in the Alembic archive. If you specify objectPath, the start / end time of that object is calculated. Returns None if the archive is constant. </ sup>
python
alembicUserProperty(abcPath, objectPath, name, sampleTime) → (value, isConstant)
Returns a tuple of None or (value, isConstant). This tuple contains the value of the attribute and a Boolean value that indicates whether the attribute is constant over time. </ sup>
python
alembicUserPropertyMetadata(abcPath, objectPath, sampleTime)
Returns None or JSON dictionary. This JSON dictionary contains a map of user property names-> user property metadata. </ sup>
python
alembicUserPropertyDictionary(abcPath, objectPath, sampleTime)
Returns None or JSON dictionary. This JSON dictionary contains a map of user property names-> user property values. </ sup>
python
alembicUserPropertyValuesAndMetadata(abcPath, objectPath, sampleTime)
Returns None or tuple. This tuple contains two JSON dictionaries. The first dictionary contains a map of user properties and values. The second dictionary contains a map of user properties and the metadata used to interpret the first dictionary. </ sup>
python
alembicVisibility(abcPath, objectPath, sampleTime, [check_ancestor=False]) → (value, isConstant)
Returns a tuple of None or (value, isConstant). This tuple contains the visibility of the object and a Boolean value that indicates whether the visibility is constant over time. The visibility return value of 0 is hidden, 1 is visible, and -1 is the defer (depending on the visibility of the parent). </ sup>
python
getLocalXform(abcPath, objectPath, sampleTime) → (xform, isConstant, inherit)
Returns a tuple of (xform, isConstant, inherits). This tuple is a local transform, a Boolean value that indicates whether the transform is constant over time, and a Boolean value that indicates whether the node inherits (or is connected to) the parent transform. Includes. </ sup>
python
getWorldXform(abcPath, objectPath, sampleTime) → (xform, isConstant, inherit)
Returns a tuple of (xform, isConstant, inherits). This tuple is a world transform, a Boolean value that indicates whether the transform is constant over time, and a Boolean value that indicates whether the node inherits (or is connected to) the parent transform. Includes. </ sup>
I don't really use all of the above functions, so I'd like to focus only on the ones that are of interest.
python
alembicGetSceneHierarchy(abcPath, objectPath)
python
alembicTimeRange(abcPath, [objectPath=None])
python
getWorldXform(abcPath, objectPath, sampleTime)
It has decreased considerably. Let's see what we can do with these three functions. I created cam1, Toy, Subnet (Pig, ShaderBall in this). Other than ShaderBall, I have set object level animation. Output this whole scene to Alembic and load it into a new scene. It captures data in the same object hierarchy as Houdini and also animates it. But when I select the cam1 object to animate, the Animation Editor doesn't show any animation curves. If you are familiar with CHOP, you can use Object CHOP to display the animation curves sampled in Motion FX View, right? I think you can think. If the original Alembic animation data is baked and linearly interpolated, that's fine, but if it's not, I'd like to get the floating point frame values as well. So you want to get the animation curve, right? That's where the ** _alembic_hom_extensions ** module comes in. As a rough flow,
--Get the object hierarchy in Alembic and determine which object has animation information --Examine the animation range of the object --Get object transform information
You can develop various pipelines with.
The code is below.
python
import _alembic_hom_extensions as abc
abcPath = "C:/data/alembicFile.abc"
def expandChild(root,child,objectHierarchy,objectType):
objectHierarchy.append(root+child[0])
objectType.append(child[1])
if len(child[2])==0:
return
else:
return expandChild(root+child[0]+"/",child[2][0],objectHierarchy,objectType)
objectHierarchy=[]
objectType=[]
childNodes = abc.alembicGetSceneHierarchy(abcPath, "/")[2]
for eachChildNode in childNodes:
expandChild("/",eachChildNode,objectHierarchy,objectType)
print objectHierarchy
print objectType
python
#Output result
['/Toy', '/Toy/testgeometry_rubbertoy1', '/cam1', '/cam1/cameraProperties', '/Subnet', '/Subnet/Pig', '/Subnet/Pig/testgeometry_pighead1', '/Subnet/Pig/testgeometry_pighead1/PigFace']
['xform', 'polymesh', 'xform', 'camera', 'cxform', 'xform', 'polymesh', 'faceset']
You can see that xform has animation information for that object and cxform has no animation information.
Now let's examine the animation range of cam1.
python
import _alembic_hom_extensions as abc
abcPath = "C:/data/alembicFile.abc"
objectPath = "/cam1"
print abc.alembicTimeRange(abcPath, objectPath)
python
#Output result
(0.041666666666666664, 2.0)
The unit of the animation range output by this function is seconds. This camera has an animation range of 1 to 48 frames and captures with FPS = 24. The result of (1.0 / 24.0, 48.0 / 24.0) is displayed.
Now let's examine the transform of the object.
python
import _alembic_hom_extensions as abc
abcPath = "C:/data/alembicFile.abc"
objectPath = "/cam1"
sampleTime = hou.frame()/hou.fps()
#The first element of the tuple returned by getWorldXform is 16 float tuples
xform = abc.getWorldXform(abcPath, objectPath, sampleTime)[0]
#Hou to convert to matrix format.Use Matrix4
xformMatrix = hou.Matrix4(xform)
#Extract move, rotate, scale
explodedDictionary = xformMatrix.explode()
print "translate:",explodedDictionary["translate"]
print "rotate",explodedDictionary["rotate"]
print "scale",explodedDictionary["scale"]
python
#Output result
translate: [5.75595, 3.34021, 10.2852]
rotate [-11.6801, 28.4249, -1.25354e-06]
scale [1, 1, 1]
```
You can use it in the single script above, but ** Houdini expressions can use Python as well as HScript **.
So, next, let's add a user parameter (called Spare parameter in Houdini) to the camera node imported by Alembic as follows.
Here, I added two Float Vector3 and prepared the Translate parameter and Rotate parameter.
Then change the expression type to Python.
![UserParms.png](https://qiita-image-store.s3.amazonaws.com/0/154191/3c170758-182d-f349-6414-f89ccfb60957.png)
In the Alembic Xform node, the parameters of File Name, Object Path, Frame, and Frame are already prepared, so I would like to use the values of these parameters to get information on camera movement and rotation.
You can enter multiple lines of Python code using the Edit Expression dialog displayed by pressing Alt + E on the parameters, but it is troublesome to enter frequently used code one by one.
It is convenient to register frequently used code as a custom function.
One way to register a custom function is to place a Python file in a specific directory and put it in the path, but that would result in an environment-dependent scene file.
I want to avoid it!
So let's define a custom function in the scene file.
#### How to register a custom function in a scene file
Custom functions can be registered in the Python Source Editor in the Windows menu.
![PythonSourceEditor.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6022a0b5-fa4d-97ad-e550-6a4fe3141500.png)
Then enter the code as shown below.
![PythonSourceEditorCode.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6285343b-318f-656a-d176-36201529f123.png)
This code is a function that works with parameters on an Alembic Xform node and returns the transform information for that node.
#### **`python`**
```python
#mode is"translate"Then location information,"rotate"Then rotation information,"scale"Returns scale information
#If the index is 0, it is the X component, if it is 1, it is the Y component, and if it is 2, it is the Z component.
def getAlembicTransform(mode="translate",index=0):
import _alembic_hom_extensions as abc
currentNode = hou.pwd()
abcPath = currentNode.parm('fileName').eval()
objectPath = currentNode.parm('objectPath').eval()
sampleTime = currentNode.parm('frame').eval()/currentNode.parm('fps').eval()
xform = abc.getWorldXform(abcPath, objectPath, sampleTime)[0]
xformMatrix = hou.Matrix4(xform)
explodedDictionary = xformMatrix.explode()
return explodedDictionary[mode][index]
```
After registering the code in the Python Source Editor window,
You will be able to call the "getAlembicTransform" custom function as follows:
![ParameterExpression.png](https://qiita-image-store.s3.amazonaws.com/0/154191/380f944f-fa7b-6f00-98e3-c6ffd7d9344a.png)
Left-click on the parameter name to see if the value is correct.
![ParameterExpressionEval.png](https://qiita-image-store.s3.amazonaws.com/0/154191/c8e5cc41-04e2-4412-55ac-3d802a64680b.png)
Now you can see the animation curve of the Alembic object in the Animation Editor.
![AnimationCurve.png](https://qiita-image-store.s3.amazonaws.com/0/154191/e4697ace-c750-d3b1-7f2a-64ff75d139f7.png)
You have now included the Alembic transform information in the parameter value.
That's it. : hugging:
Recommended Posts