[PYTHON] How to use Ass / Alembic with HtoA

Introduction

Already every year ~~ Elderly ~~ The annual Houdini Advent Calendar. Well, I thought about what to write, but since I'm only touching Arnold this year, I'd like to write information that seems to be helpful in HtoA (Arnold for Houdini). Arnold is one of the renderers

In other words, it's a renderer that most major software supports, and it's the de facto de facto standard in the video industry. Moreover, it is cost-friendly that you can render any DCC with one batch render license. Furthermore, you can write shaders not only in C ++ but also in ** O ** pen ** S ** hading ** L ** anguage (OSL), and it also supports MaterialX (described later) quickly. Or is it USD next? I'm looking forward to it. Houdini's standard Mantra renderer is free for Houdini users and can be used as much as you like, and I like it because the shader is easy to write with the familiar VEX, but the disadvantage that it only works with Houdini is painful. Arnold can be rendered (** Kick Ass ) in an ass ( A ** rnold ** S ** cene ** S ** ource) file, which is a standardized scene description format. .. In other words, if you export any DCC to ass, you can render any DCC.

If you understand Arnold, it's better to study ass. Here is a recommended page for your reference. https://arnoldsupport.com/category/ass/

** Note that using a third-party renderer like Arnold with Houdini requires a Houdini Indie, Houdini Core, or Houdini FX license. Also, the Houdini Indie version cannot output ass files: sob: ** So, please study by outputting ass in Houdini Core / FX environment, Maya environment (student version, home license is also possible).

If you want to render a render scene built in Maya coexisting with a Houdini render scene on Houdini, just export ass from MtoA and read it in HtoA. Execute ** Arnold menu → Export Stand In ** on MtoA, check the information you want to output, and export the ass file. image.png Simply add ** Arnold Procedural ** or ** Arnold Include ** in the HtoA / obj context and specify the ass file to capture the Maya Arnold scene directly on Houdini. For scenes using Maya-specific shaders, it is necessary to specify the path of that DLL in the Plugin Path of Arnold ROP, but every time the version goes up, the DCC-specific shader is switched to the pure Arnold shader. It is becoming. Expectations for the future. image.png An example of importing a Maya scene into Houdini via an ass file and coexisting with Mr. Pig. image.png It's convenient. However, if you want to edit another DCC object on Houdini, you will currently be importing it into Houdini in an intermediate format such as Alembic / FBX (hereafter USD), assigning an Arnold shader and rendering it. On top of that, it may be useful to prepare on Maya, so I would like to introduce it.

Houdini files have a ** hip ** file extension Arnold files have a ** ass ** file extension ** hip and ass !!!**: thinking: Somehow it seems to be compatible: spy_tone1:

Pass arbitrary attributes from Maya to Houdini via Alembic

Add Point / Primitive attributes for Houdini to Maya shapes

Houdini often uses Rest Position. Rest Position is the Japanese word for "rest position". Arnold refers to Pref (Position Reference), and Maya refers to Reference Object. The reason is that if you build a shader with a 2D / 3D projection of a texture onto a geometry (eg camera projection), the projected texture will appear slippery if the geometry deforms. In this case, we use the technique of returning the result of 2D / 3D projection to the untransformed geometry to the transformed geometry. If you're not sure, read here. https://houdini.prisms.xyz/wiki/index.php?title=Rest_Attrib To set the Rest Position in Maya, run ** Create Texture Reference Object ** in the ** Texture ** menu and in Arnold ** Export Reference Positions * in the Arnold property of the Maya shape to reflect that. Is to enable *. image.png I hate this method because it's Maya-specific and references geometry in a reference format. And it can't be put out as an attribute purely in Alembic. Ideally, I would like to add Rest Position as a vertex attribute to the geometry and export that attribute to Alembic.

You can optionally add vertex / face attributes to your Maya shape ** by building a MEL **, so I'll show you how. ** Generally ** Maya vertex attributes <-> Houdini point attributes, Maya face attributes <-> Houdini primitive attributes should be recognized. This time, I would like to add Rest Position as a vertex attribute, but I would also like to explain how to add a vertex / face attribute by adding a face attribute that contains the shader name for each face.

