mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-08 11:59:26 -05:00
Merge pull request #685 from PoChang007/add_meshutility_editor
MeshUtility editor
This commit is contained in:
@@ -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 |
@@ -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:
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
182
Assets/UniGLTF/MeshUtility/Editor/MeshProcessDialog.cs
Normal file
182
Assets/UniGLTF/MeshUtility/Editor/MeshProcessDialog.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/UniGLTF/MeshUtility/Editor/MeshProcessDialog.cs.meta
Normal file
11
Assets/UniGLTF/MeshUtility/Editor/MeshProcessDialog.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86cbd6470ebef2f4c976410efaf45638
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace MeshUtility
|
||||
}
|
||||
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "integrated";
|
||||
mesh.name = "MeshesIntegrated";
|
||||
|
||||
if (integrator.Positions.Count > ushort.MaxValue)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 にする")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user