public override bool OnGUI(string targetName, ref bool _dnaDirty, ref bool _textureDirty, ref bool _meshDirty)
            {
                var context = UMAContextBase.Instance;

                if (context == null)
                {
                    var _errorMessage = "Editing a recipe requires a loaded scene with a valid UMAContextBase.";
                    Debug.LogWarning(_errorMessage);
                    return(false);
                }
                bool changed = forceGUIUpdate;

                //Make a foldout for WardrobeSets - the UI for an individual WardrobeSet is added for each compatible race in the collection
                GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                GUILayout.Space(10);
                bool wsfoldoutOpen = OpenSlots["wardrobeSets"];

                wsfoldoutOpen             = EditorGUILayout.Foldout(OpenSlots["wardrobeSets"], "Wardrobe Sets");
                OpenSlots["wardrobeSets"] = wsfoldoutOpen;
                GUILayout.EndHorizontal();
                if (wsfoldoutOpen)
                {
                    GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));

                    EditorGUILayout.HelpBox("Wardrobe Sets are added for each 'Compatible Race' assigned above. 'SharedColors' in this section are derived from all the recipes assigned in the set and are will be applied to the Avatar when the wardrobe sets recipes are added.", MessageType.Info);
                    if (_compatibleRaces.Count > 0)
                    {
                        //dont show shared colors unless there are 'FullOutfits' to apply them to
                        if (_sharedColorsEditor.OnGUI(_recipe))
                        {
                            changed       = true;
                            _textureDirty = true;
                        }
                        for (int i = 0; i < _compatibleRaces.Count; i++)
                        {
                            var thisRace = context.GetRace(_compatibleRaces[i]);
                            if (thisRace != null)
                            {
                                GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                                GUILayout.Space(10);
                                bool foldoutOpen = OpenSlots[_compatibleRaces[i]];
                                foldoutOpen = EditorGUILayout.Foldout(OpenSlots[_compatibleRaces[i]], " Wardrobe Set: " + _compatibleRaces[i]);
                                OpenSlots[_compatibleRaces[i]] = foldoutOpen;
                                GUILayout.EndHorizontal();
                                if (foldoutOpen)
                                {
                                    var thisSetEditor = new WardrobeSetEditor(thisRace, _wardrobeCollection[thisRace.raceName], _recipe, false);
                                    if (thisSetEditor.OnGUI())
                                    {
                                        _wardrobeCollection[thisRace.raceName] = thisSetEditor.WardrobeSet;
                                        changed = true;
                                    }
                                }
                            }
                            else
                            {
                                //Do the foldout thing but show as 'missing'
                                GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                                GUILayout.Space(10);
                                bool foldoutOpen = OpenSlots[_compatibleRaces[i]];
                                foldoutOpen = EditorGUILayout.Foldout(OpenSlots[_compatibleRaces[i]], _compatibleRaces[i] + " Wardrobe Set (Missing)");
                                OpenSlots[_compatibleRaces[i]] = foldoutOpen;
                                GUILayout.EndHorizontal();
                                if (foldoutOpen)
                                {
                                    GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                                    EditorGUILayout.HelpBox("_compatibleRaces[i] could not be located by the Dynamic Race Library", MessageType.Warning);
                                    GUIHelper.EndVerticalPadded(10);
                                }
                            }
                        }
                    }
                    else
                    {
                        EditorGUILayout.HelpBox("Drag in compatible races at the top of this recipe and WardrobeSets for those races will show here", MessageType.Info);
                    }
                    GUIHelper.EndVerticalPadded(10);
                }
                GUILayout.Space(10);
                //the Arbitrary Recipes section
                GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                GUILayout.Space(10);
                bool arbiOpen = OpenSlots["arbitraryRecipes"];

                arbiOpen = EditorGUILayout.Foldout(OpenSlots["arbitraryRecipes"], "Arbitrary Recipes");
                OpenSlots["arbitraryRecipes"] = arbiOpen;
                Rect dropArea = new Rect();

                GUILayout.EndHorizontal();
                if (arbiOpen)
                {
                    GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                    EditorGUILayout.HelpBox("Drop recipes in to this area to create a collection that is not a full outfit or connected to any given race, for example a 'Hair Styles' pack or 'Tattoos' pack.", MessageType.Info);
                    dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true));
                    GUI.Box(dropArea, "Drag WardrobeRecipes here. " + recipesAddErrMsg);
                    if (_arbitraryRecipes.Count > 0)
                    {
                        for (int i = 0; i < _arbitraryRecipes.Count; i++)
                        {
                            GUILayout.Space(2f);
                            GUI.enabled = false;                             //we readonly to prevent typos
                            Rect crfRect    = GUILayoutUtility.GetRect(0.0f, EditorGUIUtility.singleLineHeight, GUILayout.ExpandWidth(true));
                            Rect crfDelRect = crfRect;
                            crfRect.width    = crfRect.width - 20f - 5f;
                            crfDelRect.width = 20f + 2f;
                            crfDelRect.x     = crfRect.width + 20f + 10f;
                            EditorGUI.TextField(crfRect, _arbitraryRecipes[i]);
                            GUI.enabled = true;
                            if (GUI.Button(crfDelRect, "X"))
                            {
                                _arbitraryRecipes.RemoveAt(i);
                                changed = true;
                            }
                        }
                    }
                    GUIHelper.EndVerticalPadded(10);
                    if (AddRecipesDropAreaGUI(ref recipesAddErrMsg, dropArea, _arbitraryRecipes))
                    {
                        changed = true;
                    }
                }
                return(changed);
            }
 private void DrawControllersHeader(Rect rect, string[] help, ref bool _isExpanded, ref bool _helpExpanded)
 {
     //GUIHelper.ToolbarStyleHeader(rect, new GUIContent(_dnaConvertersLabel.ToUpper()), _help, ref _helpExpanded);
     GUIHelper.ToolbarStyleFoldout(rect, new GUIContent(_dnaConvertersLabel.ToUpper()), _help, ref _isExpanded, ref _helpExpanded);
     //_isExpanded = true;
 }
        //Draws the converters in the 'By DNA' view
        private void DrawConvertersByDNA()
        {
            var           inUseNames = _target.GetUsedDNANames();
            List <string> namesToDraw;

            //if we have a dnanames asset we will loop through those names
            if (_dnaAsset != null && _dnaAsset.Names.Length > 0)
            {
                namesToDraw = new List <string>(_dnaAsset.Names);
            }
            //otherwise we will used the inUseNames from the plugins
            else
            {
                namesToDraw = inUseNames;
            }

            //Draw the output
            if (namesToDraw.Count == 0)
            {
                GUIHelper.BeginVerticalPadded(3, new Color(0.75f, 0.875f, 1f, 0.3f));
                if (_target.PluginCount == 0)
                {
                    EditorGUILayout.HelpBox("No plugins have been added yet. Use the 'Add' tool below to add some", MessageType.Info);
                }
                else
                {
                    EditorGUILayout.HelpBox("No plugins have been set up to use any dnaNames yet. Switch to the other view to add them", MessageType.Info);
                }
                GUIHelper.EndVerticalPadded(3);
            }
            else
            {
                GUIHelper.BeginVerticalPadded(3, new Color(0.75f, 0.875f, 1f, 0.3f));

                //Draw the search field
                var activeNamesToDraw = DrawDNASearchArea(EditorGUILayout.GetControlRect(), namesToDraw);

                DynamicDNAPlugin plugin;

                for (int i = 0; i < activeNamesToDraw.Count; i++)
                {
                    if (!_expandedDNANames.ContainsKey(activeNamesToDraw[i]))
                    {
                        _expandedDNANames.Add(activeNamesToDraw[i], false);
                    }
                    GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                    EditorGUI.indentLevel++;
                    _expandedDNANames[activeNamesToDraw[i]] = EditorGUILayout.Foldout(_expandedDNANames[activeNamesToDraw[i]], activeNamesToDraw[i]);
                    EditorGUI.indentLevel--;
                    GUILayout.EndHorizontal();
                    if (_expandedDNANames[activeNamesToDraw[i]])
                    {
                        GUI.color = new Color(0.75f, 0.875f, 1f, 0.3f);
                        GUILayout.BeginVertical(_pluginsByDNAAreaStyle);
                        GUI.color = Color.white;

                        //these would be a reorderable list in the other view so draw a reorderable list box around this whole list so it looks the same
                        //(plugins are not reorderable in this view because that would sort of suggest you can have plugins output in a different order
                        //depending on the dna name- which you cant)
                        GUILayout.BeginVertical(_pluginChooserAreaStyle);

                        for (int pi = 0; pi < _target.PluginCount; pi++)
                        {
                            plugin = _target.GetPlugin(pi);

                            if (plugin == null)
                            {
                                continue;
                            }

                            //make a space like the other view
                            if (pi > 0)
                            {
                                GUILayout.Space(EditorGUIUtility.standardVerticalSpacing * 2);
                            }

                            //tell the plugin to draw its entry for this dna name, plugins might use more than one dna name so its up to their drawers to sort out what to draw
                            //the general idea is that if this dna name appears anywhere in the plugin, then it should draw the relevant entry
                            _pluginsEditors[plugin].OnInspectorForDNAGUI(activeNamesToDraw[i]);
                        }

                        GUILayout.EndVertical();

                        GUILayout.EndVertical();
                    }
                }
                //TODO after we have drawn all the namesToDraw if there are any inUseNames that have not been drawn, draw those too
                GUIHelper.EndVerticalPadded(3);
            }
        }