The Pymel code is below. This is a script for Maya ** that adds a Rest Position and shop_materialpath to a shape that has an Arnold shader assigned to the SG **. Since we're getting the vertex coordinates in world space, we're assuming that the transform of the object we want to export to Alembic is frozen.

--A vertex attribute named rest that stores the position information of the geometry of the current frame --A face attribute named shop_materialpath that contains the string / shop / maya / shader name for the shader name assigned to the geometry

Can be added. If you apply this, you can add Velocity and Cd, so please try it.

rest and shop_PyMEL script to add materialpath


import pymel.core as pm
#Declare a dictionary where the key is the shape name and the value is a pair of a list of face attribute values for that shape
dictAttributes = {}
context = "/shop/maya/"
for eachSG in pm.ls(type="shadingEngine"):
	members = pm.sets(eachSG,q=True,nodesOnly=False)
	#Object to SG/Skip if no component is assigned
	if len(members)==0:
		continue
	#If no shader is assigned to SG, skip it.
	#Surface shader where the input connector of SG's aiSurfaceShader is prioritized in Arnold. If not, the surface shader of the input connector of surfaceShader is used.
	shader = (pm.listConnections(eachSG+".aiSurfaceShader",p=False,c=False,s=True,d=False) or [""])[0]
	if shader == "":
		shader = (pm.listConnections(eachSG+".surfaceShader",p=False,c=False,s=True,d=False) or [""])[0]
	if shader == "":
		continue
	shaderName = shader.name()
	#Branch processing based on whether it is an object-level allocation or a component-level allocation.
	#At the object level, prepare a list as large as the number of all faces and write the shader name to all items in the list.
	#At the component level, write shader name information to the index of that face
	for eachMember in members:
		#Processing when SG is associated with an object
		if type(eachMember) == pm.nodetypes.Mesh:
			dictAttributes[eachMember]={"shader":[],"rest":[]}
			dictAttributes[eachMember]["shader"] = [context+shaderName]*eachMember.numFaces()
			for vtxIndex in range(eachMember.numVertices()):
				position = pm.pointPosition(eachMember+".vtx["+str(vtxIndex)+"]",world=True).get()
				dictAttributes[eachMember]["rest"].append( [position[0],position[1],position[2]] )
		#Processing when SG is tied to the face
		elif eachMember._ComponentLabel__ == "f":
			listComponents = pm.ls(eachMember,flatten=True)
			shape = eachMember._node
			if shape not in dictAttributes:
				dictAttributes[shape]={"shader":[],"rest":[]}
				dictAttributes[shape]["shader"]= [""]*eachMember.totalSize()
				for vtxIndex in range(shape.numVertices()):
					dictAttributes[shape]["rest"].append( pm.pointPosition(shape+".vtx["+str(vtxIndex)+"]",world=True) )
			#Write the shader name to the index of the corresponding list
			for index in eachMember.indices():
				dictAttributes[shape]["shader"][index] = context+shaderName
restAttributeName = "rest"
shaderAttributeName = "shop_materialpath"
for eachShape in dictAttributes:
	#Added Rest Position attribute
	if not pm.attributeQuery(restAttributeName+"_AbcGeomScope",node=eachShape,exists=True):
		pm.addAttr(eachShape, dataType="string", longName=restAttributeName+"_AbcGeomScope")
	pm.setAttr(eachShape+"."+restAttributeName+"_AbcGeomScope", "var")
	if not pm.attributeQuery(restAttributeName,node=eachShape,exists=True):
		pm.addAttr(eachShape,dataType="vectorArray",longName=restAttributeName)
	pm.setAttr(eachShape+"."+restAttributeName,dictAttributes[eachShape]["rest"])
	#shop_Added materialpath attribute
	if not pm.attributeQuery(shaderAttributeName+"_AbcGeomScope",node=eachShape,exists=True):
		pm.addAttr(eachShape, dataType="string", longName=shaderAttributeName+"_AbcGeomScope")
	pm.setAttr(eachShape+"."+shaderAttributeName+"_AbcGeomScope", "uni")
	if not pm.attributeQuery(shaderAttributeName,node=eachShape,exists=True):
		pm.addAttr(eachShape,dataType="stringArray",longName=shaderAttributeName)
	pm.setAttr(eachShape+"."+shaderAttributeName,dictAttributes[eachShape]["shader"])

