Unity中从3D模型资产中批量提取材质

如何使用
只需在“项目”窗口中创建一个名为“编辑器”的文件夹,然后在其中添加此脚本即可。然后,打开Window-Batch Extract Materials,配置参数并点击“ Extract! ”。

在Unity 2019.1+上,可以将默认材质重映射条件配置为自动检测模型资源中嵌入的重复材质并为它们提取单个材质,而不是将它们提取为重复材质实例。
在这里插入图片描述

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;public class BatchExtractMaterials : EditorWindow
{private enum ExtractMode { Extract = 0, Remap = 1, Ignore = 2 };[System.Serializable]private class ExtractData{public GameObject model;public List<string> materialNames = new List<string>();public List<Material> originalMaterials = new List<Material>();public List<Material> remappedMaterials = new List<Material>();public List<ExtractMode> materialExtractModes = new List<ExtractMode>();public ExtractData() { }public ExtractData( GameObject model ) { this.model = model; }}private class RemapAllPopup : EditorWindow{private List<Material> remapFrom = new List<Material>( 2 );private Material remapTo;private bool skipIgnoredMaterials;private Vector2 scrollPos;private System.Action<List<Material>, Material, bool> onRemapConfirmed;public static void ShowAt( Rect buttonRect, Vector2 size, System.Action<List<Material>, Material, bool> onRemapConfirmed ){buttonRect.position = GUIUtility.GUIToScreenPoint( buttonRect.position );remapAllPopup = GetWindow<RemapAllPopup>( true );remapAllPopup.position = new Rect( buttonRect.position + new Vector2( ( buttonRect.width - size.x ) * 0.5f, buttonRect.height ), size );remapAllPopup.minSize = size;remapAllPopup.titleContent = new GUIContent( "Remap All..." );remapAllPopup.skipIgnoredMaterials = EditorPrefs.GetBool( "BEM_SkipIgnoredMats", true );remapAllPopup.onRemapConfirmed = onRemapConfirmed;remapAllPopup.scrollPos = Vector2.zero;remapAllPopup.Show();}public static void Hide(){if( remapAllPopup ){remapAllPopup.Close();remapAllPopup = null;}}private void OnDestroy(){remapAllPopup = null;}private void OnGUI(){if( !remapAllPopup ){Close();GUIUtility.ExitGUI();}Event ev = Event.current;EditorGUILayout.LabelField( "This will find all materials that point to 'Remap From' and remap them to 'Remap To'. If 'Remap From' is empty, all materials will be remapped to 'Remap To'.", EditorStyles.wordWrappedLabel );scrollPos = EditorGUILayout.BeginScrollView( scrollPos );GUILayout.BeginHorizontal();GUILayout.Label( "Remap From (drag & drop here)" );if( remapFrom.Count == 0 )remapFrom.Add( null );// Allow drag & dropping materials to array// Credit: https://answers.unity.com/answers/657877/view.htmlif( ( ev.type == EventType.DragPerform || ev.type == EventType.DragUpdated ) && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) ){DragAndDrop.visualMode = DragAndDropVisualMode.Copy;if( ev.type == EventType.DragPerform ){DragAndDrop.AcceptDrag();Object[] draggedObjects = DragAndDrop.objectReferences;for( int i = 0; i < draggedObjects.Length; i++ ){Material material = draggedObjects[i] as Material;if( !material )continue;if( !remapFrom.Contains( material ) ){bool replacedNullElement = false;for( int j = 0; j < remapFrom.Count; j++ ){if( !remapFrom[j] ){remapFrom[j] = material;replacedNullElement = true;break;}}if( !replacedNullElement )remapFrom.Add( material );}}}ev.Use();}if( GUILayout.Button( "+", GL_WIDTH_25 ) )remapFrom.Insert( 0, null );GUILayout.EndHorizontal();for( int i = 0; i < remapFrom.Count; i++ ){GUILayout.BeginHorizontal();remapFrom[i] = EditorGUILayout.ObjectField( GUIContent.none, remapFrom[i], typeof( Material ), false ) as Material;if( GUILayout.Button( "+", GL_WIDTH_25 ) )remapFrom.Insert( i + 1, null );if( GUILayout.Button( "-", GL_WIDTH_25 ) ){// Lists with no elements look ugly, always keep a dummy null variableif( remapFrom.Count > 1 )remapFrom.RemoveAt( i-- );elseremapFrom[0] = null;}GUILayout.EndHorizontal();}EditorGUILayout.EndScrollView();remapTo = EditorGUILayout.ObjectField( "Remap To", remapTo, typeof( Material ), false ) as Material;EditorGUI.BeginChangeCheck();skipIgnoredMaterials = EditorGUILayout.Toggle( "Skip Ignored Materials", skipIgnoredMaterials );if( EditorGUI.EndChangeCheck() )EditorPrefs.SetBool( "BEM_SkipIgnoredMats", skipIgnoredMaterials );EditorGUILayout.Space();GUILayout.BeginHorizontal();if( GUILayout.Button( "Cancel" ) )Close();if( GUILayout.Button( "Apply" ) ){if( remapTo && onRemapConfirmed != null ){bool remapFromIsFilled = false;for( int i = 0; i < remapFrom.Count; i++ ){if( remapFrom[i] ){remapFromIsFilled = true;break;}}onRemapConfirmed( remapFromIsFilled ? remapFrom : null, remapTo, skipIgnoredMaterials );}Close();}GUILayout.EndHorizontal();GUILayout.Space( 5f );}}private const string HELP_TEXT ="- Extract: material will be extracted to the destination folder\n" +"- Remap: material will be remapped to an existing material asset" +
#if UNITY_2019_1_OR_NEWER" (when Remap is the default value, then it means that a material that satisfies 'Default Material Remap Conditions' was found)" +
#endif". If Remap points to an embedded material, then that embedded material will first be extracted\n" +"- Ignore: material's current value will stay intact (when Ignore is the default value, either the material couldn't be found " +"or it was already extracted)";private static readonly GUILayoutOption GL_WIDTH_25 = GUILayout.Width( 25f );private readonly GUILayoutOption GL_WIDTH_75 = GUILayout.Width( 75f );private readonly GUILayoutOption GL_MIN_WIDTH_50 = GUILayout.MinWidth( 50f );private string materialsFolder = "Assets/Materials";private List<ExtractData> modelData = new List<ExtractData>( 16 );#if UNITY_2019_1_OR_NEWERprivate bool remappedMaterialNamesMustMatch = false;private bool remappedMaterialPropertiesMustMatch = true;private bool dontRemapExtractedMaterials = true;private bool dontRemapMaterialsAcrossDifferentModels = false;
#endifprivate bool inModelSelectionPhase = true;private Rect remapAllButtonRect;private static RemapAllPopup remapAllPopup;private Vector2 scrollPos;[MenuItem( "Window/Batch Extract Materials" )]private static void Init(){BatchExtractMaterials window = GetWindow<BatchExtractMaterials>();window.titleContent = new GUIContent( "Extract Materials" );window.minSize = new Vector2( 300f, 120f );window.Show();}private void OnDestroy(){// Close RemapAllPopup with this windowRemapAllPopup.Hide();}private void OnFocus(){// Don't let RemapAllPopup be obstructed by this window// We are using delayCall because otherwise clicking an ObjectField in this window doesn't highlight that material in the Project windowEditorApplication.delayCall += () =>{if( remapAllPopup )remapAllPopup.Focus();};}private void OnGUI(){scrollPos = EditorGUILayout.BeginScrollView( scrollPos );GUI.enabled = inModelSelectionPhase;DrawDestinationPathField();DrawModelsToProcessList();DrawMaterialRemapConditionsField();GUI.enabled = true;bool modelsToProcessListIsFilled = modelData.Find( ( data ) => data.model ) != null;if( inModelSelectionPhase ){GUI.enabled = modelsToProcessListIsFilled && !string.IsNullOrEmpty( materialsFolder ) && materialsFolder.StartsWith( "Assets" );if( GUILayout.Button( "Next" ) ){inModelSelectionPhase = false;CalculateRemappedMaterials();GUIUtility.ExitGUI();}}else{DrawMaterialRemapList();GUILayout.BeginHorizontal();if( GUILayout.Button( "Back" ) ){inModelSelectionPhase = true;RemapAllPopup.Hide();GUIUtility.ExitGUI();}Color c = GUI.backgroundColor;GUI.backgroundColor = Color.green;GUI.enabled = modelsToProcessListIsFilled;if( GUILayout.Button( "Extract!" ) ){inModelSelectionPhase = true;RemapAllPopup.Hide();ExtractMaterials();GUIUtility.ExitGUI();}GUI.backgroundColor = c;GUILayout.EndHorizontal();}GUI.enabled = true;EditorGUILayout.Space();EditorGUILayout.EndScrollView();}private void DrawDestinationPathField(){Event ev = Event.current;GUILayout.BeginHorizontal();materialsFolder = EditorGUILayout.TextField( "Extract Materials To", materialsFolder );// Allow drag & dropping a folder to the text field// Credit: https://answers.unity.com/answers/657877/view.htmlif( ( ev.type == EventType.DragPerform || ev.type == EventType.DragUpdated ) && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) ){DragAndDrop.visualMode = DragAndDropVisualMode.Copy;if( ev.type == EventType.DragPerform ){DragAndDrop.AcceptDrag();string[] draggedFiles = DragAndDrop.paths;for( int i = 0; i < draggedFiles.Length; i++ ){if( !string.IsNullOrEmpty( draggedFiles[i] ) && AssetDatabase.IsValidFolder( draggedFiles[i] ) ){materialsFolder = draggedFiles[i];break;}}}ev.Use();}if( GUILayout.Button( "o", GL_WIDTH_25 ) ){string selectedPath = EditorUtility.OpenFolderPanel( "Choose output directory", "Assets", "" );if( !string.IsNullOrEmpty( selectedPath ) ){selectedPath = selectedPath.Replace( '\\', '/' ) + "/";int relativePathIndex = selectedPath.IndexOf( "/Assets/" ) + 1;if( relativePathIndex > 0 )materialsFolder = selectedPath.Substring( relativePathIndex, selectedPath.Length - relativePathIndex - 1 );}GUIUtility.keyboardControl = 0; // Remove focus from active text field}GUILayout.EndHorizontal();EditorGUILayout.Space();}private void DrawModelsToProcessList(){Event ev = Event.current;GUILayout.BeginHorizontal();GUILayout.Label( "Models To Process (drag & drop here)" );if( modelData.Count == 0 )modelData.Add( new ExtractData() );// Allow drag & dropping models to array// Credit: https://answers.unity.com/answers/657877/view.htmlif( ( ev.type == EventType.DragPerform || ev.type == EventType.DragUpdated ) && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) ){DragAndDrop.visualMode = DragAndDropVisualMode.Copy;if( ev.type == EventType.DragPerform ){DragAndDrop.AcceptDrag();Object[] draggedObjects = DragAndDrop.objectReferences;for( int i = 0; i < draggedObjects.Length; i++ ){if( !( draggedObjects[i] as GameObject ) || PrefabUtility.GetPrefabAssetType( draggedObjects[i] ) != PrefabAssetType.Model )continue;bool modelAlreadyExists = false;for( int j = 0; j < modelData.Count; j++ ){if( modelData[j].model == draggedObjects[i] ){modelAlreadyExists = true;break;}}if( !modelAlreadyExists ){bool replacedNullElement = false;for( int j = 0; j < modelData.Count; j++ ){if( !modelData[j].model ){modelData[j] = new ExtractData( draggedObjects[i] as GameObject );replacedNullElement = true;break;}}if( !replacedNullElement )modelData.Add( new ExtractData( draggedObjects[i] as GameObject ) );}}}ev.Use();}if( GUILayout.Button( "+", GL_WIDTH_25 ) )modelData.Insert( 0, new ExtractData() );GUILayout.EndHorizontal();for( int i = 0; i < modelData.Count; i++ ){ExtractData element = modelData[i];GUI.changed = false;GUILayout.BeginHorizontal();GameObject prevObject = element.model;GameObject newObject = EditorGUILayout.ObjectField( GUIContent.none, prevObject, typeof( GameObject ), false ) as GameObject;if( newObject && PrefabUtility.GetPrefabAssetType( newObject ) != PrefabAssetType.Model )newObject = prevObject;modelData[i].model = newObject;if( GUILayout.Button( "+", GL_WIDTH_25 ) )modelData.Insert( i + 1, new ExtractData() );if( GUILayout.Button( "-", GL_WIDTH_25 ) ){// Lists with no elements look ugly, always keep a dummy null variableif( modelData.Count > 1 )modelData.RemoveAt( i-- );elsemodelData[0] = new ExtractData();}GUILayout.EndHorizontal();}EditorGUILayout.Space();}private void DrawMaterialRemapConditionsField(){
#if UNITY_2019_1_OR_NEWEREditorGUILayout.LabelField( "Default Material Remap Conditions" );EditorGUI.indentLevel++;remappedMaterialNamesMustMatch = EditorGUILayout.ToggleLeft( "Material names must match", remappedMaterialNamesMustMatch );remappedMaterialPropertiesMustMatch = EditorGUILayout.ToggleLeft( "Material properties must match", remappedMaterialPropertiesMustMatch );dontRemapExtractedMaterials = EditorGUILayout.ToggleLeft( "Don't remap already extracted materials", dontRemapExtractedMaterials );dontRemapMaterialsAcrossDifferentModels = EditorGUILayout.ToggleLeft( "Don't remap Model A's materials to Model B (i.e. different models won't share the same materials)", dontRemapMaterialsAcrossDifferentModels );EditorGUI.indentLevel--;EditorGUILayout.Space();
#endif}private void DrawMaterialRemapList(){EditorGUILayout.HelpBox( HELP_TEXT, MessageType.Info );GUILayout.BeginHorizontal();if( GUILayout.Button( "Extract All" ) ){for( int i = 0; i < modelData.Count; i++ ){for( int j = 0; j < modelData[i].materialExtractModes.Count; j++ )modelData[i].materialExtractModes[j] = ExtractMode.Extract;}}if( GUILayout.Button( "Remap All..." ) ){RemapAllPopup.ShowAt( remapAllButtonRect, new Vector2( 325f, 250f ), ( List<Material> remapFrom, Material remapTo, bool skipIgnoredMaterials ) =>{for( int i = 0; i < modelData.Count; i++ ){ExtractData data = modelData[i];for( int j = 0; j < data.remappedMaterials.Count; j++ ){switch( data.materialExtractModes[j] ){case ExtractMode.Extract:{if( remapFrom == null || ( data.originalMaterials[j] && remapFrom.Contains( data.originalMaterials[j] ) ) ){data.materialExtractModes[j] = ExtractMode.Remap;data.remappedMaterials[j] = remapTo;}break;}case ExtractMode.Remap:{if( remapFrom == null || ( data.remappedMaterials[j] && remapFrom.Contains( data.remappedMaterials[j] ) ) )data.remappedMaterials[j] = remapTo;break;}case ExtractMode.Ignore:{if( !skipIgnoredMaterials && ( remapFrom == null || ( data.originalMaterials[j] && remapFrom.Contains( data.originalMaterials[j] ) ) ) ){data.materialExtractModes[j] = ExtractMode.Remap;data.remappedMaterials[j] = remapTo;}break;}}}}Repaint();} );}if( Event.current.type == EventType.Repaint )remapAllButtonRect = GUILayoutUtility.GetLastRect();if( GUILayout.Button( "Ignore All" ) ){for( int i = 0; i < modelData.Count; i++ ){for( int j = 0; j < modelData[i].materialExtractModes.Count; j++ )modelData[i].materialExtractModes[j] = ExtractMode.Ignore;}}GUILayout.EndHorizontal();EditorGUILayout.Space();for( int i = 0; i < modelData.Count; i++ ){ExtractData data = modelData[i];if( !data.model )continue;GUI.enabled = false;EditorGUILayout.ObjectField( GUIContent.none, data.model, typeof( GameObject ), false );GUI.enabled = true;if( data.originalMaterials.Count == 0 )EditorGUILayout.LabelField( "This model has no materials..." );for( int j = 0; j < data.originalMaterials.Count; j++ ){GUILayout.BeginHorizontal();EditorGUILayout.PrefixLabel( data.materialNames[j] );data.materialExtractModes[j] = (ExtractMode) EditorGUILayout.EnumPopup( GUIContent.none, data.materialExtractModes[j], GL_WIDTH_75 );if( data.materialExtractModes[j] == ExtractMode.Remap ){EditorGUI.BeginChangeCheck();data.remappedMaterials[j] = EditorGUILayout.ObjectField( GUIContent.none, data.remappedMaterials[j], typeof( Material ), false, GL_MIN_WIDTH_50 ) as Material;if( EditorGUI.EndChangeCheck() && ( !data.remappedMaterials[j] || data.remappedMaterials[j] == data.originalMaterials[j] ) )data.materialExtractModes[j] = ExtractMode.Ignore;}else{GUI.enabled = false;EditorGUILayout.ObjectField( GUIContent.none, data.originalMaterials[j], typeof( Material ), false, GL_MIN_WIDTH_50 );GUI.enabled = true;}GUILayout.EndHorizontal();}EditorGUILayout.Space();}}private void CalculateRemappedMaterials(){
#if UNITY_2019_1_OR_NEWER// Key: Material CRC (material.ComputeCRC)// Value: All materials sharing that CRCDictionary<int, HashSet<Material>> duplicateMaterialsLookup = new Dictionary<int, HashSet<Material>>( modelData.Count * 8 );// Add all existing materials at materialsFolder to the lookup tableif( !dontRemapMaterialsAcrossDifferentModels && Directory.Exists( materialsFolder ) ){string[] existingMaterialPaths = Directory.GetFiles( materialsFolder, "*.mat", SearchOption.TopDirectoryOnly );for( int i = 0; i < existingMaterialPaths.Length; i++ ){Material material = AssetDatabase.LoadMainAssetAtPath( existingMaterialPaths[i] ) as Material;if( material )GetMaterialsWithCRC( duplicateMaterialsLookup, material ).Add( material );}}
#endiffor( int i = 0; i < modelData.Count; i++ ){ExtractData data = modelData[i];if( !data.model ){modelData.RemoveAt( i-- );continue;}string modelPath = AssetDatabase.GetAssetPath( data.model );ModelImporter modelImporter = AssetImporter.GetAtPath( modelPath ) as ModelImporter;if( !modelImporter ){Debug.LogWarning( "Couldn't get ModelImporter from asset: " + AssetDatabase.GetAssetPath( data.model ), data.model );modelData.RemoveAt( i-- );continue;}// Reset previously assigned values to this entry (if any)data = modelData[i] = new ExtractData( data.model );Object[] embeddedAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( modelPath );List<Material> embeddedMaterials = new List<Material>( embeddedAssets.Length );for( int j = 0; j < embeddedAssets.Length; j++ ){Material embeddedMaterial = embeddedAssets[j] as Material;if( embeddedMaterial )embeddedMaterials.Add( embeddedMaterial );}// Get the model's current material remapping// Credit: https://forum.unity.com/threads/batch-change-all-fbx-default-materials-help.626341/#post-6530939using( SerializedObject so = new SerializedObject( modelImporter ) ){SerializedProperty materials = so.FindProperty( "m_Materials" );SerializedProperty externalObjects = so.FindProperty( "m_ExternalObjects" );for( int materialIndex = 0; materialIndex < materials.arraySize; materialIndex++ ){SerializedProperty id = materials.GetArrayElementAtIndex( materialIndex );string name = id.FindPropertyRelative( "name" ).stringValue;string type = id.FindPropertyRelative( "type" ).stringValue;Material material = null;for( int externalObjectIndex = 0; externalObjectIndex < externalObjects.arraySize; externalObjectIndex++ ){SerializedProperty pair = externalObjects.GetArrayElementAtIndex( externalObjectIndex );string externalName = pair.FindPropertyRelative( "first.name" ).stringValue;string externalType = pair.FindPropertyRelative( "first.type" ).stringValue;if( externalType == type && externalName == name && ( pair = pair.FindPropertyRelative( "second" ) ) != null ){material = pair.objectReferenceValue as Material;break;}}if( !material )material = embeddedMaterials.Find( ( m ) => m.name == name );data.materialNames.Add( name );data.originalMaterials.Add( material );if( !material ){data.materialExtractModes.Add( ExtractMode.Ignore );data.remappedMaterials.Add( null );}else{bool materialAlreadyExtracted = AssetDatabase.IsMainAsset( material );
#if UNITY_2019_1_OR_NEWERHashSet<Material> duplicateMaterials = GetMaterialsWithCRC( duplicateMaterialsLookup, material );Material remappedMaterial = null;// - Material was already extracted: remap the material only if 'dontRemapExtractedMaterials' is false// - 'dontRemapMaterialsAcrossDifferentModels' is true: only remap with a material from the same model// - 'remappedMaterialPropertiesMustMatch' is true: only remap with a material whose properties match//   the current material's properties// - Only 'remappedMaterialNamesMustMatch' is true: remap with a material with the same name; properties//   of the two materials may not matchif( !materialAlreadyExtracted || !dontRemapExtractedMaterials ){if( remappedMaterialPropertiesMustMatch ){foreach( Material _material in duplicateMaterials ){if( _material.name == name || ( !remappedMaterial && !remappedMaterialNamesMustMatch ) )remappedMaterial = _material;}}else if( remappedMaterialNamesMustMatch )remappedMaterial = GetMaterialWithName( duplicateMaterialsLookup, name );}if( remappedMaterial && remappedMaterial != material ){data.materialExtractModes.Add( ExtractMode.Remap );data.remappedMaterials.Add( remappedMaterial );}else
#endif{data.materialExtractModes.Add( materialAlreadyExtracted ? ExtractMode.Ignore : ExtractMode.Extract );data.remappedMaterials.Add( null );#if UNITY_2019_1_OR_NEWERduplicateMaterials.Add( material );
#endif}}}}#if UNITY_2019_1_OR_NEWERif( dontRemapMaterialsAcrossDifferentModels )duplicateMaterialsLookup.Clear();
#endif}}#if UNITY_2019_1_OR_NEWERprivate HashSet<Material> GetMaterialsWithCRC( Dictionary<int, HashSet<Material>> lookup, Material material ){int crcHash = material.ComputeCRC();HashSet<Material> result;if( !lookup.TryGetValue( crcHash, out result ) )lookup[crcHash] = result = new HashSet<Material>();return result;}private Material GetMaterialWithName( Dictionary<int, HashSet<Material>> lookup, string name ){foreach( HashSet<Material> allMaterials in lookup.Values ){foreach( Material material in allMaterials ){if( material.name == name )return material;}}return null;}
#endifprivate void ExtractMaterials(){if( materialsFolder.EndsWith( "/" ) )materialsFolder = materialsFolder.Substring( 0, materialsFolder.Length - 1 );if( !Directory.Exists( materialsFolder ) ){Directory.CreateDirectory( materialsFolder );AssetDatabase.ImportAsset( materialsFolder, ImportAssetOptions.ForceUpdate );}List<AssetImporter> dirtyModelImporters = new List<AssetImporter>( modelData.Count );Dictionary<Material, Material> extractedMaterials = new Dictionary<Material, Material>( modelData.Count * 8 );for( int i = 0; i < modelData.Count; i++ ){ExtractData data = modelData[i];if( !data.model )continue;AssetImporter modelImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( data.model ) );// Remap/extract the model's materials// Credit: https://forum.unity.com/threads/batch-change-all-fbx-default-materials-help.626341/#post-6530939using( SerializedObject so = new SerializedObject( modelImporter ) ){SerializedProperty materials = so.FindProperty( "m_Materials" );SerializedProperty externalObjects = so.FindProperty( "m_ExternalObjects" );for( int materialIndex = 0; materialIndex < materials.arraySize; materialIndex++ ){SerializedProperty id = materials.GetArrayElementAtIndex( materialIndex );string name = id.FindPropertyRelative( "name" ).stringValue;string type = id.FindPropertyRelative( "type" ).stringValue;// j: index of the target material in data's listsint j = ( materialIndex < data.materialNames.Count && data.materialNames[materialIndex] == name ) ? materialIndex : data.materialNames.IndexOf( name );if( j < 0 ){// This can only occur if user reimports the model with more materials when 'inModelSelectionPhase' is falseDebug.LogWarning( data.model.name + "." + name + " material has no matching data, skipped", data.model );continue;}Material targetMaterial = null;switch( data.materialExtractModes[j] ){case ExtractMode.Extract:{if( data.originalMaterials[j] && !AssetDatabase.IsMainAsset( data.originalMaterials[j] ) )targetMaterial = data.originalMaterials[j];elseDebug.LogWarning( data.model.name + "." + name + " isn't extracted because either the material doesn't exist or it is already extracted", data.model );break;}case ExtractMode.Remap:{if( data.remappedMaterials[j] && ( data.originalMaterials[j] != data.remappedMaterials[j] || !AssetDatabase.IsMainAsset( data.remappedMaterials[j] ) ) )targetMaterial = data.remappedMaterials[j];elseDebug.LogWarning( data.model.name + "." + name + " isn't remapped because either the material doesn't exist or it is already extracted", data.model );break;}}if( !targetMaterial )continue;else if( !AssetDatabase.IsMainAsset( targetMaterial ) ){Material extractedMaterial;if( !extractedMaterials.TryGetValue( targetMaterial, out extractedMaterial ) ){extractedMaterials[targetMaterial] = extractedMaterial = new Material( targetMaterial );AssetDatabase.CreateAsset( extractedMaterial, AssetDatabase.GenerateUniqueAssetPath( materialsFolder + "/" + targetMaterial.name + ".mat" ) );}targetMaterial = extractedMaterial;}SerializedProperty materialProperty = null;for( int externalObjectIndex = 0; externalObjectIndex < externalObjects.arraySize; externalObjectIndex++ ){SerializedProperty pair = externalObjects.GetArrayElementAtIndex( externalObjectIndex );string externalName = pair.FindPropertyRelative( "first.name" ).stringValue;string externalType = pair.FindPropertyRelative( "first.type" ).stringValue;if( externalType == type && externalName == name ){materialProperty = pair.FindPropertyRelative( "second" );break;}}if( materialProperty == null ){SerializedProperty currentSerializedProperty = externalObjects.GetArrayElementAtIndex( externalObjects.arraySize++ );currentSerializedProperty.FindPropertyRelative( "first.name" ).stringValue = name;currentSerializedProperty.FindPropertyRelative( "first.type" ).stringValue = type;currentSerializedProperty.FindPropertyRelative( "first.assembly" ).stringValue = id.FindPropertyRelative( "assembly" ).stringValue;currentSerializedProperty.FindPropertyRelative( "second" ).objectReferenceValue = targetMaterial;}elsematerialProperty.objectReferenceValue = targetMaterial;}if( so.hasModifiedProperties ){dirtyModelImporters.Add( modelImporter );so.ApplyModifiedPropertiesWithoutUndo();}}}for( int i = 0; i < dirtyModelImporters.Count; i++ )dirtyModelImporters[i].SaveAndReimport();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac电脑窗口管理Magnet中文 for mac

Magnet是一款Mac窗口管理工具&#xff0c;它可以帮助用户轻松管理打开的窗口&#xff0c;提高多任务处理效率。以下是Magnet的一些主要特点和功能&#xff1a; 分屏模式支持&#xff1a;Magnet支持多种分屏模式&#xff0c;包括左/右/顶部/底部 1/2 分屏、左/中/右 1/3 分屏、…

计算机网络【CN】TCP报文段格式【20B】

序号&#xff1a;本报文段所发送的数据的第一个字节的序号确认号&#xff1a;期望收到对方下一个报文段的第一个数据字节的序号。 重要控制位&#xff1a; 紧急位URG&#xff1a;URG1时&#xff0c;标明此报文段中有紧急数据&#xff0c;是高优先级的数据&#xff0c;应尽快传送…

【斗罗二】霍雨浩迷惑审查,戴华斌故意挑衅,惨败者屈服下跪

【侵权联系删除】【文/郑尔巴金】 深度爆料&#xff0c;自《绝世唐门》宣布问世以来&#xff0c;其在国漫圈引发的关注和热议便如火如荼。作为《斗罗大陆》的续作&#xff0c;这部作品无疑继承了前作的荣光&#xff0c;甚至被无数粉丝期待着能再创辉煌。在各大社交媒体和国漫论…

【鸿蒙软件开发】ArkTS基础组件之TextTimer(文本显示计时)、TimePicker(时间选择)

文章目录 前言一、TextTimer1.1 子组件1.2 接口参数TextTimerController 1.3 属性1.4 事件1.5 示例代码 二、TimePicker2.1 子组件2.2 接口参数 2.3 属性2.4 事件TimePickerResult对象说明 2.5 示例代码 总结 前言 通过文本显示计时信息并控制其计时器状态的组件。 时间选择组…

基于单片机的IC卡门禁系统设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、主要研究内容及总体设计方案1.1 系统方案设计1.2系统工作原理 二、硬件设计2.1 主控电路 三、软件设计3.2主程序设计实物附录1 原理图附录2 源程序清单 四、 结论五、 文章目录 概要 本论文重点通过对射频技术…

『Jmeter入门万字长文』 | 从环境搭建、脚本设计、执行步骤到生成监控报告完整过程

『Jmeter入门万字长文』 | 从环境搭建、脚本设计、执行步骤到生成监控报告完整过程 1 Jmeter安装1.1 下载安装1.2 Jmeter汉化1.2.1 临时修改1.2.2 永久修改 1.3 验证环境 2 测试对象2.1 测试对象说明2.2 测试对象安装2.2.1 下载安装2.2.2 启动测试对象服务2.2.3 访问测试对象2.…

QA新人入职任务

一、背景 分享记录一下入职新公司后&#xff0c;新人第一周接到的新手任务&#xff0c;回顾总结&#xff0c;方便自己成长和思考~ 二、新人任务说明 题目1&#xff1a;接口相关 题目2&#xff1a;UI相关 UI原型图 三、任务要求 1、根据题目2原型图&#xff0c;进行UI测试…

《ATTCK视角下的红蓝对抗实战指南》一本书构建完整攻防知识体系

一. 网络安全现状趋势分析 根据中国互联网络信息中心&#xff08;CNNIC&#xff09;发布的第51次《中国互联网络发展状况统计报告》&#xff0c;截至2022年12月&#xff0c;我国网民规模为10.67亿&#xff0c;互联网普及率达75.6%。我国有潜力建设全球规模最大、应用渗透最强的…

软考系列(系统架构师)- 2009年系统架构师软考案例分析考点

试题一 软件架构设计 【问题1】&#xff08;9分&#xff09; 软件质量属性是影响软件架构设计的重要因素。请用200字以内的文字列举六种不同的软件质量属性名称并解释其含义。 常见的软件质量属性有多种&#xff0c;例如性能&#xff08;Performance)、可用性&#xff08;Ava…

贪吃蛇-c语言版本

目录 前言 贪吃蛇游戏设计与分析 设计目标&#xff1a; 设计思想&#xff1a; 坐标问题&#xff1a; 字符问题&#xff1a; 小拓展&#xff1a;C语⾔的国际化特性 本地化头文件&#xff1a; 类项 setlocale函数&#xff1a; 宽字符打印&#xff1a; 地图坐标: &am…

JAVA中的垃圾回收器(1)

一)垃圾回收器概述: 1.1)按照线程数来区分: 串行回收指的是在同一时间端内只允许有一个CPU用于执行垃圾回收操作&#xff0c;此时工作线程被暂停&#xff0c;直至垃圾回收工作结束&#xff0c;在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合&#xff0c;出行…

Kafka KRaft模式探索

1.概述 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。其核心组件包含Producer、Broker、Consumer&#xff0c;以及依赖的Zookeeper集群。其中Zookeeper集群是Kafka用来负责集群元数据的管理、控制器的选举等。 2.内容…

python爬虫request和BeautifulSoup使用

request使用 1.安装request pip install request2.引入库 import requests3.编写代码 发送请求 我们通过以下代码可以打开豆瓣top250的网站 response requests.get(f"https://movie.douban.com/top250"&#xff09;但因为该网站加入了反爬机制&#xff0c;所以…

非遗主题网站的设计与实现基于PHP实现

包括源码参考论文 下载地址: https://juzhendongli.store/commodity/details/18

Linux Centos7安装后,无法查询到IP地址,无ens0,只有lo和ens33的解决方案

文章目录 前言1 查看network-scripts目录2 创建并配置 ifcfg-ens33 文件3 禁用NetworkManager4 重新启动网络服务总结 前言 在VMware中&#xff0c;安装Linux centos7操作系统后&#xff0c;想查询本机的IP地址&#xff0c;执行ifconfig命令 ifconfig结果如下&#xff1a; 结…

【Linux】权限完结

个人主页点击直达&#xff1a;小白不是程序媛 系列专栏&#xff1a;Linux被操作记 目录 前言 chown指令 chgrp指令 文件类型 file指令 目录的权限 粘滞位 umask指令 权限总结 前言 上篇文章我们说到对于一个文件所属者和所属组都是同一个人时&#xff0c;使用所属者身…

【NLP】word复制指定内容到新的word文档

目录 1.python代码 2.结果 需求&#xff1a; 复制word文档里的两个关键字&#xff08;例如“起始位置”到“结束位置”&#xff09;之间的内容到新的word文档。 前提&#xff1a;安装win32包&#xff0c;通过pip install pywin32命令直接安装。话不多说&#xff0c;直接上代码…

RSA:基于小加密指数的攻击方式与思维技巧

目录 目录 目录 零、前言 一、小加密指数爆破 [FSCTF]RSA签到 思路&#xff1a; 二、基于小加密指数的有限域开根 [NCTF 2019]easyRSA 思路&#xff1a; 三、基于小加密指数的CRT [0CTF 2016] rsa 思路&#xff1a; 零、前言 最近&#xff0c;发现自己做题思路比较…

设计模式之桥梁模式

什么是桥梁模式 桥梁模式&#xff08;Bridge Pattern&#xff09;也称为桥接模式&#xff0c;属于结构型模式&#xff0c;它主要目的是通过组合的方式建立两个类之间的联系&#xff0c;而不是继承。桥梁模式将抽象部分与它的具体实现部分分离&#xff0c;使它们都可以独立地变…

GnuTLS recv error (-110): The TLS connection was non-properly terminated

ubuntu git下载提示 GnuTLS recv error (-110): The TLS connection was non-properly terminated解决方法 git config --global --unset http.https://github.com.proxy