Merge pull request #685 from PoChang007/add_meshutility_editor

MeshUtility editor
This commit is contained in:
ousttrue
2021-01-22 18:40:12 +09:00
committed by GitHub
15 changed files with 281 additions and 40 deletions

View File

@@ -7,7 +7,7 @@ namespace UniGLTF
{
public static class ImporterMenu
{
[MenuItem(UniGLTFVersion.MENU + "/Import(gltf, glb)", priority = 1)]
[MenuItem(UniGLTFVersion.MENU + "/Import(gltf, glb)", priority = 20)]
public static void ImportMenu()
{
var path = EditorUtility.OpenFilePanel("open gltf", "", "gltf,glb");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -8,9 +8,9 @@ Select a GameObject contained skinned mesh and BlendShape:
<img src="../images/interface_1.jpg" width="200">
Select `Mesh Utility` -> `MeshSeparator`:
From menu go to `UniGLTF` -> `Mesh Utility` -> `MeshProcessing Wizard`, select `MeshSeparator` and click `process`:
<img src="../images/interface_2.jpg" width="200">
<img src="../images/interface_2.jpg" width="300">
The separate meshes are saved in the Assets folder. GameObjects with separate meshes are also available in the Hierarchy Window:

View File

@@ -51,7 +51,7 @@ namespace MeshUtility
[SerializeField]
BoneMeshEraser.EraseBone[] m_eraseBones;
[MenuItem(MeshUtility.MENU_PARENT + "BoneMeshEraser Wizard", priority = 4)]
[MenuItem(MeshUtility.MENU_PARENT + "BoneMeshEraser Wizard", priority = 31)]
static void CreateWizard()
{
ScriptableWizard.DisplayWizard<BoneMeshEraserWizard>("BoneMeshEraser", "Erase triangles by bone", "Erase");

View File

@@ -26,7 +26,7 @@ namespace MeshUtility.Validators
DUPLICATE_BONE_NAME_EXISTS,
[LangMsg(Languages.ja, "SkinnedMeshRenderer.bones に重複する内容がある。エクスポートした場合に、bones の重複を取り除き、boneweights, bindposes を調整します")]
[LangMsg(Languages.en, "There are same bone in bones the SkinnedMeshRenderer. They will be automatically uniqued")]
[LangMsg(Languages.en, "There are duplicated bones in SkinnedMeshRenderer.bones. They will be exported as unique bones. boneweights and bindposes will also be adjusted")]
NO_UNIQUE_JOINTS,
}

View File

@@ -0,0 +1,182 @@
using UnityEngine;
using UnityEditor;
using System.Reflection;
using MeshUtility;
using System.IO;
using System;
using System.Linq;
namespace MeshUtility
{
public class MeshProcessDialog : EditorWindow
{
const string MESH_UTILITY_DICT = "UniGLTF/Mesh Utility/";
[MenuItem(MESH_UTILITY_DICT + "MeshProcessing Wizard", priority = 30)]
static void MeshProcessFromMenu()
{
var window = (MeshProcessDialog)EditorWindow.GetWindowWithRect(typeof(MeshProcessDialog), new Rect(0, 0, 500, 250));
window.titleContent = new GUIContent ("Mesh Processing Window");
}
enum Tabs
{
MeshSeparator,
MeshIntegrator,
StaticMeshIntegrator,
}
private Tabs _tab;
private GameObject _exportTarget;
private MethodInfo _processFunction;
private bool _isInvokeSuccess = false;
GUIStyle _tabButtonStyle => "LargeButton";
GUI.ToolbarButtonSize _tabButtonSize => GUI.ToolbarButtonSize.Fixed;
private void OnGUI()
{
EditorGUIUtility.labelWidth = 150;
_tab = TabBar.OnGUI(_tab, _tabButtonStyle, _tabButtonSize);
switch (_tab)
{
case Tabs.MeshSeparator:
EditorGUILayout.TextField("Meshes containing BlendShape data will be split");
break;
case Tabs.MeshIntegrator:
EditorGUILayout.TextField("Generate a single mesh. Meshes w/ BlendShape will be grouped into another one");
break;
case Tabs.StaticMeshIntegrator:
EditorGUILayout.TextField("Integrate static meshes into one");
break;
}
EditorGUILayout.LabelField("ExportTarget");
_exportTarget = (GameObject)EditorGUILayout.ObjectField(_exportTarget, typeof(GameObject), true);
if (_exportTarget == null && MeshUtility.IsGameObjectSelected())
{
_exportTarget = Selection.activeObject as GameObject;
}
// Create Other Buttons
{
GUILayout.BeginVertical();
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Process", GUILayout.MinWidth(100)))
{
switch (_tab)
{
case Tabs.MeshSeparator:
_isInvokeSuccess = InvokeWizardUpdate("MeshSeparator");
break;
case Tabs.MeshIntegrator:
_isInvokeSuccess = InvokeWizardUpdate("MeshIntegrator");
break;
case Tabs.StaticMeshIntegrator:
_isInvokeSuccess = InvokeWizardUpdate("StaticMeshIntegrator");
break;
}
if (_isInvokeSuccess)
{
Close();
GUIUtility.ExitGUI();
}
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
}
private bool InvokeWizardUpdate(string processFuntion)
{
const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
_processFunction = GetType().GetMethod(processFuntion, kInstanceInvokeFlags);
if (_processFunction != null)
{
return (Boolean)_processFunction.Invoke(this, null);
}
else
{
Debug.LogError("This function has not been implemented in script");
return false;
}
}
private bool GameObjectNull()
{
EditorUtility.DisplayDialog("Failed", "No GameObject Selected", "ok");
return false;
}
private bool MeshSeparator()
{
if (_exportTarget == null) return GameObjectNull();
var go = _exportTarget;
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length > 0)
{
MeshUtility.SeparationProcessing(go);
return true;
}
else
{
EditorUtility.DisplayDialog("Failed", "No skinned mesh contained", "ok");
return false;
}
}
private bool MeshIntegrator()
{
if (_exportTarget == null) return GameObjectNull();
var go = _exportTarget;
Component[] allComponents = go.GetComponents(typeof(Component));
var keyWord = "VRMMeta";
foreach (var component in allComponents)
{
if (component == null) continue;
var sourceString = component.ToString();
if (sourceString.Contains(keyWord))
{
EditorUtility.DisplayDialog("Failed", "Target object is VRM file, use `VRM0 -> MeshIntegrator` instead", "ok");
return false;
}
}
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length > 0 || go.GetComponentsInChildren<MeshFilter>().Length > 0)
{
MeshUtility.MeshIntegrator(go);
return true;
}
else
{
EditorUtility.DisplayDialog("Failed", "Neither skinned mesh nor static mesh contained", "ok");
return false;
}
}
private bool StaticMeshIntegrator()
{
if (_exportTarget == null) return GameObjectNull();
var go = _exportTarget;
if (go.GetComponentsInChildren<MeshFilter>().Length > 0)
{
MeshUtility.IntegrateSelected(go);
return true;
}
else
{
EditorUtility.DisplayDialog("Failed", "No static mesh contained", "ok");
return false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86cbd6470ebef2f4c976410efaf45638
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
@@ -9,12 +9,14 @@ namespace MeshUtility
public class MeshUtility
{
public const string MENU_PARENT = "UniGLTF/Mesh Utility/";
public const int MENU_PRIORITY = 11;
private const string ASSET_SUFFIX = ".mesh.asset";
private const string MENU_NAME = MENU_PARENT + "MeshSeparator";
private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero;
private const string INTEGRATED_MESH_NAME = "MeshesIntegrated";
private const string INTEGRATED_MESH_BLENDSHAPE_NAME = "MeshesBlendShapeIntegrated";
public static Object GetPrefab(GameObject instance)
{
#if UNITY_2018_2_OR_NEWER
@@ -39,31 +41,16 @@ namespace MeshUtility
return true;
}
[MenuItem(MENU_NAME, priority = 2)]
public static void SeparateSkinnedMeshContainedBlendShape()
{
var go = Selection.activeTransform.gameObject;
if (go.GetComponentsInChildren<SkinnedMeshRenderer>().Length > 0)
{
SeparationProcessing(go);
go.SetActive(false);
}
else
{
EditorUtility.DisplayDialog("Error", "No skinnedMeshRenderer contained", "ok");
}
}
[MenuItem(MENU_PARENT + "MeshSeparator Docs", priority = MeshUtility.MENU_PRIORITY)]
[MenuItem(MENU_PARENT + "MeshUtility Docs", priority = 32)]
public static void LinkToMeshSeparatorDocs()
{
Application.OpenURL("https://github.com/vrm-c/UniVRM/blob/master/Assets/UniGLTF/MeshUtility/README.md");
}
private static void SeparationProcessing(GameObject go)
public static void SeparationProcessing(GameObject go)
{
var outputObject = GameObject.Instantiate(go);
outputObject.name = outputObject.name + "_mesh_separation";
var skinnedMeshRenderers = outputObject.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
@@ -284,16 +271,13 @@ namespace MeshUtility
}
#if UNITY_EDITOR
[MenuItem(MENU_PARENT + "Integrate Static Mesh", validate = true)]
public static bool CanIntegrateSelected()
public static bool IsGameObjectSelected()
{
return Selection.activeObject != null && Selection.activeObject is GameObject;
}
[MenuItem(MENU_PARENT + "Integrate Static Mesh", priority = 3)]
public static void IntegrateSelected()
public static void IntegrateSelected(GameObject go)
{
var go = Selection.activeObject as GameObject;
var meshWithMaterials = StaticMeshIntegrator.Integrate(go.transform);
// save as asset
@@ -347,5 +331,53 @@ namespace MeshUtility
renderer.sharedMaterials = meshWithMaterials.Materials;
}
#endif
public static void MeshIntegrator(GameObject go)
{
MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: true);
MeshIntegratorUtility.Integrate(go, onlyBlendShapeRenderers: false);
var outputObject = GameObject.Instantiate(go);
outputObject.name = outputObject.name + "_mesh_integration";
var skinnedMeshes = outputObject.GetComponentsInChildren<SkinnedMeshRenderer>();
var normalMeshes = outputObject.GetComponentsInChildren<MeshFilter>();
// destroy integrated meshes in the source
foreach (var skinnedMesh in go.GetComponentsInChildren<SkinnedMeshRenderer>())
{
if (skinnedMesh.sharedMesh.name == INTEGRATED_MESH_NAME)
{
GameObject.DestroyImmediate(skinnedMesh.gameObject);
}
}
// destroy original meshes in the copied GameObject
foreach (var skinnedMesh in skinnedMeshes)
{
if (skinnedMesh.sharedMesh.name != INTEGRATED_MESH_NAME)
{
GameObject.DestroyImmediate(skinnedMesh);
}
// avoid repeated name
else if (skinnedMesh.sharedMesh.blendShapeCount > 0)
{
skinnedMesh.sharedMesh.name = INTEGRATED_MESH_BLENDSHAPE_NAME;
}
// check if the integrated mesh is empty
else if (skinnedMesh.sharedMesh.subMeshCount == 0)
{
GameObject.DestroyImmediate(skinnedMesh.gameObject);
}
}
foreach (var normalMesh in normalMeshes)
{
if (normalMesh.sharedMesh.name != INTEGRATED_MESH_NAME)
{
if (normalMesh.gameObject.GetComponent<MeshRenderer>())
{
GameObject.DestroyImmediate(normalMesh.gameObject.GetComponent<MeshRenderer>());
}
GameObject.DestroyImmediate(normalMesh);
}
}
}
}
}

View File

@@ -14,15 +14,15 @@ Currently support BlendShape mesh separation. See [documentation](Documentation/
### MeshIntegrator
Integrate all the meshes of a Prefab (Project window).
Integrate all the meshes of a (prefab) GameObject.
### Integrate Static Mesh
### StaticMeshIntegrator
Integrate all the static meshes in the Hierarchy (Root and its children).
Integrate all the static meshes of a (prefab) GameObject (Root and its children).
### MeshNormalizer
Bake the Hierarchy. This is VRM normalize backend.
Bake the Hierarchy. This is VRM normalization backend.
MeshNormalizer can do blendShape bake.
## Import MeshUtility

View File

@@ -54,7 +54,7 @@ namespace MeshUtility
}
var mesh = new Mesh();
mesh.name = "integrated";
mesh.name = "MeshesIntegrated";
if (integrator.Positions.Count > ushort.MaxValue)
{

View File

@@ -66,7 +66,7 @@ namespace MeshUtility
public Mesh ToMesh()
{
var mesh = new Mesh();
mesh.name = "integrated";
mesh.name = "MeshesIntegrated";
mesh.vertices = m_positions.ToArray();
if (m_normals.Count > 0)

View File

@@ -23,7 +23,7 @@ namespace UniGLTF
return Selection.activeObject != null && Selection.activeObject is GameObject;
}
[MenuItem(MENU_EXPORT_GLTF_KEY, false, 1)]
[MenuItem(MENU_EXPORT_GLTF_KEY, priority = 0)]
private static void ExportGltfFromMenu()
{
ExportFromMenu(false, new MeshExportSettings
@@ -33,7 +33,7 @@ namespace UniGLTF
});
}
[MenuItem(MENU_EXPORT_GLB_KEY, false, 1)]
[MenuItem(MENU_EXPORT_GLB_KEY, priority = 10)]
private static void ExportGlbFromMenu()
{
ExportFromMenu(true, MeshExportSettings.Default);

View File

@@ -118,11 +118,11 @@ namespace VRM
REMOVE_VERTEX_COLOR,
[LangMsg(Languages.ja, "このボタンで自動で T-Pose にできます。手動で T-Pose にしたり、ボタンの後で手直ししてもOKです。")]
[LangMsg(Languages.en, "You can automatically set the T pose with this button. You can change it manually to T-pose or after the button.")]
[LangMsg(Languages.en, "T-Pose can be made automatically with this button, or you can make the model as T-Pose manually. Adjusting T-Pose manually after applying this function is also OK")]
ENALBE_TPOSE_BUTTON,
[LangMsg(Languages.ja, "このボタンで自動で T-Pose にできます。prefab には実行できません。")]
[LangMsg(Languages.en, "You can set the T pose automatically with this button. It cannot be run on prefabs.")]
[LangMsg(Languages.en, "T-Pose can be made automatically with this button. It cannot be run on prefabs.")]
DISABLE_TPOSE_BUTTON,
[LangMsg(Languages.ja, "T-Pose にする")]

View File

@@ -32,6 +32,14 @@ glTF2.0をベースとしており、誰でも自由に利用することがで
## Contributing to UniVRM
以下コマンドでUniVRMリポジトリをクローンして、UnityでUniVRMフォルダを開きます。
```console
$ git clone https://github.com/vrm-c/UniVRM.git
$ cd UniVRM
$ git submodule update --init --recursive
```
[コントリビューションガイド](https://github.com/vrm-c/UniVRM/wiki/コントリビューションガイド(ja))を参照してください。
## Documents

View File

@@ -32,6 +32,14 @@ VRM is based on glTF2.0. If you comply with the MIT license, you are free to use
## Contributing to UniVRM
Use the commands below to clone UniVRM repository, and open the UniVRM folder by Unity.
```console
$ git clone https://github.com/vrm-c/UniVRM.git
$ cd UniVRM
$ git submodule update --init --recursive
```
See [contributing guidelines](https://github.com/vrm-c/UniVRM/wiki/Contributing-Guidelines).
## Documents