Hom Class


Published on

Published in: Technology
1 Like
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Hom Class

  1. 1. Python in Houdini for Technical Directors Luke Moore, Senior Software Developer, Side Effects Software http://www.sidefx.com/masterclasses/
  2. 2. Content Covered <ul><li>The places you can put Python code </li></ul><ul><li>How to integrate Houdini into your Python-based pipeline </li></ul><ul><li>How to learn Houdini's new Python API </li></ul><ul><li>Some examples </li></ul>
  3. 3. Assumptions <ul><li>You know Python </li></ul><ul><li>You know Houdini </li></ul><ul><li>You have at least a rough knowledge of Hscript </li></ul>
  4. 4. A New Scripting Interface in Houdini 9 <ul><li>Python scripts will replace Hscript scripts, but Hscript is still supported for backwards compatibility </li></ul><ul><li>Python has many advantages over Hscript. It: </li></ul><ul><ul><li>is a well used and proven language </li></ul></ul><ul><ul><li>comes with a large set of modules </li></ul></ul><ul><ul><li>is popular in the industry </li></ul></ul>
  5. 5. Overview of Houdini's Python Scripting <ul><li>Houdini embeds the Python interpreter </li></ul><ul><li>Houdini will invoke Python scripts and snippets </li></ul><ul><li>Your Python scripts have full access to Python's modules </li></ul><ul><li>With the hou Python module, you can control Houdini </li></ul>
  6. 7. Learning Python <ul><li>As I mentioned, I'm assuming you already know Python </li></ul><ul><li>If you're learning Python, I highly recommend the online Python tutorial at http://docs.python.org/tut </li></ul><ul><ul><li>The tutorial is all you need to know to start scripting Houdini </li></ul></ul><ul><ul><li>After reading the tutorial, you can explore the standard library as you're looking for modules </li></ul></ul><ul><ul><li>After you've started writing some Python, go back and read the tutorial again </li></ul></ul>
  7. 8. Experimenting with Python in Houdini <ul><li>Python shell </li></ul><ul><ul><li>Can be in a floating window or in a pane </li></ul></ul><ul><ul><li>The best place to explore the hou module </li></ul></ul><ul><ul><li>Good for interactively prototyping parts of a script </li></ul></ul><ul><li>If you use Python for nothing else, use a Python shell as a calculator </li></ul>
  8. 9. Experimenting with Python in Houdini <ul><li>Python 2.5.1 (r251:54863, Jul 17 2007, 14:40:58) </li></ul><ul><li>[GCC 4.1.3 20070629 (prerelease) (Debian 4.1.2-13) on linux2 </li></ul><ul><li>Type “help”, “copyright”, “credits” or “license” for more info. </li></ul><ul><li>>>> g = hou.node(“/obj/geo1”) </li></ul><ul><li>>>> g </li></ul><ul><li><hou.ObjNode of type geo at /obj/geo1> </li></ul><ul><li>>>> g.path() </li></ul><ul><li>'/obj/geo1' </li></ul><ul><li>>>> tx = g.parm('tx') </li></ul><ul><li>>>> tx </li></ul><ul><li><hou.Parm tx in /obj/geo> </li></ul><ul><li>>>> tx.eval() </li></ul><ul><li>0.0 </li></ul><ul><li>>>> tx.set(3.5); tx.eval() </li></ul><ul><li>3.5 </li></ul>
  9. 10. Accessing Houdini from Python: The hou Module <ul><li>The hou module is written in C++ and hooks directly into Houdini </li></ul><ul><li>It provides a brand new API called the Houdini Object Model (HOM) </li></ul><ul><ul><li>Contains classes for the conceptual entities in Houdini (e.g. nodes, parameters, keyframes, panes, vectors, Houdini digital asset definitions, etc.) </li></ul></ul><ul><li>Houdini also ships with other modules written in Python that build on the hou module. </li></ul>
  10. 11. A Simple Example: Changing Path Prefixes <ul><li>Suppose we want to change all the paths in all the file parameters in all the nodes in the scene </li></ul><ul><li>If the file starts with '/home/luke/project', we'll replace that part with '$HIP'. </li></ul>
  11. 12. A Simple Example: Changing Path Prefixes <ul><li>to </li></ul>
  12. 13. A Simple Example: Changing Path Prefixes <ul><li>Let's start by building up our script in the Python shell, one piece at a time. First, let's write the part that changes the value of a file parameter. </li></ul>
  13. 14. A Simple Example: Changing Path Prefixes <ul><li>>>> p = hou.parm(“/obj/geo1/file1/file”) </li></ul><ul><li>>>> p </li></ul><ul><li><hou.Parm file in /obj/geo1/file1> </li></ul><ul><li>>>> p.unexpandedString() </li></ul><ul><li>'/home/luke/project/image.jpg' </li></ul><ul><li>>>> p.unexpandedString()[18:] </li></ul><ul><li>'/image.jpg' </li></ul><ul><li>>>> '$HIP' + p.unexpandedString()[18:] </li></ul><ul><li>'$HIP/image.jpg' </li></ul>
  14. 15. A Simple Example: Changing Path Prefixes <ul><li>>>> t = p.parmTemplate() </li></ul><ul><li>>>> t.type() </li></ul><ul><li>parmTemplateType.String </li></ul><ul><li>>>> t.stringType() </li></ul><ul><li>stringParmType.FileReference </li></ul><ul><li>>>> def isFileParm(parm): </li></ul><ul><li>... t = parm.parmTemplate() </li></ul><ul><li>... return (t.type() == hou.parmTemplateType.String and </li></ul><ul><li>... t.stringType() == hou.stringParmType.FileReference) </li></ul><ul><li>>>> if isFileParm(p) and p.unexpandedString().startswith( </li></ul><ul><li>'/home/luke/project'): </li></ul><ul><li>parm.set('$HIP' + parm.unexpandedString()[18:]) </li></ul>
  15. 16. A Simple Example: Changing Path Prefixes <ul><li>Let's write a function to generalize this logic: </li></ul><ul><li>>>> def fixFilePrefix(parm, from_prefix, to_prefix): </li></ul><ul><li>... if isFileParm(parm) and parm.unexpandedString().startswith( </li></ul><ul><li>... from_prefix): </li></ul><ul><li>... parm.set(to_prefix + </li></ul><ul><li>... parm.unexpandedString()[len(from_prefix):]) </li></ul><ul><li>>>> fixFilePrefix( </li></ul><ul><li>... hou.parm('/obj/geo1/file1/file'), </li></ul><ul><li>... '/home/luke/project', '$HIP') </li></ul>
  16. 17. hou.session Module <ul><li>We can fix a bug in a function we've written by redefining a new definition in the Python shell. </li></ul><ul><li>Redefining long functions gets tedious, though. Luckily, we can write (and edit!) functions inside Houdini's Python Source Editor window and then test them out from the Python shell. </li></ul><ul><li>The code we write in the source editor window will become part of a module named hou.session. </li></ul>
  17. 18. hou.session Module
  18. 19. A Simple Example: Changing Path Prefixes <ul><li>Let's continue with our example and modify the function to work with all the parameters of a node. </li></ul><ul><li>>>> hou.node(“/obj/geo1”).parms() </li></ul><ul><li>(<hou.Parm stdswitcher1 in /obj/geo1>, <hou.Parm stdswitcher2 in /obj/geo1>, ..., <hou.Parm vm_computeN in /obj/geo1>) </li></ul>
  19. 20. A Simple Example: Changing Path Prefixes <ul><li>def fixFilePrefix(node, from_prefix, to_prefix): </li></ul><ul><li>for parm in node.parms(): </li></ul><ul><li>if (isFileParm(parm) and </li></ul><ul><li>parm.unexpandedString().startswith( </li></ul><ul><li>from_prefix)): </li></ul><ul><li>parm.set(to_prefix + </li></ul><ul><li>parm.unexpandedString()[len(from_prefix):]) </li></ul>
  20. 21. A Simple Example: Changing Path Prefixes <ul><li>Now all that's left is to call fixFilePrefix() for all the nodes in the scene </li></ul><ul><li>>>> hou.node('/obj').children() </li></ul><ul><li>(<hou.ObjNode of type geo at /obj/geo1>, <hou.ObjNode of type geo at /obj/geo2>, <hou.ObjNode of type cam at /obj/cam1>) </li></ul><ul><li>>>> def printNodes(node): </li></ul><ul><li>... print node.path(), </li></ul><ul><li>... for child in node.children(): </li></ul><ul><li>... printNodes(child) </li></ul><ul><li>>>> printNodes(hou.node('/')) </li></ul><ul><li>/ /obj /obj/geo1 /obj/geo2 /obj/cam1 /out /part /ch /shop ... </li></ul>
  21. 22. A Simple Example: Changing Path Prefixes <ul><li>def fixFilePrefix(node, from_prefix, to_prefix): </li></ul><ul><li>for parm in node.parms(): </li></ul><ul><li>if (isFileParm(parm) and </li></ul><ul><li>parm.unexpandedString().startswith( </li></ul><ul><li>from_prefix)): </li></ul><ul><li>parm.set(to_prefix + </li></ul><ul><li>parm.unexpandedString()[len(from_prefix):]) </li></ul><ul><li>for child in node.children(): </li></ul><ul><li>fixFilePrefix(child, from_prefix, to_prefix) </li></ul><ul><li>>>> hou.session.fixFilePrefix( </li></ul><ul><li>hou.node('/'), '/home/luke/project', '$HIP') </li></ul>
  22. 23. Interpreting Tracebacks <ul><li>At some point, we're all bound to make a typo or have a bug in our code. Hscript users will appreciate Python tracebacks. </li></ul>
  23. 25. Exploring the hou Module <ul><li>Houdini's Python shell popup help </li></ul><ul><ul><li>Attribute </li></ul></ul><ul><ul><li>completion </li></ul></ul><ul><ul><li>Function/ </li></ul></ul><ul><ul><li>method </li></ul></ul><ul><ul><li>help </li></ul></ul>
  24. 26. Exploring the hou Module <ul><ul><li>Function/ </li></ul></ul><ul><ul><li>method- </li></ul></ul><ul><ul><li>specific </li></ul></ul><ul><ul><li>argument </li></ul></ul><ul><ul><li>autocompletion </li></ul></ul>
  25. 27. Exploring the hou Module <ul><li>The Python shell also supports tab completion </li></ul><ul><li>The traditional Python dir() and help() functions also help you explore the hou module </li></ul><ul><li>>>> dir(hou.Node) </li></ul><ul><li>['__class__', ..., 'allowEditingOfContents', 'addSpareParmTuple', 'appendComment', 'changeNodeType', 'childTypeCategory', 'children', 'clearParmAliases', 'collapseIntoSubnet', 'color', 'comment', 'cook', 'copyNetworkBox', 'createDigitalAsset', 'createNetworkBox', 'createNode', 'creator', 'destroy', 'digitsInName', 'evalParm', 'evalParmTuple', 'expressionLanguage', 'extractAndDelete', 'findNetworkBox', 'findNetworkBoxes', 'hdaModule', 'indirectInputs', 'inputAncestors', 'inputConnections', 'inputConectors', 'inputs', 'insertInput', isCurrent', 'isInsideLockedHDA', ...] </li></ul>
  26. 28. Exploring the hou Module <ul><li>Houdini's help browser's help </li></ul><ul><ul><li>Contains an introduction covering the material covered here </li></ul></ul><ul><ul><li>Contains reference help for all the module functions, classes, and methods </li></ul></ul><ul><ul><li>Also lists functions and methods that are not implemented but are planned for implementation in future versions of Houdini </li></ul></ul>
  27. 30. Python for the Hscripter <ul><li>Most Hscript commands and expression functions have a “replaced by” section listing the hou module functions/methods with the same functionality </li></ul><ul><li>The hou module contains hou.hscript() and hou.hscriptExpression() functions that let you call arbitrary Hscript functions and expressions </li></ul><ul><li>Houdini 9 contains a new Hscript “python” command, including “python -c”, so you can invoke Python from hscript </li></ul>
  28. 31. Python for the Hscripter <ul><li>Tips </li></ul><ul><ul><li>Use Python variables to store nodes where you would have “cd'd” in Hscript </li></ul></ul><ul><ul><ul><ul><li>cd /obj/geo1 </li></ul></ul></ul></ul><ul><ul><ul><ul><li>opadd box </li></ul></ul></ul></ul><ul><ul><ul><ul><li>opadd sphere </li></ul></ul></ul></ul><ul><ul><ul><ul><li>cd /obj </li></ul></ul></ul></ul><ul><ul><ul><li>Could be done with: </li></ul></ul></ul><ul><ul><ul><ul><li>hou.cd('/obj/geo1') </li></ul></ul></ul></ul><ul><ul><ul><ul><li>hou.pwd().createNode('box') </li></ul></ul></ul></ul><ul><ul><ul><ul><li>hou.pwd().createNode('sphere') </li></ul></ul></ul></ul><ul><ul><ul><ul><li>hou.cd('/obj') </li></ul></ul></ul></ul>
  29. 32. Python for the Hscripter <ul><li>Tips (continued) </li></ul><ul><ul><ul><li>But you could instead write: </li></ul></ul></ul><ul><ul><ul><ul><li>n = hou.node('/obj/geo1') </li></ul></ul></ul></ul><ul><ul><ul><ul><li>n.createNode('box') </li></ul></ul></ul></ul><ul><ul><ul><ul><li>n.createNode('sphere') </li></ul></ul></ul></ul>
  30. 33. Python for the Hscripter <ul><li>Tips (continued) </li></ul><ul><ul><li>The names in the hou module are more descriptive, making Python scripts using hou much easier to read than Hscript scripts. However, they are consequently more verbose. </li></ul></ul><ul><ul><li>You can use Python tricks to make it less verbose. Consider a module named 'simplehou': </li></ul></ul><ul><ul><ul><li>import hou </li></ul></ul></ul><ul><ul><ul><li>n = hou.node </li></ul></ul></ul><ul><ul><ul><li>p = hou.parm </li></ul></ul></ul><ul><ul><ul><li>hou.Node.create = hou.Node.createNode </li></ul></ul></ul><ul><ul><ul><li>>>> from simplehou import * </li></ul></ul></ul><ul><ul><ul><li>>>> n('/obj/geo1').create('box') </li></ul></ul></ul>
  31. 34. Invoking Python from Houdini <ul><li>There are many places where Houdini will invoke the Python interpreter. Here are nine of them: </li></ul><ul><li>Houdini's Python shell </li></ul><ul><li>Use the hou.session module to store and edit one-off functions </li></ul><ul><li>A couple of Python statements can easily invoke scripts. For example, import a module from disk and call a function in it, or use execfile() to run a script </li></ul>
  32. 35. Shelf/Tab Menu <ul><li>The shelf/tab menu </li></ul><ul><li>A place to easily attach Python code to a button </li></ul><ul><li>A number of supporting modules used by the shelves can be found in $HFS/houdini/scripts/python </li></ul><ul><li>Most, if not all, of Houdini's builtin shelf scripts are written in Python, so they provide a wealth of examples </li></ul>
  33. 36. HDA Button Callback <ul><li>An HDA Button Callback </li></ul><ul><li>The PythonModule HDA section. </li></ul><ul><li>hou.Node.hdaModule and hou.NodeType.hdaModule </li></ul>
  34. 39. HDA Button Callback <ul><li>Not a good idea to call hou.session functions from button callbacks, since hou.session is intended to store hip-file specific functions. Instead, you need to store the callback script with the HDA. </li></ul>
  35. 40. HDA Event Handler <ul><li>An HDA's Event Handler </li></ul><ul><li>There currently aren't Python versions of HDA event handler scripts (OnCreated, OnUpdated, etc.) </li></ul><ul><li>However, you can use the python hscript command to call functions in the PythonModule section </li></ul><ul><li>def onCreated(node): print “created HDA”, node.path() </li></ul><ul><li>python -c “hou.node('$arg1').hdaModule().onCreated(hou.node('$arg1'))” </li></ul>
  36. 41. Parameters <ul><li>You can write Python expressions in a node's parameter </li></ul><ul><li>Each node's expression language is set to either Hscript expressions or Python </li></ul><ul><li>When you type an expressionless parameter, the expression will be in the node's language </li></ul>
  37. 42. Parameters <ul><li>Once a parameter has an expression, that expression's language will not change when you change the node's language </li></ul><ul><li>If the expression's language is different from the node's language, it will appear in red </li></ul><ul><li>You can change a parameter's language by right-clicking on it and choosing “Expression -> Change Language to ...” </li></ul>
  38. 43. Parameters <ul><li>“ from hou import *” is implicitly run before evaluating a Python expression </li></ul><ul><ul><li>You don't need the 'hou.' prefix </li></ul></ul><ul><ul><li>This way, common expressions like cubic(), linear(), ch(), etc. work in both Python and Hscript expressions </li></ul></ul><ul><li>You can call functions in the hou.session module, and “from hou.session import *” is also implicitly run </li></ul><ul><ul><li>For example, you could write a hou.session function, say foo(), that returns a tuple of 3 values, and put 'foo()[0]', 'foo()[1]', 'foo()[2]' into each of tx, ty, and tz </li></ul></ul>
  39. 44. Parameters <ul><li>Parameters in nodes inside an HDA can call functions in the PythonModule section </li></ul><ul><li>For example, if foo() is defined in PythonModule, /obj/myhda1 is the HDA, and you could write the following Python expression in /obj/myhda1/geo1/tx: node('..').hdaModule().foo() </li></ul>
  40. 45. Parameters <ul><li>You can also put the bodies of Python functions inside expressions </li></ul><ul><li>Simple rule to remember: single lines are expressions and multiple lines are function bodies </li></ul>
  41. 46. When Houdini Starts Up <ul><li>When Houdini Starts Up </li></ul><ul><li>There are Python versions of 123.cmd and 456.cmd: 123.py and 456.py </li></ul><ul><li>Houdini will look through $HOUDINI_SCRIPT_PATH, looking for the first directory with 123.* or 456.*. Once it finds a script, it will call it and stop looking. </li></ul><ul><li>123.{cmd,py} is called when Houdini starts up without a hip file </li></ul><ul><li>456.{cmd,py} is called every time a file is loaded or the session is cleared </li></ul>
  42. 47. When Houdini Starts Up <ul><li>There is a new file that's called only once, every time Houdini starts: pythonrc.py </li></ul><ul><li>Houdini will search $HOUDINI_SCRIPT_PATH for houdini/pythonrc.py and run each of the files found </li></ul><ul><li>Use pythonrc.py to store functions, aliases, etc. </li></ul>
  43. 48. Python-Based SOPs <ul><li>Python-based SOPs </li></ul><ul><li>“ File -> New Operator Type”, choose “Python type” and “Geometry Operator”, and write code in the “Cook” tab </li></ul>
  44. 50. The Help Browser <ul><li>In addition to the RunHCommand(), there are new RunPythonStatements() and RunPythonExpression() javascript functions </li></ul><ul><li><script src=”resource:///res/RunHCommand.js” /> </li></ul><ul><li><script> </li></ul><ul><li>alert(RunPythonExpression(“hou.node('/obj').children()”)); </li></ul><ul><li></script> </li></ul>
  45. 51. From Another Process <ul><li>From Another Process </li></ul><ul><li>Send XML to openport socket </li></ul><ul><ul><li>The RunPythonStatements() javascript function is just sending XML to Houdini's openport socket </li></ul></ul><ul><ul><li><xml version=”1.0”><python_statements>print “hello”</python_statements> </li></ul></ul><ul><li>Use houxmlrpc module </li></ul><ul><ul><li>From Houdini: </li></ul></ul><ul><ul><ul><li>houxmlrpc.run(port=8888) </li></ul></ul></ul><ul><ul><li>From the other process: </li></ul></ul><ul><ul><ul><li>s = houxmlrpc.ServerProxy('http://localhost:8888') </li></ul></ul></ul><ul><ul><ul><li>hou = s.hou </li></ul></ul></ul><ul><ul><ul><li># access the hou module like you normally would </li></ul></ul></ul>
  46. 52. Loading Python Scripts from External Files <ul><li>You can use the standard Python execfile function to run a script on disk </li></ul><ul><li>The hou.findFile() function can help locate a file in $HOUDINI_PATH </li></ul><ul><li>It's usually better to import a module than to use execfile, though </li></ul><ul><li>Houdini will go through all directories in $HOUDINI_SCRIPT_PATH , adding python subdirectories to sys.path. </li></ul><ul><li>For example, Houdini will look in the $HOME/houdini9.0/scripts/python directory when importing modules </li></ul>
  47. 53. Accessing Houdini from a Regular Python Shell <ul><li>You can import the hou module into a standard Python 2.5 shell </li></ul><ul><li>You'll need to add $HFS/houdini/scripts/python to sys.path </li></ul><ul><li>When hou is imported, it will initialize and create a Houdini session </li></ul><ul><li>It's very easy to bring Houdini into an existing Python-based pipeline </li></ul>
  48. 54. hython <ul><li>hython is a program that ships with Houdini </li></ul><ul><li>It's just a regular Python shell, but it automatically imports the hou module </li></ul><ul><li>You can pass hip files on the hython command line before any .py files </li></ul><ul><li>It also supports tab completion </li></ul>
  49. 55. Example: Loading a hip File and Running a ROP <ul><li>#!/usr/bin/python2.5 </li></ul><ul><li>import sys </li></ul><ul><li>sys.path.append(os.environ['HFS']+“/houdini/scripts/python”) </li></ul><ul><li>import hou </li></ul><ul><li>try: </li></ul><ul><li>hou.hipFile.load(sys.argv[1]) </li></ul><ul><li>except hou.LoadWarning, e: </li></ul><ul><li>print e </li></ul><ul><li>except hou.OperationFailed: </li></ul><ul><li>sys.exit(“Could not load “ + sys.argv[1]) </li></ul><ul><li>rop = hou.node(“/out/OUT”) </li></ul><ul><li>rop.render() </li></ul>
  50. 56. Example: A Simple Python SOP <ul><li>geo = hou.pwd().geometry() </li></ul><ul><li>bbox = geo.boundingBox() </li></ul><ul><li>cd_attrib = geo.addAttrib(hou.attribType.Point, 'Cd', </li></ul><ul><li>default_value=(1.0, 1.0, 1.0)) </li></ul><ul><li>for point in geo.points(): </li></ul><ul><li>dist_vec = hou.Vector3(point.position()) - bbox.center() </li></ul><ul><li>color = [0.5 + dist_vec[i] / bbox.sizevec()[i] </li></ul><ul><li>for i in range(3)] </li></ul><ul><li>point.setAttribValue(cd_attrib, color) </li></ul>
  51. 57. Example: Node Layout <ul><li>import pygraphviz </li></ul><ul><li>def layout(network, scale=1.0): </li></ul><ul><li>graph = pygraphviz.AGraph(directed=True) </li></ul><ul><li>graph.graph_attr['ordering'] = 'in' </li></ul><ul><li># Add graph nodes for each Houdini node in the network. </li></ul><ul><li>gv_nodes, nodes = {}, {} </li></ul><ul><li>for node in network.children(): </li></ul><ul><li>graph.add_node(node.name()) </li></ul><ul><li>gv_node = graph.nodes()[-1] </li></ul><ul><li>gv_node.attr['width'] = str(0.5 + len(node.name()) * 0.05) </li></ul><ul><li>gv_node.attr['height'] = '0.12' </li></ul><ul><li>gv_node.attr['fixedsize'] = 'true' </li></ul><ul><li>gv_nodes[node] = gv_node </li></ul><ul><li>nodes[gv_node] = node </li></ul>
  52. 58. Example: Node Layout (continued) <ul><li># Now add graph edges for all the wires in the network </li></ul><ul><li>for node in network.children(): </li></ul><ul><li>for input_node in node.inputs(): </li></ul><ul><li>graph.add_edge(gv_nodes[input_node], gv_nodes[node]) </li></ul><ul><li>edge = graph.edges()[-1] </li></ul><ul><li>edge.attr['headport'] = 'n' </li></ul><ul><li>edge.attr['tailport'] = 's' </li></ul><ul><li># Layout the graph and set the Houdini node positions </li></ul><ul><li>graph.layout(prog='neato') </li></ul><ul><li>scale *= 0.04 </li></ul><ul><li>for gv_node in graph.nodes(): </li></ul><ul><li>nodes[gv_node].setPosition( </li></ul><ul><li>[float(v) * scale </li></ul><ul><li>for v in gv_node.attr['pos'].split(',')]) </li></ul>
  53. 59. Example: Node Layout (continued) <ul><li># Call layout on the current network in the network editor </li></ul><ul><li>network_editor = hou.ui.curDesktop().paneTabOfType( </li></ul><ul><li>hou.paneTabType.NetworkEditor) </li></ul><ul><li>layout(network_editor.pwd()) </li></ul>
  54. 60. Houdini's Default Layout
  55. 61. Graphviz: 'dot' algorithm
  56. 62. Graphviz: 'neato-hier' algorithm
  57. 63. Graphviz: 'neato' algorithm