Skip to content

Instantly share code, notes, and snippets.

@mrwellmann
Last active July 18, 2024 13:29
Show Gist options
  • Save mrwellmann/4df84df0b513d8f52f5ad8dbd93b3952 to your computer and use it in GitHub Desktop.
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.
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