Ejemplo n.º 4
0
        public bool AddExtraStuff()
        {
            SerializedProperty baseRaceRecipe = serializedObject.FindProperty("baseRaceRecipe");

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(baseRaceRecipe, true);
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
            }
            if (wardrobeSlotList == null)
            {
                InitWardrobeSlotList();
            }

            EditorGUILayout.Space();

            EditorGUI.BeginChangeCheck();
            wardrobeSlotList.DoLayoutList();
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
                if (!race.ValidateWardrobeSlots())
                {
                    EditorUtility.SetDirty(race);
                }
            }
            //new CrossCompatibilitySettings
            //To push any old settings in RaceData.backwardsCompatibleWith into the new crossCompatibilitySettings we have to call GetCrossCompatibleRaces() directly on the target
#pragma warning disable 618
            if (race.backwardsCompatibleWith.Count > 0)
            {
                var cc = race.GetCrossCompatibleRaces();
                if (cc.Count > 0)
                {
                    serializedObject.Update();
                }
            }
#pragma warning restore 618
            SerializedProperty _crossCompatibilitySettings     = serializedObject.FindProperty("_crossCompatibilitySettings");
            SerializedProperty _crossCompatibilitySettingsData = _crossCompatibilitySettings.FindPropertyRelative("settingsData");
            //draw the new version of the crossCompatibility list that allows users to define what slots in this races base recipe equate to in the backwards compatible races base recipe
            _crossCompatibilitySettings.isExpanded = EditorGUILayout.Foldout(_crossCompatibilitySettings.isExpanded, "Cross Compatibility Settings");
            if (_crossCompatibilitySettings.isExpanded)
            {
                //draw an info foldout
                EditorGUI.indentLevel++;
                _crossCompatibilitySettingsData.isExpanded = EditorGUILayout.Foldout(_crossCompatibilitySettingsData.isExpanded, "Help");
                if (_crossCompatibilitySettingsData.isExpanded)
                {
                    var helpText = "CrossCompatibilitySettings allows this race to wear wardrobe slots from another race, if this race has a wardrobe slot that the recipe is set to.";
                    helpText += " You can further configure the compatibility settings for each compatible race to define 'equivalent' slotdatas in the races' base recipes.";
                    helpText += " For example you could define that this races 'highpolyMaleChest' slotdata in its base recipe is equivalent to HumanMales 'MaleChest' slot data in its base recipe.";
                    helpText += " This would mean that any recipes which hid or applied an overlay to 'MaleChest' would hide or apply an overlay to 'highPolyMaleChest' on this race.";
                    helpText += " If 'Overlays Match' is unchecked then overlays in a recipe wont be applied.";
                    EditorGUILayout.HelpBox(helpText, MessageType.Info);
                }
                EditorGUI.indentLevel--;
                if (baseRaceRecipe.objectReferenceValue != null)
                {
                    Rect dropArea = new Rect();
                    dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true));
                    GUI.Box(dropArea, "Drag cross compatible Races here. Click to pick.");
                    CompatibleRacesDropArea(dropArea, _crossCompatibilitySettingsData);
                    EditorGUILayout.Space();
                    //update the foldouts list if the dropbox changes anything
                    if (_BCFoldouts.Length != _crossCompatibilitySettingsData.arraySize)
                    {
                        Array.Resize <bool>(ref _BCFoldouts, _crossCompatibilitySettingsData.arraySize);
                    }
                    //we need an uptodate list of the slots in THIS races base recipe
                    baseSlotsList.Clear();
                    baseSlotsNamesList.Clear();
                    //editing a race will require a context too because we need to get the base recipes and their slots
                    if (UMAContext.FindInstance() == null)
                    {
                        EditorUMAContext = UMAContext.CreateEditorContext();
                    }
                    UMAData.UMARecipe thisBaseRecipe = (baseRaceRecipe.objectReferenceValue as UMARecipeBase).GetCachedRecipe(UMAContext.Instance);
                    SlotData[]        thisBaseSlots  = thisBaseRecipe.GetAllSlots();
                    foreach (SlotData slot in thisBaseSlots)
                    {
                        if (slot != null)
                        {
                            baseSlotsList.Add(slot);
                            baseSlotsNamesList.Add(slot.slotName);
                        }
                    }
                    List <int> crossCompatibleSettingsToDelete = new List <int>();
                    //draw a foldout area for each compatible race that will show an entry for each slot in this races base recipe
                    //with a picker to choose the slot from the compatible race's base recipe that it equates to
                    for (int i = 0; i < _crossCompatibilitySettingsData.arraySize; i++)
                    {
                        bool del            = false;
                        var  thisCCSettings = _crossCompatibilitySettingsData.GetArrayElementAtIndex(i).FindPropertyRelative("ccSettings");
                        var  ccRaceName     = _crossCompatibilitySettingsData.GetArrayElementAtIndex(i).FindPropertyRelative("ccRace").stringValue;
                        //this could be missing- we should show that
                        var label = ccRaceName;
                        if (GetCompatibleRaceData(ccRaceName) == null)
                        {
                            label += " (missing)";
                        }
                        GUIHelper.FoldoutBar(ref _BCFoldouts[i], label, out del);
                        if (del)
                        {
                            crossCompatibleSettingsToDelete.Add(i);
                        }
                        if (_BCFoldouts[i])
                        {
                            DrawCCUI(ccRaceName, baseRaceRecipe, thisCCSettings);
                        }
                    }
                    if (crossCompatibleSettingsToDelete.Count > 0)
                    {
                        foreach (int del in crossCompatibleSettingsToDelete)
                        {
                            _crossCompatibilitySettingsData.DeleteArrayElementAtIndex(del);
                            serializedObject.ApplyModifiedProperties();
                        }
                    }
                }
                else
                {
                    EditorGUILayout.HelpBox("Please define this races baseRaceRecipe before trying to define its cross compatibility settings.", MessageType.Info);
                }
            }

            EditorGUILayout.Space();

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(serializedObject.FindProperty("raceThumbnails"), true);
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
            }
            return(false);
        }
