Last active
July 18, 2024 13:29
-
-
Save mrwellmann/4df84df0b513d8f52f5ad8dbd93b3952 to your computer and use it in GitHub Desktop.
Menu entry for Replacing Unity ProBuilder Components with a external mesh by exporting it as glTF. Needs to be placed in a editor folder the package com.unity.cloud.gltfast and a Assembly Definition containing glTFast, glTFast.Exprot and Unity.Probuilder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using UnityEngine.ProBuilder; | |
using GLTFast.Export; | |
using GLTFast.Logging; | |
using System.Threading.Tasks; | |
using System.Reflection; | |
public class ExportAndReplaceProBuilderMeshes : MonoBehaviour | |
{ | |
const string k_GltfBinaryExtension = "glb"; | |
static string SaveFolderPath | |
{ | |
get | |
{ | |
var saveFolderPath = EditorUserSettings.GetConfigValue("glTF.saveFilePath"); | |
if (string.IsNullOrEmpty(saveFolderPath)) | |
{ | |
saveFolderPath = Application.streamingAssetsPath; | |
} | |
return saveFolderPath; | |
} | |
set => EditorUserSettings.SetConfigValue("glTF.saveFilePath", value); | |
} | |
/// <summary> | |
/// Menu item to export and replace ProBuilder meshes in the selected GameObject. | |
/// </summary> | |
/// <param name="command">The menu command context.</param> | |
[MenuItem("GameObject/ProBuilder/Replace ProBuilder Meshes", false, 31)] | |
static async void ExportAndReplaceProBuilderMeshesMenu(MenuCommand command) | |
{ | |
var go = command.context as GameObject; | |
if (go == null) | |
{ | |
Debug.LogWarning("No GameObject selected."); | |
return; | |
} | |
string path = EditorUtility.SaveFilePanel("Save GLTF File", "Assets/", go.name, k_GltfBinaryExtension); | |
if (string.IsNullOrEmpty(path)) | |
{ | |
Debug.LogWarning("Invalid file path. GLTF not saved."); | |
return; | |
} | |
await ExportAndReplaceGLTF(path, go); | |
Debug.Log($"ProBuilder meshes have been exported and replaced to {path}"); | |
} | |
/// <summary> | |
/// Exports the selected GameObject to a GLTF file and replaces ProBuilder components. | |
/// </summary> | |
/// <param name="path">The file path to save the GLTF file.</param> | |
/// <param name="rootObject">The root GameObject to export and replace components.</param> | |
/// <returns>A task that represents the asynchronous operation.</returns> | |
static async Task ExportAndReplaceGLTF(string path, GameObject rootObject) | |
{ | |
// Create export settings | |
var exportSettings = new ExportSettings | |
{ | |
Format = GltfFormat.Binary, | |
FileConflictResolution = FileConflictResolution.Overwrite, | |
LightIntensityFactor = 100f, | |
PreservedVertexAttributes = VertexAttributeUsage.AllTexCoords | VertexAttributeUsage.Color, | |
}; | |
// Create GameObject export settings | |
var gameObjectExportSettings = new GameObjectExportSettings | |
{ | |
OnlyActiveInHierarchy = false, | |
DisabledComponents = true, | |
LayerMask = LayerMask.GetMask("Default"), | |
}; | |
// Create a logger to collect export messages | |
var logger = new CollectingLogger(); | |
// Create a GLTF export context | |
var export = new GameObjectExport(exportSettings, gameObjectExportSettings, logger: logger); | |
// Rename meshes with the name of the GameObject | |
RenameMeshesWithGameObjectName(rootObject); | |
// Add the root GameObject to the export context | |
export.AddScene(new[] { rootObject }, rootObject.name); | |
// Async glTF export | |
var success = await export.SaveToFileAndDispose(path); | |
if (!success) | |
{ | |
Debug.LogError("Something went wrong exporting a glTF"); | |
logger.LogAll(); | |
return; | |
} | |
// Reload the asset database | |
AssetDatabase.Refresh(); | |
// Handle the GLTF import process | |
ReplaceProBuilderComponentsWithGltf(path, rootObject); | |
} | |
/// <summary> | |
/// Renames all meshes in the children of the root object to the name of their GameObject. | |
/// </summary> | |
/// <param name="rootObject">The root GameObject whose children's meshes will be renamed.</param> | |
static void RenameMeshesWithGameObjectName(GameObject rootObject) | |
{ | |
var filters = rootObject.GetComponentsInChildren<MeshFilter>(); | |
foreach (var filter in filters) | |
{ | |
if (filter.sharedMesh != null) | |
{ | |
filter.sharedMesh.name = filter.gameObject.name; | |
} | |
} | |
} | |
/// <summary> | |
/// Replaces ProBuilder components with standard Unity components and applies the GLTF mesh and materials. | |
/// </summary> | |
/// <param name="path">The file path to the GLTF file.</param> | |
/// <param name="rootObject">The root GameObject whose components will be replaced.</param> | |
static void ReplaceProBuilderComponentsWithGltf(string path, GameObject rootObject) | |
{ | |
// Ensure path is relative to the project folder | |
var relativePath = "Assets" + path.Substring(Application.dataPath.Length); | |
// Load the GLTF asset | |
var gltfAsset = AssetDatabase.LoadAssetAtPath<GameObject>(relativePath); | |
if (gltfAsset == null) | |
{ | |
Debug.LogError("Failed to load the GLTF asset."); | |
return; | |
} | |
// Get all MeshFilters from the GLTF asset | |
var gltfMeshFilters = gltfAsset.GetComponentsInChildren<MeshFilter>(); | |
var filters = rootObject.GetComponentsInChildren<MeshFilter>(); | |
foreach (var filter in filters) | |
{ | |
var gameObject = filter.gameObject; | |
// Remove ProBuilderShape component using reflection because ProBuilderShape is internal | |
var assembly = Assembly.Load("Unity.ProBuilder"); | |
var proBuilderShapeType = assembly.GetType("UnityEngine.ProBuilder.Shapes.ProBuilderShape"); | |
if (proBuilderShapeType != null) | |
{ | |
var proBuilderShape = gameObject.GetComponent(proBuilderShapeType); | |
if (proBuilderShape != null) | |
{ | |
DestroyImmediate(proBuilderShape); | |
} | |
} | |
// Remove ProBuilderMesh component if it exists | |
var pbMesh = gameObject.GetComponent<ProBuilderMesh>(); | |
if (pbMesh != null) | |
{ | |
DestroyImmediate(pbMesh); | |
} | |
// Add MeshFilter and replace mesh filter mesh | |
var meshFilter = gameObject.GetComponent<MeshFilter>(); | |
if (meshFilter == null) | |
{ | |
meshFilter = gameObject.AddComponent<MeshFilter>(); | |
} | |
var gltfMesh = FindGltfMesh(gltfMeshFilters, gameObject.name); | |
if (gltfMesh != null) | |
{ | |
meshFilter.sharedMesh = gltfMesh.sharedMesh; | |
} | |
// Add MeshRenderer and replace material | |
var renderer = gameObject.GetComponent<MeshRenderer>(); | |
if (renderer == null) | |
{ | |
renderer = gameObject.AddComponent<MeshRenderer>(); | |
} | |
var gltfRenderer = FindGltfRenderer(gltfAsset, gameObject.name); | |
if (gltfRenderer != null) | |
{ | |
renderer.sharedMaterial = gltfRenderer.sharedMaterial; | |
} | |
// Replace mesh collider mesh if exists | |
var collider = gameObject.GetComponent<MeshCollider>(); | |
if (collider != null) | |
{ | |
collider.sharedMesh = gltfMesh.sharedMesh; | |
} | |
} | |
} | |
/// <summary> | |
/// Finds the GLTF mesh that corresponds to the specified name. | |
/// </summary> | |
/// <param name="gltfMeshFilters">The array of MeshFilters from the GLTF asset.</param> | |
/// <param name="name">The name of the GameObject to find the mesh for.</param> | |
/// <returns>The corresponding MeshFilter, or null if not found.</returns> | |
static MeshFilter FindGltfMesh(MeshFilter[] gltfMeshFilters, string name) | |
{ | |
foreach (var filter in gltfMeshFilters) | |
{ | |
if (filter.gameObject.name == name) | |
{ | |
return filter; | |
} | |
} | |
return null; | |
} | |
/// <summary> | |
/// Finds the GLTF renderer that corresponds to the specified name. | |
/// </summary> | |
/// <param name="gltfAsset">The root GameObject of the GLTF asset.</param> | |
/// <param name="name">The name of the GameObject to find the renderer for.</param> | |
/// <returns>The corresponding MeshRenderer, or null if not found.</returns> | |
static MeshRenderer FindGltfRenderer(GameObject gltfAsset, string name) | |
{ | |
var renderers = gltfAsset.GetComponentsInChildren<MeshRenderer>(); | |
foreach (var renderer in renderers) | |
{ | |
if (renderer.gameObject.name == name) | |
{ | |
return renderer; | |
} | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment