using UnityEngine; using System.Linq; using System.Collections.Generic; using UnityEngine.ProBuilder; using UnityEngine.ProBuilder.MeshOperations; using UnityEditor.Actions; using UnityEngine.UIElements; namespace UnityEditor.ProBuilder.Actions { sealed class ProBuilderize : MenuAction { bool m_Enabled; Pref m_Quads = new Pref("meshImporter.quads", true); Pref m_Smoothing = new Pref("meshImporter.smoothing", true); Pref m_SmoothingAngle = new Pref("meshImporter.smoothingAngle", 1f); public ProBuilderize() { MeshSelection.objectSelectionChanged += OnObjectSelectionChanged; OnObjectSelectionChanged(); // invoke once as we might already have a selection in Hierarchy } private void OnObjectSelectionChanged() { // can't just check if any MeshFilter is present because we need to know whether or not it's already a // probuilder mesh int meshCount = Selection.transforms.SelectMany(x => x.GetComponentsInChildren()).Count(); m_Enabled = meshCount > 0 && meshCount != MeshSelection.selectedObjectCount; } public override ToolbarGroup group { get { return ToolbarGroup.Object; } } public override string iconPath => "Toolbar/Object_ProBuilderize"; public override Texture2D icon => IconUtility.GetIcon(iconPath); public override TooltipContent tooltip { get { return s_Tooltip; } } GUIContent m_QuadsTooltip = new GUIContent("Import Quads", "Create ProBuilder mesh using quads where " + "possible instead of triangles."); GUIContent m_SmoothingTooltip = new GUIContent("Import Smoothing", "Import smoothing groups by " + "testing adjacent faces against an angle threshold."); GUIContent m_SmoothingThresholdTooltip = new GUIContent("Smoothing Threshold", "When importing " + "smoothing groups any adjacent faces with an adjoining angle difference of less than this value will be " + "grouped together in a smoothing group."); private static readonly TooltipContent s_Tooltip = new TooltipContent ( "ProBuilderize", @"Creates ProBuilder-modifiable objects from meshes." ); public override bool enabled { get { return base.enabled && m_Enabled; } } protected override MenuActionState optionsMenuState { get { return MenuActionState.VisibleAndEnabled; } } // This boolean allows to call the action only once in case of multi-selection as PB actions // are called on the entire selection and not per element. static bool s_ActionAlreadyTriggered = false; [InitializeOnEnterPlayMode] static void ResetStaticsOnLoad() { s_ActionAlreadyTriggered = false; } [MenuItem("CONTEXT/MeshFilter/ProBuilderize", true, 11)] static bool ProBuilderizeMeshAction_Validate(MenuCommand command) { return !MenuActionSettings.IsCurrentAction(new ProBuilderize()); } [MenuItem("CONTEXT/MeshFilter/ProBuilderize", false, 11)] static void ProBuilderizeMeshAction(MenuCommand command) { if (!s_ActionAlreadyTriggered) { var filter = (MeshFilter)command.context; //Check if we are not trying to Probuilderize a PB mesh if (filter.GetComponent() != null) return; s_ActionAlreadyTriggered = true; //Once again, delayCall is necessary to prevent multiple call in case of multi-selection EditorApplication.delayCall += () => { EditorAction.Start(new MenuActionSettings(new ProBuilderize())); s_ActionAlreadyTriggered = false; }; } } public override VisualElement CreateSettingsContent() { var root = new VisualElement(); var quadsToggle = new Toggle(m_QuadsTooltip.text); quadsToggle.tooltip = m_QuadsTooltip.tooltip; quadsToggle.SetValueWithoutNotify(m_Quads); root.Add(quadsToggle); var smoothingToggle = new Toggle(m_SmoothingTooltip.text); smoothingToggle.tooltip = m_SmoothingTooltip.tooltip; smoothingToggle.SetValueWithoutNotify(m_Smoothing); root.Add(smoothingToggle); var line = new VisualElement(); line.style.flexDirection = FlexDirection.Row; root.Add(line); var smoothingSlider = new Slider(m_SmoothingThresholdTooltip.text, 0.0001f, 45f); smoothingSlider.tooltip = m_SmoothingThresholdTooltip.tooltip; smoothingSlider.SetValueWithoutNotify(m_SmoothingAngle); smoothingSlider.style.flexGrow = 1; smoothingSlider.SetEnabled(m_Smoothing); line.Add(smoothingSlider); var smoothingSliderValue = new FloatField(); smoothingSliderValue.SetValueWithoutNotify(m_SmoothingAngle); smoothingSliderValue.isDelayed = true; smoothingSliderValue.style.width = 50; line.Add(smoothingSliderValue); quadsToggle.RegisterCallback>(evt => { m_Quads.SetValue(evt.newValue); }); smoothingToggle.RegisterCallback>(evt => { m_Smoothing.SetValue(evt.newValue); smoothingSlider.SetEnabled(m_Smoothing); }); smoothingSlider.RegisterCallback>(evt => { m_SmoothingAngle.SetValue(evt.newValue); smoothingSliderValue.SetValueWithoutNotify(m_SmoothingAngle); }); smoothingSliderValue.RegisterCallback>(evt => { m_SmoothingAngle.SetValue(evt.newValue); smoothingSlider.SetValueWithoutNotify(m_SmoothingAngle); }); return root; } protected override void OnSettingsGUI() { GUILayout.Label("ProBuilderize Options", EditorStyles.boldLabel); EditorGUILayout.HelpBox("When Preserve Faces is enabled ProBuilder will try to group adjacent triangles into faces.", MessageType.Info); EditorGUI.BeginChangeCheck(); m_Quads.value = EditorGUILayout.Toggle(m_QuadsTooltip, m_Quads); m_Smoothing.value = EditorGUILayout.Toggle(m_SmoothingTooltip, m_Smoothing); GUI.enabled = m_Smoothing; EditorGUILayout.PrefixLabel(m_SmoothingThresholdTooltip); m_SmoothingAngle.value = EditorGUILayout.Slider(m_SmoothingAngle, 0.0001f, 45f); GUI.enabled = true; if (EditorGUI.EndChangeCheck()) ProBuilderSettings.Save(); GUILayout.FlexibleSpace(); GUI.enabled = enabled; if (GUILayout.Button("ProBuilderize")) EditorUtility.ShowNotification(PerformAction().notification); GUI.enabled = true; } protected override ActionResult PerformActionImplementation() { IEnumerable top = Selection.transforms.Select(x => x.GetComponent()).Where(y => y != null); IEnumerable all = Selection.gameObjects.SelectMany(x => x.GetComponentsInChildren()).Where(x => x != null); MeshImportSettings settings = new MeshImportSettings() { quads = m_Quads, smoothing = m_Smoothing, smoothingAngle = m_SmoothingAngle }; if (top.Count() != all.Count()) { int result = UnityEditor.EditorUtility.DisplayDialogComplex("ProBuilderize Selection", "ProBuilderize children of selection?", "Yes", "No", "Cancel"); if (result == 0) return DoProBuilderize(all, settings); else if (result == 1) return DoProBuilderize(top, settings); else return ActionResult.UserCanceled; } return DoProBuilderize(all, settings); } [System.Obsolete("Please use DoProBuilderize(IEnumerable, pb_MeshImporter.Settings")] public static ActionResult DoProBuilderize( IEnumerable selected, bool preserveFaces) { return DoProBuilderize(selected, new MeshImportSettings() { quads = preserveFaces, smoothing = false, smoothingAngle = 1f }); } /// /// Adds pb_Object component without duplicating the objcet. Is undo-able. /// /// /// /// public static ActionResult DoProBuilderize( IEnumerable selected, MeshImportSettings settings) { int i = 0; float count = selected.Count(); // Return immediately from the action so that the GUI can resolve. Displaying a progress bar interrupts the // event loop causing a layoutting error. EditorApplication.delayCall += () => { foreach (var mf in selected) { if (mf.sharedMesh == null) continue; GameObject go = mf.gameObject; var renderer = go.GetComponent(); if (renderer.isPartOfStaticBatch) { Debug.LogError($"Probuilderize is not supported for renderers that have `IsPartOfStaticBatch` set.\n{go.name} will not probuilderize."); continue; } Mesh sourceMesh = mf.sharedMesh; Material[] sourceMaterials = go.GetComponent()?.sharedMaterials; try { var destination = Undo.AddComponent(go); var meshImporter = new MeshImporter(sourceMesh, sourceMaterials, destination); meshImporter.Import(settings); destination.Rebuild(); destination.Optimize(); i++; } catch (System.Exception e) { Debug.LogWarning("Failed ProBuilderizing: " + go.name + "\n" + e.ToString()); } UnityEditor.EditorUtility.DisplayProgressBar("ProBuilderizing", mf.gameObject.name, i / count); } UnityEditor.EditorUtility.ClearProgressBar(); MeshSelection.OnObjectSelectionChanged(); ProBuilderEditor.Refresh(); }; if (i < 1) return new ActionResult(ActionResult.Status.Canceled, "Nothing Selected"); return new ActionResult(ActionResult.Status.Success, "ProBuilderize " + i + (i > 1 ? " Objects" : " Object").ToString()); } } }