Ejemplo n.º 5
0
 private void DrawCCUI(string ccRaceName, SerializedProperty baseRaceRecipe, SerializedProperty thisCCSettings)
 {
     GUIHelper.BeginVerticalPadded(5, new Color(0.75f, 0.875f, 1f));
     EditorGUILayout.LabelField("Equivalent Slots with " + ccRaceName, EditorStyles.centeredGreyMiniLabel);
     if (baseRaceRecipe.objectReferenceValue == null)
     {
         EditorGUILayout.HelpBox("Please set this Races 'Base Race Recipe' before trying to set equivalent Slots.", MessageType.Warning);
     }
     else
     {
         //we need to get the base raceRecipeSlots for this compatible race
         var ccRaceData = GetCompatibleRaceData(ccRaceName);
         if (ccRaceData != null)
         {
             if (ccRaceData.baseRaceRecipe == null)
             {
                 EditorGUILayout.HelpBox("Please set " + ccRaceData.raceName + " Races 'Base Race Recipe' before trying to set equivalent Slots.", MessageType.Warning);
             }
             else
             {
                 var ccSlotsList                = new List <SlotData>();
                 var ccSlotsNamesList           = new List <string>();
                 UMAData.UMARecipe ccBaseRecipe = ccRaceData.baseRaceRecipe.GetCachedRecipe(UMAContext.Instance);
                 SlotData[]        ccBaseSlots  = ccBaseRecipe.GetAllSlots();
                 foreach (SlotData slot in ccBaseSlots)
                 {
                     if (slot != null)
                     {
                         ccSlotsList.Add(slot);
                         ccSlotsNamesList.Add(slot.slotName);
                     }
                 }
                 //if that worked we can draw the UI for any set values and a button to add new ones
                 GUIHelper.BeginVerticalPadded(2, new Color(1f, 1f, 1f, 0.5f));
                 var headerRect             = GUILayoutUtility.GetRect(0.0f, (EditorGUIUtility.singleLineHeight * 2), GUILayout.ExpandWidth(true));
                 var slotLabelRect          = headerRect;
                 var gapRect                = headerRect;
                 var cSlotLabelRect         = headerRect;
                 var overlaysMatchLabelRect = headerRect;
                 var deleteRect             = headerRect;
                 slotLabelRect.width          = (headerRect.width - 50f - 22f - 22f) / 2;
                 gapRect.xMin                 = slotLabelRect.xMax;
                 gapRect.width                = 22f;
                 cSlotLabelRect.xMin          = gapRect.xMax;
                 cSlotLabelRect.width         = slotLabelRect.width;
                 overlaysMatchLabelRect.xMin  = cSlotLabelRect.xMax;
                 overlaysMatchLabelRect.width = 50f;
                 deleteRect.xMin              = overlaysMatchLabelRect.xMax;
                 deleteRect.width             = 22f;
                 //move this up
                 var tableHeaderStyle = EditorStyles.wordWrappedMiniLabel;
                 tableHeaderStyle.alignment = TextAnchor.MiddleCenter;
                 //we need a gui style for this that wraps the text and vertically centers it in the space
                 EditorGUI.LabelField(slotLabelRect, "This Races Slot", tableHeaderStyle);
                 EditorGUI.LabelField(gapRect, "", tableHeaderStyle);
                 EditorGUI.LabelField(cSlotLabelRect, "Compatible Races Slot", tableHeaderStyle);
                 EditorGUI.LabelField(overlaysMatchLabelRect, "Overlays Match", tableHeaderStyle);
                 GUIHelper.EndVerticalPadded(2);
                 GUIHelper.BeginVerticalPadded(2, new Color(0.75f, 0.875f, 1f));
                 if (thisCCSettings.arraySize > 0)
                 {
                     for (int ccsd = 0; ccsd < thisCCSettings.arraySize; ccsd++)
                     {
                         if (DrawCCUISetting(ccsd, thisCCSettings, ccSlotsNamesList))
                         {
                             serializedObject.ApplyModifiedProperties();
                         }
                     }
                 }
                 else
                 {
                     EditorGUILayout.LabelField("No equivalent slots defined", EditorStyles.miniLabel);
                 }
                 GUIHelper.EndVerticalPadded(2);
                 var addButtonRect = GUILayoutUtility.GetRect(0.0f, EditorGUIUtility.singleLineHeight, GUILayout.ExpandWidth(true));
                 addButtonRect.xMin  = addButtonRect.xMax - 70f;
                 addButtonRect.width = 70f;
                 if (GUI.Button(addButtonRect, "Add"))
                 {
                     thisCCSettings.InsertArrayElementAtIndex(thisCCSettings.arraySize);
                     serializedObject.ApplyModifiedProperties();
                 }
             }
         }
         else
         {
             EditorGUILayout.HelpBox("The cross compatible race " + ccRaceName + " could not be found!", MessageType.Warning);
         }
     }
     GUIHelper.EndVerticalPadded(5);
 }
        public override void OnInspectorGUI()
        {
            OverlayDataAsset od = target as OverlayDataAsset;

            if (od.lastActionTime == 0)
            {
                od.lastActionTime = Time.realtimeSinceStartup;
            }

            serializedObject.Update();

            EditorGUI.BeginChangeCheck();

            EditorGUILayout.PropertyField(_overlayName);
            EditorGUILayout.PropertyField(_overlayType);
            EditorGUILayout.PropertyField(_rect);
            EditorGUILayout.LabelField("Note: It is recommended to use UV coordinates (0.0 -> 1.0) in 2.10+ for rect fields.", EditorStyles.helpBox);

            EditorGUILayout.PropertyField(_umaMaterial);

            if (_umaMaterial != null && _umaMaterial.objectReferenceValue != null)
            {
                int textureChannelCount  = 0;
                SerializedObject tempObj = new SerializedObject(_umaMaterial.objectReferenceValue);
                _channels = tempObj.FindProperty("channels");

                if (_channels == null)
                {
                    EditorGUILayout.HelpBox("Channels not found!", MessageType.Error);
                }
                else
                {
                    textureChannelCount = _channels.arraySize;
                }

                od.textureFoldout = GUIHelper.FoldoutBar(od.textureFoldout, "Texture Channels");

                if (od.textureFoldout)
                {
                    GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                    EditorGUILayout.PropertyField(_textureList.FindPropertyRelative("Array.size"));
                    for (int i = 0; i < _textureList.arraySize; i++)
                    {
                        SerializedProperty textureElement = _textureList.GetArrayElementAtIndex(i);
                        string             materialName   = "Unknown";

                        if (i < _channels.arraySize)
                        {
                            SerializedProperty channel = _channels.GetArrayElementAtIndex(i);
                            if (channel != null)
                            {
                                SerializedProperty materialPropertyName = channel.FindPropertyRelative("materialPropertyName");
                                if (materialPropertyName != null)
                                {
                                    materialName = materialPropertyName.stringValue;
                                }
                            }
                        }

                        EditorGUILayout.PropertyField(textureElement, new GUIContent(materialName));
                    }
                    GUIHelper.EndVerticalPadded(10);
                }

                if (_textureList.arraySize != textureChannelCount)
                {
                    EditorGUILayout.HelpBox("Overlay Texture count and UMA Material channel count don't match!", MessageType.Error);
                }

                if (!_textureList.hasMultipleDifferentValues)
                {
                    bool allValid = true;
                    for (int i = 0; i < _textureList.arraySize; i++)
                    {
                        if (_textureList.GetArrayElementAtIndex(i).objectReferenceValue == null)
                        {
                            allValid = false;
                        }
                    }
                    if (!allValid)
                    {
                        EditorGUILayout.HelpBox("Not all textures in Texture List set. This overlay will only work as an additional overlay in a recipe", MessageType.Warning);
                    }
                }
            }
            else
            {
                EditorGUILayout.HelpBox("No UMA Material selected!", MessageType.Warning);
            }

            GUILayout.Space(20f);
            od.additionalFoldout = GUIHelper.FoldoutBar(od.additionalFoldout, "Additional Parameters");
            if (od.additionalFoldout)
            {
                GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                EditorGUILayout.PropertyField(_alphaMask);
                EditorGUILayout.PropertyField(_tags, true);
                EditorGUILayout.PropertyField(_occlusionEntries, true);
                GUIHelper.EndVerticalPadded(10);
            }

            serializedObject.ApplyModifiedProperties();
            if (EditorGUI.EndChangeCheck())
            {
                od.lastActionTime = Time.realtimeSinceStartup;
                od.doSave         = true;
            }
        }
Ejemplo n.º 7
0
        void OnGUI()
        {
            if (!boneListInitialized || boneList == null)
            {
                InitBoneList();
            }
            GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.85f, 1f), EditorStyles.helpBox);
            GUILayout.Label("Common Parameters", EditorStyles.boldLabel);
            normalReferenceMesh = EditorGUILayout.ObjectField("Seams Mesh (Optional)  ", normalReferenceMesh, typeof(SkinnedMeshRenderer), false) as SkinnedMeshRenderer;

            slotMaterial = EditorGUILayout.ObjectField("UMAMaterial	 ", slotMaterial, typeof(UMAMaterial), false) as UMAMaterial;
            slotFolder   = EditorGUILayout.ObjectField("Slot Destination Folder", slotFolder, typeof(UnityEngine.Object), false) as UnityEngine.Object;

            //EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
            EditorGUILayout.BeginHorizontal();
            createOverlay = EditorGUILayout.Toggle("Create Overlay", createOverlay);
            createRecipe  = EditorGUILayout.Toggle("Create Wardrobe Recipe ", createRecipe);
            EditorGUILayout.EndHorizontal();
            //EditorGUILayout.LabelField(slotName + "_Overlay");
            //EditorGUILayout.EndHorizontal();
            //EditorGUILayout.BeginHorizontal();
            //EditorGUILayout.LabelField(slotName + "_Recipe");
            //EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginHorizontal();
            binarySerialization = EditorGUILayout.Toggle(new GUIContent("Binary Serialization", "Forces the created Mesh object to be serialized as binary. Recommended for large meshes and blendshapes."), binarySerialization);
            addToGlobalLibrary  = EditorGUILayout.Toggle("Add To Global Library", addToGlobalLibrary);
            EditorGUILayout.EndHorizontal();
            calcTangents = EditorGUILayout.Toggle("Calculate Tangents", calcTangents);
            boneList.DoLayoutList();
            GUIHelper.EndVerticalPadded(10);
            DoDragDrop();

            EnforceFolder(ref slotFolder);
            //
            // For now, we will disable this option.
            // It doesn't work in most cases.
            // RootBone = EditorGUILayout.TextField("Root Bone (ex:'Global')", RootBone);
            //
            GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.85f, 1f), EditorStyles.helpBox);
            GUILayout.Label("Single Slot Processing", EditorStyles.boldLabel);

            var newslotMesh = EditorGUILayout.ObjectField("Slot Mesh  ", slotMesh, typeof(SkinnedMeshRenderer), false) as SkinnedMeshRenderer;

            if (newslotMesh != slotMesh)
            {
                errmsg   = "";
                slotMesh = newslotMesh;
            }

            slotName = EditorGUILayout.TextField("Slot Name", slotName);



            if (GUILayout.Button("Verify Slot"))
            {
                if (slotMesh == null)
                {
                    errmsg = "Slot is null.";
                }
                else
                {
                    Vector2[] uv = slotMesh.sharedMesh.uv;
                    foreach (Vector2 v in uv)
                    {
                        if (v.x > 1.0f || v.x < 0.0f || v.y > 1.0f || v.y < 0.0f)
                        {
                            errmsg = "UV Coordinates are out of range and will likely have issues with atlassed materials. Textures should not be tiled unless using non-atlassed materials.";
                            break;
                        }
                    }
                    if (string.IsNullOrEmpty(errmsg))
                    {
                        errmsg = "No errors found";
                    }
                }
            }

            if (!string.IsNullOrEmpty(errmsg))
            {
                EditorGUILayout.HelpBox(errmsg, MessageType.Warning);
            }

            if (GUILayout.Button("Create Slot"))
            {
                Debug.Log("Processing...");
                SlotDataAsset sd = CreateSlot();
                if (sd != null)
                {
                    Debug.Log("Success.");
                    string AssetPath = AssetDatabase.GetAssetPath(sd.GetInstanceID());
                    if (addToGlobalLibrary)
                    {
                        UMAAssetIndexer.Instance.EvilAddAsset(typeof(SlotDataAsset), sd);
                    }
                    OverlayDataAsset od = null;
                    if (createOverlay)
                    {
                        od = CreateOverlay(AssetPath.Replace(sd.name, sd.slotName + "_Overlay"), sd);
                    }
                    if (createRecipe)
                    {
                        CreateRecipe(AssetPath.Replace(sd.name, sd.slotName + "_Recipe"), sd, od);
                    }
                }
            }

            GUIHelper.EndVerticalPadded(10);

            GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
            GUILayout.Space(10);
            showTags = EditorGUILayout.Foldout(showTags, "Tags");
            GUILayout.EndHorizontal();
            if (showTags)
            {
                GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f), EditorStyles.helpBox);
                // Draw the button area
                GUILayout.BeginHorizontal();
                if (GUILayout.Button("Add Tag", GUILayout.Width(80)))
                {
                    Tags.Add("");
                    Repaint();
                }

                GUILayout.Label(Tags.Count + " Tags defined");
                GUILayout.EndHorizontal();

                if (Tags.Count == 0)
                {
                    GUILayout.Label("No tags defined", EditorStyles.helpBox);
                }
                else
                {
                    int del = -1;

                    for (int i = 0; i < Tags.Count; i++)
                    {
                        GUILayout.BeginHorizontal();
                        Tags[i] = GUILayout.TextField(Tags[i]);
                        if (GUILayout.Button("\u0078", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
                        {
                            del = i;
                        }
                        GUILayout.EndHorizontal();
                    }
                    if (del >= 0)
                    {
                        Tags.RemoveAt(del);
                        Repaint();
                    }
                }
                // Draw the tags (or "No tags defined");
                GUIHelper.EndVerticalPadded(10);
            }


            if (slotMesh != null)
            {
                if (slotMesh.localBounds.size.x > 10.0f || slotMesh.localBounds.size.y > 10.0f || slotMesh.localBounds.size.z > 10.0f)
                {
                    EditorGUILayout.HelpBox("This slot's size is very large. It's import scale may be incorrect!", MessageType.Warning);
                }

                if (slotMesh.localBounds.size.x < 0.01f || slotMesh.localBounds.size.y < 0.01f || slotMesh.localBounds.size.z < 0.01f)
                {
                    EditorGUILayout.HelpBox("This slot's size is very small. It's import scale may be incorrect!", MessageType.Warning);
                }

                if (slotName == null || slotName == "")
                {
                    slotName = slotMesh.name;
                }
                if (RootBone == null || RootBone == "")
                {
                    RootBone = "Global";
                }
            }
        }
