diff --git a/Editor/Configurator.meta b/Editor/Configurator.meta
new file mode 100644
index 00000000..db078275
--- /dev/null
+++ b/Editor/Configurator.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 54100430fca943e46a942410509a2543
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Avatar.meta b/Editor/Configurator/Avatar.meta
new file mode 100644
index 00000000..aff2ddce
--- /dev/null
+++ b/Editor/Configurator/Avatar.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 49af73e30aa550e42aae80796afe4039
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Avatar/AvatarUtils.cs b/Editor/Configurator/Avatar/AvatarUtils.cs
new file mode 100644
index 00000000..3ed11957
--- /dev/null
+++ b/Editor/Configurator/Avatar/AvatarUtils.cs
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingFramework;
+using Chocopoi.DressingTools.Components.Cabinet;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.Configurator.Cabinet;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal static class AvatarUtils
+ {
+ public static List FindSceneAvatars(Scene scene)
+ {
+ return DKRuntimeUtils.FindSceneAvatars(scene);
+ }
+
+ public static GameObject GetAvatarRoot(GameObject gameObject)
+ {
+ return DKRuntimeUtils.GetAvatarRoot(gameObject);
+ }
+
+ public static IAvatarSettings GetAvatarSettings(GameObject avatarGameObject)
+ {
+ if (avatarGameObject == null)
+ {
+ return null;
+ }
+
+ if (avatarGameObject.TryGetComponent(out _))
+ {
+ return new OneConfAvatarSettings(avatarGameObject);
+ }
+ // TODO: standalone avatar settings component
+ return null;
+ }
+
+ public static IWardrobeProvider GetWardrobeProvider(GameObject avatarGameObject)
+ {
+ if (avatarGameObject == null)
+ {
+ return null;
+ }
+
+ if (avatarGameObject.TryGetComponent(out _))
+ {
+ return new OneConfCabinetProvider(avatarGameObject);
+ }
+ if (avatarGameObject.TryGetComponent(out _))
+ {
+ return new DTWardrobeProvider(avatarGameObject);
+ }
+ return null;
+ }
+ }
+}
diff --git a/Editor/UI/Views/WearableConfigView.cs.meta b/Editor/Configurator/Avatar/AvatarUtils.cs.meta
similarity index 83%
rename from Editor/UI/Views/WearableConfigView.cs.meta
rename to Editor/Configurator/Avatar/AvatarUtils.cs.meta
index 62fdbfe3..50c651c5 100644
--- a/Editor/UI/Views/WearableConfigView.cs.meta
+++ b/Editor/Configurator/Avatar/AvatarUtils.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: b2ad4dd1add6d4e4993ac574131c425c
+guid: 2929f0fb8421b5b4bb28cb6a08500a78
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Editor/Configurator/Avatar/IAvatarSettings.cs b/Editor/Configurator/Avatar/IAvatarSettings.cs
new file mode 100644
index 00000000..40d4fdf2
--- /dev/null
+++ b/Editor/Configurator/Avatar/IAvatarSettings.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal enum WriteDefaultsModes
+ {
+ Auto = 0,
+ On = 1,
+ Off = 2
+ }
+
+ internal interface IAvatarSettings
+ {
+ WriteDefaultsModes WriteDefaultsMode { get; set; }
+ }
+}
diff --git a/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta
similarity index 83%
rename from Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta
rename to Editor/Configurator/Avatar/IAvatarSettings.cs.meta
index 3dce1765..a7e34d00 100644
--- a/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta
+++ b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: f2e152828377d2c499af2ca00f7fdbb7
+guid: 190df016052766240b40cfd53fd01fe0
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Editor/Configurator/Avatar/OneConfAvatarSettings.cs b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs
new file mode 100644
index 00000000..3884dc1d
--- /dev/null
+++ b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.OneConf.Cabinet;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal class OneConfAvatarSettings : IAvatarSettings
+ {
+ public WriteDefaultsModes WriteDefaultsMode
+ {
+ get
+ {
+ ReadCabinetConfig(out _, out var config);
+ // TODO: should explicit convert them one by one
+ return (WriteDefaultsModes)config.animationWriteDefaultsMode;
+ }
+ set
+ {
+ WriteCabinetConfig((comp, config) =>
+ {
+ // TODO: should explicit convert them one by one
+ config.animationWriteDefaultsMode = (CabinetConfig.WriteDefaultsMode)value;
+ });
+ }
+ }
+
+ private readonly GameObject _avatarGameObject;
+
+ public OneConfAvatarSettings(GameObject avatarGameObject)
+ {
+ _avatarGameObject = avatarGameObject;
+ }
+
+ private void ReadCabinetConfig(out DTCabinet comp, out CabinetConfig config)
+ {
+ if (_avatarGameObject.TryGetComponent(out comp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(comp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ comp = null;
+ config = new CabinetConfig();
+ }
+ }
+
+ private void WriteCabinetConfig(Action func)
+ {
+ CabinetConfig config;
+ if (_avatarGameObject.TryGetComponent(out var cabinetComp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(cabinetComp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ cabinetComp = _avatarGameObject.AddComponent();
+ config = new CabinetConfig();
+ }
+ func?.Invoke(cabinetComp, config);
+ cabinetComp.ConfigJson = CabinetConfigUtility.Serialize(config);
+ }
+ }
+}
diff --git a/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta
new file mode 100644
index 00000000..95a86393
--- /dev/null
+++ b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ae375916b7bc14648aa69e2399f3532d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/AvatarPreviewUtility.cs b/Editor/Configurator/AvatarPreviewUtility.cs
new file mode 100644
index 00000000..324a9444
--- /dev/null
+++ b/Editor/Configurator/AvatarPreviewUtility.cs
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingFramework;
+using Chocopoi.DressingTools.Configurator.Cabinet;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.Configurator
+{
+ internal static class AvatarPreviewUtility
+ {
+#if UNITY_2020_1_OR_NEWER
+ private class AvatarPreviewStage : PreviewSceneStage
+ {
+ public GameObject avatarGameObject;
+ public IConfigurableOutfit outfit;
+
+ private GameObject _previewAvatarGameObject;
+ private GameObject _previewOutfitGameObject;
+
+ protected override GUIContent CreateHeaderContent()
+ {
+ return new GUIContent("DressingTools Preview");
+ }
+
+ private void PrepareAvatarPreview()
+ {
+ var lighting = new GameObject("Lighting");
+ lighting.AddComponent().type = LightType.Directional;
+ SceneManager.MoveGameObjectToScene(lighting, scene);
+
+ _previewAvatarGameObject = Instantiate(avatarGameObject);
+ _previewAvatarGameObject.name = avatarGameObject.name;
+ if (DKEditorUtils.IsGrandParent(_previewAvatarGameObject.transform, outfit.RootTransform))
+ {
+ // if it's inside, reuse it
+ var path = DKEditorUtils.GetRelativePath(outfit.RootTransform, _previewAvatarGameObject.transform);
+ _previewOutfitGameObject = _previewAvatarGameObject.transform.Find(path).gameObject;
+ }
+ else
+ {
+ // if it's outside, create a new one and set parent
+ _previewOutfitGameObject = Instantiate(outfit.RootTransform.gameObject);
+ _previewOutfitGameObject.name = outfit.RootTransform.name;
+ _previewOutfitGameObject.transform.SetParent(_previewAvatarGameObject.transform, false);
+ }
+ outfit.Preview(_previewAvatarGameObject, _previewOutfitGameObject);
+ SceneManager.MoveGameObjectToScene(_previewAvatarGameObject, scene);
+ }
+
+ protected override bool OnOpenStage()
+ {
+ base.OnOpenStage();
+ scene = EditorSceneManager.NewPreviewScene();
+ PrepareAvatarPreview();
+ return true;
+ }
+
+ protected override void OnCloseStage()
+ {
+ foreach (var go in scene.GetRootGameObjects())
+ {
+ DestroyImmediate(go);
+ }
+ EditorSceneManager.ClosePreviewScene(scene);
+ base.OnCloseStage();
+ }
+ }
+#endif
+
+ public static void StartAvatarPreview(GameObject avatarGameObject, IConfigurableOutfit outfit, bool legacy = false)
+ {
+#if UNITY_2020_1_OR_NEWER
+ if (!legacy)
+ {
+ var stage = ScriptableObject.CreateInstance();
+ stage.avatarGameObject = avatarGameObject;
+ stage.outfit = outfit;
+ StageUtility.GoToStage(stage, true);
+ return;
+ }
+#endif
+ throw new NotImplementedException("Legacy preview is not yet implemented.");
+ }
+ }
+}
diff --git a/Editor/Configurator/AvatarPreviewUtility.cs.meta b/Editor/Configurator/AvatarPreviewUtility.cs.meta
new file mode 100644
index 00000000..9bf28d8e
--- /dev/null
+++ b/Editor/Configurator/AvatarPreviewUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f495cab911e96f8489ae4ec21e4f7c60
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet.meta b/Editor/Configurator/Cabinet.meta
new file mode 100644
index 00000000..f70e2df6
--- /dev/null
+++ b/Editor/Configurator/Cabinet.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 835453005c4ccea42a22471323239648
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs
new file mode 100644
index 00000000..6658e315
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class DTBaseOutfitProvider : IBaseOutfitProvider
+ {
+ }
+}
diff --git a/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta
new file mode 100644
index 00000000..c3373fce
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2d742936c41466a459a80d7d12f34071
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs
new file mode 100644
index 00000000..b3b1a4d8
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingTools.Components.Cabinet;
+using Chocopoi.DressingTools.Configurator.Modules;
+using Chocopoi.DressingTools.UI.Views;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class DTConfigurableOutfit : IConfigurableOutfit
+ {
+ public Transform RootTransform { get => _outfitComp.RootTransform; }
+ public string Name => _outfitComp.Name;
+ public Texture2D Icon => _outfitComp.Icon;
+
+ private readonly DTAlternateOutfit _outfitComp;
+
+ public DTConfigurableOutfit(DTAlternateOutfit outfitComp)
+ {
+ _outfitComp = outfitComp;
+ }
+
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public List GetModules()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta
new file mode 100644
index 00000000..df17adc0
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e3d09a3cbb4ff1142a5df53ee3195a81
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/DTWardrobeProvider.cs b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs
new file mode 100644
index 00000000..05da2186
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingTools.Components.Cabinet;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class DTWardrobeProvider : IWardrobeProvider
+ {
+ private readonly GameObject _avatarGameObject;
+
+ public DTWardrobeProvider(GameObject avatarGameObject)
+ {
+ _avatarGameObject = avatarGameObject;
+ }
+
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public List GetOutfits()
+ {
+ var comps = _avatarGameObject.GetComponentsInChildren(true);
+ var outfits = new List();
+ foreach (var comp in comps)
+ {
+ outfits.Add(new DTConfigurableOutfit(comp));
+ }
+ return outfits;
+ }
+
+ public void RemoveOutfit(IConfigurableOutfit outfit)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta
new file mode 100644
index 00000000..28792871
--- /dev/null
+++ b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0798e99e32176664d95f987ff0ce47d6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs
new file mode 100644
index 00000000..f9c3df13
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal interface IBaseOutfitProvider
+ {
+ }
+}
diff --git a/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta
new file mode 100644
index 00000000..cf49bac4
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c74cc891dc33f3a4abaa1a95de76e3ad
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/IConfigurableOutfit.cs b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs
new file mode 100644
index 00000000..32c6f64d
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingTools.Configurator.Modules;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal interface IConfigurableOutfit
+ {
+ Transform RootTransform { get; }
+ string Name { get; }
+ Texture2D Icon { get; }
+
+ List GetModules();
+ VisualElement CreateView();
+ void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject);
+ }
+}
diff --git a/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta
new file mode 100644
index 00000000..c7e8f444
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 747be604a05b06b49a4f07ccd5743fa0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/IWardrobeProvider.cs b/Editor/Configurator/Cabinet/IWardrobeProvider.cs
new file mode 100644
index 00000000..4942436f
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IWardrobeProvider.cs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal interface IWardrobeProvider
+ {
+ List GetOutfits();
+ void RemoveOutfit(IConfigurableOutfit outfit);
+ VisualElement CreateView();
+ }
+}
diff --git a/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta b/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta
new file mode 100644
index 00000000..635ecde4
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27d0b049760deac419a346f76d20d67a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs
new file mode 100644
index 00000000..dc3bcd7c
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.OneConf;
+using UnityEngine;
+using UnityEngine.UIElements;
+using UnityEditor;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using Chocopoi.DressingTools.OneConf.Cabinet.Modules.BuiltIn;
+using Chocopoi.DressingTools.OneConf.Cabinet;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class OneConfCabinetProvider : IWardrobeProvider
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+
+ private readonly GameObject _avatarGameObject;
+
+ public OneConfCabinetProvider(GameObject avatarGameObject)
+ {
+ _avatarGameObject = avatarGameObject;
+ }
+
+ public VisualElement CreateView()
+ {
+ var container = new VisualElement();
+
+ var cabinet = OneConfUtils.GetAvatarCabinet(_avatarGameObject);
+ if (cabinet == null || !CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var config))
+ {
+ // TODO: show message
+ return container;
+ }
+
+ var armatureNameField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.avatarArmatureName")) { value = config.avatarArmatureName };
+ container.Add(armatureNameField);
+ var groupDynToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.groupDynamics")) { value = config.groupDynamics };
+ container.Add(groupDynToggle);
+ var groupDynSepToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.groupDynamicsSeparate")) { value = config.groupDynamicsSeparateGameObjects };
+ container.Add(groupDynSepToggle);
+
+ var cabAnimConfig = config.FindModuleConfig();
+ if (cabAnimConfig == null)
+ {
+ cabAnimConfig = new CabinetAnimCabinetModuleConfig();
+ config.modules.Add(new CabinetModule()
+ {
+ config = cabAnimConfig,
+ moduleName = CabinetAnimCabinetModuleConfig.ModuleIdentifier
+ });
+ }
+
+ var useThumbnailsToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.useThumbnailsAsMenuIcons")) { value = cabAnimConfig.thumbnails };
+ container.Add(useThumbnailsToggle);
+ var resetCustomizablesOnSwitch = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.resetCustomizablesOnSwitch")) { value = cabAnimConfig.resetCustomizablesOnSwitch };
+ container.Add(resetCustomizablesOnSwitch);
+
+ container.Add(new IMGUIContainer(() => EditorGUILayout.HelpBox(t._("editor.main.avatar.settings.oneConf.helpbox.installPathDescription"), MessageType.Info)));
+
+ var installPathField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.menuInstallPath")) { value = cabAnimConfig.menuInstallPath };
+ container.Add(installPathField);
+ var itemNameField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.menuItemName")) { value = cabAnimConfig.menuItemName };
+ container.Add(itemNameField);
+
+ var networkSyncedToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.networkSynced")) { value = cabAnimConfig.networkSynced };
+ container.Add(networkSyncedToggle);
+ var savedToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.saved")) { value = cabAnimConfig.saved };
+ container.Add(savedToggle);
+
+ return container;
+ }
+
+ public List GetOutfits()
+ {
+ var wearables = OneConfUtils.GetCabinetWearables(_avatarGameObject);
+ var outfits = new List();
+ foreach (var wearable in wearables)
+ {
+ outfits.Add(new OneConfConfigurableOutfit(_avatarGameObject, wearable));
+ }
+ return outfits;
+ }
+
+ public void RemoveOutfit(IConfigurableOutfit outfit)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta
new file mode 100644
index 00000000..f5cb62bc
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c1c6be9fd8672fb48be304cc5ac7780c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs
new file mode 100644
index 00000000..6fed89c2
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Chocopoi.DressingFramework.Components;
+using Chocopoi.DressingFramework.Detail.DK;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.Configurator.Modules;
+using Chocopoi.DressingTools.Dynamics;
+using Chocopoi.DressingTools.OneConf;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using Chocopoi.DressingTools.OneConf.Wearable.Modules;
+using Chocopoi.DressingTools.OneConf.Wearable.Modules.BuiltIn;
+using Chocopoi.DressingTools.UI.Views;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class OneConfConfigurableOutfit : IConfigurableOutfit
+ {
+ public Transform RootTransform
+ {
+ get => _wearableComp.RootGameObject.transform;
+ }
+ public string Name
+ {
+ get => WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config) ?
+ config.info.name :
+ "(Error)";
+
+ }
+ public Texture2D Icon
+ {
+ get => WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config) ?
+ (string.IsNullOrEmpty(config.info.thumbnail) ?
+ null :
+ OneConfUtils.GetTextureFromBase64(config.info.thumbnail)) :
+ null;
+ }
+
+ private readonly GameObject _avatarGameObject;
+ private readonly DTWearable _wearableComp;
+
+ public OneConfConfigurableOutfit(GameObject avatarGameObject, DTWearable wearableComp)
+ {
+ _avatarGameObject = avatarGameObject;
+ _wearableComp = wearableComp;
+ }
+
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public List GetModules()
+ {
+ var modules = new List();
+ if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config))
+ {
+ Debug.LogError("[DressingTools] Unable to deserialize OneConf wearable config, returning empty modules");
+ return modules;
+ }
+
+ foreach (var oneConfModule in config.modules)
+ {
+ // TODO
+ if (oneConfModule.moduleName == ArmatureMappingWearableModuleConfig.ModuleIdentifier &&
+ oneConfModule.config is ArmatureMappingWearableModuleConfig)
+ {
+ modules.Add(new OneConfArmatureMappingModule(_avatarGameObject, _wearableComp));
+ }
+ }
+
+ return modules;
+ }
+
+ public void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject)
+ {
+ if (previewAvatarGameObject == null || previewOutfitGameObject == null)
+ {
+ return;
+ }
+
+ var cabinet = OneConfUtils.GetAvatarCabinet(previewAvatarGameObject);
+ if (cabinet == null)
+ {
+ return;
+ }
+
+ if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var cabinetConfig))
+ {
+ Debug.LogError("[DressingTools] Unable to deserialize cabinet config for preview");
+ return;
+ }
+
+ if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var wearableConfig))
+ {
+ Debug.LogError("[DressingTools] Unable to deserialize OneConf wearable config for preview");
+ return;
+ }
+
+ var dkCtx = new DKNativeContext(previewAvatarGameObject);
+ var cabCtx = new CabinetContext
+ {
+ dkCtx = dkCtx,
+ cabinetConfig = cabinetConfig
+ };
+
+ var wearCtx = new WearableContext
+ {
+ wearableConfig = wearableConfig,
+ wearableGameObject = previewOutfitGameObject,
+ wearableDynamics = DynamicsUtils.ScanDynamics(previewOutfitGameObject)
+ };
+
+ var providers = ModuleManager.Instance.GetAllWearableModuleProviders();
+
+ foreach (var provider in providers)
+ {
+ if (!provider.Invoke(cabCtx, wearCtx, new ReadOnlyCollection(wearableConfig.FindModules(provider.Identifier)), true))
+ {
+ Debug.LogError("[DressingTools] Error applying wearable in preview!");
+ break;
+ }
+ }
+
+ // remove all DK components
+ var dkComps = previewAvatarGameObject.GetComponentsInChildren();
+ foreach (var comp in dkComps)
+ {
+ Object.DestroyImmediate(comp);
+ }
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta
new file mode 100644
index 00000000..2f4831d6
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 40b11c7d165facb4a9d3f805dc964276
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules.meta b/Editor/Configurator/Modules.meta
new file mode 100644
index 00000000..59af3ea1
--- /dev/null
+++ b/Editor/Configurator/Modules.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3785e4e8e0f78a748a10bb3f47b8d93a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/ArmatureMerging.meta b/Editor/Configurator/Modules/ArmatureMerging.meta
new file mode 100644
index 00000000..8b6ac717
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e65aac42077722345b341ce308eed3e7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs
new file mode 100644
index 00000000..0e533edf
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using Chocopoi.AvatarLib.Animations;
+using Chocopoi.DressingTools.Components.Modifiers;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class DTArmatureMappingModule : IArmatureMergingModule
+ {
+ public Transform TargetArmature
+ {
+ get => string.IsNullOrEmpty(_comp.TargetArmaturePath) ?
+ null :
+ _avatarGameObject.transform.Find(_comp.TargetArmaturePath);
+ set =>
+ _comp.TargetArmaturePath =
+ (value == null || value == _avatarGameObject.transform) ?
+ "" :
+ AnimationUtils.GetRelativePath(value.transform, _avatarGameObject.transform);
+ }
+ public Transform SourceArmature { get => _comp.SourceArmature; set => _comp.SourceArmature = value; }
+
+ private readonly GameObject _avatarGameObject;
+ private readonly DTArmatureMapping _comp;
+
+ public DTArmatureMappingModule(GameObject avatarGameObject, DTArmatureMapping comp)
+ {
+ _avatarGameObject = avatarGameObject;
+ _comp = comp;
+ }
+
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta
new file mode 100644
index 00000000..b50ee4be
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bdafac00c0d9bda4a9f3213fc7450291
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs
new file mode 100644
index 00000000..0e084a3f
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal interface IArmatureMergingModule : IModule
+ {
+ Transform TargetArmature { get; set; }
+ Transform SourceArmature { get; set; }
+ }
+}
diff --git a/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta
new file mode 100644
index 00000000..7737f443
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ed350f2e48837b74aa3c72a2d62630cd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs
new file mode 100644
index 00000000..374c8ddb
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+#if DT_MA
+using Chocopoi.DressingFramework;
+using nadena.dev.modular_avatar.core;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class MAMergeArmatureModule : IArmatureMergingModule
+ {
+ public Transform TargetArmature
+ {
+ get => _comp.mergeTargetObject != null ? _comp.mergeTargetObject.transform : null;
+ set => _comp.mergeTarget.Set(value != null ? value.gameObject : null);
+ }
+ public Transform SourceArmature
+ {
+ get => _comp.transform;
+ set => MoveToNewSourceArmature(value);
+ }
+
+ private ModularAvatarMergeArmature _comp;
+
+ public MAMergeArmatureModule(ModularAvatarMergeArmature comp)
+ {
+ _comp = comp;
+ }
+
+ private void MoveToNewSourceArmature(Transform sourceArmature)
+ {
+ if (sourceArmature == null)
+ {
+ return;
+ }
+
+ if (sourceArmature.gameObject.TryGetComponent(out var existingComp))
+ {
+ Debug.LogWarning($"Existing ModularAvatarMergeArmature component detected at {sourceArmature.name}, removing");
+ Object.DestroyImmediate(existingComp);
+ }
+
+ var comp = DKEditorUtils.CopyComponent(_comp, sourceArmature.gameObject);
+ if (comp == null || !(comp is ModularAvatarMergeArmature newComp))
+ {
+ Debug.LogError("Failed to clone ModularAvatarMergeArmature component");
+ return;
+ }
+
+ Object.DestroyImmediate(_comp);
+ _comp = newComp;
+ }
+
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
+#endif
diff --git a/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta
new file mode 100644
index 00000000..2fee3b9a
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a289e3dea4e3ad54ab55d6d2b2d07f66
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs
new file mode 100644
index 00000000..d76a35a1
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.AvatarLib.Animations;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.OneConf;
+using Chocopoi.DressingTools.OneConf.Wearable.Modules.BuiltIn;
+using Chocopoi.DressingTools.UI.Views.Modules;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class OneConfArmatureMappingModule : OneConfModuleBase, IArmatureMergingModule
+ {
+ public Transform TargetArmature
+ {
+ get
+ {
+ ReadCabinetConfig(out _, out var config);
+ return string.IsNullOrEmpty(config.avatarArmatureName) ?
+ null :
+ OneConfUtils.GuessArmature(_avatarGameObject, config.avatarArmatureName);
+ }
+ set
+ {
+ WriteCabinetConfig((comp, config) =>
+ {
+ config.avatarArmatureName =
+ value == null ?
+ "" :
+ AnimationUtils.GetRelativePath(value.transform, comp.transform);
+ });
+ }
+ }
+ public Transform SourceArmature
+ {
+ get
+ {
+ ReadWearableModule(out var module);
+ if (module == null) return null;
+ return string.IsNullOrEmpty(module.wearableArmatureName) ?
+ null :
+ OneConfUtils.GuessArmature(_wearableComp.gameObject, module.wearableArmatureName);
+ }
+ set
+ {
+ WriteWearableModule(module =>
+ {
+ module.wearableArmatureName =
+ value == null ?
+ "" :
+ AnimationUtils.GetRelativePath(value.transform, _wearableComp.transform);
+ });
+ }
+ }
+
+ public OneConfArmatureMappingModule(GameObject avatarGameObject, DTWearable wearableComp)
+ : base(avatarGameObject, wearableComp)
+ {
+ }
+
+ public override VisualElement CreateView()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta
new file mode 100644
index 00000000..ca51f749
--- /dev/null
+++ b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 729a7863d8e76ae479fb19cdb85f4934
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/BlendshapeSync.meta b/Editor/Configurator/Modules/BlendshapeSync.meta
new file mode 100644
index 00000000..02233876
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1c82e513b4c9b69479f8dc293a548e3e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs
new file mode 100644
index 00000000..3cb200c2
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using Chocopoi.DressingTools.UI.Views;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class DTBlendshapeSyncModule : IBlendshapeSyncModule
+ {
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta
new file mode 100644
index 00000000..08fbbe26
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 779e29f9433456b4c8fa889907624dc0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs
new file mode 100644
index 00000000..56cca446
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal interface IBlendshapeSyncModule : IModule
+ {
+ }
+}
diff --git a/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta
new file mode 100644
index 00000000..0316d348
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0e01f4f6b8a67ea43bc320e24c7b63b9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs
new file mode 100644
index 00000000..ea2b4e28
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+#if DT_MA
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class MABlendshapeSyncModule : IBlendshapeSyncModule
+ {
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
+#endif
diff --git a/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta
new file mode 100644
index 00000000..3761cf14
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 38320b0d2808c7149aba1380055e5f92
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs
new file mode 100644
index 00000000..db083679
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal class OneConfBlendshapeSyncModule : IBlendshapeSyncModule
+ {
+ public VisualElement CreateView()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta
new file mode 100644
index 00000000..95d73e70
--- /dev/null
+++ b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5e6173a74c0269040a8d92c9e491f3a7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/IModule.cs b/Editor/Configurator/Modules/IModule.cs
new file mode 100644
index 00000000..38bf3dcb
--- /dev/null
+++ b/Editor/Configurator/Modules/IModule.cs
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal interface IModule
+ {
+ VisualElement CreateView();
+ }
+}
diff --git a/Editor/Configurator/Modules/IModule.cs.meta b/Editor/Configurator/Modules/IModule.cs.meta
new file mode 100644
index 00000000..bc66abc4
--- /dev/null
+++ b/Editor/Configurator/Modules/IModule.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d66cf2dc76dd3834ea6a6d9b17dd6d52
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Modules/OneConfModuleBase.cs b/Editor/Configurator/Modules/OneConfModuleBase.cs
new file mode 100644
index 00000000..d05912e5
--- /dev/null
+++ b/Editor/Configurator/Modules/OneConfModuleBase.cs
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.OneConf.Cabinet;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using Chocopoi.DressingTools.OneConf.Wearable;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Modules
+{
+ internal abstract class OneConfModuleBase : IModule
+ {
+ protected readonly GameObject _avatarGameObject;
+ protected readonly DTWearable _wearableComp;
+
+ public OneConfModuleBase(GameObject avatarGameObject, DTWearable wearableComp)
+ {
+ _avatarGameObject = avatarGameObject;
+ _wearableComp = wearableComp;
+ }
+
+ public abstract VisualElement CreateView();
+
+ protected void ReadCabinetConfig(out DTCabinet comp, out CabinetConfig config)
+ {
+ if (_avatarGameObject.TryGetComponent(out comp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(comp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ comp = null;
+ config = new CabinetConfig();
+ }
+ }
+
+ protected void WriteCabinetConfig(Action func)
+ {
+ CabinetConfig config;
+ if (_avatarGameObject.TryGetComponent(out var cabinetComp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(cabinetComp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ cabinetComp = _avatarGameObject.AddComponent();
+ config = new CabinetConfig();
+ }
+ func?.Invoke(cabinetComp, config);
+ cabinetComp.ConfigJson = CabinetConfigUtility.Serialize(config);
+ }
+
+ protected void ReadWearableConfig(out WearableConfig config)
+ {
+ if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out config))
+ {
+ config = null;
+ }
+ }
+
+ protected void ReadWearableModule(out T module) where T : IModuleConfig
+ {
+ ReadWearableConfig(out var config);
+ if (config == null)
+ {
+ module = default;
+ return;
+ }
+ module = config.FindModuleConfig();
+ }
+
+ protected void WriteWearableConfig(Action func)
+ {
+ if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config))
+ {
+ return;
+ }
+ func?.Invoke(config);
+ _wearableComp.ConfigJson = WearableConfigUtility.Serialize(config);
+ }
+
+ protected void WriteWearableModule(Action func) where T : IModuleConfig
+ {
+ WriteWearableConfig((config) =>
+ {
+ var module = config.FindModuleConfig();
+ if (module == null)
+ {
+ return;
+ }
+ func?.Invoke(module);
+ });
+ }
+ }
+}
diff --git a/Editor/Configurator/Modules/OneConfModuleBase.cs.meta b/Editor/Configurator/Modules/OneConfModuleBase.cs.meta
new file mode 100644
index 00000000..223d1672
--- /dev/null
+++ b/Editor/Configurator/Modules/OneConfModuleBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 720a49557cbe58b48add646ef9850596
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Inspector/DTCabinetEditor.cs b/Editor/Inspector/DTCabinetEditor.cs
index 08dab8dd..83dda1c5 100644
--- a/Editor/Inspector/DTCabinetEditor.cs
+++ b/Editor/Inspector/DTCabinetEditor.cs
@@ -46,7 +46,7 @@ public override void OnInspectorGUI()
var window = (DTMainEditorWindow)EditorWindow.GetWindow(typeof(DTMainEditorWindow));
window.titleContent = new GUIContent(t._("tool.name"));
window.Show();
- window.SelectCabinet(cabinet);
+ window.SelectAvatar(cabinet.RootGameObject);
}
}
}
diff --git a/Editor/Passes/Cabinet/WardrobePass.cs b/Editor/Passes/Cabinet/WardrobePass.cs
index 1edd4187..a4327b1a 100644
--- a/Editor/Passes/Cabinet/WardrobePass.cs
+++ b/Editor/Passes/Cabinet/WardrobePass.cs
@@ -76,7 +76,7 @@ private static bool LookUpForWardrobe(Transform root, out DTWardrobe wardrobe)
return false;
}
- private static bool LookUpForOutfit(Transform root, out IOutfit outfit)
+ private static bool LookUpForOutfit(Transform root, out IOutfitComponent outfit)
{
while (root != null)
{
@@ -212,7 +212,7 @@ private static void ComposeOutfitItems(Transform parent)
}
}
- private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Transform outfitTransform, IOutfit outfit)
+ private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Transform outfitTransform, IOutfitComponent outfit)
{
if (wardrobeComp.UseAsMenuGroup)
{
@@ -238,7 +238,7 @@ private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Tr
}
}
- private static void CreateOutfitEnableItem(Context ctx, DTWardrobe wardrobeComp, DTParameterSlot slot, Transform outfitTransform, IOutfit outfit, float mappedVal)
+ private static void CreateOutfitEnableItem(Context ctx, DTWardrobe wardrobeComp, DTParameterSlot slot, Transform outfitTransform, IOutfitComponent outfit, float mappedVal)
{
if (wardrobeComp.UseAsMenuGroup)
{
@@ -456,7 +456,7 @@ private static void ComposeOutfitEnableItems(Context ctx, DTParameterSlot slot,
if (LookUpForWardrobe(outfitEnableItem.transform, out var wardrobeComp) && !wardrobeComp.UseAsMenuGroup)
{
// look up for wardrobe. if found, check if not used as menu group
- IOutfit outfit;
+ IOutfitComponent outfit;
if (outfitEnableItem.TargetOutfit != null)
{
outfit = outfitEnableItem.TargetOutfit;
diff --git a/Editor/UI/DTMainEditorWindow.cs b/Editor/UI/DTMainEditorWindow.cs
index d74fd95e..78042d02 100644
--- a/Editor/UI/DTMainEditorWindow.cs
+++ b/Editor/UI/DTMainEditorWindow.cs
@@ -17,7 +17,6 @@
using System.Diagnostics.CodeAnalysis;
using Chocopoi.DressingFramework.Localization;
-using Chocopoi.DressingTools.Components.OneConf;
using Chocopoi.DressingTools.Localization;
using Chocopoi.DressingTools.UI.Views;
using UnityEditor;
@@ -46,7 +45,7 @@ public static void ShowWindow()
window.Show();
}
- public void SelectCabinet(DTCabinet cabinet) => _view.SelectCabinet(cabinet);
+ public void SelectAvatar(GameObject avatarGameObject) => _view.SelectAvatar(avatarGameObject);
public void StartDressing(GameObject avatarGameObject, GameObject wearableGameObject)
{
diff --git a/Editor/UI/Presenters/AvatarPresenter.cs b/Editor/UI/Presenters/AvatarPresenter.cs
new file mode 100644
index 00000000..6259e3ba
--- /dev/null
+++ b/Editor/UI/Presenters/AvatarPresenter.cs
@@ -0,0 +1,156 @@
+/*
+ * File: CabinetPresenter.cs
+ * Project: DressingTools
+ * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
+ * Author: chocopoi (poi@chocopoi.com)
+ * -----
+ * Copyright (c) 2023 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingTools.UI.Views;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.UI.Presenters
+{
+ internal class AvatarPresenter
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+
+ private IAvatarSubView _view;
+
+ public AvatarPresenter(IAvatarSubView view)
+ {
+ _view = view;
+
+ SubscribeEvents();
+ }
+
+ private void SubscribeEvents()
+ {
+ _view.Load += OnLoad;
+ _view.Unload += OnUnload;
+
+ _view.AddOutfitButtonClick += OnAddOutfitButtonClick;
+ _view.ForceUpdateView += OnForceUpdateView;
+ _view.AvatarSettingsChange += OnAvatarSettingsChange;
+
+ EditorApplication.hierarchyChanged += OnHierarchyChanged;
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ }
+
+ private void UnsubscribeEvents()
+ {
+ _view.Load -= OnLoad;
+ _view.Unload -= OnUnload;
+
+ _view.AddOutfitButtonClick -= OnAddOutfitButtonClick;
+ _view.ForceUpdateView -= OnForceUpdateView;
+ _view.AvatarSettingsChange -= OnAvatarSettingsChange;
+
+ EditorApplication.hierarchyChanged -= OnHierarchyChanged;
+ EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
+ }
+
+ private void OnPlayModeStateChanged(PlayModeStateChange change)
+ {
+ if (change == PlayModeStateChange.EnteredEditMode)
+ {
+ UpdateView();
+ }
+ }
+
+ private void OnHierarchyChanged()
+ {
+ if (Application.isPlaying) return;
+ UpdateView();
+ }
+
+ private void OnAvatarSettingsChange()
+ {
+ var settings = AvatarUtils.GetAvatarSettings(_view.SelectedAvatarGameObject);
+ if (settings == null)
+ {
+ return;
+ }
+
+ // TODO: explicitly convert one by one
+ settings.WriteDefaultsMode = (WriteDefaultsModes)_view.SettingsAnimationWriteDefaultsMode;
+ }
+
+ private void OnForceUpdateView()
+ {
+ UpdateView();
+ }
+
+ private void UpdateAvatarContentView()
+ {
+ _view.InstalledOutfitPreviews.Clear();
+
+ // TODO: explicitly convert one by one
+ var settings = AvatarUtils.GetAvatarSettings(_view.SelectedAvatarGameObject);
+ if (settings != null)
+ {
+ _view.SettingsAnimationWriteDefaultsMode = (int)settings.WriteDefaultsMode;
+ }
+
+ var wardrobe = AvatarUtils.GetWardrobeProvider(_view.SelectedAvatarGameObject);
+ if (wardrobe != null)
+ {
+ var outfits = wardrobe.GetOutfits();
+
+ foreach (var outfit in outfits)
+ {
+ _view.InstalledOutfitPreviews.Add(new OutfitPreview()
+ {
+ name = outfit.Name,
+ thumbnail = outfit.Icon,
+ RemoveButtonClick = () =>
+ {
+ wardrobe.RemoveOutfit(outfit);
+ UpdateView();
+ },
+ EditButtonClick = () =>
+ {
+ _view.StartDressing(_view.SelectedAvatarGameObject, outfit.RootTransform.gameObject);
+ }
+ });
+ }
+ }
+ }
+
+ private void UpdateView()
+ {
+ UpdateAvatarContentView();
+ _view.Repaint();
+ }
+
+ private void OnLoad()
+ {
+ UpdateView();
+ }
+
+ private void OnUnload()
+ {
+ UnsubscribeEvents();
+ }
+
+ private void OnAddOutfitButtonClick()
+ {
+ _view.StartDressing(_view.SelectedAvatarGameObject);
+ }
+ }
+}
diff --git a/Editor/UI/Presenters/CabinetPresenter.cs.meta b/Editor/UI/Presenters/AvatarPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/CabinetPresenter.cs.meta
rename to Editor/UI/Presenters/AvatarPresenter.cs.meta
diff --git a/Editor/UI/Presenters/CabinetPresenter.cs b/Editor/UI/Presenters/CabinetPresenter.cs
deleted file mode 100644
index 78b56e72..00000000
--- a/Editor/UI/Presenters/CabinetPresenter.cs
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * File: CabinetPresenter.cs
- * Project: DressingTools
- * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
- * Author: chocopoi (poi@chocopoi.com)
- * -----
- * Copyright (c) 2023 chocopoi
- *
- * This file is part of DressingTools.
- *
- * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
- */
-
-using Chocopoi.DressingFramework.Localization;
-using Chocopoi.DressingTools.Components.OneConf;
-using Chocopoi.DressingTools.Localization;
-using Chocopoi.DressingTools.OneConf;
-using Chocopoi.DressingTools.OneConf.Cabinet;
-using Chocopoi.DressingTools.OneConf.Cabinet.Modules.BuiltIn;
-using Chocopoi.DressingTools.OneConf.Serialization;
-using Chocopoi.DressingTools.UI.Views;
-using UnityEditor;
-using UnityEngine;
-
-namespace Chocopoi.DressingTools.UI.Presenters
-{
- internal class CabinetPresenter
- {
- private static readonly I18nTranslator t = I18n.ToolTranslator;
-
- private ICabinetSubView _view;
- private CabinetConfig _cabinetConfig;
-
- public CabinetPresenter(ICabinetSubView view)
- {
- _view = view;
-
- SubscribeEvents();
- }
-
- private void SubscribeEvents()
- {
- _view.Load += OnLoad;
- _view.Unload += OnUnload;
-
- _view.AddWearableButtonClick += OnAddWearableButtonClick;
- _view.ForceUpdateView += OnForceUpdateView;
- _view.SelectedCabinetChange += OnSelectedCabinetChange;
- _view.CabinetSettingsChange += OnCabinetSettingsChange;
- _view.ToolbarCreateCabinetButtonClick += OnToolbarCreateCabinetButtonClick;
- _view.CreateCabinetStartButtonClick += OnCreateCabinetStartButtonClick;
- _view.CreateCabinetBackButtonClick += OnCreateCabinetBackButtonClick;
-
- EditorApplication.hierarchyChanged += OnHierarchyChanged;
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
- }
-
- private void UnsubscribeEvents()
- {
- _view.Load -= OnLoad;
- _view.Unload -= OnUnload;
-
- _view.AddWearableButtonClick -= OnAddWearableButtonClick;
- _view.ForceUpdateView -= OnForceUpdateView;
- _view.SelectedCabinetChange -= OnSelectedCabinetChange;
- _view.CabinetSettingsChange -= OnCabinetSettingsChange;
- _view.ToolbarCreateCabinetButtonClick -= OnToolbarCreateCabinetButtonClick;
- _view.CreateCabinetStartButtonClick -= OnCreateCabinetStartButtonClick;
- _view.CreateCabinetBackButtonClick -= OnCreateCabinetBackButtonClick;
-
- EditorApplication.hierarchyChanged -= OnHierarchyChanged;
- EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
- }
-
- private void OnPlayModeStateChanged(PlayModeStateChange change)
- {
- if (change == PlayModeStateChange.EnteredEditMode)
- {
- UpdateView();
- }
- }
-
- private void OnCreateCabinetBackButtonClick()
- {
- _view.ShowCreateCabinetPanel = false;
- UpdateView();
- }
-
- private void OnCreateCabinetStartButtonClick()
- {
- if (_view.CreateCabinetAvatarGameObject == null)
- {
- return;
- }
- OneConfUtils.GetAvatarCabinet(_view.CreateCabinetAvatarGameObject, true);
- _view.ShowCreateCabinetPanel = false;
- UpdateView();
- }
-
- private void OnToolbarCreateCabinetButtonClick()
- {
- _view.ShowCreateCabinetPanel = true;
- _view.ShowCreateCabinetBackButton = true;
- _view.CreateCabinetAvatarGameObject = null;
-
- UpdateView();
- }
-
- private void OnHierarchyChanged()
- {
- if (Application.isPlaying) return;
- UpdateView();
- }
-
-
- private void OnSelectedCabinetChange()
- {
- UpdateView();
- }
-
- private void OnCabinetSettingsChange()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
-
- var cabinet = cabinets[_view.SelectedCabinetIndex];
-
- cabinet.RootGameObject = _view.CabinetAvatarGameObject;
-
- if (_cabinetConfig == null)
- {
- Debug.LogWarning("[DressingTools] Cabinet config is uninitialized from UI but cabinet settings changed.");
- return;
- }
-
- _cabinetConfig.avatarArmatureName = _view.CabinetAvatarArmatureName;
- _cabinetConfig.groupDynamics = _view.CabinetGroupDynamics;
- _cabinetConfig.groupDynamicsSeparateGameObjects = _view.CabinetGroupDynamicsSeparateGameObjects;
- _cabinetConfig.animationWriteDefaultsMode = (CabinetConfig.WriteDefaultsMode)_view.CabinetAnimationWriteDefaultsMode;
-
- var cabAnimConfig = _cabinetConfig.FindModuleConfig();
- if (cabAnimConfig == null)
- {
- cabAnimConfig = new CabinetAnimCabinetModuleConfig();
- _cabinetConfig.modules.Add(new CabinetModule()
- {
- config = cabAnimConfig,
- moduleName = CabinetAnimCabinetModuleConfig.ModuleIdentifier
- });
- }
-
- cabAnimConfig.thumbnails = _view.CabinetUseThumbnailsAsMenuIcons;
- cabAnimConfig.menuInstallPath = _view.CabinetMenuInstallPath;
- cabAnimConfig.menuItemName = _view.CabinetMenuItemName;
- cabAnimConfig.networkSynced = _view.CabinetNetworkSynced;
- cabAnimConfig.saved = _view.CabinetSaved;
- cabAnimConfig.resetCustomizablesOnSwitch = _view.CabinetResetCustomizablesOnSwitch;
-
- cabinet.ConfigJson = CabinetConfigUtility.Serialize(_cabinetConfig);
- }
-
- private void OnForceUpdateView()
- {
- UpdateView();
- }
-
- public void SelectCabinet(DTCabinet cabinet)
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
- _view.ShowCreateCabinetPanel = false;
-
- // refresh the keys first
- UpdateCabinetSelectionDropdown(cabinets);
-
- // find matching index
- for (var i = 0; i < cabinets.Length; i++)
- {
- if (cabinets[i] == cabinet)
- {
- _view.SelectedCabinetIndex = i;
- break;
- }
- }
-
- // update
- UpdateView();
- }
-
- private void UpdateCabinetSelectionDropdown(DTCabinet[] cabinets)
- {
- // cabinet selection dropdown
- _view.AvailableCabinetSelections.Clear();
- for (var i = 0; i < cabinets.Length; i++)
- {
- _view.AvailableCabinetSelections.Add(cabinets[i].RootGameObject != null ? cabinets[i].RootGameObject.name : t._("cabinet.editor.cabinetContent.popup.cabinetOptions.cabinetNameNoGameObjectAttached", i + 1));
- }
- }
-
- private void UpdateCabinetContentView()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
-
- UpdateCabinetSelectionDropdown(cabinets);
-
- if (_view.SelectedCabinetIndex < 0 || _view.SelectedCabinetIndex >= cabinets.Length)
- {
- // invalid selected cabinet index, setting it back to 0
- _view.SelectedCabinetIndex = 0;
- }
-
- // clear views
- _view.InstalledWearablePreviews.Clear();
-
- // update selected cabinet view
- var cabinet = cabinets[_view.SelectedCabinetIndex];
-
- // cabinet json is broken, ask user whether to make a new one or not
- if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out _cabinetConfig) || !_cabinetConfig.IsValid())
- {
- Debug.LogWarning("[DressingTools] [CabinetPresenter] Unable to deserialize cabinet config or invalid configuration! Using new config instead");
- _cabinetConfig = new CabinetConfig();
- cabinet.ConfigJson = CabinetConfigUtility.Serialize(_cabinetConfig);
- }
-
- _view.CabinetAvatarGameObject = cabinet.RootGameObject;
- _view.CabinetAvatarArmatureName = _cabinetConfig.avatarArmatureName;
- _view.CabinetGroupDynamics = _cabinetConfig.groupDynamics;
- _view.CabinetGroupDynamicsSeparateGameObjects = _cabinetConfig.groupDynamicsSeparateGameObjects;
- _view.CabinetAnimationWriteDefaultsMode = (int)_cabinetConfig.animationWriteDefaultsMode;
- UpdateCabinetAnimationConfig();
-
- var wearables = OneConfUtils.GetCabinetWearables(cabinet.RootGameObject);
-
- foreach (var wearable in wearables)
- {
- var config = WearableConfigUtility.Deserialize(wearable.ConfigJson);
- _view.InstalledWearablePreviews.Add(new WearablePreview()
- {
- name = config != null ?
- config.info.name :
- t._("cabinet.editor.cabinetContent.wearablePreview.name.unableToLoadConfiguration"),
- thumbnail = config != null && config.info.thumbnail != null ?
- OneConfUtils.GetTextureFromBase64(config.info.thumbnail) :
- null,
- RemoveButtonClick = () =>
- {
- if (wearable is DTWearable dtWearable)
- {
- cabinet.RemoveWearable(dtWearable);
- UpdateView();
- }
- else
- {
- Debug.LogWarning("[DressingTools] Removing non-DressingTools wearable is not currently supported");
- }
- },
- EditButtonClick = () =>
- {
- _view.StartDressing(cabinet.RootGameObject, wearable.RootGameObject);
- }
- });
- }
- }
-
- private void UpdateCabinetAnimationConfig()
- {
- var cabAnimConfig = _cabinetConfig.FindModuleConfig() ?? new CabinetAnimCabinetModuleConfig();
- _view.CabinetUseThumbnailsAsMenuIcons = cabAnimConfig.thumbnails;
- _view.CabinetMenuInstallPath = cabAnimConfig.menuInstallPath;
- _view.CabinetMenuItemName = cabAnimConfig.menuItemName;
- _view.CabinetNetworkSynced = cabAnimConfig.networkSynced;
- _view.CabinetSaved = cabAnimConfig.saved;
- _view.CabinetResetCustomizablesOnSwitch = cabAnimConfig.resetCustomizablesOnSwitch;
- }
-
- private void UpdateView()
- {
- UpdateCabinetContentView();
- _view.Repaint();
- }
-
- private void OnLoad()
- {
- UpdateView();
- }
-
- private void OnUnload()
- {
- UnsubscribeEvents();
- }
-
- private void OnAddWearableButtonClick()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0 || _view.SelectedCabinetIndex < 0 || _view.SelectedCabinetIndex >= cabinets.Length)
- {
- return;
- }
-
- var cabinet = cabinets[_view.SelectedCabinetIndex];
- _view.StartDressing(cabinet.RootGameObject);
- }
- }
-}
diff --git a/Editor/UI/Presenters/DressPresenter.cs b/Editor/UI/Presenters/DressPresenter.cs
new file mode 100644
index 00000000..a66626de
--- /dev/null
+++ b/Editor/UI/Presenters/DressPresenter.cs
@@ -0,0 +1,102 @@
+/*
+ * File: DressingPresenter.cs
+ * Project: DressingTools
+ * Created Date: Saturday, August 12th 2023, 1:22:09 am
+ * Author: chocopoi (poi@chocopoi.com)
+ * -----
+ * Copyright (c) 2023 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingFramework;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.Configurator;
+using Chocopoi.DressingTools.Configurator.Cabinet;
+using Chocopoi.DressingTools.UI.Views;
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.UI.Presenters
+{
+ internal class DressPresenter
+ {
+ private IDressSubView _view;
+
+ public DressPresenter(IDressSubView view)
+ {
+ _view = view;
+
+ SubscribeEvents();
+ }
+
+ private void SubscribeEvents()
+ {
+ _view.Load += OnLoad;
+ _view.Unload += OnUnload;
+
+ _view.ForceUpdateView += OnForceUpdateView;
+ _view.StartButtonClick += OnStartButtonClick;
+ }
+
+ private void UnsubscribeEvents()
+ {
+ _view.Load -= OnLoad;
+ _view.Unload -= OnUnload;
+
+ _view.ForceUpdateView -= OnForceUpdateView;
+ _view.StartButtonClick -= OnStartButtonClick;
+ }
+
+ private void OnForceUpdateView()
+ {
+ UpdateView();
+ }
+
+ private void OnStartButtonClick()
+ {
+ if (_view.SelectedAvatarGameObject == null)
+ {
+ Debug.LogError("Avatar is not selected.");
+ return;
+ }
+ if (_view.SelectedOutfitGameObject == null)
+ {
+ Debug.LogError("Outfit is not selected.");
+ return;
+ }
+ if (!DKEditorUtils.IsGrandParent(_view.SelectedAvatarGameObject.transform, _view.SelectedOutfitGameObject.transform) && _view.SelectedAvatarGameObject.transform.Find(_view.SelectedOutfitGameObject.name) != null)
+ {
+ Debug.LogError("There is already an outfit with the same name in the avatar.");
+ return;
+ }
+ if (!_view.SelectedOutfitGameObject.TryGetComponent(out var oneConfWearableComp))
+ {
+ oneConfWearableComp = _view.SelectedOutfitGameObject.AddComponent();
+ }
+ var outfit = new OneConfConfigurableOutfit(_view.SelectedAvatarGameObject, oneConfWearableComp);
+ AvatarPreviewUtility.StartAvatarPreview(_view.SelectedAvatarGameObject, outfit);
+ }
+
+ private void UpdateView()
+ {
+ _view.Repaint();
+ }
+
+ private void OnLoad()
+ {
+ UpdateView();
+ }
+
+ private void OnUnload()
+ {
+ UnsubscribeEvents();
+ }
+ }
+}
diff --git a/Editor/UI/Presenters/DressingPresenter.cs.meta b/Editor/UI/Presenters/DressPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/DressingPresenter.cs.meta
rename to Editor/UI/Presenters/DressPresenter.cs.meta
diff --git a/Editor/UI/Presenters/DressingPresenter.cs b/Editor/UI/Presenters/DressingPresenter.cs
deleted file mode 100644
index 76272ec8..00000000
--- a/Editor/UI/Presenters/DressingPresenter.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * File: DressingPresenter.cs
- * Project: DressingTools
- * Created Date: Saturday, August 12th 2023, 1:22:09 am
- * Author: chocopoi (poi@chocopoi.com)
- * -----
- * Copyright (c) 2023 chocopoi
- *
- * This file is part of DressingTools.
- *
- * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
- */
-
-using Chocopoi.DressingTools.OneConf;
-using Chocopoi.DressingTools.OneConf.Cabinet;
-using Chocopoi.DressingTools.OneConf.Serialization;
-using Chocopoi.DressingTools.OneConf.Wearable;
-using Chocopoi.DressingTools.UI.Views;
-using UnityEngine;
-
-namespace Chocopoi.DressingTools.UI.Presenters
-{
- internal class DressingPresenter
- {
- private IDressingSubView _view;
-
- public DressingPresenter(IDressingSubView view)
- {
- _view = view;
-
- SubscribeEvents();
- }
-
- private void SubscribeEvents()
- {
- _view.Load += OnLoad;
- _view.Unload += OnUnload;
-
- _view.ForceUpdateView += OnForceUpdateView;
- _view.TargetAvatarOrWearableChange += OnTargetAvatarOrWearableChange;
- _view.AddToCabinetButtonClick += OnAddToCabinetButtonClick;
- }
-
- private void UnsubscribeEvents()
- {
- _view.Load -= OnLoad;
- _view.Unload -= OnUnload;
-
- _view.ForceUpdateView -= OnForceUpdateView;
- _view.TargetAvatarOrWearableChange -= OnTargetAvatarOrWearableChange;
- _view.AddToCabinetButtonClick -= OnAddToCabinetButtonClick;
- }
-
- private void OnForceUpdateView()
- {
- UpdateView();
- }
-
- private void UpdateView()
- {
- var cabinet = OneConfUtils.GetAvatarCabinet(_view.TargetAvatar);
- _view.DisableAddToCabinetButton = cabinet == null;
- _view.Repaint();
- }
-
- private void OnLoad()
- {
- UpdateView();
- }
-
- private void OnUnload()
- {
- UnsubscribeEvents();
- }
-
- private void CreateNewConfig()
- {
- if (_view.TargetAvatar == null)
- {
- return;
- }
-
- _view.Config = new WearableConfig();
- OneConfUtils.PrepareWearableConfig(_view.Config, _view.TargetAvatar, _view.TargetWearable);
- _view.ForceUpdateConfigView();
- _view.AutoSetup();
- }
-
- private void OnTargetAvatarOrWearableChange()
- {
- // try find if the wearable has a config, if yes, use advanced mode for editing
- if (_view.TargetWearable != null)
- {
- var cabinetWearable = OneConfUtils.GetCabinetWearable(_view.TargetWearable);
- if (cabinetWearable != null)
- {
- if (WearableConfigUtility.TryDeserialize(cabinetWearable.ConfigJson, out var config))
- {
- _view.Config = config;
- _view.SelectedDressingMode = 1;
- _view.ForceUpdateConfigView();
- }
- else
- {
- CreateNewConfig();
- }
- }
- else
- {
- CreateNewConfig();
- }
- }
-
- UpdateView();
- }
-
- private void OnAddToCabinetButtonClick()
- {
- if (!_view.IsConfigValid())
- {
- _view.ShowFixAllInvalidConfig();
- Debug.Log("[DressingTools] Invalid configuration. Cannot proceed adding to cabinet");
- return;
- }
-
- var cabinet = OneConfUtils.GetAvatarCabinet(_view.TargetAvatar);
-
- if (cabinet == null)
- {
- return;
- }
-
- if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var cabinetConfig))
- {
- // TODO: handle deserialization error
- Debug.Log("[DressingTools] Could not deserialize cabinet config");
- return;
- }
-
- // remove previews
- DTEditorUtils.CleanUpPreviewAvatars();
- DTEditorUtils.FocusGameObjectInSceneView(_view.TargetAvatar);
-
- _view.ApplyToConfig();
-
- if (cabinet.AddWearable(_view.Config, _view.TargetWearable))
- {
- // reset and return
- _view.ResetWizardAndConfigView();
- _view.SelectTab(0);
-
- _view.ForceUpdateCabinetSubView();
- }
- else
- {
- _view.ShowFixAllInvalidConfig();
- }
- }
- }
-}
diff --git a/Editor/UI/Presenters/MainPresenter.cs b/Editor/UI/Presenters/MainPresenter.cs
index 8efa263e..737b597b 100644
--- a/Editor/UI/Presenters/MainPresenter.cs
+++ b/Editor/UI/Presenters/MainPresenter.cs
@@ -15,9 +15,14 @@
* You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
*/
+using System;
+using System.Collections.Generic;
using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
using Chocopoi.DressingTools.UI.Views;
using UnityEditor;
+using UnityEngine;
+using UnityEngine.SceneManagement;
#if UNITY_2021_2_OR_NEWER
using PrefabStage = UnityEditor.SceneManagement.PrefabStage;
@@ -53,9 +58,13 @@ private void SubscribeEvents()
_view.MouseMove += OnMouseMove;
_view.UpdateAvailableUpdateButtonClick += OnUpdateAvailableUpdateButtonClick;
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
_view.PrefabStageOpened += OnPrefabStageOpened;
_view.PrefabStageClosing += OnPrefabStageClosing;
+ _view.AvatarSelectionPopupChange += OnSelectedAvatarChange;
+
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ EditorApplication.hierarchyChanged += OnHierarchyChanged;
+ Selection.selectionChanged += OnGameObjectSelectionChanged;
}
private void UnsubscribeEvents()
@@ -65,9 +74,80 @@ private void UnsubscribeEvents()
_view.MouseMove -= OnMouseMove;
_view.UpdateAvailableUpdateButtonClick -= OnUpdateAvailableUpdateButtonClick;
+ _view.PrefabStageOpened -= OnPrefabStageOpened;
+ _view.PrefabStageClosing -= OnPrefabStageClosing;
+ _view.AvatarSelectionPopupChange -= OnSelectedAvatarChange;
+
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
- PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
- PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
+ EditorApplication.hierarchyChanged -= OnHierarchyChanged;
+ Selection.selectionChanged -= OnGameObjectSelectionChanged;
+ }
+
+ private void OnHierarchyChanged()
+ {
+ if (Application.isPlaying) return;
+ UpdateView();
+ }
+
+ private void OnGameObjectSelectionChanged()
+ {
+ if (Selection.activeGameObject == null) return;
+
+ var avatar = AvatarUtils.GetAvatarRoot(Selection.activeGameObject);
+ if (avatar != null)
+ {
+ SelectAvatar(avatar);
+ }
+ }
+
+ private void OnSelectedAvatarChange()
+ {
+ UpdateView();
+ }
+
+ private void UpdateAvatarSelectionDropdown(List avatars)
+ {
+ // avatar selection dropdown
+ _view.AvailableAvatarSelections.Clear();
+ for (var i = 0; i < avatars.Count; i++)
+ {
+ _view.AvailableAvatarSelections.Add(avatars[i].name);
+ }
+ }
+ public void SelectAvatar(GameObject avatarGameObject)
+ {
+ // refresh the keys first
+ var avatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene());
+ UpdateAvatarSelectionDropdown(avatars);
+
+ // find matching index
+ for (var i = 0; i < avatars.Count; i++)
+ {
+ if (avatars[i] == avatarGameObject)
+ {
+ _view.SelectedAvatarIndex = i;
+ break;
+ }
+ }
+
+ // update
+ UpdateView();
+ }
+
+ private void UpdateView()
+ {
+ var avatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene());
+ UpdateAvatarSelectionDropdown(avatars);
+ if (_view.SelectedAvatarIndex < 0 || _view.SelectedAvatarIndex >= avatars.Count)
+ {
+ // invalid selected cabinet index, setting it back to 0
+ _view.SelectedAvatarIndex = 0;
+ }
+ _view.SelectedAvatarGameObject = avatars[_view.SelectedAvatarIndex];
+
+ _view.ToolVersionText = UpdateChecker.CurrentVersion?.fullString;
+
+ _view.Repaint();
}
private void OnPlayModeStateChanged(PlayModeStateChange change)
@@ -123,6 +203,7 @@ private void OnPrefabStageClosing(PrefabStage stage)
private void OnLoad()
{
+ UpdateView();
}
private void OnUnload()
diff --git a/Editor/UI/Views/AvatarSubView.cs b/Editor/UI/Views/AvatarSubView.cs
new file mode 100644
index 00000000..e74db370
--- /dev/null
+++ b/Editor/UI/Views/AvatarSubView.cs
@@ -0,0 +1,233 @@
+/*
+ * File: CabinetSubView.cs
+ * Project: DressingTools
+ * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
+ * Author: chocopoi (poi@chocopoi.com)
+ * -----
+ * Copyright (c) 2023 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingTools.UI.Presenters;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.UI.Views
+{
+ [ExcludeFromCodeCoverage]
+ internal class AvatarSubView : ElementView, IAvatarSubView
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+ private static VisualTreeAsset s_avatarOutfitPreviewVisualTree = null;
+ private static VisualTreeAsset s_addWearablePlaceholderVisualTree = null;
+ private static StyleSheet s_avatarSubViewStyleSheet = null;
+ private static Texture2D s_thumbnailPlaceholder = null;
+
+ public event Action AddOutfitButtonClick;
+ public event Action AvatarSettingsChange;
+
+ public GameObject SelectedAvatarGameObject { get => _mainView.SelectedAvatarGameObject; set => _mainView.SelectedAvatarGameObject = value; }
+ public int SettingsAnimationWriteDefaultsMode { get; set; }
+ public List InstalledOutfitPreviews { get; set; }
+
+ private IMainView _mainView;
+ private AvatarPresenter _avatarPresenter;
+ private VisualElement _avatarContentContainer;
+ private VisualElement _wardrobeSettingsContainer;
+ private VisualElement _installedOutfitContainer;
+ private PopupField _animationWriteDefaultsPopup;
+
+ public AvatarSubView(IMainView mainView)
+ {
+ _mainView = mainView;
+ _avatarPresenter = new AvatarPresenter(this);
+
+ InstalledOutfitPreviews = new List();
+
+ SettingsAnimationWriteDefaultsMode = 0;
+ }
+
+ public void SelectTab(int selectedTab)
+ {
+ _mainView.SelectedTab = selectedTab;
+ }
+
+ public void StartDressing(GameObject avatarGameObject = null, GameObject wearableGameObject = null)
+ {
+ _mainView.StartDressing(avatarGameObject != null ? avatarGameObject : SelectedAvatarGameObject, wearableGameObject);
+ }
+
+ private void InitSettings()
+ {
+ // TODO: Create view from avatar settings directly?
+ var container = Q("settings-container").First();
+ container.Clear();
+
+ container.Add(CreateHelpBox(t._("editor.main.avatar.settings.avatar.helpbox.conceptAndLocationChanged"), MessageType.Info));
+
+ var choices = new List() {
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.auto"),
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.on"),
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.off")
+ };
+ _animationWriteDefaultsPopup = new PopupField(t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode"), choices, 0);
+ _animationWriteDefaultsPopup.RegisterValueChangedCallback((evt) =>
+ {
+ SettingsAnimationWriteDefaultsMode = _animationWriteDefaultsPopup.index;
+ AvatarSettingsChange?.Invoke();
+ });
+ container.Add(_animationWriteDefaultsPopup);
+
+ _wardrobeSettingsContainer = new VisualElement();
+ container.Add(_wardrobeSettingsContainer);
+ }
+
+ private void BindFoldouts()
+ {
+ BindFoldoutHeaderWithContainer("foldout-settings", "settings-container");
+ BindFoldoutHeaderWithContainer("foldout-outfits", "outfits-container");
+ }
+
+ private void InitAvatarContent()
+ {
+ _avatarContentContainer = Q("cabinet-content-container").First();
+ _installedOutfitContainer = Q("outfits-container").First();
+
+ InitSettings();
+ BindFoldouts();
+ }
+
+ private void InitVisualTree()
+ {
+ var tree = Resources.Load("AvatarSubView");
+ tree.CloneTree(this);
+ // var styleSheet = Resources.Load("AvatarSubViewStyles");
+ // if (!styleSheets.Contains(styleSheet))
+ // {
+ // styleSheets.Add(styleSheet);
+ // }
+ }
+
+ public override void OnEnable()
+ {
+ InitVisualTree();
+ InitAvatarContent();
+
+ t.LocalizeElement(this);
+
+ RaiseLoadEvent();
+ }
+
+ private VisualElement CreateAddPlaceholderElement()
+ {
+ if (s_addWearablePlaceholderVisualTree == null)
+ {
+ s_addWearablePlaceholderVisualTree = Resources.Load("AddOutfitPlaceholder");
+ }
+
+ if (s_avatarSubViewStyleSheet == null)
+ {
+ s_avatarSubViewStyleSheet = Resources.Load("AvatarSubViewStyles");
+ }
+
+ var element = new VisualElement();
+ element.style.width = 128;
+ element.style.height = 128;
+ element.styleSheets.Add(s_avatarSubViewStyleSheet);
+ s_addWearablePlaceholderVisualTree.CloneTree(element);
+ t.LocalizeElement(element);
+
+ element.RegisterCallback((MouseDownEvent evt) => AddOutfitButtonClick?.Invoke());
+ element.RegisterCallback((MouseEnterEvent evt) => element.EnableInClassList("hover", true));
+ element.RegisterCallback((MouseLeaveEvent evt) => element.EnableInClassList("hover", false));
+
+ return element;
+ }
+
+ private VisualElement CreateAvatarOutfitPreviewElement(string wearableName, Texture2D thumbnail, Action removeBtnClick, Action editBtnClick)
+ {
+ if (s_avatarOutfitPreviewVisualTree == null)
+ {
+ s_avatarOutfitPreviewVisualTree = Resources.Load("AvatarOutfitPreview");
+ }
+
+ if (s_avatarSubViewStyleSheet == null)
+ {
+ s_avatarSubViewStyleSheet = Resources.Load("AvatarSubViewStyles");
+ }
+
+ if (s_thumbnailPlaceholder == null)
+ {
+ s_thumbnailPlaceholder = Resources.Load("thumbnailPlaceholder");
+ }
+
+ var element = new VisualElement();
+ element.style.width = 128;
+ element.style.height = 128;
+ element.styleSheets.Add(s_avatarSubViewStyleSheet);
+ s_avatarOutfitPreviewVisualTree.CloneTree(element);
+ t.LocalizeElement(element);
+
+ element.Q