diff --git a/Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs b/Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs index e766d689..eb0bd8f2 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs @@ -18,6 +18,7 @@ using Chocopoi.DressingTools.Cabinet; using Chocopoi.DressingTools.Lib.Cabinet; using Chocopoi.DressingTools.Lib.Wearable; +using Chocopoi.DressingTools.Lib.Wearable.Modules.Providers; using Newtonsoft.Json; using UnityEditor; using UnityEngine; @@ -80,8 +81,14 @@ public static DTCabinet GetAvatarCabinet(GameObject avatar, bool createIfNotExis return comp; } - public static void AddCabinetWearable(DTCabinet cabinet, WearableConfig config, GameObject wearableGameObject) + public static bool AddCabinetWearable(DTCabinet cabinet, WearableConfig config, GameObject wearableGameObject) { + // do not add if there's an existing component + if (wearableGameObject.GetComponent() != null) + { + return false; + } + if (PrefabUtility.IsPartOfAnyPrefab(wearableGameObject) && PrefabUtility.GetPrefabInstanceStatus(wearableGameObject) == PrefabInstanceStatus.NotAPrefab) { // if not in scene, we instantiate it with a prefab connection @@ -91,14 +98,31 @@ public static void AddCabinetWearable(DTCabinet cabinet, WearableConfig config, // parent to avatar wearableGameObject.transform.SetParent(cabinet.transform); - if (wearableGameObject.GetComponent() == null) + // add cabinet wearable component + var cabinetWearable = wearableGameObject.AddComponent(); + + cabinetWearable.wearableGameObject = wearableGameObject; + cabinetWearable.configJson = config.Serialize().ToString(Formatting.None); + + // do provider hooks + var providers = ModuleProviderLocator.Instance.GetAllProviders(); + foreach (var provider in providers) { - // add cabinet wearable component - var cabinetWearable = wearableGameObject.AddComponent(); + var module = DTRuntimeUtils.FindWearableModule(config, provider.ModuleIdentifier); + if (module == null) + { + // config does not have such module + continue; + } - cabinetWearable.wearableGameObject = wearableGameObject; - cabinetWearable.configJson = config.Serialize().ToString(Formatting.None); + if (!provider.OnAddWearableToCabinet(cabinet, config, wearableGameObject, module)) + { + Debug.LogWarning("[DressingTools] [AddCabinetWearable] Error processing provider OnAddWearableToCabinet hook: " + provider.ModuleIdentifier); + return false; + } } + + return true; } public static void RemoveCabinetWearable(DTCabinet cabinet, DTCabinetWearable wearable) diff --git a/Packages/com.chocopoi.vrc.dressingtools/Editor/Integrations/VRChat/GenerateAnimationsHook.cs b/Packages/com.chocopoi.vrc.dressingtools/Editor/Integrations/VRChat/GenerateAnimationsHook.cs index c75d8443..b7858eb5 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Editor/Integrations/VRChat/GenerateAnimationsHook.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Editor/Integrations/VRChat/GenerateAnimationsHook.cs @@ -47,18 +47,6 @@ public GenerateAnimationsHook(DTReport report, DTCabinet cabinet) _cabinet = cabinet; } - private AnimationGenerationModuleConfig FindAnimationGenerationModuleConfig(WearableConfig config) - { - foreach (var module in config.Modules) - { - if (module.config is AnimationGenerationModuleConfig agm) - { - return agm; - } - } - return null; - } - public bool OnPreprocessAvatar() { EditorUtility.DisplayProgressBar("DressingTools", "Generating animations...", 0); @@ -151,7 +139,7 @@ public bool OnPreprocessAvatar() var wearableDynamics = DTRuntimeUtils.ScanDynamics(wearables[i].wearableGameObject, false); // find the animation generation module - var module = FindAnimationGenerationModuleConfig(config); + var module = DTRuntimeUtils.FindWearableModuleConfig(config); if (module == null) { Debug.Log("[DressingTools] [BuildDTCabinetCallback] [GenerateAnimationHook] " + config.Info.name + " has no AnimationGenerationModule, skipping this wearable generation"); diff --git a/Packages/com.chocopoi.vrc.dressingtools/Lib/Runtime/Wearable/Providers/ModuleProviderBase.cs b/Packages/com.chocopoi.vrc.dressingtools/Lib/Runtime/Wearable/Providers/ModuleProviderBase.cs index 9b01628d..04fbd422 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Lib/Runtime/Wearable/Providers/ModuleProviderBase.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Lib/Runtime/Wearable/Providers/ModuleProviderBase.cs @@ -15,9 +15,11 @@ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see . */ +using Chocopoi.DressingTools.Lib.Cabinet; using Chocopoi.DressingTools.Lib.Wearable.Modules; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using UnityEngine; namespace Chocopoi.DressingTools.Lib.Wearable.Modules.Providers { @@ -34,5 +36,6 @@ public abstract class ModuleProviderBase public virtual bool OnBeforeApplyCabinet(ApplyCabinetContext ctx, IModuleConfig moduleConfig) => true; public virtual bool OnApplyWearable(ApplyCabinetContext cabCtx, ApplyWearableContext wearCtx, IModuleConfig moduleConfig) => true; public virtual bool OnAfterApplyCabinet(ApplyCabinetContext ctx, IModuleConfig moduleConfig) => true; + public virtual bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module) => true; } } diff --git a/Packages/com.chocopoi.vrc.dressingtools/Runtime/DTRuntimeUtils.cs b/Packages/com.chocopoi.vrc.dressingtools/Runtime/DTRuntimeUtils.cs index f334dce2..62ffb471 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Runtime/DTRuntimeUtils.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Runtime/DTRuntimeUtils.cs @@ -23,6 +23,7 @@ using Chocopoi.DressingTools.Lib.Cabinet; using Chocopoi.DressingTools.Lib.Proxy; using Chocopoi.DressingTools.Lib.Wearable; +using Chocopoi.DressingTools.Lib.Wearable.Modules; using Chocopoi.DressingTools.Proxy; using Newtonsoft.Json; using UnityEngine; @@ -310,6 +311,30 @@ public static Component CopyComponent(Component originalComponent, GameObject de return destComp; } + public static T FindWearableModuleConfig(WearableConfig config) where T : IModuleConfig + { + foreach (var module in config.Modules) + { + if (module.config is T moduleConfig) + { + return moduleConfig; + } + } + return default; + } + + public static WearableModule FindWearableModule(WearableConfig config, string moduleName) + { + foreach (var module in config.Modules) + { + if (moduleName == module.moduleName) + { + return module; + } + } + return null; + } + public static bool IsOriginatedFromAnyWearable(Transform root, Transform transform) { var found = false; diff --git a/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/AnimationGenerationModule.cs b/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/AnimationGenerationModule.cs index 003b62ba..3e24a212 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/AnimationGenerationModule.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/AnimationGenerationModule.cs @@ -61,6 +61,56 @@ static AnimationGenerationModuleProvider() public override IModuleConfig DeserializeModuleConfig(JObject jObject) => jObject.ToObject(); public override IModuleConfig NewModuleConfig() => new AnimationGenerationModuleConfig(); + + public override bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module) + { + var agm = (AnimationGenerationModuleConfig)module.config; + var avatarGameObject = cabinet.AvatarGameObject; + + // invert avatar toggles + foreach (var toggle in agm.avatarAnimationOnWear.toggles) + { + var avatarToggleObj = avatarGameObject.transform.Find(toggle.path); + if (avatarToggleObj == null) + { + Debug.LogWarning("[DressingTools] [AnimationGenerationModule] Avatar toggle GameObject not found at path: " + toggle.path); + continue; + } + avatarToggleObj.gameObject.SetActive(!toggle.state); + } + + // invert wearable toggles + foreach (var toggle in agm.wearableAnimationOnWear.toggles) + { + var wearableToggleObj = wearableGameObject.transform.Find(toggle.path); + if (wearableGameObject == null) + { + Debug.LogWarning("[DressingTools] [AnimationGenerationModule] Wearable toggle GameObject not found at path: " + toggle.path); + continue; + } + wearableToggleObj.gameObject.SetActive(!toggle.state); + } + + // set wearable dynamics inactive + var wearableDynamics = DTRuntimeUtils.ScanDynamics(wearableGameObject, false); + var visitedDynamicsTransforms = new List(); + foreach (var dynamics in wearableDynamics) + { + if (visitedDynamicsTransforms.Contains(dynamics.Transform)) + { + // skip duplicates since it's meaningless + continue; + } + + // we toggle GameObjects instead of components + dynamics.GameObject.SetActive(false); + + // mark as visited + visitedDynamicsTransforms.Add(dynamics.Transform); + } + + return true; + } } } diff --git a/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/BlendshapeSyncModule.cs b/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/BlendshapeSyncModule.cs index 67ad336e..43a27d56 100644 --- a/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/BlendshapeSyncModule.cs +++ b/Packages/com.chocopoi.vrc.dressingtools/Runtime/Wearable/Modules/BlendshapeSyncModule.cs @@ -57,5 +57,62 @@ static BlendshapeSyncModuleProvider() public override IModuleConfig DeserializeModuleConfig(JObject jObject) => jObject.ToObject(); public override IModuleConfig NewModuleConfig() => new BlendshapeSyncModuleConfig(); + + public override bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module) + { + var avatarGameObject = cabinet.AvatarGameObject; + var bsm = (BlendshapeSyncModuleConfig)module.config; + + // follow blendshape sync + foreach (var bs in bsm.blendshapeSyncs) + { + var avatarSmrObj = avatarGameObject.transform.Find(bs.avatarPath); + if (avatarSmrObj == null) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject at path not found: " + bs.avatarPath); + continue; + } + + var avatarSmr = avatarSmrObj.GetComponent(); + if (avatarSmr == null || avatarSmr.sharedMesh == null) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject at path does not have SkinnedMeshRenderer or Mesh attached: " + bs.avatarPath); + continue; + } + + var avatarBlendshapeIndex = avatarSmr.sharedMesh.GetBlendShapeIndex(bs.avatarBlendshapeName); + if (avatarBlendshapeIndex == -1) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject does not have blendshape: " + bs.avatarBlendshapeName); + continue; + } + + var wearableSmrObj = wearableGameObject.transform.Find(bs.wearablePath); + if (wearableSmrObj == null) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject at path not found: " + bs.wearablePath); + continue; + } + + var wearableSmr = wearableSmrObj.GetComponent(); + if (wearableSmr == null) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject at path does not have SkinnedMeshRenderer or Mesh attached: " + bs.avatarPath); + continue; + } + + var wearableBlendshapeIndex = wearableSmr.sharedMesh.GetBlendShapeIndex(bs.wearableBlendshapeName); + if (wearableBlendshapeIndex == -1) + { + Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject does not have blendshape: " + bs.wearableBlendshapeName); + continue; + } + + // copy value from avatar to wearable + wearableSmr.SetBlendShapeWeight(wearableBlendshapeIndex, avatarSmr.GetBlendShapeWeight(avatarBlendshapeIndex)); + } + + return true; + } } }