Ejemplo n.º 8
0
            public override bool OnGUI(string targetName, ref bool _dnaDirty, ref bool _textureDirty, ref bool _meshDirty)
            {
                bool changed = false;

                if (!OpenSlots.ContainsKey("wardrobeSet"))
                {
                    OpenSlots.Add("wardrobeSet", true);
                }

                if (_sharedColorsEditor.OnGUI(_recipe))
                {
                    changed = true;
                }
                //if this is a backwards compatible DCS recipe (i.e. has SlotData AND a Wardrobe set) we need to show BOTH things
                //for this to really work youd need to be able to edit the WardrobeSet and have that modify the slotDataList
                //Hence the epic UpdateBackwardsCompatibleData method
                if (_recipe.slotDataList.Length > 0)
                {
                    EditorGUILayout.HelpBox("This is a 'Backwards Compatible' DynamicCharacterAvatar recipe. The slots and overlays in the 'BackwardsCompatibleData' section will update as you change the items in the WardrobeSet.", MessageType.Info);
                }
                if (DrawWardrobeSetUI())
                {
                    changed = true;
                    if (_recipe.slotDataList.Length > 0)
                    {
                        UpdateBackwardsCompatibleData();
                    }
                }
                if (_recipe.slotDataList.Length > 0)
                {
                    if (!OpenSlots.ContainsKey("backwardsCompatibleData"))
                    {
                        OpenSlots.Add("backwardsCompatibleData", false);
                    }
                    GUILayout.BeginHorizontal(EditorStyles.toolbarButton);
                    GUILayout.Space(10);
                    bool bcdfoldoutOpen = OpenSlots["backwardsCompatibleData"];
                    bcdfoldoutOpen = EditorGUILayout.Foldout(OpenSlots["backwardsCompatibleData"], "Backwards Compatible Data");
                    OpenSlots["backwardsCompatibleData"] = bcdfoldoutOpen;
                    GUILayout.EndHorizontal();
                    if (bcdfoldoutOpen)
                    {
                        EditorGUI.BeginDisabledGroup(true);
                        GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                        EditorGUILayout.HelpBox("If you are going to use the recipe with a DynamicCharacterAvatar, it is recommended that you that you edit the 'WardrobeSet' section above and/or the base/wardrobe recipes directly. Changes you make manually below will only affect old style UMAAvatars", MessageType.Info);
                        for (int i = 0; i < _slotEditors.Count; i++)
                        {
                            var editor = _slotEditors[i];

                            if (editor == null)
                            {
                                GUILayout.Label("Empty Slot");
                                continue;
                            }

                            changed |= editor.OnGUI(ref _dnaDirty, ref _textureDirty, ref _meshDirty);

                            if (editor.Delete)
                            {
                                _dnaDirty     = true;
                                _textureDirty = true;
                                _meshDirty    = true;

                                _slotEditors.RemoveAt(i);
                                _recipe.SetSlot(editor.idx, null);
                                i--;
                                changed = true;
                            }
                        }
                        GUIHelper.EndVerticalPadded(10);
                        EditorGUI.EndDisabledGroup();
                    }
                }
                return(changed);
            }