When executed, custom attributes will be added as shown below. image.png I think that the value is displayed in the format of numeric / string field if it is a normal attribute, but since the added rest / shop_materialpath is an array value for each vertex / face, the value cannot be confirmed from the GUI. I wish I could check it in the Component Editor. .. .. I've intentionally added an attribute name named attribute name_AbcGeomScope here, which makes sense.

Did you notice that when you add an array value attribute to a shape, that's not enough information? Is it a per-object array attribute? Is it a vertex-based attribute? Is it a face-based attribute? You need to specify it explicitly. Even in Houdini, when you touch the Wrangle SOP, the "Run Over" parameter will intentionally specify which attribute is Point / Vertex / Primitive / Detail. ** Maya is adding attributes on the shape ** Above, you have to specify which attribute is the vertex, face, or object as supplementary information. Maya's Alembic Importer / Exporter has a _AbcGeomScope suffix as the metadata that plays that role. That information can be found in the code published on Alembic's Github. https://github.com/alembic/alembic/blob/master/maya/AbcExport/AttributesWriter.cpp It seems that you can specify vtx, fvr, ʻuni, and var for the value of the _AbcGeomScopestring. If no other value or_AbcGeomScope is set, it will be treated as const`, that is, it will be an attribute per object. This time, Alembic written from Maya side wants to be recognized as Houdini's Point attribute in Houdini and shop_materialpath as Houdini's Primitive attribute in Houdini, so the above code is It is such a designation.

Now, prepare the model. Here, we have prepared three types: a deforming sphere, a sphere with a shader face assigned, and the ground. With attributes added to it and multiple geometries you want to export to Houdini selected, ** Run the Cache menu → Alembic Cache → Export Selection to Alembic ... **. image.png Add an Attribute as shown below. image.png

_AbcGeomScope is only needed by Maya's Alembic importer / exporter to determine the type of attribute, so only rest and shop_materialpath are needed. ** Ogawa ** is required for ** File Format **. Arnold only supports ** Ogawa **. ** Write Face Sets ** is required if per-face attributes have been assigned. When I imported the Alembic file from Maya into Houdini, the Rest Position was captured as a restPoint attribute: relaxed: image.png The shop_materialpath Primitive attribute is also loaded. Placing the shader in this path eliminates the need for material reassignment. image.png Place the shader in the / shop context. You can use the Arnold Python API to analyze the shader information in the ass file exported from Maya and reproduce it as a shader graph in Houdini. I'm sorry I can't publish the method. You can try it yourself by referring to the Arnold Python API described below: japanese_ogre:

(Added on December 13, 2019) From HtoA5.0.0 (Arnold6), the function to build an ass file as a shader graph has been added. https://docs.arnoldrenderer.com/display/A5AFHUG/HtoA+5.0.0

After placing the shader, when I try to render it with Arnold ROP, if I load Alembic as normal Houdini Geometry, it will be rendered, but if it remains packed, the shader will not be reflected. image.png How to assign shaders to Packed Alembic

--Use material_attribute --Use MaterialX --Use aiOperator

It is possible by using any of these. The method using MaterialX and aiOperator currently only allows ** object level ** assignments.

Shader assignment using material_attribute

The material_attribute function can recognize the shop_materialpath defined inside Packed Alembic and assign shaders. This is something I've always wanted to do, but it's made possible with HtoA 4.2.0 released this year. https://docs.arnoldrenderer.com/display/A5AFHUG/HtoA+4.2.0 In this release note,

Add per primitive shop_materialpath translation in the alembic procedural

