├── BUILDING.md ├── LICENSE ├── NOTICE ├── README.md ├── build.bat ├── build_linux.sh ├── build_osx.sh └── exportPRT.cpp /BUILDING.md: -------------------------------------------------------------------------------- 1 | Building HoudiniPRTExporter from source 2 | ======================================= 3 | 4 | ## Prerequisites 5 | 6 | 1. Ensure Houdini 16 is installed. 7 | 2. Ensure that the PRT I/O reference library headers are copied locally. 8 | https://github.com/ThinkboxSoftware/PRT-IO-Library 9 | 3. You might need to install OpenEXR libraries and headers. 10 | http://www.openexr.com/ 11 | 4. You might need to install Zlib libraries and headers. http://www.zlib.net/ 12 | 13 | 14 | ## Instructions 15 | 16 | 1. Create a local copy of this repository. 17 | 2. Modify `build.bat` (Windows), `build_linux.sh` (Linux) or `build_osx.sh` 18 | (macOS) to point to the prerequisite libraries. Normally, OpenEXR and zlib 19 | libraries are included in your Houdini Installation Folder. 20 | 3. Launch the "Houdini Command Line Tools". Alternatively, on Linux and on 21 | macOS, you can open a Command Line Terminal and call `source houdini_setup` 22 | from 23 | `/Library/Frameworks/Houdini.framework/Versions/{Your Houdini Version}/Resources` 24 | on macOS or from `{Your Houdini Installation Folder}` on Linux. 25 | 5. Run `build.bat`, `build_linux.sh` or `build_osx.sh` from the "Houdini Command 26 | Line Tools" or from the Command Line Terminal where Houdini's environment has 27 | been initialized. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The HoudiniPRTExporter includes the following third-party software/licensing: 2 | 3 | ** OpenEXR - http://www.openexr.com/ 4 | 5 | /////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright (c) 2002, Industrial Light & Magic, a division of Lucas 8 | // Digital Ltd. LLC 9 | // 10 | // All rights reserved. 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // * Redistributions of source code must retain the above copyright 16 | // notice, this list of conditions and the following disclaimer. 17 | // * Redistributions in binary form must reproduce the above 18 | // copyright notice, this list of conditions and the following disclaimer 19 | // in the documentation and/or other materials provided with the 20 | // distribution. 21 | // * Neither the name of Industrial Light & Magic nor the names of 22 | // its contributors may be used to endorse or promote products derived 23 | // from this software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | // 37 | /////////////////////////////////////////////////////////////////////////// 38 | 39 | ---------------- 40 | 41 | ** zlib - https://www.zlib.net/ 42 | 43 | /* zlib.h -- interface of the 'zlib' general purpose compression library 44 | version 1.2.7, May 2nd, 2012 45 | 46 | Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler 47 | 48 | This software is provided 'as-is', without any express or implied 49 | warranty. In no event will the authors be held liable for any damages 50 | arising from the use of this software. 51 | 52 | Permission is granted to anyone to use this software for any purpose, 53 | including commercial applications, and to alter it and redistribute it 54 | freely, subject to the following restrictions: 55 | 56 | 1. The origin of this software must not be misrepresented; you must not 57 | claim that you wrote the original software. If you use this software 58 | in a product, an acknowledgment in the product documentation would be 59 | appreciated but is not required. 60 | 2. Altered source versions must be plainly marked as such, and must not be 61 | misrepresented as being the original software. 62 | 3. This notice may not be removed or altered from any source distribution. 63 | 64 | Jean-loup Gailly Mark Adler 65 | jloup@gzip.org madler@alumni.caltech.edu 66 | 67 | 68 | The data format used by the zlib library is described by RFCs (Request for 69 | Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 70 | (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). 71 | */ 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HoudiniPRTExporter 2 | ================== 3 | 4 | Python module for Houdini that allows users to export Houdini particles to 5 | Thinkbox PRT format. 6 | 7 | This plugin supports Houdini 16. 8 | 9 | 10 | ## Download Binary 11 | 12 | See our [releases 13 | page](https://github.com/ThinkboxSoftware/HoudiniPRTExporter/releases). 14 | 15 | 16 | ## Building from Source 17 | 18 | See [BUILDING.md](BUILDING.md). 19 | 20 | 21 | ## Install instructions 22 | 23 | Ensure the plugin has either been built from source, or the pre-built binary has 24 | been downloaded. 25 | 26 | On Windows and Linux, copy `exportPRT.dll` or `exportPRT.so` to 27 | `{Houdini Install Directory}/houdini/dso/`. 28 | 29 | On macOS, copy `exportPRT.dylib` to 30 | `{Your Home Directory}/Library/Preferences/houdini/{Your Version Of Houdini}/dso/` 31 | or to `/Users/Shared/houdini/{Your Version of Houdini}/dso/`. 32 | 33 | 34 | ## Running instructions 35 | 36 | Once the plugin has been installed, Houdini will have a new Python module called 37 | `Krakatoa`. 38 | 39 | Launch Houdini, and create a new scene with a particle system. 40 | 41 | To perform an export, open Python inside Houdini and use the following commands 42 | (substitute your own node path and file path): 43 | 44 | ```python 45 | import Krakatoa 46 | 47 | Krakatoa.exportParticles(hou.node('/obj/fireworks_particles/import_fireworks'), 48 | 'my_path/output_particles.prt') 49 | ``` 50 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright 2017 Thinkbox Software Inc. 2 | @REM . 3 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 4 | @REM you may not use this file except in compliance with the License. 5 | @REM You may obtain a copy of the License at 6 | @REM . 7 | @REM http://www.apache.org/licenses/LICENSE-2.0 8 | @REM . 9 | @REM Unless required by applicable law or agreed to in writing, software 10 | @REM distributed under the License is distributed on an "AS IS" BASIS, 11 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @REM See the License for the specific language governing permissions and 13 | @REM limitations under the License. 14 | 15 | @REM This is a Windows build script to compile the PRT Exporter for Houdini 16. 16 | 17 | @REM This program must be launched from the "Houdini Command Line Tools". 18 | @REM You will need to link with PRT-IO-Library Reference Library. It can be downloaded from the Thinkbox Github. 19 | @REM You will need to link with OpenEXR and Zlib Libaries. Normally, those libraries are included in your Houdini install directory. 20 | @REM This build file assumes PRT-IO-Library is in the user's home directory and that %HFS% points to your Houdini install directory. Please modify this as needed. 21 | 22 | @echo( 23 | @echo WARNING: hcustom may have a bug where it will crash on exit, even upon 24 | @echo successful compilation and installation. 25 | @echo Check the log for compiler errors to see if compilation was unsuccessful. 26 | 27 | hcustom -I %userprofile%\PRT-IO-Library\ -I %HFS%\toolkit\include\OpenEXR\ -I %HFS%\toolkit\include\zlib -L %HFS%\custom\houdini\dsolib\ -l Half.lib exportPRT.cpp 28 | -------------------------------------------------------------------------------- /build_linux.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Thinkbox Software Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This is a Linux build script to compile the PRT Exporter for Houdini 16. 16 | 17 | # This program must be launched from the "Houdini Command Line Tools" or from a Terminal where Houdini's environment has been initialized. 18 | # You will need to link with PRT-IO-Library Reference Library. It can be downloaded from the Thinkbox Github. 19 | # You will need to link with OpenEXR and Zlib Libaries. Normally, those libraries are included in your Houdini install directory. 20 | # This build file assumes PRT-IO-Library is in the user's home directory and that $HFS points to your Houdini install directory. Please modify this as needed. 21 | 22 | hcustom -I $HOME/PRT-IO-Library -I $HFS/toolkit/include -I $HFS/toolkit/include/OpenEXR -L $HFS/dsolib -L /usr/lib -l Half -l z exportPRT.cpp 23 | -------------------------------------------------------------------------------- /build_osx.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Thinkbox Software Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This is a macOS build script to compile the PRT Exporter for Houdini 16. 16 | 17 | # This program must be launched from the "Houdini Command Line Tools" or from a Terminal where Houdini's environment has been initialized. 18 | # You will need to link with PRT-IO-Library Reference Library. It can be downloaded from the Thinkbox Github. 19 | # You will need to link with OpenEXR and Zlib Libaries. Normally, those libraries are included in your Houdini install directory. 20 | # This build file assumes PRT-IO-Library is in the user's home directory and that $HFS points to your Houdini install directory. Please modify this as needed. 21 | 22 | hcustom -I $HOME/PRT-IO-Library -I $HFS/toolkit/include -I $HFS/toolkit/include/OpenEXR -L $HFS/../Libraries -l Half -l z exportPRT.cpp 23 | -------------------------------------------------------------------------------- /exportPRT.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Thinkbox Software Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * This file contains a Houdini plugin for exporting Houdini particles in PRT format. 17 | * The plugin supports Houdini 16. 18 | */ 19 | 20 | #define OPENEXR_DLL 21 | #include 22 | 23 | #ifdef WIN32 24 | #ifndef NOMINMAX 25 | #define NOMINMAX 26 | #endif 27 | #include 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | 48 | using namespace prtio; 49 | 50 | 51 | template 52 | struct bound_attribute{ 53 | GA_ROAttributeRef attr; 54 | T* data; 55 | int count; 56 | 57 | bound_attribute() : data( NULL ), count( 0 ) 58 | {} 59 | 60 | ~bound_attribute(){ 61 | if( data ) 62 | delete data; 63 | } 64 | }; 65 | 66 | typedef std::pair channel_type; 67 | 68 | static void exportParticlesDetail( const GU_Detail* gdp, 69 | const std::string& filePath, 70 | const std::map& desiredChannels ) 72 | { 73 | prt_ofstream ostream; 74 | 75 | static std::map s_reservedChannels; 76 | if( s_reservedChannels.empty() ) { 77 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_NORMAL ) ] = "Normal"; 78 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_TEXTURE ) ] = "TextureCoord"; 79 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_VELOCITY ) ] = "Velocity"; 80 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_DIFFUSE ) ] = "Color"; 81 | //s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_ALPHA ) ] = "Density"; 82 | //s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_MASS ) ] = "Density"; 83 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_LIFE ) ] = ""; 84 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_ID ) ] = "ID"; 85 | s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_PSCALE ) ] = "Scale"; 86 | s_reservedChannels[ "accel" ] = "Acceleration"; 87 | } 88 | 89 | float posVal[3]; 90 | float lifeVal[2]; 91 | 92 | ostream.bind( "Position", posVal, 3 ); 93 | 94 | //We handle the life channel in a special manner 95 | GA_ROAttributeRef lifeAttrib = gdp->findPointAttribute( gdp->getStdAttributeName( GEO_ATTRIBUTE_LIFE ) ); 96 | if( lifeAttrib.isValid() ){ 97 | std::map::const_iterator it; 98 | 99 | it = desiredChannels.find( "Age" ); 100 | if( it != desiredChannels.end() && it->second.second == 1 ) 101 | ostream.bind( "Age", &lifeVal[0], 1, it->second.first ); 102 | else if( desiredChannels.empty() ) 103 | ostream.bind( "Age", &lifeVal[0], 1, prtio::data_types::type_float16 ); 104 | 105 | it = desiredChannels.find( "LifeSpan" ); 106 | if( it != desiredChannels.end() && it->second.second == 1 ) 107 | ostream.bind( "LifeSpan", &lifeVal[1], 1, it->second.first ); 108 | else if( desiredChannels.empty() ) 109 | ostream.bind( "LifeSpan", &lifeVal[1], 1, prtio::data_types::type_float16 ); 110 | } 111 | 112 | //Using a deque to prevent the memory from moving around after adding the bound_attribute to the container. 113 | std::deque< bound_attribute > m_intAttrs; 114 | std::deque< bound_attribute > m_floatAttrs; 115 | std::deque< bound_attribute > m_vectorAttrs; 116 | 117 | for ( GA_AttributeDict::iterator it = gdp->getAttributes().getDict(GA_ATTRIB_POINT).begin(GA_SCOPE_PUBLIC); !it.atEnd(); ++it) { 118 | 119 | GA_Attribute *node = it.attrib(); 120 | 121 | auto channelName = node->getName().toStdString(); 122 | 123 | // Translate special names 124 | auto itResChannel = s_reservedChannels.find( channelName ); 125 | if( itResChannel != s_reservedChannels.end() ){ 126 | //If its empty, that means we reserve some sort of special handling. 127 | if( itResChannel->second.empty() ) 128 | continue; 129 | channelName = itResChannel->second; 130 | } 131 | 132 | //Skip channels that aren't on the list. 133 | std::map::const_iterator itChannel = desiredChannels.find( channelName ); 134 | bool channelIsDesired = ( itChannel != desiredChannels.end() ); 135 | 136 | if( !desiredChannels.empty() && !channelIsDesired ) 137 | continue; 138 | 139 | //Only add valid channel names 140 | if( detail::is_valid_channel_name( channelName.c_str() ) ) { 141 | //I add the new item to the deque, THEN initialize it since a deque will not move the object around and this allows 142 | //me to allocate the float array and not have to worry about the object getting deleted too early. 143 | switch( node->getStorageClass() ) { 144 | case GA_STORECLASS_FLOAT: { 145 | if( node->getTupleSize() == 3 ) { 146 | m_vectorAttrs.push_back( bound_attribute() ); 147 | m_vectorAttrs.back().attr = gdp->findPointAttribute( node->getName() ); 148 | m_vectorAttrs.back().count = node->getTupleSize(); 149 | m_vectorAttrs.back().data = new float[m_vectorAttrs.back().count]; 150 | 151 | prtio::data_types::enum_t type = prtio::data_types::type_float16; 152 | if( channelIsDesired ) { 153 | type = itChannel->second.first; 154 | if( itChannel->second.second != m_vectorAttrs.back().count ) 155 | continue; 156 | } 157 | 158 | ostream.bind( channelName, m_vectorAttrs.back().data, m_vectorAttrs.back().count, type ); 159 | 160 | } else { 161 | m_floatAttrs.push_back( bound_attribute() ); 162 | m_floatAttrs.back().attr = gdp->findPointAttribute( node->getName() ); 163 | m_floatAttrs.back().count = node->getTupleSize(); 164 | m_floatAttrs.back().data = new float[m_floatAttrs.back().count]; 165 | 166 | prtio::data_types::enum_t type = prtio::data_types::type_float16; 167 | if( channelIsDesired ) { 168 | type = itChannel->second.first; 169 | if( itChannel->second.second != m_floatAttrs.back().count ) 170 | continue; 171 | } 172 | 173 | ostream.bind( channelName, m_floatAttrs.back().data, m_floatAttrs.back().count, type ); 174 | } 175 | break; 176 | } 177 | case GA_STORECLASS_INT: { 178 | m_intAttrs.push_back( bound_attribute() ); 179 | m_intAttrs.back().attr = gdp->findPointAttribute( node->getName() ); 180 | m_intAttrs.back().count = node->getTupleSize(); 181 | m_intAttrs.back().data = new int[m_intAttrs.back().count]; 182 | 183 | prtio::data_types::enum_t type = prtio::data_types::type_int32; 184 | if( channelIsDesired ) { 185 | type = itChannel->second.first; 186 | if( itChannel->second.second != m_intAttrs.back().count ) 187 | continue; 188 | } 189 | 190 | ostream.bind( channelName, m_intAttrs.back().data, m_intAttrs.back().count, type ); 191 | break; 192 | } 193 | default: 194 | break; 195 | } 196 | } 197 | } 198 | 199 | try{ 200 | ostream.open( filePath ); 201 | } catch( const std::ios::failure& e ) { 202 | std::cerr << e.what() << std::endl; 203 | throw HOM_OperationFailed( "Failed to open the file" ); 204 | } 205 | 206 | for( auto it = GA_Iterator( gdp->getPointRange() ); !it.atEnd(); ++it ) { 207 | GA_Offset offset = it.getOffset(); 208 | const UT_Vector3 p = gdp->getPos3( offset ); 209 | posVal[0] = p.x(); 210 | posVal[1] = p.y(); 211 | posVal[2] = -p.z(); 212 | 213 | // TODO: Convert this into appropriate time values. Is it seconds or frames or what?! 214 | if( lifeAttrib.isValid() ) { 215 | GA_ROHandleF handle( lifeAttrib ); 216 | if( handle.isInvalid() ) { 217 | auto msg = "Handle for " + lifeAttrib->getExportName() + " was invalid."; 218 | throw HOM_OperationFailed( msg.c_str() ); 219 | } 220 | handle.getV( offset, lifeVal, 2 ); 221 | } 222 | for( auto it = m_floatAttrs.begin(), itEnd = m_floatAttrs.end(); it != itEnd; ++it ) { 223 | GA_ROHandleF handle( it->attr ); 224 | if( handle.isInvalid() ) { 225 | auto msg = "Handle for " + it->attr->getExportName() + " was invalid."; 226 | throw HOM_OperationFailed( msg.c_str() ); 227 | } 228 | handle.getV( offset, it->data, it->count ); 229 | } 230 | for( auto it = m_vectorAttrs.begin(), itEnd = m_vectorAttrs.end(); it != itEnd; ++it ) { 231 | // TODO: Optionally transform into some consistent world space for PRT files. 232 | GA_ROHandleV3 handle( it->attr ); 233 | if( handle.isInvalid() ) { 234 | auto msg = "Handle for " + it->attr->getExportName() + " was invalid."; 235 | throw HOM_OperationFailed( msg.c_str() ); 236 | } 237 | auto v = handle( offset ); 238 | for( std::size_t i = 0; i < 3; ++i ) { 239 | it->data[i] = v[i]; 240 | } 241 | } 242 | for( auto it = m_intAttrs.begin(), itEnd = m_intAttrs.end(); it != itEnd; ++it ) { 243 | GA_ROHandleI handle( it->attr ); 244 | if( handle.isInvalid() ) { 245 | auto msg = "Handle for " + it->attr->getExportName() + " was invalid."; 246 | throw HOM_OperationFailed( msg.c_str() ); 247 | } 248 | handle.getV( offset, it->data, it->count ); 249 | } 250 | 251 | ostream.write_next_particle(); 252 | } 253 | 254 | ostream.close(); 255 | } 256 | 257 | static void exportParticles( const char *node_path, const char *filePath, const std::map& channels ) 258 | { 259 | OP_Node *op_node = OPgetDirector()->findNode( node_path ); 260 | if ( !op_node ) 261 | throw HOM_OperationFailed( "Internal error (could not find node)" ); 262 | 263 | float t = HOM().time(); 264 | 265 | SOP_Node* sopNode = CAST_SOPNODE( op_node ); 266 | if( !sopNode ) 267 | throw HOM_OperationFailed( "Internal error (not a valid node type)" ); 268 | 269 | // Get our parent. 270 | OP_Node *parent_node = sopNode->getParent(); 271 | 272 | // Store the cooking status of our parent node. 273 | bool was_cooking = false; 274 | if( parent_node ){ 275 | was_cooking = parent_node->isCookingRender(); 276 | parent_node->setCookingRender( true ); 277 | } 278 | 279 | // Create a context with the time we want the geometry at. 280 | OP_Context context( t ); 281 | // Get a handle to the geometry. 282 | GU_DetailHandle gd_handle = sopNode->getCookedGeoHandle( context ); 283 | 284 | // Restore the cooking flag, if we changed it. 285 | if( parent_node ) 286 | parent_node->setCookingRender( was_cooking ); 287 | 288 | // Check if we have a valid detail handle. 289 | if( gd_handle.isNull() ) 290 | throw HOM_OperationFailed( "Internal error (not a valid detail handle)" ); 291 | 292 | // Lock it for reading. 293 | GU_DetailHandleAutoReadLock gd_lock( gd_handle ); 294 | 295 | // Finally, get at the actual GU_Detail. 296 | const GU_Detail* gdp = gd_lock.getGdp(); 297 | 298 | exportParticlesDetail( gdp, filePath, channels ); 299 | } 300 | 301 | static PY_PyObject* createHouException( const char *exception_class_name, 302 | const char *instance_message, 303 | PY_PyObject *&exception_class ) 304 | { 305 | // Create an instance of the given exception class from the hou 306 | // module, passing the instance message into the exeption class's 307 | // __init__ method. This function returns a new exception instance, or 308 | // NULL if an error occurred. The class is returned in exception_class 309 | // and is a borrowed reference. 310 | exception_class = NULL; 311 | 312 | // Note that a PY_AutoObject class is just a wrapper around a 313 | // PY_PyObject pointer that will call PY_Py_XDECREF when the it's destroyed. 314 | // We use it for Python API functions that return new object instances. 315 | // Because this HDK extension is installed after the hou module is 316 | // imported, we can be sure that we can be sure hou_module won't be null. 317 | PY_AutoObject hou_module( PY_PyImport_ImportModule( "hou" ) ); 318 | 319 | // Look up the exception by name in the module's dictionary. Note that 320 | // PY_PyModule_GetDict returns a borrowed reference and that it never 321 | // returns NULL. PY_PyDict_GetItemString also returns a borrowed 322 | // reference. 323 | PY_PyObject *hou_module_dict = PY_PyModule_GetDict( hou_module ); 324 | exception_class = PY_PyDict_GetItemString( hou_module_dict, exception_class_name ); 325 | 326 | // PY_PyDict_GetItemString doesn't set a Python exception, so we are careful 327 | // to set it ourselves if the class name isn't valid. 328 | if ( !exception_class ) 329 | { 330 | PY_PyErr_SetString( PY_PyExc_RuntimeError(), "Could not find exception class in hou module" ); 331 | return NULL; 332 | } 333 | 334 | // Create an instance of the exception. First create a tuple containing 335 | // the arguments to __init__. 336 | PY_AutoObject args( PY_Py_BuildValue( "(s)", instance_message ) ); 337 | if ( !args ) 338 | return NULL; 339 | 340 | return PY_PyObject_Call( exception_class, args, /*kwargs=*/NULL ); 341 | } 342 | 343 | static PY_PyObject* exportParticles_Wrapper( PY_PyObject *self, PY_PyObject *args ) 344 | { 345 | // This is a wrapper that is called from the Python runtime engine. It 346 | // translates the Python arguments to C/C++ ones, calls a function to do 347 | // the actual work, and converts exceptions raised by that function intotest 348 | // Python exceptions. 349 | // 350 | // Note that you could also use swig to automatically generate wrapper 351 | // functions like this. 352 | // 353 | // Since this function is called from the Python runtime engine, we 354 | // don't need to manually acquire the Python global interpreter lock (GIL). 355 | 356 | // First extract the arguments: a string and a string. 357 | //const char *node_path; 358 | PY_PyObject* node; 359 | PY_PyObject* channelList = NULL; 360 | const char *file_path; 361 | 362 | if( !PY_PyArg_ParseTuple( args, "Os|O", &node, &file_path, &channelList ) ) 363 | return NULL; 364 | 365 | PY_AutoObject nodePath = PY_PyObject_CallMethod( node, "path", NULL ); 366 | if( !nodePath ) 367 | return NULL; 368 | 369 | std::map< std::string, channel_type > channels; 370 | 371 | if( channelList != NULL ){ 372 | try{ 373 | PY_Py_ssize_t listLen = PY_PySequence_Size( channelList ); 374 | 375 | for( PY_Py_ssize_t i = 0; i < listLen; ++i ){ 376 | PY_PyObject* p = PY_PySequence_GetItem( channelList, i ); 377 | 378 | char* curString = p ? PY_PyString_AsString( p ) : NULL; 379 | if( !curString ) 380 | return NULL; 381 | 382 | const char *nameStart = curString, *nameEnd = curString; 383 | while( *nameEnd != '\0' && std::isalnum( *nameEnd ) ) 384 | ++nameEnd; 385 | 386 | std::string name( nameStart, nameEnd ); 387 | 388 | channel_type type = prtio::data_types::parse_data_type( nameEnd ); 389 | 390 | channels[name] = type; 391 | } 392 | }catch( const std::exception& e ){ 393 | PY_PyErr_SetString( PY_PyExc_TypeError(), e.what() ); 394 | return NULL; 395 | } 396 | } 397 | 398 | // Now call ObjNode_setSelectable to do the actual work, taking care 399 | // of the locking and exception handling here. 400 | try 401 | { 402 | // If this code is called from a thread other than the main thread, 403 | // creating a HOM_AutoLock instance will lock, waiting until Houdini 404 | // is sitting idle in its event loop. This way, we can be sure that 405 | // any code that accesses Houdini's internal state is threadsafe. 406 | HOM_AutoLock hom_lock; 407 | 408 | // Call the wrapped function to do the actual work. 409 | exportParticles( PY_PyString_AsString( nodePath ), file_path, channels ); 410 | 411 | // Return PY_Py_None to indicate that no error occurred. If your 412 | // wrapped function returns a value, you'll need to convert it into 413 | // a Python object here. 414 | return PY_Py_None(); 415 | } 416 | catch ( HOM_Error &error ) 417 | { 418 | std::cerr << error.instanceMessage() << std::endl; 419 | 420 | // The exceptions used by the hou module are subclasses of HOM_Error (and can be found in HOM_Errors.h). 421 | // We use RTTI to get the class name and look up the corresponding exception class in the hou Python module. 422 | const std::string exception_class_name = error.exceptionTypeName(); 423 | 424 | // Note that a PY_AutoObject class is just a wrapper around a 425 | // PY_PyObject pointer that will call PY_Py_XDECREF when the it's 426 | // destroyed. 427 | PY_PyObject* exception_class; 428 | PY_AutoObject exception_instance( createHouException( exception_class_name.c_str(), error.instanceMessage().c_str(),exception_class ) ); 429 | if ( !exception_instance ) 430 | return NULL; 431 | 432 | // Set the exception and return NULL so Python knows an exception was 433 | // raised. 434 | PY_PyErr_SetObject( exception_class, exception_instance ); 435 | return NULL; 436 | } 437 | } 438 | 439 | void HOMextendLibrary() 440 | { 441 | // This function installs the C++ HOM extension. When the hou module is 442 | // first imported, Houdini will call functions named HOMextendLibrary in 443 | // HDK dso's. This function is declared in an extern "C" in HOM_Module.h. 444 | 445 | { 446 | // A PY_InterpreterAutoLock will grab the Python global interpreter 447 | // lock (GIL). It's important that we have the GIL before making 448 | // any calls into the Python API. 449 | PY_InterpreterAutoLock interpreter_auto_lock; 450 | 451 | // We'll create a new module named "_hom_extensions", and add functions 452 | // to it. We don't give a docstring here because it's given in the 453 | // Python implementation below. 454 | static PY_PyMethodDef krakatoaModule[] = { 455 | {"exportParticles", exportParticles_Wrapper, PY_METH_VARARGS(), ""}, 456 | { NULL, NULL, 0, NULL } 457 | }; 458 | 459 | PY_Py_InitModule( "Krakatoa", krakatoaModule ); 460 | } 461 | } 462 | --------------------------------------------------------------------------------