/// <summary> /// Loads a copy of the srcAsset at copyPath. Creates a copy if not found. /// </summary> /// <param name="srcAsset">The source asset object</param> /// <param name="copyPath">The full path to the copy</param> /// <param name="type">The type of source asset</param> /// <param name="bOverwriteExisting">Whether to overwrite existing copy if found</param> /// <returns>Returns loaded copy if exists or created, otherwise null</returns> public static Object CopyAndLoadAssetAtAnyPath(Object srcAsset, string copyPath, System.Type type, bool bOverwriteExisting) { #if UNITY_EDITOR string srcAssetPath = GetAssetPath(srcAsset); if (!string.IsNullOrEmpty(srcAssetPath)) { CreatePathWithFolders(copyPath); string fileName = HEU_Platform.GetFileName(srcAssetPath); string fullCopyPath = HEU_Platform.BuildPath(copyPath, fileName); if ((!bOverwriteExisting && HEU_Platform.DoesFileExist(fullCopyPath)) || CopyAsset(srcAssetPath, fullCopyPath)) { // Refresh database as otherwise we won't be able to load it in the next line. SaveAndRefreshDatabase(); return LoadAssetAtPath(fullCopyPath, type); } else { HEU_Logger.LogErrorFormat("Failed to copy and load asset from {0} to {1}!", srcAssetPath, fullCopyPath); } } return null; #else // TODO RUNTIME: AssetDatabase is not supported at runtime. Do we need to support this for runtime? HEU_Logger.LogWarning(HEU_Defines.HEU_USERMSG_NONEDITOR_NOT_SUPPORTED); return null; #endif }
/// <summary> /// Copy the file of the given srcAsset into the given targetPath, which must be absolute. /// If targetPath doesn't have a file name, the srcAsset's file name will be used. /// </summary> /// <param name="srcAsset">Source asset to copy</param> /// <param name="targetPath">Absolute path of destination</param> /// <param name="type">Type of the asset</param> /// <returns></returns> public static Object CopyAndLoadAssetAtGivenPath(Object srcAsset, string targetPath, System.Type type) { #if UNITY_EDITOR string srcAssetPath = GetAssetPath(srcAsset); if (!string.IsNullOrEmpty(srcAssetPath)) { string targetFolderPath = HEU_Platform.GetFolderPath(targetPath); CreatePathWithFolders(targetFolderPath); string targetFileName = HEU_Platform.GetFileName(targetPath); if (string.IsNullOrEmpty(targetFileName)) { HEU_Logger.LogErrorFormat("Copying asset failed. Destination path must end with a file name: {0}!", targetPath); return null; } if (CopyAsset(srcAssetPath, targetPath)) { // Refresh database as otherwise we won't be able to load it in the next line. SaveAndRefreshDatabase(); return LoadAssetAtPath(targetPath, type); } else { HEU_Logger.LogErrorFormat("Failed to copy and load asset from {0} to {1}!", srcAssetPath, targetPath); } } return null; #else // TODO RUNTIME: AssetDatabase is not supported at runtime. Do we need to support this for runtime? HEU_Logger.LogWarning(HEU_Defines.HEU_USERMSG_NONEDITOR_NOT_SUPPORTED); return null; #endif }
/// <summary> /// Populate TOP data from linked HDA /// </summary> private void PopulateFromHDA() { if (!_heu.IsAssetValid()) { _linkState = LinkState.ERROR_NOT_LINKED; return; } if (_heu != null) { _assetID = _heu.AssetID; _assetName = _heu.AssetName; } if (PopulateTOPNetworks()) { _linkState = LinkState.LINKED; } else { _linkState = LinkState.ERROR_NOT_LINKED; HEU_Logger.LogErrorFormat("Failed to populate TOP network info for asset {0}!", _assetName); } RepaintUI(); }
public static Material LoadSubstanceMaterialWithIndex(string materialPath, int substanceMaterialIndex) { Material material = LoadUnityMaterial(materialPath); #if UNITY_2017_4_OR_NEWER || UNITY_2018_1_OR_NEWER HEU_Logger.LogErrorFormat("Houdini Engine for Unity does not support the new Substance plugin as of yet!"); #elif UNITY_EDITOR if (material != null) { string assetPath = HEU_AssetDatabase.GetAssetPath(material); SubstanceImporter substanceImporter = AssetImporter.GetAtPath(assetPath) as SubstanceImporter; if(substanceMaterialIndex >= 0 && substanceMaterialIndex < substanceImporter.GetMaterialCount()) { material = substanceImporter.GetMaterials()[substanceMaterialIndex]; } } #endif if (material != null) { HEU_Logger.LogFormat("Loaded Substance material with index {0} from path {1}.", substanceMaterialIndex, materialPath); } else { HEU_Logger.LogWarningFormat("Failed to load Substance material with index {0} from path {1}.", substanceMaterialIndex, materialPath); } return material; }
public static bool LoadToolsFromDirectory(string folderPath, out List<HEU_ShelfToolData> tools) { tools = new List<HEU_ShelfToolData>(); string[] filePaths = HEU_Platform.GetFilesInFolder(folderPath, "*.json", true); bool bResult = false; try { if (filePaths != null) { foreach (string fileName in filePaths) { HEU_ShelfToolData tool = LoadToolFromJsonFile(fileName); if (tool != null) { tools.Add(tool); } } bResult = true; } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Parsing JSON files in directory caused exception: {0}", ex); return false; } return bResult; }
/// <summary> /// Cook the PDG graph of the specified TOP network /// </summary> /// <param name="topNetwork"></param> public void CookTOPNetworkOutputNode(HEU_TOPNetworkData topNetwork, System.Action <HEU_PDGCookedEventData> OnCook = null) { #if HOUDINIENGINEUNITY_ENABLED ClearEventMessages(); HEU_SessionBase session = GetHAPIPDGSession(); if (session == null || !session.IsSessionValid()) { return; } // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook // state (bug?) if (_pdgContextIDs != null) { foreach (HAPI_PDG_GraphContextId contextID in _pdgContextIDs) { session.CancelPDGCook(contextID); } } if (!session.CookPDG(topNetwork._nodeID, 0, 0)) { HEU_Logger.LogErrorFormat("Cook node failed!"); } _cookedDataEvent = OnCook; ResetCallbackVariables(); #endif }
private GameObject GetGameObjectFromType(Object obj, System.Type type) { if (type == typeof(GameObject)) { return(obj as GameObject); } else if (type == typeof(HEU_HoudiniAssetRoot)) { HEU_HoudiniAssetRoot heuRoot = obj as HEU_HoudiniAssetRoot; return(heuRoot.gameObject); } else if (type == typeof(Terrain)) { Terrain terrain = obj as Terrain; return(terrain.gameObject); } else if (type == typeof(HEU_BoundingVolume)) { HEU_BoundingVolume volume = obj as HEU_BoundingVolume; return(volume.gameObject); } else if (type == typeof(Tilemap)) { Tilemap tilemap = obj as Tilemap; return(tilemap.gameObject); } else { HEU_Logger.LogErrorFormat("Unsupported type {0} for Selection Window.", type); return(null); } }
public static Material LoadSubstanceMaterialWithName(string materialPath, string substanceName) { Material material = LoadUnityMaterial(materialPath); #if UNITY_2017_4_OR_NEWER || UNITY_2018_1_OR_NEWER HEU_Logger.LogErrorFormat("Houdini Engine for Unity does not support the new Substance plugin as of yet!"); #elif UNITY_EDITOR if (material != null) { string assetPath = HEU_AssetDatabase.GetAssetPath(material); SubstanceImporter substanceImporter = AssetImporter.GetAtPath(assetPath) as SubstanceImporter; ProceduralMaterial[] proceduralMaterials = substanceImporter.GetMaterials(); for(int i = 0; i < proceduralMaterials.Length; ++i) { if(proceduralMaterials[i].name.Equals(substanceName)) { material = proceduralMaterials[i]; break; } } } #endif if (material != null) { HEU_Logger.LogFormat("Loaded Substance material with name {0} from path {1}.", substanceName, materialPath); } else { HEU_Logger.LogWarningFormat("Failed to load Substance material with name {0} from path {1}.", substanceName, materialPath); } return material; }
public static void CreateNodeSync(HEU_SessionBase session, string opName, string nodeNabel) { if (session == null) { session = HEU_SessionManager.GetDefaultSession(); } if (session == null || !session.IsSessionValid()) { return; } HAPI_NodeId newNodeID = -1; HAPI_NodeId parentNodeId = -1; if (!session.CreateNode(parentNodeId, opName, nodeNabel, true, out newNodeID)) { HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets."); return; } if (parentNodeId == -1) { // When creating a node without a parent, for SOP nodes, a container // geometry object will have been created by HAPI. // In all cases we want to use the node ID of that object container // so the below code sets the parent's node ID. // But for SOP/subnet we actually do want the subnet SOP node ID // hence the useSOPNodeID argument here is to override it. bool useSOPNodeID = opName.Equals("SOP/subnet"); HAPI_NodeInfo nodeInfo = new HAPI_NodeInfo(); if (!session.GetNodeInfo(newNodeID, ref nodeInfo)) { return; } if (nodeInfo.type == HAPI_NodeType.HAPI_NODETYPE_SOP) { if (!useSOPNodeID) { newNodeID = nodeInfo.parentId; } } else if (nodeInfo.type != HAPI_NodeType.HAPI_NODETYPE_OBJ) { HEU_Logger.LogErrorFormat("Unsupported node type {0}", nodeInfo.type); return; } } GameObject newGO = HEU_GeneralUtility.CreateNewGameObject(nodeNabel); HEU_NodeSync nodeSync = newGO.AddComponent<HEU_NodeSync>(); nodeSync.InitializeFromHoudini(session, newNodeID, nodeNabel, ""); }
public virtual bool DoAssetLoad() { string assetPath = _filePath; if (!HEU_Platform.DoesFileExist(assetPath)) { assetPath = HEU_AssetDatabase.GetValidAssetPath(assetPath); } HAPI_NodeId libraryID = -1; HAPI_NodeId newNodeID = -1; byte[] buffer = null; bool bResult = HEU_Platform.LoadFileIntoMemory(assetPath, out buffer); if (bResult) { if (!_session.LoadAssetLibraryFromMemory(buffer, true, out libraryID)) { HEU_Logger.LogErrorFormat("Unable to load asset library."); return false; } //HEU_Logger.Log("Loaded asset"); int assetCount = 0; bResult = _session.GetAvailableAssetCount(libraryID, out assetCount); if (!bResult) { return false; } int[] assetNameLengths = new int[assetCount]; bResult = _session.GetAvailableAssets(libraryID, ref assetNameLengths, assetCount); if (!bResult) { return false; } string[] assetNames = new string[assetCount]; for (int i = 0; i < assetCount; ++i) { assetNames[i] = HEU_SessionManager.GetString(assetNameLengths[i], _session); } // Create top level node. Note that CreateNode will cook the node if HAPI was initialized with threaded cook setting on. string topNodeName = assetNames[0]; bResult = _session.CreateNode(-1, topNodeName, "", false, out newNodeID); if (!bResult) { return false; } //HEU_Logger.Log("Created asset node"); _loadData._cookNodeID = newNodeID; } return true; }
private void UpdateAttribute(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, HEU_AttributeData attributeData) { int attrCount = attributeData._attributeInfo.count; // Presuming we are working with point attributes HAPI_AttributeInfo newAttrInfo = new HAPI_AttributeInfo(); newAttrInfo.exists = true; newAttrInfo.owner = attributeData._attributeInfo.owner; newAttrInfo.storage = attributeData._attributeInfo.storage; newAttrInfo.count = attributeData._attributeInfo.count; newAttrInfo.tupleSize = attributeData._attributeInfo.tupleSize; newAttrInfo.originalOwner = attributeData._attributeInfo.originalOwner; if (!session.AddAttribute(geoID, partID, attributeData._name, ref newAttrInfo)) { HEU_Logger.LogErrorFormat("Failed to add attribute: {0}", attributeData._name); return; } if (newAttrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_INT) { int[] pointData = new int[attrCount * newAttrInfo.tupleSize]; for (int j = 0; j < attrCount; ++j) { for (int tuple = 0; tuple < newAttrInfo.tupleSize; ++tuple) { pointData[j * newAttrInfo.tupleSize + tuple] = attributeData._intValues[j * newAttrInfo.tupleSize + tuple]; } } HEU_GeneralUtility.SetAttributeArray(geoID, partID, attributeData._name, ref newAttrInfo, pointData, session.SetAttributeIntData, attrCount); } else if (newAttrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { float[] pointData = new float[attrCount * newAttrInfo.tupleSize]; for (int j = 0; j < attrCount; ++j) { for (int tuple = 0; tuple < newAttrInfo.tupleSize; ++tuple) { pointData[j * newAttrInfo.tupleSize + tuple] = attributeData._floatValues[j * newAttrInfo.tupleSize + tuple]; } } HEU_GeneralUtility.SetAttributeArray(geoID, partID, attributeData._name, ref newAttrInfo, pointData, session.SetAttributeFloatData, attrCount); } else if (newAttrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_STRING) { string[] pointData = new string[attrCount * newAttrInfo.tupleSize]; for (int j = 0; j < attrCount; ++j) { for (int tuple = 0; tuple < newAttrInfo.tupleSize; ++tuple) { pointData[j * newAttrInfo.tupleSize + tuple] = attributeData._stringValues[j * newAttrInfo.tupleSize + tuple]; } } HEU_GeneralUtility.SetAttributeArray(geoID, partID, attributeData._name, ref newAttrInfo, pointData, session.SetAttributeStringData, attrCount); } }
internal static bool CreateInputNodeWithMultiAssets(HEU_SessionBase session, HEU_HoudiniAsset parentAsset, ref HAPI_NodeId connectMergeID, ref List<HEU_InputHDAInfo> inputAssetInfos, bool bKeepWorldTransform, HAPI_NodeId mergeParentID = -1) { // Create the merge SOP node that the input nodes are going to connect to. if (!session.CreateNode(mergeParentID, "SOP/merge", null, true, out connectMergeID)) { HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets."); return false; } int numInputs = inputAssetInfos.Count; for (int i = 0; i < numInputs; ++i) { inputAssetInfos[i]._connectedInputNodeID = HEU_Defines.HEU_INVALID_NODE_ID; if (inputAssetInfos[i]._pendingGO == null) { continue; } // ID of the asset that will be connected HAPI_NodeId inputAssetID = HEU_Defines.HEU_INVALID_NODE_ID; HEU_HoudiniAssetRoot inputAssetRoot = inputAssetInfos[i]._pendingGO.GetComponent<HEU_HoudiniAssetRoot>(); if (inputAssetRoot != null && inputAssetRoot._houdiniAsset != null) { if (!inputAssetRoot._houdiniAsset.IsAssetValidInHoudini(session)) { // Force a recook if its not valid (in case it hasn't been loaded into the session) inputAssetRoot._houdiniAsset.RequestCook(true, false, true, true); } inputAssetID = inputAssetRoot._houdiniAsset.AssetID; } if (inputAssetID == HEU_Defines.HEU_INVALID_NODE_ID) { continue; } if (!session.ConnectNodeInput(connectMergeID, i, inputAssetID)) { HEU_Logger.LogErrorFormat("Unable to connect input nodes!"); return false; } inputAssetInfos[i]._connectedInputNodeID = inputAssetID; inputAssetInfos[i]._connectedGO = inputAssetInfos[i]._pendingGO; inputAssetInfos[i]._connectedMergeNodeID = connectMergeID; parentAsset.ConnectToUpstream(inputAssetRoot._houdiniAsset); } return true; }
public static HEU_MaterialData CreateUnitySubstanceMaterialData(int materialKey, string materialPath, string substanceName, int substanceIndex, List<HEU_MaterialData> materialCache, string assetCacheFolderPath) { // Let's make sure we can find the Unity or Substance material first Material material = null; HEU_MaterialData.Source sourceType = HEU_MaterialData.Source.UNITY; if (!string.IsNullOrEmpty(substanceName)) { sourceType = HEU_MaterialData.Source.SUBSTANCE; material = HEU_MaterialFactory.LoadSubstanceMaterialWithName(materialPath, substanceName); } else if (substanceIndex >= 0) { sourceType = HEU_MaterialData.Source.SUBSTANCE; material = HEU_MaterialFactory.LoadSubstanceMaterialWithIndex(materialPath, substanceIndex); } else if (!string.IsNullOrEmpty(materialPath)) { material = HEU_MaterialFactory.LoadUnityMaterial(materialPath); } if (material != null) { HEU_MaterialData materialData = ScriptableObject.CreateInstance<HEU_MaterialData>(); materialData._materialSource = sourceType; materialData._materialKey = materialKey; materialData._material = material; materialCache.Add(materialData); return materialData; } else { // We can't find the material in Unity, so notify user and use a default one which allows to at least get the geometry in. if (string.IsNullOrEmpty(materialPath)) { HEU_Logger.LogWarningFormat("Empty material name found. Using default material."); } else if (materialPath.Contains("Resources/unity_builtin_extra")) { // Built in material. Don't display error. } else { HEU_Logger.LogErrorFormat("Unable to find {0} material {1}. Using a default material instead. Please check material exists in project and reload asset!", sourceType, materialPath); } // The materialKey helps uniquely identify this material for further look ups. But we also need to get a valid file name // to create the default material, so strip out just the file name. string strippedFileName = HEU_Platform.GetFileName(materialPath); return CreateMaterialInCache(materialKey, strippedFileName, HEU_MaterialData.Source.UNITY, false, materialCache, assetCacheFolderPath); } }
/// <summary> /// Returns all files (with their paths) in a given folder, with or without pattern, either recursively or just the first. /// </summary> /// <param name="folderPath">Path to folder</param> /// <param name="searchPattern">File name pattern to search for</param> /// <param name="bRecursive">Search all directories or just the top</param> /// <returns>Array of file paths found or null if error</returns> public static string[] GetFilesInFolder(string folderPath, string searchPattern, bool bRecursive) { try { return(Directory.GetFiles(folderPath, searchPattern, bRecursive ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories)); } catch (Exception ex) { HEU_Logger.LogErrorFormat("Getting files in directory {0} threw exception: {1}", folderPath, ex); return(null); } }
public static bool WriteAllText(string path, string text) { try { File.WriteAllText(path, text); return(true); } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Unable to save session to file: {0}. Exception: {1}", text, ex.ToString()); } return(false); }
/// <summary> /// Helper to set heightfield data for a specific volume node. /// Used for a specific terrain layer. /// </summary> /// <param name="session">Session that the volume node resides in.</param> /// <param name="volumeNodeID">ID of the target volume node</param> /// <param name="partID">Part ID</param> /// <param name="heightValues">Array of height or alpha values</param> /// <param name="heightFieldName">Name of the layer</param> /// <returns>True if successfully uploaded heightfield values</returns> public bool SetHeightFieldData(HEU_SessionBase session, HAPI_NodeId volumeNodeID, HAPI_PartId partID, float[] heightValues, string heightFieldName, ref HAPI_VolumeInfo baseVolumeInfo) { // Cook the node to get infos below if (!session.CookNode(volumeNodeID, false)) { return(false); } // Get Geo, Part, and Volume infos HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if (!session.GetGeoInfo(volumeNodeID, ref geoInfo)) { return(false); } HAPI_PartInfo partInfo = new HAPI_PartInfo(); if (!session.GetPartInfo(geoInfo.nodeId, partID, ref partInfo)) { return(false); } HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); if (!session.GetVolumeInfo(volumeNodeID, partInfo.id, ref volumeInfo)) { return(false); } volumeInfo.tileSize = 1; // Use same transform as base layer volumeInfo.transform = baseVolumeInfo.transform; if (!session.SetVolumeInfo(volumeNodeID, partInfo.id, ref volumeInfo)) { HEU_Logger.LogError("Unable to set volume info on input heightfield node!"); return(false); } // Now set the height data if (!session.SetHeightFieldData(geoInfo.nodeId, partInfo.id, heightFieldName, heightValues, 0, heightValues.Length)) { HEU_Logger.LogErrorFormat("Unable to set `{0}` height values on input heightfield node!\n" + "Check your terrain sizes including Control Texture Resolution is less than the Heightmap Resolution.", heightFieldName); return(false); } return(true); }
public static string ReadAllText(string path) { try { if (File.Exists(path)) { return(File.ReadAllText(path)); } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Unable to load from file: {0}. Exception: {1}", path, ex.ToString()); } return(""); }
/// <summary> /// Calculate the index format based on number of vertices. /// </summary> public void CalculateIndexFormat(int numVertices) { uint maxVertexCount = ushort.MaxValue; uint vertexCount = Convert.ToUInt32(numVertices); if (vertexCount > maxVertexCount) { #if UNITY_2017_3_OR_NEWER // For vertex count larger than 16-bit, use 32-bit buffer _indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #else HEU_Logger.LogErrorFormat("Vertex count {0} which is above Unity maximum of {1}.\nUse Unity 2017.3+ or reduce this in Houdini.", vertexCount, maxVertexCount); #endif } }
private static Texture2D ExtractHoudiniImageToTextureRaw(HEU_SessionBase session, HAPI_MaterialInfo materialInfo, string imagePlanes) { Texture2D textureResult = null; HAPI_ImageInfo imageInfo = new HAPI_ImageInfo(); if (!session.GetImageInfo(materialInfo.nodeId, ref imageInfo)) { return textureResult; } imageInfo.dataFormat = HAPI_ImageDataFormat.HAPI_IMAGE_DATA_INT8; imageInfo.interleaved = true; imageInfo.packing = HAPI_ImagePacking.HAPI_IMAGE_PACKING_RGBA; imageInfo.gamma = HEU_PluginSettings.ImageGamma; session.SetImageInfo(materialInfo.nodeId, ref imageInfo); // Extract image to buffer byte[] imageData = null; if (!session.ExtractImageToMemory(materialInfo.nodeId, HEU_HAPIConstants.HAPI_RAW_FORMAT_NAME, imagePlanes, out imageData)) { return textureResult; } int colorDataSize = imageInfo.xRes * imageInfo.yRes; if (colorDataSize * 4 != imageData.Length) { HEU_Logger.LogErrorFormat("Extracted image size does not match expected image info size." + " Try using non-raw format for texture extraction."); return textureResult; } Color32[] colorData = new Color32[colorDataSize]; for (int i = 0; i < colorDataSize; ++i) { colorData[i].r = imageData[i * 4 + 0]; colorData[i].g = imageData[i * 4 + 1]; colorData[i].b = imageData[i * 4 + 2]; colorData[i].a = imageData[i * 4 + 3]; } textureResult = new Texture2D(imageInfo.xRes, imageInfo.yRes, TextureFormat.ARGB32, false); textureResult.SetPixels32(colorData); textureResult.Apply(); return textureResult; }
/// <summary> /// Removes the session data file. /// </summary> public static void DeleteAllSavedSessionData() { #if UNITY_EDITOR string path = SessionFilePath(); try { if (File.Exists(path)) { File.Delete(SessionFilePath()); } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Unable to deletion session file: {0}. Exception: {1}", path, ex.ToString()); } #endif }
public static HEU_ShelfToolData LoadToolFromJsonFile(string jsonFilePath) { string json = null; try { StreamReader fileReader = new StreamReader(jsonFilePath); json = fileReader.ReadToEnd(); fileReader.Close(); } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Exception while reading {0}: {1}", jsonFilePath, ex); return null; } HEU_ShelfToolData tool = LoadToolFromJsonString(json, jsonFilePath); return tool; }
/// <summary> /// Create a unique asset cache folder for the given asset path. /// The given asset path should be the HDA's path in the project. /// </summary> /// <param name="suggestedAssetPath">A suggested path to try. Will use default if empty or null./param> /// <returns>Unique asset cache folder for given asset path</returns> public static string CreateAssetCacheFolder(string suggestedAssetPath, int hash = 0) { #if UNITY_EDITOR // We create a unique folder inside our plugin's asset database cache folder. string assetDBPath = GetAssetCachePath(); string assetWorkingPath = HEU_Platform.BuildPath(assetDBPath, HEU_Defines.HEU_WORKING_PATH); if (!AssetDatabase.IsValidFolder(assetWorkingPath)) { AssetDatabase.CreateFolder(assetDBPath, HEU_Defines.HEU_WORKING_PATH); } string fileName = HEU_Platform.GetFileNameWithoutExtension(suggestedAssetPath); if (string.IsNullOrEmpty(fileName)) { fileName = "AssetCache"; HEU_Logger.LogWarningFormat("Unable to get file name from {0}. Using default value: {1}.", suggestedAssetPath, fileName); } if (HEU_PluginSettings.ShortenFolderPaths && fileName.Length >= 3 && hash != 0) { fileName = fileName.Substring(0, 3) + hash; } string fullPath = HEU_Platform.BuildPath(assetWorkingPath, fileName); // Gives us the unique folder path, which we then separate out to create this folder fullPath = AssetDatabase.GenerateUniqueAssetPath(fullPath); CreatePathWithFolders(fullPath); if (!AssetDatabase.IsValidFolder(fullPath)) { HEU_Logger.LogErrorFormat("Unable to create a valid asset cache folder: {0}! Check directory permission or that enough space is available!", fullPath); fullPath = null; } return fullPath; #else // TODO RUNTIME: AssetDatabase is not supported at runtime. Do we need to support this for runtime? HEU_Logger.LogWarning(HEU_Defines.HEU_USERMSG_NONEDITOR_NOT_SUPPORTED); return null; #endif }
/// <summary> /// Load the preset file at the specified path into the specified asset and cook it. /// </summary> /// <param name="asset">Asset to load preset into</param> /// <param name="filePath">Full path to file containing preset. File must have been written out by SaveAssetPresetToFile.</param> public static void LoadPresetFileIntoAssetAndCook(HEU_HoudiniAsset asset, string filePath) { try { HEU_AssetPreset assetPreset = null; using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { BinaryFormatter formatter = new BinaryFormatter(); HEU_Vector3SerializationSurrogate vector3S = new HEU_Vector3SerializationSurrogate(); HEU_Vector2SerializationSurrogate vector2S = new HEU_Vector2SerializationSurrogate(); SurrogateSelector surrogateSelector = new SurrogateSelector(); surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), vector3S); surrogateSelector.AddSurrogate(typeof(Vector2), new StreamingContext(StreamingContextStates.All), vector2S); formatter.SurrogateSelector = surrogateSelector; assetPreset = (HEU_AssetPreset)formatter.Deserialize(fs); } if (assetPreset != null) { if (PRESET_IDENTIFIER.SequenceEqual(assetPreset._identifier)) { asset.LoadAssetPresetAndCook(assetPreset); } else { HEU_Logger.LogErrorFormat("Unable to load preset. Specified file is not a saved HDA preset: {0}", filePath); } } else { HEU_Logger.LogErrorFormat("Failed to load preset file {0}.", filePath); } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Failed to load preset due to exception: " + ex.ToString()); } }
public static bool LoadFileIntoMemory(string path, out byte[] buffer) { buffer = null; try { if (File.Exists(path)) { buffer = File.ReadAllBytes(path); } else { HEU_Logger.LogErrorFormat("Failed to open (0}. File doesn't exist!", path); } } catch (Exception ex) { HEU_Logger.LogErrorFormat("Failed to open (0}. Exception: {1}", path, ex.ToString()); } return(buffer != null); }
/// <summary> /// Save plugin data to disk. /// </summary> public bool SavePluginData() { try { string settingsFilePath = SettingsFilePath(); using (StreamWriter writer = new StreamWriter(settingsFilePath, false)) { writer.WriteLine("Houdini Engine for Unity Plugin Settings"); writer.WriteLine("Version=" + PluginSettingsVersion); foreach (KeyValuePair<string, StoreData> kvpair in _dataMap) { // key(type)=value writer.WriteLine("{0}({1})={2}", kvpair.Key, kvpair.Value._type, kvpair.Value._valueStr); } } #if UNITY_EDITOR // Remove old keys from EditorPrefs as its the deprecated method of saving the plugin settings if (EditorPrefs.HasKey(HEU_Defines.PLUGIN_STORE_KEYS)) { EditorPrefs.DeleteKey(HEU_Defines.PLUGIN_STORE_KEYS); } if (EditorPrefs.HasKey(HEU_Defines.PLUGIN_STORE_DATA)) { EditorPrefs.DeleteKey(HEU_Defines.PLUGIN_STORE_DATA); } #endif _requiresSave = false; } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Exception when trying to save settings file: {0}", ex.ToString()); return false; } return true; }
/// <summary> /// Save the specified asset's preset data to file at specified path. /// </summary> /// <param name="asset">The asset's preset data will be saved</param> /// <param name="filePath">The file to save to</param> public static void SaveAssetPresetToFile(HEU_HoudiniAsset asset, string filePath) { // This should return an object filled with preset data, and which we can serialize directly HEU_AssetPreset assetPreset = asset.GetAssetPreset(); if (assetPreset != null) { try { int len = PRESET_IDENTIFIER.Length; assetPreset._identifier = PRESET_IDENTIFIER; assetPreset._version = PRESET_VERSION; using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { IFormatter formatter = new BinaryFormatter(); HEU_Vector3SerializationSurrogate vector3S = new HEU_Vector3SerializationSurrogate(); HEU_Vector2SerializationSurrogate vector2S = new HEU_Vector2SerializationSurrogate(); SurrogateSelector surrogateSelector = new SurrogateSelector(); surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), vector3S); surrogateSelector.AddSurrogate(typeof(Vector2), new StreamingContext(StreamingContextStates.All), vector2S); formatter.SurrogateSelector = surrogateSelector; formatter.Serialize(fs, assetPreset); } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Failed to save preset due to exception: " + ex.ToString()); } } else { HEU_Logger.LogErrorFormat("Failed to save preset due to unable to retrieve the preset buffer!"); } }
/// <summary> /// Generates a cube mesh using quad faces from given points, with vertex colours on selected and non-selected points. /// </summary> /// <param name="points">A cube will be created for each point in this list</param> /// <param name="selectedPtsFlag">Indices of selected points</param> /// <param name="defaultColor">Non-selected vertex color of cubes</param> /// <param name="selectedColor">Selected vertex color of cubes</param> /// <param name="size">Length of one side of cube</param> /// <returns>The generated mesh</returns> public static Mesh GenerateCubeMeshFromPoints(Vector3[] points, Color[] pointsColor, float size = 1f) { float halfSize = size * 0.5f; int totalPoints = points.Length; // Each cube face will get unique vertices due to splitting the normals int totalVertices = 24 * totalPoints; Vector3[] vertices = new Vector3[totalVertices]; Color[] colors = new Color[totalVertices]; Vector3[] normals = new Vector3[totalVertices]; int[] indices = new int[totalVertices]; int ptIndex = 0; int vertsPerPt = 24; foreach (Vector3 pt in points) { Vector3 v0 = new Vector3(pt.x - halfSize, pt.y + halfSize, pt.z + halfSize); Vector3 v1 = new Vector3(pt.x - halfSize, pt.y + halfSize, pt.z - halfSize); Vector3 v2 = new Vector3(pt.x + halfSize, pt.y + halfSize, pt.z - halfSize); Vector3 v3 = new Vector3(pt.x + halfSize, pt.y + halfSize, pt.z + halfSize); Vector3 v4 = new Vector3(pt.x - halfSize, pt.y - halfSize, pt.z + halfSize); Vector3 v5 = new Vector3(pt.x - halfSize, pt.y - halfSize, pt.z - halfSize); Vector3 v6 = new Vector3(pt.x + halfSize, pt.y - halfSize, pt.z - halfSize); Vector3 v7 = new Vector3(pt.x + halfSize, pt.y - halfSize, pt.z + halfSize); int vertIndex = ptIndex * vertsPerPt; // Top vertices[vertIndex + 0] = v0; vertices[vertIndex + 1] = v3; vertices[vertIndex + 2] = v2; vertices[vertIndex + 3] = v1; normals[vertIndex + 0] = Vector3.up; normals[vertIndex + 1] = Vector3.up; normals[vertIndex + 2] = Vector3.up; normals[vertIndex + 3] = Vector3.up; // Bottom vertices[vertIndex + 4] = v4; vertices[vertIndex + 5] = v5; vertices[vertIndex + 6] = v6; vertices[vertIndex + 7] = v7; normals[vertIndex + 4] = Vector3.down; normals[vertIndex + 5] = Vector3.down; normals[vertIndex + 6] = Vector3.down; normals[vertIndex + 7] = Vector3.down; // Front vertices[vertIndex + 8] = v0; vertices[vertIndex + 9] = v4; vertices[vertIndex + 10] = v7; vertices[vertIndex + 11] = v3; normals[vertIndex + 8] = Vector3.forward; normals[vertIndex + 9] = Vector3.forward; normals[vertIndex + 10] = Vector3.forward; normals[vertIndex + 11] = Vector3.forward; // Back vertices[vertIndex + 12] = v1; vertices[vertIndex + 13] = v2; vertices[vertIndex + 14] = v6; vertices[vertIndex + 15] = v5; normals[vertIndex + 12] = Vector3.back; normals[vertIndex + 13] = Vector3.back; normals[vertIndex + 14] = Vector3.back; normals[vertIndex + 15] = Vector3.back; // Left vertices[vertIndex + 16] = v0; vertices[vertIndex + 17] = v1; vertices[vertIndex + 18] = v5; vertices[vertIndex + 19] = v4; normals[vertIndex + 16] = Vector3.left; normals[vertIndex + 17] = Vector3.left; normals[vertIndex + 18] = Vector3.left; normals[vertIndex + 19] = Vector3.left; // Right vertices[vertIndex + 20] = v2; vertices[vertIndex + 21] = v3; vertices[vertIndex + 22] = v7; vertices[vertIndex + 23] = v6; normals[vertIndex + 20] = Vector3.right; normals[vertIndex + 21] = Vector3.right; normals[vertIndex + 22] = Vector3.right; normals[vertIndex + 23] = Vector3.right; // Vertex colors for (int i = 0; i < vertsPerPt; ++i) { colors[ptIndex * vertsPerPt + i] = pointsColor[ptIndex]; } // Indices for (int i = 0; i < vertsPerPt; ++i) { indices[ptIndex * vertsPerPt + i] = ptIndex * vertsPerPt + i; } ptIndex++; } Mesh mesh = new Mesh(); if (indices.Length > ushort.MaxValue) { #if UNITY_2017_3_OR_NEWER mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #else HEU_Logger.LogErrorFormat("Unable to generate mesh from points due to larger than supported geometry (> {0} vertices). Use Unity 2017.3+ for large geometry.", ushort.MaxValue); return mesh; #endif } mesh.vertices = vertices; mesh.colors = colors; mesh.normals = normals; mesh.SetIndices(indices, MeshTopology.Quads, 0); return mesh; }
/// <summary> /// Create an input node network and upload the given set of input objects. /// This creates a SOP/merge node, and input nodes for each object in inputObjects /// which are then connected to the merge node. /// It finds the input interface that supports each object in inputObjects for creating /// the input node and uploading the data based on the type of data. /// </summary> /// <param name="session">Session to create the input node in</param> /// <param name="assetID">Main asset ID</param> /// <param name="connectMergeID">Created SOP/merge node ID</param> /// <param name="inputObjects">List of input objects to upload</param> /// <param name="inputObjectsConnectedAssetIDs">List of input node IDs for the input nodes created</param> /// <param name="inputNode">The specified inputNode to create the node for (used for settings)</param> /// <returns>True if successfully uploading input nodes</returns> internal static bool CreateInputNodeWithMultiObjects(HEU_SessionBase session, HAPI_NodeId assetID, ref HAPI_NodeId connectMergeID, ref List<HEU_InputObjectInfo> inputObjects, ref List<HAPI_NodeId> inputObjectsConnectedAssetIDs, HEU_InputNode inputNode) { bool bKeepWorldTransform = inputNode.KeepWorldTransform; // Create the merge SOP node that the input nodes are going to connect to. if (!session.CreateNode(-1, "SOP/merge", null, true, out connectMergeID)) { HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets."); return false; } int numObjects = inputObjects.Count; for (int i = 0; i < numObjects; ++i) { HAPI_NodeId newConnectInputID = HEU_Defines.HEU_INVALID_NODE_ID; inputObjectsConnectedAssetIDs.Add(newConnectInputID); // Skipping null gameobjects. Though if this causes issues, can always let it continue // to create input node, but not upload mesh data if (inputObjects[i]._gameObject == null) { continue; } HEU_InputInterface inputInterface = GetInputInterface(inputObjects[i]); if (inputInterface == null) { HEU_Logger.LogWarningFormat("No input interface found for gameobject: {0}. Skipping upload!", inputObjects[i]._gameObject.name); continue; } // Apply settings based on the interface type. System.Type inputInterfaceType = inputInterface.GetType(); if (inputInterfaceType == typeof(HEU_InputInterfaceMesh)) { HEU_InputInterfaceMesh meshInterface = inputInterface as HEU_InputInterfaceMesh; meshInterface.Initialize(inputNode.MeshSettings); } if (inputInterfaceType == typeof(HEU_InputInterfaceTilemap)) { HEU_InputInterfaceTilemap tilemapInterface = inputInterface as HEU_InputInterfaceTilemap; tilemapInterface.Initialize(inputNode.TilemapSettings); } bool bResult = inputInterface.CreateInputNodeWithDataUpload(session, connectMergeID, inputObjects[i]._gameObject, out newConnectInputID); if (!bResult || newConnectInputID == HEU_Defines.HEU_INVALID_NODE_ID) { HEU_Logger.LogError("Failed to upload input."); continue; } inputObjectsConnectedAssetIDs[i] = newConnectInputID; if (!session.ConnectNodeInput(connectMergeID, i, newConnectInputID)) { HEU_Logger.LogErrorFormat("Unable to connect input nodes!"); return false; } UploadInputObjectTransform(session, inputObjects[i], newConnectInputID, bKeepWorldTransform); } return true; }
/// <summary> /// Process a PDG event. Notify the relevant HEU_PDGAssetLink object. /// </summary> /// <param name="session">Houdini Engine session</param> /// <param name="contextID">PDG graph context ID</param> /// <param name="eventInfo">PDG event info</param> private void ProcessPDGEvent(HEU_SessionBase session, HAPI_PDG_GraphContextId contextID, ref HAPI_PDG_EventInfo eventInfo) { #if HOUDINIENGINEUNITY_ENABLED HEU_PDGAssetLink assetLink = null; HEU_TOPNodeData topNode = null; HAPI_PDG_EventType evType = (HAPI_PDG_EventType)eventInfo.eventType; HAPI_PDG_WorkitemState currentState = (HAPI_PDG_WorkitemState)eventInfo.currentState; HAPI_PDG_WorkitemState lastState = (HAPI_PDG_WorkitemState)eventInfo.lastState; GetTOPAssetLinkAndNode(eventInfo.nodeId, out assetLink, out topNode); //string topNodeName = topNode != null ? string.Format("node={0}", topNode._nodeName) : string.Format("id={0}", eventInfo.nodeId); //HEU_Logger.LogFormat("PDG Event: {0}, type={1}, workitem={2}, curState={3}, lastState={4}", topNodeName, evType.ToString(), // eventInfo.workitemId, currentState, lastState); if (assetLink == null || topNode == null || topNode._nodeID != eventInfo.nodeId) { return; } EventMessageColor msgColor = EventMessageColor.DEFAULT; // Events can be split into TOP node specific or work item specific if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_NULL) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.NONE); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_NODE_CLEAR) { NotifyTOPNodePDGStateClear(assetLink, topNode); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_DIRTY_START) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.DIRTYING); //HEU_PDGAssetLink.ClearTOPNodeWorkItemResults(topNode); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_DIRTY_STOP) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.DIRTIED); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_COOK_ERROR) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.COOK_FAILED); msgColor = EventMessageColor.ERROR; } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_COOK_WARNING) { msgColor = EventMessageColor.WARNING; } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_COOK_COMPLETE) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.COOK_COMPLETE); } else { // Work item events HEU_TOPNodeData.PDGState currentTOPPDGState = topNode._pdgState; if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_WORKITEM_ADD) { _totalNumItems++; NotifyTOPNodeTotalWorkItem(assetLink, topNode, 1); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_WORKITEM_REMOVE) { NotifyTOPNodeTotalWorkItem(assetLink, topNode, -1); } else if (evType == HAPI_PDG_EventType.HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE) { // Last states if (lastState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_WAITING && currentState != HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_WAITING) { NotifyTOPNodeWaitingWorkItem(assetLink, topNode, -1); } else if (lastState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKING && currentState != HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKING) { NotifyTOPNodeCookingWorkItem(assetLink, topNode, -1); } else if (lastState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_SCHEDULED && currentState != HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_SCHEDULED) { NotifyTOPNodeScheduledWorkItem(assetLink, topNode, -1); } // New states if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_WAITING) { NotifyTOPNodeWaitingWorkItem(assetLink, topNode, 1); } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_UNCOOKED) { } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_DIRTY) { //HEU_Logger.LogFormat("Dirty: id={0}", eventInfo.workitemId); ClearWorkItemResult(session, contextID, eventInfo, topNode); } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_SCHEDULED) { NotifyTOPNodeScheduledWorkItem(assetLink, topNode, 1); } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKING) { NotifyTOPNodeCookingWorkItem(assetLink, topNode, 1); } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKED_SUCCESS || currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKED_CACHE) { NotifyTOPNodeCookedWorkItem(assetLink, topNode); // On cook success, handle results if (topNode._tags._autoload) { HAPI_PDG_WorkitemInfo workItemInfo = new HAPI_PDG_WorkitemInfo(); if (!session.GetWorkItemInfo(contextID, eventInfo.workitemId, ref workItemInfo)) { HEU_Logger.LogErrorFormat("Failed to get work item {1} info for {0}", topNode._nodeName, eventInfo.workitemId); return; } if (workItemInfo.numResults > 0) { HAPI_PDG_WorkitemResultInfo[] resultInfos = new HAPI_PDG_WorkitemResultInfo[workItemInfo.numResults]; int resultCount = workItemInfo.numResults; if (!session.GetWorkitemResultInfo(topNode._nodeID, eventInfo.workitemId, resultInfos, resultCount)) { HEU_Logger.LogErrorFormat("Failed to get work item {1} result info for {0}", topNode._nodeName, eventInfo.workitemId); return; } assetLink.LoadResults(session, topNode, workItemInfo, resultInfos, eventInfo.workitemId, OnWorkItemLoadResults); } } } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKED_FAIL) { // TODO: on cook failure, get log path? NotifyTOPNodeErrorWorkItem(assetLink, topNode); msgColor = EventMessageColor.ERROR; } else if (currentState == HAPI_PDG_WorkitemState.HAPI_PDG_WORKITEM_COOKED_CANCEL) { // Ignore it because in-progress cooks can be cancelled when automatically recooking graph } } if (currentTOPPDGState == HEU_TOPNodeData.PDGState.COOKING) { if (topNode.AreAllWorkItemsComplete()) { if (topNode.AnyWorkItemsFailed()) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.COOK_FAILED); } else { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.COOK_COMPLETE); } } } else if (topNode.AnyWorkItemsPending()) { SetTOPNodePDGState(assetLink, topNode, HEU_TOPNodeData.PDGState.COOKING); } } if (eventInfo.msgSH >= 0) { string eventMsg = HEU_SessionManager.GetString(eventInfo.msgSH, session); if (!string.IsNullOrEmpty(eventMsg)) { AddEventMessage(string.Format("<color={0}>{1} - {2}: {3}</color>\n", _eventMessageColorCode[(int)msgColor], evType, topNode._nodeName, eventMsg)); } } CheckCallback(topNode); #endif }
/// <summary> /// Creates a mesh input node and uploads the mesh data from inputObject. /// </summary> /// <param name="session">Session that connectNodeID exists in</param> /// <param name="connectNodeID">The node to connect the network to. Most likely a SOP/merge node</param> /// <param name="inputObject">The gameobject containing the mesh components</param> /// <param name="inputNodeID">The created input node ID</param> /// <returns>True if created network and uploaded mesh data.</returns> public override bool CreateInputNodeWithDataUpload(HEU_SessionBase session, HAPI_NodeId connectNodeID, GameObject inputObject, out HAPI_NodeId inputNodeID) { inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID; // Create input node, cook it, then upload the geometry data if (!HEU_HAPIUtility.IsNodeValidInHoudini(session, connectNodeID)) { HEU_Logger.LogError("Connection node is invalid."); return false; } bool bExportColliders = settings != null && settings.ExportColliders == true; // Get upload meshes from input object HEU_InputDataMeshes inputMeshes = GenerateMeshDatasFromGameObject(inputObject, bExportColliders); if (inputMeshes == null || inputMeshes._inputMeshes == null || inputMeshes._inputMeshes.Count == 0) { HEU_Logger.LogError("No valid meshes found on input objects."); return false; } string inputName = null; HAPI_NodeId newNodeID = HEU_Defines.HEU_INVALID_NODE_ID; session.CreateInputNode(out newNodeID, inputName); if (newNodeID == HEU_Defines.HEU_INVALID_NODE_ID || !HEU_HAPIUtility.IsNodeValidInHoudini(session, newNodeID)) { HEU_Logger.LogError("Failed to create new input node in Houdini session!"); return false; } inputNodeID = newNodeID; if (!UploadData(session, inputNodeID, inputMeshes)) { if (!session.CookNode(inputNodeID, false)) { HEU_Logger.LogError("New input node failed to cook!"); return false; } return false; } bool createMergeNode = false; HAPI_NodeId mergeNodeId = HEU_Defines.HEU_INVALID_NODE_ID; if (bExportColliders) { createMergeNode = true; } if (!createMergeNode) { return true; } HAPI_NodeId parentId = HEU_HAPIUtility.GetParentNodeID(session, newNodeID); if (!session.CreateNode(parentId, "merge", null, false, out mergeNodeId)) { HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets."); return false; } if (!session.ConnectNodeInput(mergeNodeId, 0, newNodeID)) { HEU_Logger.LogErrorFormat("Unable to connect to input node!"); return false; } if (!session.SetNodeDisplay(mergeNodeId, 1)) { HEU_Logger.LogWarningFormat("Unable to set display flag!"); } inputNodeID = mergeNodeId; if (bExportColliders) { if (!UploadColliderData(session, mergeNodeId, inputMeshes, parentId)) { return false; } } if (!session.CookNode(inputNodeID, false)) { HEU_Logger.LogError("New input node failed to cook!"); return false; } return true; }