Is written. Yes, this is why I added the shop_materialpath attribute in Maya. Just check ʻExport Referenced Materials in Arnold ROP and it will look at the path of the shop_materialpath` attribute inside Packed Alembic and assign a shader. image.png

Shader assignment using MaterialX

Both MtoA and HtoA have MaterialX exporters. I'm making my own exporter because it's so shabby, but here I'll show you how to shader assign to Packed Alembic using a standard MaterialX exporter. What is MaterialX? For those who are interested, please refer to https://www.materialx.org/. Actually, I have translated the specifications into Japanese. https://materialx.prisms.xyz/ With multiple geometries you want to output to MaterialX selected in Maya, execute ** Arnold menu Utilities → Export Selection to MaterialX **. Simply set the MaterialX file ( .mtlx) output destination to ** Filename ** and the appropriate name to ** Look Name ** and ** Export **. image.png Then, an XML file like this will be generated. image.png Define the shader schema with the xi: include element. If you added a custom shader, add a new shader schema here. ** Of note is the value of the geom attribute of the<materialassign>element. ** ** MaterialX can access the Packed Alembic's internal geometry hierarchy with the * wildcard * specified by this geom attribute. This value output by standard MaterialX is inconvenient. This time, let's rewrite the part of geom =" / Gonyogonyo " to geom =" * / Gonyogonyo " with a text editor. image.png Simply connect the MaterialX ROP to the Arnold ROP, set the Selection to the Alembic object path, the MaterialX File to the MaterialX file path, and the Look to the look name and render, and the shader graph and shader assignments will be processed automatically. .. image.png The nice thing about MaterialX is that you don't have to rebuild the shader graph on HtoA, and you don't have to be aware of the hierarchy because you can do shader assignments with wildcards. As you can see from the rendering result, shader assignment for each face is not possible.

Shader assignment using aiOperator

** material_attribute ** allows face-by-face assignments, but the actual shader graph needs to be built in the / shop context, which seems like a high hurdle. ** MaterialX ** is handy, but it can be hard for artists to edit its XML. With ** aiOperator **, even an artist can edit with a little study, and Maya shader graphs can be imported and controlled as an ass file. When I'm touching Arnold, I think the ass file is useful. ** The ass file is mainly used to describe the scene, but it is also prepared as a file that defines shader graphs, a file that defines lights, and a file that defines rendering settings. You can prepare it as. ** ** Describes how to use aiOperator to import Maya shader graphs via an ass file. First, on Maya, output the ass file of only the shader. ** Run Arnold Menu → Stand In → Export Stand In **. Check only Shaders in the Export item and export the ass file. Name this file, for example, ShadersOnly.ass. image.png

On Houdini, place a ** Arnold Include ** object in the / obj context. Specify the ShadersOnly.ass file here. This allows you to import Maya shader graphs. image.png

To assign a shader graph captured by ** Arnold Include ** to any object, add aiOperator's ** Set Parameter ROP ** in the / out context and add it to ** Arnold ROP **. Connect Then, in ** Selection **, enter the path of the object to which you want to assign the shader, and in the ** Assignment_1 ** parameter, enter theshader [0] = "shader name"described in the ass file. image.png When I rendered it with this, I was able to assign a shader. image.png You can assign shaders to other objects by connecting to Arnold ROP in the same way.

e? : ear: Is it troublesome?

Well, that's right. So let's create a script that decrypts the ass file and sets up ** aiOperator ** automatically. The ass file you need here is the shader information and the shape that binds that shader. The ass file read by ** Arnold Include ** contains only shader information, not which shader to bind to which shape. Therefore, execute ** Export StandIn ** again and In the Export item, check Shapes and Shaders to export the ass file. For example, ShapesShaders.ass. image.png

Python script to automatically set up aiOperator


import arnold
arnold.AiBegin()
assFilePath = "F:/AdventCalendar2019/ShapesShaders.ass"
dicShapes={}
result = arnold.AiASSLoad(assFilePath)
nodeIter = arnold.AiUniverseGetNodeIterator(arnold.AI_NODE_SHAPE)
while not arnold.AiNodeIteratorFinished(nodeIter):
	node = arnold.AiNodeIteratorGetNext(nodeIter)
	nodeName = arnold.AiNodeGetName(node)
	nodeEntry = arnold.AiNodeGetNodeEntry(node)
	if nodeName == "root" or nodeName == "":
		continue
	dicShapes[nodeName]={}
	parmIter = arnold.AiNodeEntryGetParamIterator(nodeEntry)
	while not arnold.AiParamIteratorFinished(parmIter):
		parm = arnold.AiParamIteratorGetNext(parmIter)
		parmName = arnold.AiParamGetName(parm)
		if parmName == "shader":
			dicShapes[nodeName][parmName] = []
			parmArray = arnold.AiNodeGetArray(node,parmName)
			for arrayIndex in range(arnold.AiArrayGetNumElements(parmArray)):
				dicShapes[nodeName][parmName].append(arnold.AiNodeGetName(arnold.AiArrayGetPtr(parmArray, arrayIndex)))
		elif parmName == "visibility":
			dicShapes[nodeName][parmName] = arnold.AiNodeGetInt(node,parmName)
	arnold.AiParamIteratorDestroy(parmIter)
arnold.AiNodeIteratorDestroy(nodeIter)

subnet = hou.node("/out").createNode("subnet","setParms")
merge = subnet.createNode("merge","Merge")
for shapeIndex,eachShape in enumerate(dicShapes):
	setParameterNode = subnet.createNode("set_parameter",eachShape)
	merge.setInput(shapeIndex,setParameterNode)
	setParameterNode.parm("selection").set("*/"+eachShape)
	setParameterNode.parm("assignment").set(1)
	setParameterNode.parm("assignment_1").set("visibility="+str(dicShapes[eachShape]["visibility"]))
	for shaderIndex,eachShader in enumerate(dicShapes[eachShape]["shader"]):
		setParameterNode.parm("assignment").set(shaderIndex+2)
		setParameterNode.parm("assignment_"+str(shaderIndex+2)).set("shader["+str(shaderIndex)+"]=\""+str(dicShapes[eachShape]["shader"][shaderIndex])+"\"")

When I run this script, a setParms subnet is created in the / out context, and Set Parameter ROPs are created and merged in it for the number of objects defined in the ass file. image.png Connect this subnet to the Arnold ROP. Now you can render. image.png Like MaterialX, aiOperator only supports object-level shader assignments, so this is the result. However, if your model doesn't have face-by-face shader assignments, you can use the Arnold Python API with an ass file to lookDev in Houdini, even if you're not in Maya.

Verification environment: Maya2019 MtoA3.3.0.2 Houdini17.5.425 HtoA4.4.1

Summary

--You can also add vertex / face attributes in Maya so you can bring them into Houdini via Alembic. --If you understand Arnold, you should understand ass --I taught you how to use the Arnold Python API --If you build a pipeline with ass, you can render between multiple DCCs.

End: hugging:

Recommended Posts

How to use Ass / Alembic with HtoA
Python: How to use async with
How to use virtualenv with PowerShell
How to use FTP with Python
How to use ManyToManyField with Django's Admin
How to use OpenVPN with Ubuntu 18.04.3 LTS
How to use Cmder with PyCharm (Windows)
How to use Japanese with NLTK plot
How to use jupyter notebook with ABCI
How to use CUT command (with sample)
How to use SQLAlchemy / Connect with aiomysql
How to use JDBC driver with Redash
How to use xml.etree.ElementTree
How to use Python-shell
How to use tf.data
How to use virtualenv
How to use image-match
How to use shogun
How to use Pandas 2
How to use Virtualenv
How to use numpy.vectorize
How to use pytest_report_header
How to use partial
How to use Bio.Phylo
How to use SymPy
How to use x-means
How to use WikiExtractor.py
How to use IPython
How to use virtualenv
How to use Matplotlib
How to use iptables
How to use numpy
How to use TokyoTechFes2015
How to use venv
How to use dictionary {}
How to use Pyenv
How to use list []
How to use python-kabusapi
How to use OptParse
How to use return
How to use dotenv
How to use pyenv-virtualenv
How to use Go.mod
How to use imutils
How to use import
How to use GCP trace with open Telemetry
How to use tkinter with python in pyenv
How to use Qt Designer
How to use search sorted
How to use xgboost: Multi-class classification with iris data
[gensim] How to use Doc2Vec
python3: How to use bottle (2)
Understand how to use django-filter
How to use the generator
[Python] How to use list 1
How to use FastAPI ③ OpenAPI
How to use python interactive mode with git bash
How to use Python argparse
How to use IPython Notebook
How to update with SQLAlchemy?
How to use Pandas Rolling