├── images_examples ├── create_shader.png ├── parms_camera.png ├── parms_camera_ui.png ├── import_agent_clip.png ├── import_files_path.png └── scatter │ ├── scatter_preview.png │ ├── Scatter_compiled_1.png │ └── Scatter_compiled_2.png ├── README.md ├── .gitignore ├── PythonTools.md ├── Instance_percentage_based.py ├── PrincipalShader_from_path.py ├── Import_multiple_agent_clips.py ├── Camera_comments.py ├── Import_files.py ├── CheatSheet.md └── OPENCL.md /images_examples/create_shader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/create_shader.png -------------------------------------------------------------------------------- /images_examples/parms_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/parms_camera.png -------------------------------------------------------------------------------- /images_examples/parms_camera_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/parms_camera_ui.png -------------------------------------------------------------------------------- /images_examples/import_agent_clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/import_agent_clip.png -------------------------------------------------------------------------------- /images_examples/import_files_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/import_files_path.png -------------------------------------------------------------------------------- /images_examples/scatter/scatter_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/scatter/scatter_preview.png -------------------------------------------------------------------------------- /images_examples/scatter/Scatter_compiled_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/scatter/Scatter_compiled_1.png -------------------------------------------------------------------------------- /images_examples/scatter/Scatter_compiled_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/HEAD/images_examples/scatter/Scatter_compiled_2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Houdini-scripts 2 | A collection of my personal Houdini scripts and tools. 3 | 4 | ## Python Tools 5 | A set of python tools I have been creating overtime. 6 | 7 | [Python Tools](https://github.com/JoseZalez/Houdini-scripts/blob/master/PythonTools.md) 8 | 9 | ## CheatSheet 10 | Some tips and short useful scripts with vex and python 11 | 12 | [CheatSheet](https://github.com/JoseZalez/Houdini-scripts/blob/master/CheatSheet.md) 13 | 14 | ## OPENCL 15 | Quick introduction to OpenCL with a few kernels 16 | 17 | [OPENCL](https://github.com/JoseZalez/Houdini-scripts/blob/master/OPENCL.md) 18 | 19 | ### Contact 20 | 21 | [LinkedIn](https://www.linkedin.com/in/jose-gonzalezvfx/) 22 | 23 | ## License 24 | 25 | Public domain. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /PythonTools.md: -------------------------------------------------------------------------------- 1 | 2 | # Scripts 3 | 4 | * Import files 5 | * Add parameter comments to camera 6 | * Import multiple agent clips 7 | * Principal shader from path 8 | * Instace based on percentage 9 | 10 | 11 | ### Install 12 | 13 | Simply create a new tool in your toolbar, paste the code, save and execute. 14 | 15 | # Import files 16 | 17 | Given a path and a file extension, gets a list of the files in that current path or subdirectories. Includes a word filter. Supports FBX, Alembic and Houdini files. 18 | 19 | *Import as FBX* allows you to import FBX with animation in *SOP* level. 20 | 21 | [Code](https://github.com/JoseZalez/Houdini-scripts/blob/master/Import_files.py) 22 | 23 | ![alt tag](https://i.gyazo.com/002e6248921f93cfc83b0b2a7ad9e809.png) 24 | 25 | # Add parameter comments to camera 26 | 27 | Selecting a camera and a node, gets the node parameters and allows you to select from a list the parameters you want to display on the camera. 28 | 29 | Allows to *Create*, *Add* and *Reset* the parameter interface. 30 | 31 | With the *Add* function you can add parameters from different nodes. 32 | 33 | The parameters update automatically. 34 | 35 | [Code](https://github.com/JoseZalez/Houdini-scripts/blob/master/Camera_comments.py) 36 | 37 | 38 | ## Usage 39 | 40 | Simply add the code to a custom script on your toolbar, save, open it and CTRL+C your camera node, CTRL+V on the camera node box on the plugin to paste the path, do the same for the node you want the parameters from. 41 | 42 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/parms_camera_ui.png) 43 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/parms_camera.png) 44 | 45 | # Import multiple agent clips 46 | 47 | Allows you to import multiple clips to an *Agent Clip* node selecting them from a list of the path introduced, setting automatically the path and the name. 48 | 49 | [Code](https://github.com/JoseZalez/Houdini-scripts/blob/master/Import_multiple_agent_clips.py) 50 | 51 | ## Usage 52 | 53 | Paste your clip path into the path box and select *Import* to import the selected clips into the *Agent Clip* node, if there are already clips in the node the new ones will be just added at the end. 54 | 55 | If your animations are in place simply check the box to set them automatically to In-Place. 56 | 57 | ### Reminder 58 | 59 | If you select to convert the clips to In-Place remember to select a *Locomotion Node* in the *Locomotion Settings*. 60 | 61 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/import_agent_clip.png) 62 | 63 | # Batch change paths 64 | 65 | Simple script to change your path to $HIP given a drive letter and a folder name 66 | 67 | ``` 68 | keyword = "HIP_FOLDER_NAME" #"explosion" for example 69 | drive = "Drive letter" #"D:" for example 70 | 71 | nodes = hou.node("/").allSubChildren() 72 | 73 | 74 | tempPath = "" 75 | 76 | for node in nodes: 77 | 78 | parms = node.parms() 79 | 80 | for x in parms: 81 | if x.parmTemplate().type().name() == "String": 82 | 83 | tempPath = x.eval() 84 | 85 | if tempPath.startswith(drive): 86 | 87 | x.set("$HIP"+tempPath.split(keyword)[1]) 88 | 89 | ``` 90 | 91 | # Principal shader from path 92 | 93 | Given a path with the textures, creates a principal shader with all the images connected to their respective inputs including displacement, bump and normal maps. 94 | 95 | Returns a message if a image input wasnt found. 96 | 97 | [Code](https://github.com/JoseZalez/Houdini-scripts/blob/master/PrincipalShader_from_path.py) 98 | 99 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/create_shader.png) 100 | 101 | 102 | 103 | # Instace based on percentage 104 | 105 | Creates a copy to points and Controller with the percentage of each geometry you want to copy inside a foreach loop 106 | 107 | [Code](https://github.com/JoseZalez/Houdini-scripts/blob/master/Instance_percentage_based.py) 108 | 109 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/scatter/scatter_preview.png) 110 | 111 | ## Usage 112 | 113 | With the points where you want to copy the geometry created, select the geometry you want to copy on the points: 114 | 115 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/scatter/Scatter_compiled_1.png) 116 | 117 | Then execute the script. Select the created nodes and press Shift+L to lay out them. It will create a *for loop* network with *compile blocks* already with the setup needed and a *controller* 118 | 119 | Connect your points to the *Input_points wrangle* and visualize the *compile_end*. Then simply use the *controller* to select the percentages you want. 120 | 121 | ![alt tag](https://raw.githubusercontent.com/JoseZalez/Houdini-scripts/master/images_examples/scatter/Scatter_compiled_2.png) 122 | 123 | 124 | ### Reminder 125 | 126 | For now please always use percentages that add up to 100% to get the desired results. 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /Instance_percentage_based.py: -------------------------------------------------------------------------------- 1 | #Create a Controller to set a percentage in a copy to points for each geometry 2 | #https://www.linkedin.com/in/jose-gonzalezvfx/ 3 | 4 | def createexpression(controller): 5 | 6 | list=[] 7 | for parm in controller.parms(): 8 | 9 | if parm.name().startswith("percen"): 10 | path=parm.path() 11 | val="ch('{}')".format(path) 12 | list.append(val) 13 | 14 | #Create the string for the expression with a initial data 15 | 16 | expression="{\n"+ "float v=0;\n" 17 | n=1 18 | 19 | 20 | list_set=[] 21 | 22 | setval="" 23 | 24 | 25 | for path in list: 26 | 27 | expression= expression +"float set"+str(n)+ " = " + path + ";\n" 28 | setval=setval+"set"+str(n)+"+" 29 | list_set.append(setval) 30 | n+=1 31 | 32 | expression= expression + "if(pulse(int(point(-1,0,'range',0)),0,set1)) {v=0;} \n" 33 | 34 | #Add the conditionals for the percentages 35 | 36 | for j in range(len(list_set)-1): 37 | 38 | expression= expression + "if(pulse(int(point(-1,0,'range',0)),{},{}))".format(list_set[j][:-1],list_set[j+1][:-1]) 39 | expression=expression+ " {"+"v={};".format(j+1)+"}\n" 40 | 41 | 42 | #Close the expression 43 | expression = expression + "return v;\n}" 44 | 45 | return expression 46 | 47 | #Main 48 | 49 | #Get a list of the selected node 50 | 51 | nodes = hou.selectedNodes() 52 | 53 | if not nodes: 54 | 55 | hou.ui.displayMessage("Please select the geometry nodes to scatter", buttons=('OK',), severity=hou.severityType.Message, default_choice=0, close_choice=0, title="Select a node",details_expanded=False) 56 | 57 | geo=nodes[0].parent() 58 | 59 | wrangle=geo.createNode("attribwrangle","Input_points") 60 | controller=geo.createNode("null","Controller") 61 | switch=geo.createNode("switch") 62 | copytopts=geo.createNode("copytopoints") 63 | block_begin_input=geo.createNode("block_begin") 64 | compile_end=geo.createNode("compile_end") 65 | block_end=geo.createNode("block_end") 66 | 67 | 68 | #Set the block_end parms 69 | 70 | block_begin_input.parm("method").set(1) 71 | block_begin_input.parm("blockpath").set("../"+block_end.name()) 72 | 73 | block_end.parm("itermethod").set(1) 74 | block_end.parm("method").set(1) 75 | block_end.parm("useattrib").set(0) 76 | block_end.parm("blockpath").set("../"+block_begin_input.name()) 77 | block_end.parm("templatepath").set("../"+block_begin_input.name()) 78 | block_end.parm("multithread").set(1) 79 | 80 | 81 | #Add the rand attribute to the wrangle 82 | 83 | wrangle.parm("snippet").set("f@range=rand(@ptnum)*100;") 84 | 85 | #Add the percentages controls to the null node 86 | 87 | ptg = controller.parmTemplateGroup() 88 | parm_folder = hou.FolderParmTemplate('folder', 'Controls') 89 | 90 | i=1 91 | 92 | default=100/len(nodes) 93 | 94 | for node in nodes: 95 | 96 | parmtemplate=hou.IntParmTemplate('percentage_'+str(i), 'Geo '+str(i)+' %', 1,default_value=(default,0),min =0,max=100) 97 | parm_folder.addParmTemplate(parmtemplate) 98 | i+=1 99 | 100 | ptg.append(parm_folder) 101 | controller.setParmTemplateGroup(ptg) 102 | 103 | #Create the compile and block begins and connect them to the switch 104 | 105 | compile_begin_first=geo.createNode("compile_begin") 106 | compile_begin_first.parm("blockpath").set("../"+compile_end.name()) 107 | 108 | for node in nodes: 109 | compile_begin=geo.createNode("compile_begin") 110 | compile_begin.parm("blockpath").set("../"+compile_end.name()) 111 | compile_begin.setInput(0,node) 112 | block_begin_loop=geo.createNode("block_begin") 113 | block_begin_loop.parm("method").set(3) 114 | block_begin_loop.parm("blockpath").set("../"+block_end.name()) 115 | block_begin_loop.setInput(0,compile_begin) 116 | 117 | switch.setNextInput(block_begin_loop) 118 | 119 | 120 | #Connect the inputs from the nodes to create the network 121 | 122 | compile_begin_first.setInput(0,wrangle) 123 | block_begin_input.setInput(0,compile_begin_first) 124 | 125 | copytopts.setInput(0,switch) 126 | copytopts.setInput(1,block_begin_input) 127 | 128 | block_end.setInput(0,copytopts) 129 | compile_end.setInput(0,block_end) 130 | 131 | switch.moveToGoodPosition() 132 | copytopts.moveToGoodPosition() 133 | 134 | pos=switch.position() 135 | 136 | v1=hou.Vector2((2.0,0.0)) 137 | v2=hou.Vector2((4.0,0.0)) 138 | 139 | pos1=pos.__add__(v1) 140 | pos2=pos.__add__(v2) 141 | 142 | controller.setPosition(pos1) 143 | wrangle.setPosition(pos2) 144 | 145 | 146 | #Set the expression on the switch parm 147 | 148 | switch.parm("input").setExpression(createexpression(controller)) 149 | 150 | ptg = switch.parmTemplateGroup() 151 | parm_folder = hou.FolderParmTemplate('folder', 'Spare Input') 152 | parmtemplate=hou.StringParmTemplate ('spare_input0','Spare Input 0', 1 ,naming_scheme=hou.parmNamingScheme.Base1, string_type=hou.stringParmType.FileReference, tags={ "opfilter" : "!!SOP!!", "oprelative" : ".", }) 153 | parm_folder.addParmTemplate(parmtemplate) 154 | ptg.append(parm_folder) 155 | switch.setParmTemplateGroup(ptg) 156 | 157 | switch.parm("spare_input0").set("../"+block_begin_input.name()) 158 | 159 | -------------------------------------------------------------------------------- /PrincipalShader_from_path.py: -------------------------------------------------------------------------------- 1 | #Creates a principle shader with the texture from a path connected 2 | #https://www.linkedin.com/in/jose-gonzalezvfx/ 3 | 4 | import os 5 | 6 | import sys 7 | 8 | from PySide2.QtWidgets import QDialog, QApplication, QLineEdit, QLabel, QPushButton, QCheckBox, QHBoxLayout, QVBoxLayout 9 | from PySide2.QtCore import Qt 10 | 11 | class UI(QDialog): 12 | 13 | 14 | def __init__(self, parent=None): 15 | 16 | super(UI, self).__init__(parent) 17 | main_layout = QVBoxLayout() 18 | self.setWindowTitle("Create shader") 19 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) 20 | 21 | #Get Houdini window style and apply to interface 22 | self.setStyleSheet(hou.qt.styleSheet()) 23 | self.setProperty("houdiniStyle", True) 24 | 25 | #Create a path input 26 | filepath_layout = QHBoxLayout() 27 | lbl = QLabel("Texture path:") 28 | self.filepath = QLineEdit("") 29 | filepath_layout.addWidget(lbl) 30 | filepath_layout.addWidget(self.filepath) 31 | filepath_layout.setSpacing(10) 32 | 33 | #Create an extension input 34 | shadername_layout = QHBoxLayout() 35 | lbl = QLabel("Shader name:") 36 | self.shadername = QLineEdit("") 37 | shadername_layout.addWidget(lbl) 38 | shadername_layout.addWidget(self.shadername) 39 | shadername_layout.setSpacing(10) 40 | 41 | #Set a button to start 42 | self.button = QPushButton('Create') 43 | 44 | #Add all the layout together 45 | main_layout.addLayout(filepath_layout, stretch=1) 46 | main_layout.addLayout(shadername_layout, stretch=1) 47 | main_layout.addWidget(self.button) 48 | self.setLayout(main_layout) 49 | 50 | #Start the main code 51 | self.button.clicked.connect(self.createshader) 52 | 53 | def createshader(self): 54 | 55 | #Store the path and the extension strings previously input by the user 56 | path = self.filepath.text() 57 | shadername = self.shadername.text() 58 | 59 | #Checks if the user closed the path, if not it closes it 60 | if not path.endswith("\\"): 61 | path=path + "\\" 62 | 63 | shader = hou.node('mat').createNode('principledshader',shadername) 64 | 65 | textures = os.listdir(path) 66 | 67 | 68 | for files in textures: 69 | 70 | texture_path = path+files 71 | 72 | texture = hou.node('mat').createNode('texture',files) 73 | 74 | 75 | texture.parm("map").set(texture_path) 76 | 77 | name = files.lower() 78 | 79 | if "color" in name or "diff" in name: 80 | 81 | shader.setInput(1,texture,0) 82 | texture.moveToGoodPosition() 83 | 84 | 85 | elif "rough" in name: 86 | 87 | shader.setInput(6,texture,0) 88 | texture.moveToGoodPosition() 89 | 90 | elif "metal" in name: 91 | 92 | shader.setInput(9,texture,0) 93 | texture.moveToGoodPosition() 94 | 95 | elif "specular" in name or "spec" in name: 96 | 97 | shader.setInput(10,texture,0) 98 | texture.moveToGoodPosition() 99 | 100 | elif "emis" in name: 101 | 102 | shader.setInput(27,texture,0) 103 | texture.moveToGoodPosition() 104 | 105 | elif "bump" in name or "normal" in name: 106 | 107 | texture.destroy() 108 | shader.parm("baseBumpAndNormal_enable").set(True) 109 | 110 | if "bump" in name: 111 | shader.parm("baseBumpAndNormal_type").set("Bump") 112 | shader.parm("baseBump_bumpTexture").set(texture_path) 113 | 114 | else: 115 | shader.parm("baseNormal_texture").set(texture_path) 116 | 117 | 118 | elif "height" in name or "disp" in name or "depth" in name: 119 | 120 | texture.destroy() 121 | shader.parm("dispTex_enable").set(True) 122 | shader.parm("dispTex_texture").set(texture_path) 123 | 124 | 125 | else: 126 | 127 | hou.ui.displayMessage("Couldnt connect " +files, severity=hou.severityType.Message, default_choice=0, close_choice=0, title="Texture not applied",details_expanded=False) 128 | texture.moveToGoodPosition() 129 | 130 | 131 | shader.moveToGoodPosition() 132 | 133 | 134 | #Starts the script window for the user 135 | app = QApplication.instance() 136 | if app is None: 137 | app = QApplication(sys.argv) 138 | shaderUI = UI() 139 | shaderUI.show() 140 | -------------------------------------------------------------------------------- /Import_multiple_agent_clips.py: -------------------------------------------------------------------------------- 1 | #Imports multiple clips selected from a list to a Houdini agent clip node 2 | #https://www.linkedin.com/in/jose-gonzalezvfx/ 3 | 4 | import os 5 | 6 | import sys 7 | 8 | from PySide2.QtWidgets import QDialog, QApplication, QLineEdit, QLabel, QPushButton, QCheckBox, QHBoxLayout, QVBoxLayout 9 | from PySide2.QtCore import Qt 10 | 11 | class UI(QDialog): 12 | """""" 13 | 14 | def __init__(self, parent=None): 15 | """Constructor""" 16 | super(UI, self).__init__(parent) 17 | main_layout = QVBoxLayout() 18 | self.setWindowTitle("Import animation clips") 19 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) 20 | 21 | #Get Houdini window style and apply to interface 22 | self.setStyleSheet(hou.qt.styleSheet()) 23 | self.setProperty("houdiniStyle", True) 24 | 25 | #Create a path input 26 | filepath_layout = QHBoxLayout() 27 | lbl = QLabel("Path:") 28 | self.filepath = QLineEdit("") 29 | filepath_layout.addWidget(lbl) 30 | filepath_layout.addWidget(self.filepath) 31 | filepath_layout.setSpacing(10) 32 | 33 | #Create a check for convert to In-Place 34 | inplace_layout = QHBoxLayout() 35 | self.inplace = QCheckBox("", self) 36 | inplace_layout.addWidget(QLabel("Convert to In-Place:")) 37 | inplace_layout.addWidget(self.inplace) 38 | inplace_layout.setSpacing(10) 39 | 40 | #Set a button to import 41 | self.button = QPushButton('Import') 42 | 43 | #Add all the layout together 44 | main_layout.addLayout(filepath_layout, stretch=1) 45 | main_layout.addLayout(inplace_layout, stretch=1) 46 | main_layout.addWidget(self.button) 47 | self.setLayout(main_layout) 48 | 49 | #Start the main code 50 | self.button.clicked.connect(self.main) 51 | 52 | def main(self): #Check if the user selected a node and check if its a agent clip node 53 | 54 | 55 | nodes = hou.selectedNodes() 56 | 57 | if not nodes or 'agentclip' not in nodes[0].type().name() : 58 | 59 | hou.ui.displayMessage("Please select a Agent Clip node to import the clips into", buttons=('OK',), severity=hou.severityType.Message, default_choice=0, close_choice=0, title="Select a node",details_expanded=False) 60 | 61 | else: 62 | 63 | self.importclips() 64 | 65 | def path(self): #Returns the fixed path 66 | 67 | path = self.filepath.text() 68 | 69 | if not path.endswith("\\"): 70 | path=path + "\\" 71 | 72 | return path 73 | 74 | def getlist_paths(self): 75 | 76 | empty=0 77 | 78 | path=self.path() 79 | file_list = os.listdir(path) 80 | list_paths=[] 81 | 82 | #Gets all the clip paths in the input path 83 | for clip in file_list: 84 | 85 | clip_path = path+clip 86 | 87 | 88 | if(clip.endswith("fbx")): 89 | 90 | list_paths.append(clip_path) 91 | 92 | #Returns a list of all the files with a short name 93 | name_list_all=self.getlist_names(list_paths) 94 | 95 | #Creates a list with the index of the clips selected by the user 96 | index_list=hou.ui.selectFromList(name_list_all, exclusive=False, title='Select clips', column_header="Clips", num_visible_rows=10, clear_on_cancel=False) 97 | 98 | if not index_list: 99 | empty=1 100 | 101 | chosen_clips=[] 102 | name_list=[] 103 | 104 | #Creates a new name list with just the clips selected from the user 105 | for j in index_list: 106 | 107 | name_list.append(name_list_all[j]) 108 | 109 | #Creates a new list with just the clips selected from the user 110 | for x in index_list: 111 | 112 | chosen_clips.append(list_paths[x]) 113 | 114 | return chosen_clips,name_list,empty; 115 | 116 | def getlist_names(self,list_paths): #Get a short name for each clip 117 | 118 | name_list_all=[] 119 | 120 | for i in list_paths: 121 | i=i[i.rfind('\\')+1:] 122 | i=i[:-4] 123 | name_list_all.append(i) 124 | 125 | return name_list_all 126 | 127 | 128 | def importclips(self): #Imports the clips into the agent clip node 129 | 130 | n=0 131 | j=0 132 | 133 | nodes = hou.selectedNodes() 134 | 135 | load_clip=nodes[0] 136 | clipsparm = load_clip.parm("clips") 137 | 138 | i=clipsparm.eval() 139 | 140 | 141 | tuple_list=self.getlist_paths() 142 | chosen_clips=tuple_list[0] 143 | name_list = tuple_list[1] 144 | empty = tuple_list[2] 145 | 146 | if empty != 1: 147 | 148 | if clipsparm.eval() == 0: 149 | i=0 150 | else: 151 | first_clip = load_clip.parm("name1") 152 | 153 | if not first_clip.eval(): 154 | 155 | i=0 156 | 157 | clipsparm.set(len(chosen_clips)+i) 158 | 159 | parms = load_clip.parms() 160 | 161 | #Gives to each clip parm a name and the file path, sets it to fbx and convert to in-place if selected in the UI 162 | for x in parms: 163 | if x.name().startswith("name"): 164 | if not x.eval(): 165 | x.set(name_list[n]) 166 | n+=1 167 | elif x.name().startswith("file"): 168 | if not x.eval(): 169 | x.set(chosen_clips[j]) 170 | j+=1 171 | elif x.name().startswith("source") and int(x.name()[-1:]) > i: 172 | x.set(1) 173 | elif x.name().startswith("converttoinplace") and self.inplace.isChecked() and int(x.name()[-1:]) > i: 174 | x.set(1) 175 | 176 | #Starts the script window for the user 177 | app = QApplication.instance() 178 | if app is None: 179 | app = QApplication(sys.argv) 180 | clipsUI = UI() 181 | clipsUI.show() 182 | -------------------------------------------------------------------------------- /Camera_comments.py: -------------------------------------------------------------------------------- 1 | #Add parameters to the camera 2 | #https://www.linkedin.com/in/jose-gonzalezvfx/ 3 | 4 | import os 5 | 6 | import sys 7 | 8 | from PySide2.QtWidgets import QDialog, QApplication, QLineEdit, QLabel, QPushButton, QCheckBox, QHBoxLayout, QVBoxLayout 9 | from PySide2.QtCore import Qt 10 | 11 | class UI(QDialog): 12 | """""" 13 | 14 | def __init__(self, parent=None): 15 | """Constructor""" 16 | super(UI, self).__init__(parent) 17 | main_layout = QVBoxLayout() 18 | self.setWindowTitle("Add parameters to camera") 19 | 20 | #Keep the window on top always 21 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) 22 | 23 | #Get Houdini window style and apply to interface 24 | self.setStyleSheet(hou.qt.styleSheet()) 25 | self.setProperty("houdiniStyle", True) 26 | 27 | #Create a path input 28 | camera_layout = QHBoxLayout() 29 | lbl = QLabel("Camera node:") 30 | self.camera = QLineEdit("") 31 | camera_layout.addWidget(lbl) 32 | camera_layout.addWidget(self.camera) 33 | camera_layout.setSpacing(10) 34 | 35 | #Create an extension input 36 | solver_layout = QHBoxLayout() 37 | lbl = QLabel("Node:") 38 | self.solver = QLineEdit("") 39 | solver_layout.addWidget(lbl) 40 | solver_layout.addWidget(self.solver) 41 | solver_layout.setSpacing(10) 42 | 43 | #Set a button to start 44 | self.buttonCreate = QPushButton('Create') 45 | 46 | #Set a button to delete old parms 47 | self.buttonAdd = QPushButton('Add') 48 | 49 | #Set a button to delete old parms 50 | self.buttonReset = QPushButton('Reset') 51 | 52 | #Add all the layout together 53 | main_layout.addLayout(camera_layout, stretch=1) 54 | main_layout.addLayout(solver_layout, stretch=1) 55 | main_layout.addWidget(self.buttonCreate) 56 | main_layout.addWidget(self.buttonAdd) 57 | main_layout.addWidget(self.buttonReset) 58 | self.setLayout(main_layout) 59 | 60 | #Start the main code 61 | self.buttonCreate.clicked.connect(self.createcomment) 62 | self.buttonAdd.clicked.connect(self.add) 63 | self.buttonReset.clicked.connect(self.reset) 64 | 65 | def getCameraNode(self): 66 | 67 | camera_path = self.camera.text() 68 | return hou.node(camera_path) 69 | 70 | 71 | def gettext(self): 72 | 73 | #Gets the camera and solver node 74 | solver_path = self.solver.text() 75 | solver = hou.node(solver_path) 76 | 77 | camera = self.getCameraNode() 78 | 79 | 80 | #Creates a list of parms from the selected node 81 | all_parms = solver.parms() 82 | 83 | #Initialize a dictionary and a list for the parms 84 | thisdict ={} 85 | parmlist=[] 86 | 87 | i=0 88 | 89 | #Iterates for each paramater, adds the name with the value in the dictionary, also adds the name to the list 90 | filter_word=["Interpolation","Position","Value","Visualization","Control"] 91 | 92 | previousname="" 93 | 94 | for parm in all_parms: 95 | name=parm.name() 96 | long_name=parm.description() 97 | 98 | if not any(x in long_name for x in filter_word): 99 | if "enable" not in name: 100 | 101 | if long_name == previousname: 102 | 103 | vectorname=name[:-1] 104 | del parmlist[-1] 105 | i-=1 106 | thisdict[str(i)] = "data+=" + "'"+ long_name +": "+"'"+" + "+ 'str(solver.parmTuple({}).eval())'.format("'"+vectorname+"'") + "+" + "'\\n'" 107 | previousname=long_name 108 | long_name+=" (vector) " 109 | else: 110 | thisdict[str(i)] = "data+=" + "'"+ long_name +": "+"'"+" + "+ 'str(solver.parm({}).eval())'.format("'"+name+"'") + "+" + "'\\n'" 111 | previousname=long_name 112 | 113 | 114 | parmlist.append(long_name) 115 | i+=1 116 | 117 | text = 'data=""' + "\n"+'solver = hou.node({})'.format("'"+self.solver.text()+"'") +"\n" 118 | 119 | #Shows a list of all the parameters for the user to select which he wants 120 | selected = hou.ui.selectFromList(parmlist, exclusive=False, title='Import parameters', column_header="Parameters", num_visible_rows=10, clear_on_cancel=False) 121 | 122 | #Iterates for all the parms with the values from the ditionary and appends it to a string with a line jump 123 | for x in range(len(selected)): 124 | index = str(selected[x]) 125 | text += thisdict[index] + '\n' 126 | 127 | return text 128 | 129 | 130 | def createcomment(self): 131 | 132 | text_out=self.gettext() 133 | 134 | camera = self.getCameraNode() 135 | 136 | text_out+="return data" 137 | 138 | if not camera.parm("vcomment"): 139 | #Add a string parameter to the camera input 140 | ptg = camera.parmTemplateGroup() 141 | parm_folder = hou.FolderParmTemplate('folder', 'Notes') 142 | parmtemplate=hou.StringParmTemplate('vcomment', 'Text', 1) 143 | parmtemplate.setTags({"editor": "1","editorlang": "python"}) 144 | parm_folder.addParmTemplate(parmtemplate) 145 | ptg.append(parm_folder) 146 | camera.setParmTemplateGroup(ptg) 147 | 148 | #Set the paramaters with the values in the string parameter as a expression 149 | camera.parm("vcomment").setExpression(text_out, hou.exprLanguage.Python) 150 | else: 151 | hou.ui.displayMessage("Please click 'Reset' to create new parameters or 'Add' to add new parameters", buttons=('OK',), severity=hou.severityType.Message, default_choice=0, close_choice=0, title="Select a node",details_expanded=False) 152 | 153 | 154 | def add(self): 155 | text_out=self.gettext() 156 | camera = self.getCameraNode() 157 | 158 | 159 | #Set the paramaters with the values in the string parameter 160 | current_text=camera.parm("vcomment").expression() 161 | old_out=current_text.split("\n", 1)[1] 162 | new_text=text_out+old_out 163 | 164 | camera.parm("vcomment").setExpression(new_text, hou.exprLanguage.Python) 165 | 166 | 167 | def reset(self): 168 | 169 | #Deletes the folder and the comment stored in the camera node 170 | camera = self.getCameraNode() 171 | 172 | ptg = camera.parmTemplateGroup() 173 | folder_to_delete = ptg.findFolder('Notes') 174 | ptg.remove(folder_to_delete) 175 | camera.setParmTemplateGroup(ptg) 176 | 177 | #Starts the script window for the user 178 | app = QApplication.instance() 179 | if app is None: 180 | app = QApplication(sys.argv) 181 | CommentUI = UI() 182 | CommentUI.show() 183 | -------------------------------------------------------------------------------- /Import_files.py: -------------------------------------------------------------------------------- 1 | #Get a path and a file type and import all the objects matching in the folder + subdirectories 2 | #https://www.linkedin.com/in/jose-gonzalezvfx/ 3 | 4 | import os 5 | 6 | import sys 7 | 8 | from PySide2.QtWidgets import QDialog, QApplication, QLineEdit, QLabel, QPushButton, QCheckBox, QHBoxLayout, QVBoxLayout 9 | from PySide2.QtCore import Qt 10 | 11 | class UI(QDialog): 12 | """""" 13 | 14 | def __init__(self, parent=None): 15 | """Constructor""" 16 | super(UI, self).__init__(parent) 17 | main_layout = QVBoxLayout() 18 | self.setWindowTitle("Import files") 19 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) 20 | 21 | #Get Houdini window style and apply to interface 22 | self.setStyleSheet(hou.qt.styleSheet()) 23 | self.setProperty("houdiniStyle", True) 24 | 25 | #Create a path input 26 | filepath_layout = QHBoxLayout() 27 | lbl = QLabel("Path:") 28 | self.filepath = QLineEdit("") 29 | filepath_layout.addWidget(lbl) 30 | filepath_layout.addWidget(self.filepath) 31 | filepath_layout.setSpacing(10) 32 | 33 | #Create an extension input 34 | extension_layout = QHBoxLayout() 35 | lbl = QLabel("File extension:") 36 | self.extension = QLineEdit("") 37 | extension_layout.addWidget(lbl) 38 | extension_layout.addWidget(self.extension) 39 | extension_layout.setSpacing(10) 40 | 41 | #Create a word filter 42 | filter_layout = QHBoxLayout() 43 | lbl = QLabel("Word filter:") 44 | self.filter = QLineEdit("") 45 | filter_layout.addWidget(lbl) 46 | filter_layout.addWidget(self.filter) 47 | filter_layout.setSpacing(10) 48 | 49 | #Create a check for looking for files in the subdirectories 50 | subdir_layout = QHBoxLayout() 51 | self.subdir = QCheckBox("", self) 52 | subdir_layout.addWidget(QLabel("Check in subdirectories:")) 53 | subdir_layout.addWidget(self.subdir) 54 | subdir_layout.setSpacing(10) 55 | 56 | #Create a check for fbx import 57 | fbx_layout = QHBoxLayout() 58 | self.fbx = QCheckBox("", self) 59 | fbx_layout.addWidget(QLabel("Import as FBX:")) 60 | fbx_layout.addWidget(self.fbx) 61 | fbx_layout.setSpacing(1) 62 | 63 | #Create a check for abc import 64 | abc_layout = QHBoxLayout() 65 | self.abc = QCheckBox("", self) 66 | abc_layout.addWidget(QLabel("Import as Alembic:")) 67 | abc_layout.addWidget(self.abc) 68 | abc_layout.setSpacing(1) 69 | 70 | #Set a button to start 71 | self.button = QPushButton('Create') 72 | 73 | #Add all the layout together 74 | main_layout.addLayout(filepath_layout, stretch=1) 75 | main_layout.addLayout(extension_layout, stretch=1) 76 | main_layout.addLayout(filter_layout, stretch=1) 77 | main_layout.addLayout(subdir_layout, stretch=1) 78 | main_layout.addLayout(fbx_layout, stretch=1) 79 | main_layout.addLayout(abc_layout, stretch=1) 80 | main_layout.addWidget(self.button) 81 | self.setLayout(main_layout) 82 | 83 | #Start the main code 84 | self.button.clicked.connect(self.main) 85 | 86 | def main(self): 87 | 88 | #Get a list of the nodes selected, we will be using just the first one 89 | geo_node=hou.selectedNodes() 90 | 91 | #Check if the user selected a Node, if not returns a message 92 | if not geo_node: 93 | hou.ui.displayMessage("Please select a node to import the files into", buttons=('OK',), severity=hou.severityType.Message, default_choice=0, close_choice=0, title="Select a node",details_expanded=False) 94 | 95 | else: 96 | self.importfiles() 97 | 98 | def getlist_names(self,list_paths): #Get a short name for each file 99 | 100 | name_list_all=[] 101 | 102 | for i in list_paths: 103 | i=i[i.rfind('\\')+1:] 104 | i=i[:-4] 105 | name_list_all.append(i) 106 | 107 | return name_list_all 108 | 109 | def getlist_paths(self): 110 | 111 | empty=0 112 | 113 | path=self.filepath.text() 114 | extension = self.extension.text() 115 | filter = self.filter.text() 116 | list_paths=[] 117 | file_list =[] 118 | 119 | if not path.endswith("\\"): 120 | path=path + "\\" 121 | 122 | if self.subdir.isChecked(): 123 | 124 | 125 | #Gets all the subdirectories files and creates a path for each 126 | for r, d, f in os.walk(path): 127 | 128 | for file in f: 129 | 130 | #Append files 131 | file_list.append(os.path.join(r, file)) 132 | 133 | else: 134 | 135 | for file in os.listdir(path): 136 | #Gets all the file paths in the input path 137 | file_list.append(path+file) 138 | 139 | 140 | for file in file_list: 141 | 142 | file_path = file 143 | 144 | 145 | if(file.endswith(extension) and file.find(filter)!=-1): 146 | 147 | list_paths.append(file_path) 148 | 149 | #Returns a list of all the files with a short name 150 | name_list_all=self.getlist_names(list_paths) 151 | 152 | #Creates a list with the index of the files selected by the user 153 | index_list=hou.ui.selectFromList(name_list_all, exclusive=False, title='Select files', column_header="Files", num_visible_rows=10, clear_on_cancel=False) 154 | 155 | if not index_list: 156 | empty=1 157 | 158 | chosen_files=[] 159 | name_list=[] 160 | 161 | #Creates a new name list with just the files selected from the user 162 | for j in index_list: 163 | 164 | name_list.append(name_list_all[j]) 165 | 166 | #Creates a new list with just the files selected from the user 167 | for x in index_list: 168 | 169 | chosen_files.append(list_paths[x]) 170 | 171 | return chosen_files,name_list,empty; 172 | 173 | 174 | 175 | def importfiles(self): 176 | 177 | #Get a list of the nodes selected, we will be using just the first one 178 | geo_node=hou.selectedNodes() 179 | 180 | tuple_list=self.getlist_paths() 181 | chosen_files=tuple_list[0] 182 | name_list = tuple_list[1] 183 | empty = tuple_list[2] 184 | 185 | #Iterate for every file path and creates a file node with the path loaded 186 | for file_path in chosen_files: 187 | if self.fbx.isChecked(): 188 | 189 | fbx_file = hou.hipFile.importFBX(file_path) 190 | #fbx_file.moveToGoodPosition() 191 | 192 | elif self.abc.isChecked(): 193 | 194 | file_node= geo_node[0].createNode("alembic") 195 | file_node.parm("fileName").set(file_path) 196 | file_node.moveToGoodPosition() 197 | 198 | else: 199 | 200 | file_node= geo_node[0].createNode("file") 201 | file_node.parm("file").set(file_path) 202 | file_node.moveToGoodPosition() 203 | 204 | 205 | #Starts the script window for the user 206 | app = QApplication.instance() 207 | if app is None: 208 | app = QApplication(sys.argv) 209 | geoUI = UI() 210 | geoUI.show() 211 | -------------------------------------------------------------------------------- /CheatSheet.md: -------------------------------------------------------------------------------- 1 | ## Vector types and 3 float 2 | 3 | There are different types of float3 vectors, the most commons types are: 4 | 5 | -Point: Stores position data, affected by transformations, scales, and rotations 6 | 7 | -Vector: Used for storing fixed values as V, affected by scales and rotations, not transformations 8 | 9 | -Normal: Only affected by rotations (and inverse-trasnpose scaling) 10 | 11 | -Color: Not affected by neither transformation or rotations 12 | 13 | -None: raw data, not affected by any transformation 14 | 15 | Depending on the software you want to export these too, Gaffer for example, attributes like P are stored as point, and vectors as rest are stored as vector since we dont want the values to update. 16 | 17 | Vectors are modified by transforms, 3 floats can be both transformed or not depending on the type we stated before. 18 | 19 | Theres more information [here](https://www.sidefx.com/docs/houdini/vex/functions/setattribtypeinfo.html) 20 | 21 | and the code to change the attrib type is setattribtypeinfo(geoself(), aclass, attrib, atype); 22 | 23 | as setattribtypeinfo(0, "point", "myVector", "point"); 24 | 25 | ## Using abcframe in alembics 26 | 27 | When using the intrinsic attribute, always set the new time parameter to be a float (1.0 instead of 1 even when declaring it as a float) or the attribute wont work. 28 | 29 | 30 | ## Substeping 31 | 32 | Inside dops there are 3 ways of using substeps. 33 | 34 | -Solver substeps: this will run all the microsolvers, including the source nodes as many timesteps as substeps we have. CFL shouldnt be modified from 1. 35 | 36 | -Dop substeps: they are a special case, this are for brute forcing the sim, and should only be used when there are animated parameters inside the dopnet and we need them to be interpolated between frames (I.E: keyframing the disturbance from 3 to 0.2 in very few frames). 37 | 38 | -Gas substeps node: this one allows to run substeps in a specific connection of our dopnet. You just need to be careful since if the source is mving fast and is connected to this node, other parameters like dissipation wont be calculated in timesteps so they would dissipate in a block shape. This could be fixed if insted of using keyframes we use an expression. 39 | 40 | ## Retiming 41 | 42 | When retiming volumes, use the cubic interpolation with the advected blend mode. You might get flickering on .0 frames, a hack to prevent this is to offseet the start frame by +.25 for example, using now only interpolated frames. 43 | 44 | ## Split and get last element from a string 45 | 46 | ``` 47 | i@__nameid = atoi(split(@name,"_")[-1]); 48 | ``` 49 | 50 | ## Random orient 51 | ``` 52 | vector axis=sample_direction_uniform(rand(@ptnum)); 53 | 54 | float angle=ch("angle"); 55 | 56 | p@orient=quaternion(angle,axis); 57 | ``` 58 | ## Intersection vector and grid 59 | ``` 60 | vector dir2=point(1,"dir2",0); 61 | 62 | @P+=v@dir1*(dot(dir2,point(1,"P",0)-@P)/dot(dir2,v@dir1)); 63 | ``` 64 | https://sites.google.com/site/fujitarium/Houdini/fx-procedural-processes/geometry-intersection 65 | 66 | ## Get transformation matrix 67 | 68 | ``` 69 | vector P0 = point(0,"P",0); 70 | vector P1 = point(0,"P",1); 71 | vector P2 = point(0,"P",2); 72 | 73 | v@xAxis=normalize(P2-P1); 74 | v@zAxis=normalize(P1-P0); 75 | v@yAxis=normalize(cross(@zAxis,@xAxis)); 76 | 77 | 4@mytransform=set(@xAxis,@yAxis,@zAxis,@P); 78 | 79 | setcomp(@mytransform,0,0,3); 80 | setcomp(@mytransform,0,1,3); 81 | setcomp(@mytransform,0,2,3); 82 | ``` 83 | 84 | ### 2 point based with Normal 85 | ``` 86 | vector P0 = point(0,"P",0); 87 | vector P1 = point(0,"P",1); 88 | vector N = point(0,"N",0); 89 | 90 | v@xAxis=normalize(P0-P1); 91 | v@zAxis=normalize(cross(@xAxis,N)); 92 | v@yAxis=normalize(N); 93 | 94 | 4@mytransform=set(@xAxis,@yAxis,@zAxis,@P); 95 | 96 | setcomp(@mytransform,0,0,3); 97 | setcomp(@mytransform,0,1,3); 98 | setcomp(@mytransform,0,2,3); 99 | ``` 100 | ## Apply it 101 | ``` 102 | matrix mytransform = point(1,"mytransform",0); 103 | v@P*=invert(mytransform); 104 | v@v*=matrix3(invert(mytransform)); 105 | ``` 106 | 107 | ~~ Using transform by attrb will also multiply custom attributes if wanted 108 | 109 | ## Check attribute exist 110 | 111 | i@hasAttrib = hasattrib(0, "point", "attribName"); 112 | 113 | ## Remove particles below geo 114 | ``` 115 | vector ray = {0,1,0}; 116 | 117 | vector p; 118 | float u,v; 119 | int intersect = intersect(1,@P,ray*1e6,p,u,v); 120 | 121 | if(intersect !=-1) i@dead = 1; 122 | ``` 123 | 124 | ## Use .chan camera data 125 | 126 | To use .chan files automatically create a chop network, load the .chan with a file node, use an export node with the export flag on, set the channels you want to load in as "chan[1-6]" for example, the camera node in the node parameter and the attributes in order they were written in the .chan file "t[xyz] r[xyz]" ,for example. 127 | 128 | ## Get min max 129 | 130 | Shorter method than attribute promote. 131 | 132 | IMPORTANT : run over detail 133 | 134 | This method is slower than attribute promote, just in case we cant use them. 135 | 136 | ``` 137 | int pts[]=expandpointgroup(0,"*"); 138 | float min,max; 139 | 140 | foreach(int pt;pts){ 141 | 142 | float ptmax=point(0,"__test",pt); 143 | float ptmin=point(0,"__test",pt); 144 | 145 | if(pt == 0){ 146 | min = ptmin; 147 | max = ptmax; 148 | } 149 | else{ 150 | if(ptmax > max) max = ptmax; 151 | else if(ptmin < min) min = ptmin; 152 | } 153 | } 154 | 155 | f@min=min; 156 | f@max=max; 157 | ``` 158 | 159 | Shorter version but really heavy, DONT USE 160 | 161 | ``` 162 | int pts[]=expandpointgroup(0,"*"); 163 | float val[]; 164 | 165 | foreach(int pt;pts){ 166 | 167 | append(val,float(point(0,"__test",pt))); 168 | } 169 | 170 | f@min=min(val); 171 | f@max=max(val); 172 | ``` 173 | ### OPENCL implementation 174 | 175 | For a way faster method check a opencl implementation https://github.com/JoseZalez/Houdini-scripts/blob/master/OPENCL.md 176 | 177 | # Intersection 2 lines 178 | 179 | ## PYTHON 180 | 181 | ``` 182 | A = geo.point(0).position() 183 | B = geo.point(1).position() 184 | C = geo.point(2).position() 185 | D = geo.point(3).position() 186 | 187 | A=(A[0],A[2]) 188 | B=(B[0],B[2]) 189 | C=(C[0],C[2]) 190 | D=(D[0],D[2]) 191 | 192 | def line_intersection(line1, line2): 193 | xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0]) 194 | ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1]) 195 | 196 | def det(a, b): 197 | return a[0] * b[1] - a[1] * b[0] 198 | 199 | div = det(xdiff, ydiff) 200 | if div == 0: 201 | raise Exception('lines do not intersect') 202 | 203 | d = (det(*line1), det(*line2)) 204 | x = det(d, xdiff) / div 205 | y = det(d, ydiff) / div 206 | 207 | inter=(x,y) 208 | 209 | geo.addAttrib(hou.attribType.Global, "inter", inter) 210 | 211 | 212 | print line_intersection((A, B), (C, D)) 213 | ``` 214 | 215 | ### Faster implementation with vex (line 1 first input, line 2 second input) 216 | 217 | ``` 218 | vector dir = point(1,"P",1)-point(1,"P",0); 219 | 220 | vector p,uv; 221 | 222 | intersect(0,point(1,"P",0),dir,p,uv); 223 | 224 | addpoint(0,p); 225 | ``` 226 | Vex function planepointdistance might also work for some cases (gets the intersection based on the plane normal not the line direction) 227 | 228 | 229 | ## Get index for random shared attribute 230 | 231 | When you have different instances with an id but there are number missing between them and you need to iterate in a wedge or something similar. 232 | 233 | Result is much faster just using a for loop with the iteration metadata. 234 | 235 | ``` 236 | node = hou.pwd() 237 | geo = node.geometry() 238 | 239 | scatter_list=[] 240 | 241 | geo.addAttrib(hou.attribType.Prim,"id", 0) 242 | 243 | id=0 244 | 245 | 246 | for prim in geo.prims(): 247 | scatter_val=prim.attribValue("scatter_id") 248 | if scatter_val not in scatter_list: 249 | scatter_list.append(scatter_val) 250 | 251 | for prim in geo.prims(): 252 | scatter_value=prim.attribValue("scatter_id") 253 | id=scatter_list.index(scatter_value) 254 | prim.setAttribValue("id",id) 255 | ``` 256 | Extra thing if you need to write the list on detail, if its not declared as an array the list will be written as individual lists. 257 | 258 | ``` 259 | attrib = geo.addArrayAttrib(hou.attribType.Global, "scatter_id", hou.attribData.Int , tuple_size=10) 260 | geo.setGlobalAttribValue(attrib, scatter_list) 261 | ``` 262 | Another vex solution using uniquevals, also slower than using a for loop. 263 | 264 | ``` 265 | int values [] = uniquevals(0, "prim", "index"); 266 | 267 | int prims[] = expandprimgroup(0, "*"); 268 | 269 | foreach(int prim;prims){ 270 | int prim_value=primattrib(0,"index",prim,0); 271 | int scatter_id=find(values,prim_value); 272 | setprimattrib(0,"scatter_id",prim,scatter_id); 273 | } 274 | ``` 275 | -------------------------------------------------------------------------------- /OPENCL.md: -------------------------------------------------------------------------------- 1 | 2 | # OPENCL 3 | 4 | Welcome to the world of OpenCL. 5 | 6 | OpenCL in houdini can take advantage of processing data much faster than vex or compiled c++ sops. 7 | 8 | The main takeaway is that you need to prepare the inputs and error feedback couldnt be worse sometimes. 9 | 10 | I will post here a few code examples I create from time to time. 11 | 12 | Before using any of these kernels, you will need to go to the bindings tab and add the different atributes you will be using. 13 | 14 | ## IMPORTANT NOTE 15 | 16 | If you are copying a kernel from other person, the binding attributes MUST be in the exact order as they are imported in the kernel definition. 17 | 18 | 19 | ### Example of importing a index attribute and using to to modify the y parameter 20 | 21 | In the OpenCL bindings add the parameter P as an attribute with size 3. 22 | 23 | ``` 24 | #include "interpolate.h" 25 | float lerpConstant( constant float * in, int size, float pos); 26 | 27 | kernel void kernelName( 28 | int P_length, 29 | global float * P 30 | ) 31 | { 32 | int idx = get_global_id(0); 33 | if (idx >= P_length) 34 | return; 35 | 36 | float3 pos = vload3(idx,P); 37 | pos.y = pos.y+(idx*.2); 38 | vstore3(pos,idx,P); 39 | 40 | } 41 | ``` 42 | 43 | ### Get min and max 44 | 45 | Following the vex scripts from my cheatsheet, this is the implementation on OpenCL, its way faster than any other method. 46 | 47 | For 10m points: 48 | 49 | - Vex: 5s 50 | - Attribute promote: 2.5s 51 | - OpenCL: 0.5s 52 | 53 | Wrangle before OpenCL node 54 | ``` 55 | f@val=rand(@ptnum)*1651; 56 | f@min; 57 | f@max; 58 | f@__scratchMin; 59 | f@__scratchMax; 60 | ``` 61 | 62 | In the OpenCL node, add temp_max and temp_min as float parameters and the previous attributes from the wrangle. 63 | 64 | Set val as only readable, min and max as writeable and __scratchMin and __scratchMax as both. 65 | 66 | ``` 67 | #include "interpolate.h" 68 | float lerpConstant( constant float * in, int size, float pos); 69 | 70 | kernel void getminmax( 71 | float temp_max , 72 | float temp_min , 73 | int val_length, 74 | global float * val , 75 | int max_length, 76 | global float * max , 77 | int min_length, 78 | global float * min , 79 | int __scratchMax_length, 80 | global float * __scratchMax , 81 | int __scratchMin_length, 82 | global float * __scratchMin 83 | ) 84 | { 85 | int idx = get_global_id(0); 86 | if (idx > 0) 87 | return; 88 | 89 | 90 | temp_min=vload(0,val); 91 | temp_max=vload(0,val); 92 | 93 | for(int pt=0; pttemp_max) 98 | temp_max=value; 99 | if(value= max_length) 126 | return; 127 | 128 | temp_min=vload(0,__scratchMin); 129 | temp_max=vload(0,__scratchMax); 130 | 131 | 132 | vstore(temp_min, idx, min); 133 | vstore(temp_max, idx ,max); 134 | 135 | } 136 | ``` 137 | 138 | ### Volume Noise 139 | 140 | Add the density volume atribute in bindings with everything but 141 | volume transform to voxel, and a freq float. 142 | 143 | Also activate the time and xnoise function imports on the second tab. 144 | 145 | If we compare this method to using a volume vop it runs at 5 times faster, but as always, customizing the noise is a bit annoying. This method shows how to run our kernel over all voxels and how to get the voxel position in our global scene. 146 | 147 | ``` 148 | #include "interpolate.h" 149 | #include 150 | 151 | float lerpConstant( constant float * in, int size, float pos); 152 | 153 | kernel void kernelName( 154 | float time, 155 | global const void *theXNoise, 156 | float freq, 157 | int density_stride_x, 158 | int density_stride_y, 159 | int density_stride_z, 160 | int density_stride_offset, 161 | int density_res_x, 162 | int density_res_y, 163 | int density_res_z, 164 | float density_voxelsize_x, 165 | float density_voxelsize_y, 166 | float density_voxelsize_z, 167 | float16 density_xformtoworld, 168 | global float * density 169 | ) 170 | { 171 | int gidx = get_global_id(0); 172 | int gidy = get_global_id(1); 173 | int gidz = get_global_id(2); 174 | int idx = density_stride_offset + density_stride_x * gidx 175 | + density_stride_y * gidy 176 | + density_stride_z * gidz; 177 | 178 | 179 | 180 | // Voxel position in in local space. 181 | 182 | float4 voxposglobal = gidx * density_xformtoworld.lo.lo + 183 | gidy * density_xformtoworld.lo.hi + 184 | gidz * density_xformtoworld.hi.lo + 185 | 1 * density_xformtoworld.hi.hi; 186 | 187 | // 4D position for noise 188 | float4 P = (float4)(voxposglobal)*freq; 189 | 190 | P.w = time; 191 | 192 | float3 noise = 0; 193 | 194 | noise = curlxnoise4(theXNoise,P); 195 | 196 | density[idx]=noise.x*density[idx]; 197 | 198 | } 199 | ``` 200 | 201 | ### Volume Displacement 202 | 203 | Similar to the volume noise but we add that noise to the idx values and import that density back. We need to import a copy of the density as a temp volume to store the new density values. 204 | 205 | 206 | ``` 207 | #include "interpolate.h" 208 | #include 209 | 210 | float lerpConstant( constant float * in, int size, float pos); 211 | 212 | kernel void kernelName( 213 | float time, 214 | global const void *theXNoise, 215 | float mult , 216 | int density_stride_x, 217 | int density_stride_y, 218 | int density_stride_z, 219 | int density_stride_offset, 220 | float16 density_xformtoworld, 221 | float16 density_xformtovoxel, 222 | global float * density , 223 | float freq , 224 | global float * temp 225 | ) 226 | { 227 | int gidx = get_global_id(0); 228 | int gidy = get_global_id(1); 229 | int gidz = get_global_id(2); 230 | int idx = density_stride_offset + density_stride_x * gidx 231 | + density_stride_y * gidy 232 | + density_stride_z * gidz; 233 | 234 | 235 | 236 | // Voxel position in in local space. 237 | 238 | float4 voxposglobal = gidx * density_xformtoworld.lo.lo + 239 | gidy * density_xformtoworld.lo.hi + 240 | gidz * density_xformtoworld.hi.lo + 241 | 1 * density_xformtoworld.hi.hi; 242 | 243 | // 4D position for noise 244 | float4 P = (float4)(voxposglobal)*freq; 245 | 246 | P.w = time; 247 | 248 | float3 noise = 0; 249 | 250 | noise = curlxnoise4(theXNoise,P); 251 | 252 | noise*=mult; 253 | 254 | float4 voxposdisp = (voxposglobal.x+noise.x) * density_xformtovoxel.lo.lo + 255 | (voxposglobal.y+noise.y) * density_xformtovoxel.lo.hi + 256 | (voxposglobal.z+noise.z) * density_xformtovoxel.hi.lo + 257 | 1 * density_xformtovoxel.hi.hi; 258 | 259 | int idxdisp = density_stride_offset + density_stride_x *voxposdisp.x 260 | + density_stride_y *voxposdisp.y 261 | + density_stride_z *voxposdisp.z; 262 | 263 | float dens = vload(idxdisp,density); 264 | 265 | temp[idx]=dens; 266 | 267 | } 268 | 269 | kernel void writeBack( 270 | float time, 271 | global const void *theXNoise, 272 | float mult , 273 | int density_stride_x, 274 | int density_stride_y, 275 | int density_stride_z, 276 | int density_stride_offset, 277 | float16 density_xformtoworld, 278 | float16 density_xformtovoxel, 279 | global float * density , 280 | float freq , 281 | global float * temp 282 | ) 283 | { 284 | int gidx = get_global_id(0); 285 | int gidy = get_global_id(1); 286 | int gidz = get_global_id(2); 287 | int idx = density_stride_offset + density_stride_x * gidx 288 | + density_stride_y * gidy 289 | + density_stride_z * gidz; 290 | 291 | 292 | density[idx]=temp[idx]; 293 | 294 | } 295 | ``` 296 | 297 | 298 | ### Julia Set 299 | 300 | Create a grid with high subdivs and a point to control the range. 301 | 302 | Wrangle before OpenCL node with the point connected to second input. 303 | ``` 304 | i@iter=0; 305 | 306 | v@ext=point(1,"P",0); 307 | 308 | v@Cd=1; 309 | ``` 310 | And the an OPENCL node with max_iter as an int, ext, Cd, max_iter, iter and P as attributes, with only iter and Cd as readable and writeable. 311 | 312 | ``` 313 | #include "interpolate.h" 314 | 315 | inline float3 saturate(float x, float y, float z){ 316 | float min = 0; 317 | float max = 1; 318 | float R= fmax(min,fmin(max,x)); 319 | float G= fmax(min,fmin(max,y)); 320 | float B= fmax(min,fmin(max,z)); 321 | return (float3)(R,G,B); 322 | } 323 | 324 | inline float3 HUEtoRGB(float H){ 325 | float R = fabs (H * 6 - 3) - 1; 326 | float G = 2 - fabs (H * 6 - 2); 327 | float B = 2 - fabs (H * 6 - 4); 328 | return saturate(R,G,B); 329 | } 330 | 331 | inline float3 HSVtoRGB(float3 HSV){ 332 | float3 RGB = HUEtoRGB(HSV.x); 333 | return ((RGB - 1) * HSV.y + 1) * HSV.z; 334 | } 335 | 336 | inline int julia_func( float a, 337 | float b, 338 | float iA, 339 | float iB, 340 | int imax ) 341 | 342 | { 343 | float count = 0; 344 | while (count2){ 356 | break; 357 | } 358 | 359 | 360 | } 361 | return count; 362 | } 363 | 364 | float lerpConstant( constant float * in, int size, float pos); 365 | 366 | kernel void julia( 367 | int ext_length, 368 | global float * ext , 369 | int color_length, 370 | global float * color , 371 | int max_iter, 372 | int iter_length, 373 | global int * iter , 374 | int P_length, 375 | global float * P 376 | ) 377 | { 378 | int idx = get_global_id(0); 379 | if (idx >= color_length) 380 | return; 381 | 382 | 383 | float3 pos = vload3(idx,P); 384 | 385 | float3 extP = vload3(idx,ext); 386 | 387 | float value = julia_func(pos.x, pos.z,extP.x,extP.z,max_iter); 388 | 389 | float3 color_value = (float3)(value/max_iter,1,1); 390 | 391 | float3 cd= HSVtoRGB(color_value); 392 | 393 | vstore(value, idx ,iter); 394 | 395 | vstore3(cd, idx ,color); 396 | } 397 | ``` 398 | 399 | --------------------------------------------------------------------------------