Ejemplo n.º 9
0
            public bool OnGUI()
            {
                bool changed = false;

                if (_race != null)
                {
                    if (_race.wardrobeSlots.Count > 0)
                    {
                        var context = UMAContext.FindInstance();
                        if (context == null)
                        {
                            var _errorMessage = "Editing a recipe requires a loaded scene with a valid UMAContext.";
                            Debug.LogWarning(_errorMessage);
                        }

                        if (_wardrobeSet == null || context == null)
                        {
                            return(false);
                        }
                        GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));

                        EditorGUILayout.HelpBox("Recently added recipes not showing up? Make sure you have added them to the 'UMA Global Library' and click the 'Refresh Recipes' button below.", MessageType.Info);
                        if (GUILayout.Button("Refresh Recipes"))
                        {
                            context.dynamicCharacterSystem.Refresh(false);
                            return(false);
                        }
                        //a dictionary of slots that are being assigned by WardrobeCollections
                        var slotsAssignedByWCs = new Dictionary <string, string>();
                        if (_allowWardrobeCollectionSlot)
                        {
                            var wcRecipesForRace = context.dynamicCharacterSystem.GetRecipesForRaceSlot(_race.raceName, "WardrobeCollection");
                            var wcGroupDict      = new Dictionary <string, List <UMARecipeBase> >();
                            //I'm using reflection here to get fields and methods from the UMAWardrobeCollection type so this will still work if 'StandardAssets' is moved to 'Standard Assets'
                            for (int i = 0; i < wcRecipesForRace.Count; i++)
                            {
                                Type wcType = wcRecipesForRace[i].GetType();
                                if (wcType.ToString().Replace(wcType.Namespace + ".", "") == "UMAWardrobeCollection")
                                {
                                    FieldInfo wcRecipeSlotField = wcType.GetField("wardrobeSlot", BindingFlags.Public | BindingFlags.Instance);
                                    var       wcRecipeSlot      = (string)wcRecipeSlotField.GetValue(wcRecipesForRace[i]);
                                    if (!wcGroupDict.ContainsKey(wcRecipeSlot))
                                    {
                                        wcGroupDict.Add(wcRecipeSlot, new List <UMARecipeBase>());
                                    }
                                    wcGroupDict[wcRecipeSlot].Add(wcRecipesForRace[i]);
                                }
                            }
                            if (wcGroupDict.Count > 0)
                            {
                                MethodInfo WCGetRacesWardrobeSetMethod = null;
                                EditorGUILayout.LabelField("WardrobeCollections");
                                EditorGUI.indentLevel++;
                                foreach (KeyValuePair <string, List <UMARecipeBase> > kp in wcGroupDict)
                                {
                                    if (WCGetRacesWardrobeSetMethod == null)
                                    {
                                        WCGetRacesWardrobeSetMethod = kp.Value[0].GetType().GetMethod("GetRacesWardrobeSet", new Type[] { typeof(RaceData) });
                                    }
                                    var selected      = 0;
                                    var prevRecipe    = "";
                                    var thisPopupVals = new List <string>();
                                    thisPopupVals.Add("None");
                                    for (int i = 0; i < kp.Value.Count; i++)
                                    {
                                        thisPopupVals.Add(kp.Value[i].name);
                                        //check if this is selected
                                        for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                                        {
                                            if (kp.Value[i].name == _wardrobeSet[wsi].recipe)
                                            {
                                                prevRecipe = _wardrobeSet[wsi].recipe;
                                                selected   = i + 1;
                                                var thisWCWardrobeSet = (List <WardrobeSettings>)WCGetRacesWardrobeSetMethod.Invoke(kp.Value[i], new object[] { _race });
                                                for (int wcwsi = 0; wcwsi < thisWCWardrobeSet.Count; wcwsi++)
                                                {
                                                    if (!slotsAssignedByWCs.ContainsKey(thisWCWardrobeSet[wcwsi].slot))
                                                    {
                                                        slotsAssignedByWCs.Add(thisWCWardrobeSet[wcwsi].slot, kp.Value[i].name);
                                                    }
                                                    else
                                                    {
                                                        slotsAssignedByWCs[thisWCWardrobeSet[wcwsi].slot] = kp.Value[i].name;
                                                    }
                                                }
                                                break;
                                            }
                                        }
                                    }
                                    EditorGUI.BeginChangeCheck();
                                    var newSelected = EditorGUILayout.Popup(kp.Key, selected, thisPopupVals.ToArray());
                                    if (EditorGUI.EndChangeCheck())
                                    {
                                        for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                                        {
                                            if (_wardrobeSet[wsi].recipe == prevRecipe)
                                            {
                                                //we need to remove the wardrobeSettings that has prevRecipe as its value from _wardrobeSettings
                                                if (newSelected == 0)
                                                {
                                                    _wardrobeSet.RemoveAt(wsi);
                                                }
                                                else
                                                {
                                                    //we need to make wardrobeSettings that has prevRecipe have the new value
                                                    _wardrobeSet[wsi].recipe = thisPopupVals[newSelected];
                                                }
                                            }
                                        }
                                        changed = true;
                                    }
                                }
                                EditorGUI.indentLevel--;
                                EditorGUILayout.Space();
                                EditorGUILayout.LabelField("WardrobeSlots");
                                EditorGUI.indentLevel++;
                            }
                        }
                        foreach (string wsl in _race.wardrobeSlots)
                        {
                            if (wsl == "None")
                            {
                                continue;
                            }

                            //Obsolete- now wardrobeCollections apply their WardrobeSet to any slots
                            //if (wsl == "FullOutfit" && _allowWardrobeCollectionSlot == false)
                            //	continue;

                            WardrobeSlotRecipePopup thisPicker = null;
                            bool assignedPicker = false;
                            for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                            {
                                if (_wardrobeSet[wsi].slot == wsl)
                                {
                                    thisPicker     = new WardrobeSlotRecipePopup(_race.raceName, wsl, _wardrobeSet[wsi].recipe);
                                    assignedPicker = true;
                                    break;
                                }
                            }
                            if (!assignedPicker)                            //means there was nothing in the wardrobe set for it
                            {
                                //This may still be being assigned by a wardrobe collection though so show that
                                var wcOverrideName = "";
                                if (slotsAssignedByWCs.ContainsKey(wsl))
                                {
                                    wcOverrideName = slotsAssignedByWCs[wsl];
                                }
                                thisPicker = new WardrobeSlotRecipePopup(_race.raceName, wsl, "");
                            }
                            if (thisPicker.OnGUI())
                            {
                                changed = true;
                                if (thisPicker.RecipeName != "None" && thisPicker.RecipeName != "")
                                {
                                    bool contained = false;
                                    for (int i = 0; i < _wardrobeSet.Count; i++)
                                    {
                                        if (_wardrobeSet[i].slot == wsl)
                                        {
                                            _wardrobeSet[i].recipe = thisPicker.RecipeName;
                                            contained = true;
                                            break;
                                        }
                                    }
                                    if (!contained)
                                    {
                                        _wardrobeSet.Add(new WardrobeSettings(wsl, thisPicker.RecipeName));
                                    }
                                }
                                else
                                {
                                    for (int i = 0; i < _wardrobeSet.Count; i++)
                                    {
                                        if (_wardrobeSet[i].slot == wsl)
                                        {
                                            _wardrobeSet.RemoveAt(i);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        if (_allowWardrobeCollectionSlot)
                        {
                            EditorGUI.indentLevel--;
                        }
                        if (WardrobeSet.Count > 0)
                        {
                            EditorGUILayout.Space();
                            if (GUILayout.Button(new GUIContent("UpdateSharedColors", "Automatically adds any shared colors defined in the selected recipes to this recipes SharedColors")))
                            {
                                for (int i = 0; i < _wardrobeSet.Count; i++)
                                {
                                    changed = AddSharedColorsFromRecipe(_wardrobeSet[i].recipe, _recipe) == true ? true : changed;
                                }
                            }
                        }
                        GUIHelper.EndVerticalPadded(10);
                    }
                }
                return(changed);
            }
Ejemplo n.º 10
0
            public bool OnGUI()
            {
                bool changed = false;

                if (_race != null)
                {
                    if (_race.wardrobeSlots.Count > 0)
                    {
                        var context = UMAContext.FindInstance();
                        if (context == null)
                        {
                            var _errorMessage = "Editing a recipe requires a loaded scene with a valid UMAContext.";
                            Debug.LogWarning(_errorMessage);
                        }

                        if (_wardrobeSet == null || context == null)
                        {
                            return(false);
                        }
                        GUIHelper.BeginVerticalPadded(10, new Color(0.75f, 0.875f, 1f));
                        if (_allowWardrobeCollectionSlot)
                        {
                            var wcRecipesForRace = context.dynamicCharacterSystem.GetRecipesForRaceSlot(_race.raceName, "WardrobeCollection");
                            var wcGroupDict      = new Dictionary <string, List <string> >();
                            //for 'Standard Assets' we need to do some kind of get Types thing I think because we then need to use reflection to get the wardrobeSlot field
                            //how can we get what we want here when WardrobeCollections dont exist in Standard Assets (if 'StandardAssets' has been moved there)
                            for (int i = 0; i < wcRecipesForRace.Count; i++)
                            {
                                Type wcType = wcRecipesForRace[i].GetType();
                                if (wcType.ToString().Replace(wcType.Namespace + ".", "") == "UMAWardrobeCollection")
                                {
                                    FieldInfo wcRecipeSlotField = wcType.GetField("wardrobeSlot", BindingFlags.Public | BindingFlags.Instance);
                                    var       wcRecipeSlot      = (string)wcRecipeSlotField.GetValue(wcRecipesForRace[i]);
                                    if (!wcGroupDict.ContainsKey(wcRecipeSlot))
                                    {
                                        wcGroupDict.Add(wcRecipeSlot, new List <string>());
                                    }
                                    wcGroupDict[wcRecipeSlot].Add(wcRecipesForRace[i].name);
                                }
                            }
                            if (wcGroupDict.Count > 0)
                            {
                                EditorGUILayout.LabelField("WardrobeCollections");
                                EditorGUI.indentLevel++;
                                foreach (KeyValuePair <string, List <string> > kp in wcGroupDict)
                                {
                                    var thisPopupVals = new List <string>();
                                    thisPopupVals.Add("None");
                                    thisPopupVals.AddRange(kp.Value);
                                    var selected   = 0;
                                    var prevRecipe = "";
                                    //if one of the recipes in the wardrobe set is one of these then its selected
                                    for (int pvi = 0; pvi < thisPopupVals.Count; pvi++)
                                    {
                                        for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                                        {
                                            if (thisPopupVals[pvi] == _wardrobeSet[wsi].recipe)
                                            {
                                                prevRecipe = _wardrobeSet[wsi].recipe;
                                                selected   = pvi;
                                                break;
                                            }
                                        }
                                    }
                                    EditorGUI.BeginChangeCheck();
                                    var newSelected = EditorGUILayout.Popup(kp.Key, selected, thisPopupVals.ToArray());
                                    if (EditorGUI.EndChangeCheck())
                                    {
                                        for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                                        {
                                            if (_wardrobeSet[wsi].recipe == prevRecipe)
                                            {
                                                //we need to remove the wardrobeSettings that has prevRecipe as its value from _wardrobeSettings
                                                if (newSelected == 0)
                                                {
                                                    _wardrobeSet.RemoveAt(wsi);
                                                }
                                                else
                                                {
                                                    //we need to make wardrobeSettings that has prevRecipe have the new value
                                                    _wardrobeSet[wsi].recipe = thisPopupVals[newSelected];
                                                }
                                            }
                                        }
                                        changed = true;
                                    }
                                }
                                EditorGUI.indentLevel--;
                                EditorGUILayout.Space();
                                EditorGUILayout.LabelField("WardrobeSlots");
                                EditorGUI.indentLevel++;
                            }
                        }
                        foreach (string wsl in _race.wardrobeSlots)
                        {
                            if (wsl == "None")
                            {
                                continue;
                            }

                            if (wsl == "FullOutfit" && _allowWardrobeCollectionSlot == false)
                            {
                                continue;
                            }

                            WardrobeSlotRecipePopup thisPicker = null;
                            bool assignedPicker = false;
                            for (int wsi = 0; wsi < _wardrobeSet.Count; wsi++)
                            {
                                if (_wardrobeSet[wsi].slot == wsl)
                                {
                                    thisPicker     = new WardrobeSlotRecipePopup(_race.raceName, wsl, _wardrobeSet[wsi].recipe);
                                    assignedPicker = true;
                                    break;
                                }
                            }
                            if (!assignedPicker)                            //means there was nothing in the wardrobe set for it
                            {
                                thisPicker = new WardrobeSlotRecipePopup(_race.raceName, wsl, "");
                            }
                            if (thisPicker.OnGUI())
                            {
                                changed = true;
                                if (thisPicker.RecipeName != "None" && thisPicker.RecipeName != "")
                                {
                                    bool contained = false;
                                    for (int i = 0; i < _wardrobeSet.Count; i++)
                                    {
                                        if (_wardrobeSet[i].slot == wsl)
                                        {
                                            _wardrobeSet[i].recipe = thisPicker.RecipeName;
                                            contained = true;
                                            break;
                                        }
                                    }
                                    if (!contained)
                                    {
                                        _wardrobeSet.Add(new WardrobeSettings(wsl, thisPicker.RecipeName));
                                    }
                                }
                                else
                                {
                                    for (int i = 0; i < _wardrobeSet.Count; i++)
                                    {
                                        if (_wardrobeSet[i].slot == wsl)
                                        {
                                            _wardrobeSet.RemoveAt(i);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        if (_allowWardrobeCollectionSlot)
                        {
                            EditorGUI.indentLevel--;
                        }
                        if (WardrobeSet.Count > 0)
                        {
                            EditorGUILayout.Space();
                            if (GUILayout.Button(new GUIContent("UpdateSharedColors", "Automatically adds any shared colors defined in the selected recipes to this recipes SharedColors")))
                            {
                                for (int i = 0; i < _wardrobeSet.Count; i++)
                                {
                                    changed = AddSharedColorsFromRecipe(_wardrobeSet[i].recipe, _recipe) == true ? true : changed;
                                }
                            }
                        }
                        GUIHelper.EndVerticalPadded(10);
                    }
                }
                return(changed);
            }