├── .github └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG-jp.md ├── CHANGELOG-jp.md.meta ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Editor.meta ├── Editor ├── AssetSaver.cs ├── AssetSaver.cs.meta ├── CleanUpHierarchy.cs ├── CleanUpHierarchy.cs.meta ├── MenuItem.cs ├── MenuItem.cs.meta ├── ModuleCreatorProcessor.cs ├── ModuleCreatorProcessor.cs.meta ├── RendererDepedency.cs ├── RendererDepedency.cs.meta ├── Settings.cs ├── Settings.cs.meta ├── Utils.cs ├── Utils.cs.meta ├── com.aoyon.modulecreator.Editor.asmdef └── com.aoyon.modulecreator.Editor.asmdef.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | env: 13 | packagePath: . 14 | steps: 15 | 16 | # Checkout Local Repository 17 | - name: Checkout 18 | uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac 19 | 20 | # Get the Package version based on the package.json file 21 | - name: Get Version 22 | id: version 23 | uses: zoexx/github-action-json-file-properties@b9f36ce6ee6fe2680cd3c32b2c62e22eade7e590 24 | with: 25 | file_path: "${{ env.packagePath }}/package.json" 26 | prop_path: "version" 27 | 28 | - name: Get Package Name 29 | id: name 30 | uses: zoexx/github-action-json-file-properties@b9f36ce6ee6fe2680cd3c32b2c62e22eade7e590 31 | with: 32 | file_path: "${{ env.packagePath }}/package.json" 33 | prop_path: "name" 34 | 35 | - name: Set Environment Variables 36 | run: | 37 | echo "zipFile=${{ steps.name.outputs.value }}-${{ steps.version.outputs.value }}".zip >> $GITHUB_ENV 38 | echo "version=${{ steps.version.outputs.value }}" >> $GITHUB_ENV 39 | 40 | # Zip the Package for release 41 | - name: Create Package Zip 42 | working-directory: "${{ env.packagePath }}" 43 | run: zip -r "${{ github.workspace }}/${{ env.zipFile }}" . -x '.*' '*/.*' 44 | 45 | # Make a release tag of the version from the package.json file 46 | - name: Create Tag 47 | id: tag_version 48 | uses: rickstaa/action-create-tag@88dbf7ff6fe2405f8e8f6c6fdfd78829bc631f83 49 | with: 50 | tag: "${{ env.version }}" 51 | beta: ${{ github.ref == 'refs/heads/dev' }} 52 | 53 | # Publish the Release to GitHub 54 | - name: Make Release 55 | uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 56 | with: 57 | files: | 58 | ${{ env.zipFile }} 59 | ${{ env.packagePath }}/package.json 60 | tag_name: ${{ env.version }} 61 | prerelease: ${{ github.ref == 'refs/heads/dev' }} 62 | 63 | - name: call-build-listing 64 | uses: benc-uk/workflow-dispatch@v1 65 | with: 66 | workflow: build-listing.yml 67 | repo: Tliks/vpm-repos 68 | token: ${{ secrets.DISPATCH_TOKEN }} 69 | ref: main 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /CHANGELOG-jp.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | ### Added 5 | 6 | ### Changed 7 | 8 | ### Deprecated 9 | 10 | ### Removed 11 | 12 | ### Fixed 13 | 14 | ### Security 15 | 16 | ## [0.6.1] - 2025-04-25 17 | ### Added 18 | - 日本語の変更履歴の追加。 19 | 20 | ### Changed 21 | - パフォーマンスを改善。 22 | 23 | ### Fixed 24 | - 単一のレンダラーを選択していた際のPrefabの名称を修正。 25 | - PhysBoneのIgnoreTransformsに関する処理を修正。 26 | 27 | 28 | ## 以下のリリースに日本語の変更履歴はありません。 29 | 30 | 31 | ## [0.6.0] - 2025-04-13 32 | ### Changed 33 | - The behavior of renaming the root transform of phybone, which was enabled by default in v0.5.0, has been disabled. 34 | - Because this could cause unintended behavior, prioritize compatibility with previous versions. 35 | 36 | ## [0.5.0] - 2025-04-03 37 | ### Added 38 | - Add support for MeshRenderer, UnityConstraints and VRCConstraints 39 | - Add contextmenu at `Tools/ModuleCreator` 40 | - Specify whether each component should be included in the prefab. 41 | - Specify whether to unpack the prefab to the origin. 42 | - Add .meta file 43 | 44 | ### Changed 45 | - The parent Prefab is now set to the original Prefab (typically the FBX) instead of the target Prefab. 46 | - This is intended to be used as a static asset that will not be affected by changes of other Prefabs while still maintaining a connection to the FBX. 47 | - When multiple renderers are selected, a prefab containing all the selected renderers is now output. 48 | - Complete rewrite. 49 | 50 | ### Removed 51 | - Removed the "Window/Module Creator" window. 52 | - Removed the check for the existence of hips. 53 | 54 | ### Fixed 55 | - Fixed an issue where PhysBones on costumes might not work when applying the module to the original avatar with Modular Avatar. 56 | - The option to automatically rename the root bone of PhysBones is now enabled by default. 57 | - Note: This may lead to duplicate PhysBones. 58 | - Fixed an issue where prefabs could not be saved with missing components. 59 | - Fixed the "cyclic prefab nesting not supported" error. 60 | 61 | ## [0.4.3] - 2024-07-31 62 | ### Deprecated 63 | - The ability for prefab instances to be selected during generation has been deprecated due to an error 64 | 65 | ### Fixed 66 | - ArgumentOutOfRangeException 67 | 68 | ## [0.4.2] - 2024-07-31 69 | ### Changed 70 | - add changelogurl to package.json 71 | - prefab instance is now selected when generated 72 | - The operation is now performed only on object with skinnedmeshrender 73 | 74 | ### Fixed 75 | - ArgumentException: Can't save part of a Prefab instance as a Prefab 76 | - operations could be performed on prefab assets. 77 | - prefab instance was placed in different scene 78 | - skinnedMeshRenderer.bones was being called too much 79 | - namespace is not used 80 | 81 | ## [0.4.1] - 2024-05-18 82 | ### Changed 83 | - Change log message to appropriate format 84 | 85 | ### Fixed 86 | - rootbone of skinned mesh is not held 87 | - anchor of skinned mesh is not held 88 | - Prefab Asset is not updated 89 | 90 | ## [0.4.0] - 2024-05-09 91 | ### Added 92 | - window to Window/Module Creator 93 | - option to disable PhysBone/PhysBoneColider output 94 | - option to rename PhysBone RootTransform 95 | - option to specify Root Object 96 | - option to output additional PhysBones Affected Transforms for exact movement 97 | - option to include IgnoreTransforms 98 | - tooltip to advanced options 99 | 100 | ### Changed 101 | - Changed to assume IgnoreTransforms 102 | - Use Hips search instead of armature search 103 | - Log messages are now unified in English 104 | - All unnecessary components will be removed 105 | 106 | ### Fixed 107 | - Error occurred when a null collider was associated with physBone. 108 | - Error occurred when a null ignoreTransform was associated with physBone. 109 | - Error when a bone associated with a mesh cannot be found was not appropriate. 110 | - Unnecessary PhysBone is not removed 111 | - Some objects are not active 112 | 113 | ## [0.3.2] - 2024-04-27 114 | ### Changed 115 | - Relaxed error to warning for armature search 116 | 117 | ## [0.3.1] - 2024-04-23 118 | ### Changed 119 | - Save as prefab variant if possible 120 | - Exclude avatar name from prefabs. 121 | - Specify unique names for prefabs. 122 | 123 | ## [0.3.0] - 2024-04-12 124 | ### Added 125 | - Add CHANGELOG 126 | 127 | ### Removed 128 | - AvatarDynamics movement 129 | - rootTransform renaming 130 | 131 | ### Fixed 132 | - Fixed issue with scripts running during build 133 | - Fixed unnecessary PB Transforms output with multiple meshes under one PB component 134 | 135 | ## [0.2.0] - 2024-04-08 136 | ### Changed 137 | - Disabled AvatarDynamics movement functionality by default 138 | - Disabled rootTransform renaming functionality by default 139 | 140 | ## [0.1.2] - 2024-04-07 141 | ### Fixed 142 | - Fixed to properly search for armature 143 | 144 | ## [0.1.1] - 2024-04-07 145 | ### Changed 146 | - Changed packaging 147 | 148 | ## [0.1.0] - 2024-04-07 149 | ### Added 150 | - initial release 151 | -------------------------------------------------------------------------------- /CHANGELOG-jp.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 430de836718def14eb7f3beb3edd0254 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | ### Added 5 | 6 | ### Changed 7 | 8 | ### Deprecated 9 | 10 | ### Removed 11 | 12 | ### Fixed 13 | 14 | ### Security 15 | 16 | ## [0.6.1] - 2025-04-25 17 | ### Added 18 | - Add Japanese changelog. 19 | 20 | ### Changed 21 | - Improved performance. 22 | 23 | ### Fixed 24 | - Fixed the name of the Prefab when a single renderer was selected. 25 | - Fixed processing related to PhysBone's IgnoreTransforms. 26 | 27 | ## [0.6.0] - 2025-04-13 28 | ### Changed 29 | - The behavior of renaming the root transform of phybone, which was enabled by default in v0.5.0, has been disabled. 30 | - Because this could cause unintended behavior, prioritize compatibility with previous versions. 31 | 32 | ## [0.5.0] - 2025-04-03 33 | ### Added 34 | - Add support for MeshRenderer, UnityConstraints and VRCConstraints 35 | - Add contextmenu at `Tools/ModuleCreator` 36 | - Specify whether each component should be included in the prefab. 37 | - Specify whether to unpack the prefab to the origin. 38 | - Add .meta file 39 | 40 | ### Changed 41 | - The parent Prefab is now set to the original Prefab (typically the FBX) instead of the target Prefab. 42 | - This is intended to be used as a static asset that will not be affected by changes of other Prefabs while still maintaining a connection to the FBX. 43 | - When multiple renderers are selected, a prefab containing all the selected renderers is now output. 44 | - Complete rewrite. 45 | 46 | ### Removed 47 | - Removed the "Window/Module Creator" window. 48 | - Removed the check for the existence of hips. 49 | 50 | ### Fixed 51 | - Fixed an issue where PhysBones on costumes might not work when applying the module to the original avatar with Modular Avatar. 52 | - The option to automatically rename the root bone of PhysBones is now enabled by default. 53 | - Note: This may lead to duplicate PhysBones. 54 | - Fixed an issue where prefabs could not be saved with missing components. 55 | - Fixed the "cyclic prefab nesting not supported" error. 56 | 57 | ## [0.4.3] - 2024-07-31 58 | ### Deprecated 59 | - The ability for prefab instances to be selected during generation has been deprecated due to an error 60 | 61 | ### Fixed 62 | - ArgumentOutOfRangeException 63 | 64 | ## [0.4.2] - 2024-07-31 65 | ### Changed 66 | - add changelogurl to package.json 67 | - prefab instance is now selected when generated 68 | - The operation is now performed only on object with skinnedmeshrender 69 | 70 | ### Fixed 71 | - ArgumentException: Can't save part of a Prefab instance as a Prefab 72 | - operations could be performed on prefab assets. 73 | - prefab instance was placed in different scene 74 | - skinnedMeshRenderer.bones was being called too much 75 | - namespace is not used 76 | 77 | ## [0.4.1] - 2024-05-18 78 | ### Changed 79 | - Change log message to appropriate format 80 | 81 | ### Fixed 82 | - rootbone of skinned mesh is not held 83 | - anchor of skinned mesh is not held 84 | - Prefab Asset is not updated 85 | 86 | ## [0.4.0] - 2024-05-09 87 | ### Added 88 | - window to Window/Module Creator 89 | - option to disable PhysBone/PhysBoneColider output 90 | - option to rename PhysBone RootTransform 91 | - option to specify Root Object 92 | - option to output additional PhysBones Affected Transforms for exact movement 93 | - option to include IgnoreTransforms 94 | - tooltip to advanced options 95 | 96 | ### Changed 97 | - Changed to assume IgnoreTransforms 98 | - Use Hips search instead of armature search 99 | - Log messages are now unified in English 100 | - All unnecessary components will be removed 101 | 102 | ### Fixed 103 | - Error occurred when a null collider was associated with physBone. 104 | - Error occurred when a null ignoreTransform was associated with physBone. 105 | - Error when a bone associated with a mesh cannot be found was not appropriate. 106 | - Unnecessary PhysBone is not removed 107 | - Some objects are not active 108 | 109 | ## [0.3.2] - 2024-04-27 110 | ### Changed 111 | - Relaxed error to warning for armature search 112 | 113 | ## [0.3.1] - 2024-04-23 114 | ### Changed 115 | - Save as prefab variant if possible 116 | - Exclude avatar name from prefabs. 117 | - Specify unique names for prefabs. 118 | 119 | ## [0.3.0] - 2024-04-12 120 | ### Added 121 | - Add CHANGELOG 122 | 123 | ### Removed 124 | - AvatarDynamics movement 125 | - rootTransform renaming 126 | 127 | ### Fixed 128 | - Fixed issue with scripts running during build 129 | - Fixed unnecessary PB Transforms output with multiple meshes under one PB component 130 | 131 | ## [0.2.0] - 2024-04-08 132 | ### Changed 133 | - Disabled AvatarDynamics movement functionality by default 134 | - Disabled rootTransform renaming functionality by default 135 | 136 | ## [0.1.2] - 2024-04-07 137 | ### Fixed 138 | - Fixed to properly search for armature 139 | 140 | ## [0.1.1] - 2024-04-07 141 | ### Changed 142 | - Changed packaging 143 | 144 | ## [0.1.0] - 2024-04-07 145 | ### Added 146 | - initial release 147 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6068d568886d3e641a7fda458d239304 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e5bbeb0d7a2f5d4083171e8ea3f5c98 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/AssetSaver.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEditor; 3 | 4 | namespace com.aoyon.modulecreator 5 | { 6 | internal static class AssetSaver 7 | { 8 | private const string BASEPATH = "Assets/ModuleCreator"; 9 | 10 | public static string GenerateMeshPath(string folderName, string fileName) 11 | { 12 | return GenerateVaildPath($"{BASEPATH}/{folderName}/Mesh", fileName, "asset"); 13 | } 14 | 15 | public static string GeneratePrefabPath(string folderName, string fileName) 16 | { 17 | return GenerateVaildPath($"{BASEPATH}/{folderName}/Prefab", fileName, "prefab"); 18 | } 19 | 20 | public static string GenerateVaildPath(string folderpath, string fileName, string fileExtension) 21 | { 22 | CreateDirectory(folderpath); 23 | string path = folderpath + "/" + fileName + "." + fileExtension; 24 | return AssetDatabase.GenerateUniqueAssetPath(path); 25 | } 26 | 27 | public static void CreateDirectory(string folderpath) 28 | { 29 | if (!Directory.Exists(folderpath)) 30 | { 31 | Directory.CreateDirectory(folderpath); 32 | AssetDatabase.SaveAssets(); 33 | AssetDatabase.Refresh(); 34 | } 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /Editor/AssetSaver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdbf97912415fb349bcefbef8833c964 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CleanUpHierarchy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | namespace com.aoyon.modulecreator 6 | { 7 | internal class CleanUpHierarchy 8 | { 9 | public static void CheckAndDeleteRecursive(GameObject gameObject, HashSet componentsToSave) 10 | { 11 | var gameObjectsToSave = componentsToSave.Select(c => c.gameObject).ToHashSet(); 12 | CheckAndDeleteRecursiveImpl(gameObject, gameObjectsToSave, componentsToSave); 13 | } 14 | 15 | private static void CheckAndDeleteRecursiveImpl(GameObject gameObject, HashSet gameObjectsToSave, HashSet componentsToSave) 16 | { 17 | var children = UnityUtils.GetChildren(gameObject.transform); 18 | // 子オブジェクトに対して再帰的に処理を適用 19 | foreach (Transform child in children) 20 | { 21 | CheckAndDeleteRecursiveImpl(child.gameObject, gameObjectsToSave, componentsToSave); 22 | } 23 | 24 | // 削除しない条件 25 | if (gameObjectsToSave.Contains(gameObject) || gameObject.transform.childCount != 0) 26 | { 27 | ActivateGameObject(gameObject); 28 | RemoveComponents(gameObject, componentsToSave); 29 | return; 30 | } 31 | 32 | Object.DestroyImmediate(gameObject, true); 33 | } 34 | 35 | private static void ActivateGameObject(GameObject gameObject) 36 | { 37 | gameObject.SetActive(true); 38 | gameObject.tag = "Untagged"; 39 | } 40 | 41 | private static void RemoveComponents(GameObject gameObject, HashSet componentsToSave) 42 | { 43 | var components = gameObject.GetComponents(); 44 | foreach (var component in components) 45 | { 46 | if (!(component is Transform) && !componentsToSave.Contains(component)) 47 | { 48 | Object.DestroyImmediate(component, true); 49 | } 50 | } 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /Editor/CleanUpHierarchy.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d4acab6de89dcc4eab86503fe847538 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/MenuItem.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace com.aoyon.modulecreator 6 | { 7 | internal class MenuItems 8 | { 9 | private const string GameObject = "GameObject"; 10 | private const string Tools = "Tools"; 11 | 12 | private const string MC = "Module Creator"; 13 | 14 | private const int MENU_PRIORITY = 49; 15 | private const string CREATEMODULEPATH = GameObject + "/" + MC + "/" + "Create Module"; 16 | 17 | [MenuItem(CREATEMODULEPATH, false, MENU_PRIORITY)] 18 | static void CreateModule() 19 | { 20 | var renderers = Selection.objects.OfType() 21 | .Select(g => g.GetComponent()) 22 | .Where(r => r is SkinnedMeshRenderer or MeshRenderer) 23 | .ToArray(); 24 | 25 | if (renderers.Length == 0) return; 26 | 27 | ModuleCreatorProcessor.CreateModule(renderers, new ModuleCreatorOptions()); 28 | 29 | Selection.objects = null; 30 | } 31 | 32 | private const string INCLUDE_PHYSBONE_PATH = Tools + "/" + MC + "/" + "Include PhysBone"; 33 | private const string INCLUDE_PHYSBONE_COLIDER_PATH = Tools + "/" + MC + "/" + "Include PhysBone Colider"; 34 | private const string INCLUDE_CONSTRAINTS = Tools + "/" + MC + "/" + "Include Constraints"; 35 | private const string UNPACK_PREFAB_PATH = Tools + "/" + MC + "/" + "Unpack Prefab To Origin"; 36 | 37 | [MenuItem(INCLUDE_PHYSBONE_PATH, true)] 38 | private static bool ValidateIncludePhysBone() 39 | { 40 | Menu.SetChecked(INCLUDE_PHYSBONE_PATH, ModuleCreatorSettings.IncludePhysBone); 41 | return true; 42 | } 43 | 44 | [MenuItem(INCLUDE_PHYSBONE_PATH, false)] 45 | private static void IncludePhysBone() 46 | { 47 | ModuleCreatorSettings.IncludePhysBone = !ModuleCreatorSettings.IncludePhysBone; 48 | UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); 49 | } 50 | 51 | [MenuItem(INCLUDE_PHYSBONE_COLIDER_PATH, true)] 52 | private static bool ValidateIncludePhysBoneColider() 53 | { 54 | Menu.SetChecked(INCLUDE_PHYSBONE_COLIDER_PATH, ModuleCreatorSettings.IncludePhysBoneColider); 55 | return true; 56 | } 57 | 58 | [MenuItem(INCLUDE_PHYSBONE_COLIDER_PATH, false)] 59 | private static void IncludePhysBoneColider() 60 | { 61 | ModuleCreatorSettings.IncludePhysBoneColider = !ModuleCreatorSettings.IncludePhysBoneColider; 62 | UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); 63 | } 64 | 65 | [MenuItem(INCLUDE_CONSTRAINTS, true)] 66 | private static bool ValidateIncludeConstraints() 67 | { 68 | Menu.SetChecked(INCLUDE_CONSTRAINTS, ModuleCreatorSettings.IncludeConstraints); 69 | return true; 70 | } 71 | 72 | [MenuItem(INCLUDE_CONSTRAINTS, false)] 73 | private static void IncludeConstraints() 74 | { 75 | ModuleCreatorSettings.IncludeConstraints = !ModuleCreatorSettings.IncludeConstraints; 76 | UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); 77 | } 78 | 79 | [MenuItem(UNPACK_PREFAB_PATH, true)] 80 | private static bool ValidateUnpackPrefab() 81 | { 82 | Menu.SetChecked(UNPACK_PREFAB_PATH, ModuleCreatorSettings.UnpackPrefabToOrigin); 83 | return true; 84 | } 85 | 86 | [MenuItem(UNPACK_PREFAB_PATH, false)] 87 | private static void UnpackPrefab() 88 | { 89 | ModuleCreatorSettings.UnpackPrefabToOrigin = !ModuleCreatorSettings.UnpackPrefabToOrigin; 90 | UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Editor/MenuItem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe3848689b0ca5041a3b1787f6048846 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ModuleCreatorProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEditor.SceneManagement; 7 | using UnityEngine.SceneManagement; 8 | using VRC.SDK3.Dynamics.PhysBone.Components; 9 | 10 | namespace com.aoyon.modulecreator 11 | { 12 | public class ModuleCreatorOptions 13 | { 14 | public bool IncludePhysBone; 15 | public bool IncludePhysBoneColider; 16 | public bool IncludeConstraints; 17 | 18 | public string SaveName; 19 | 20 | public bool UnpackPrefabToOrigin; 21 | public bool RenameRootTransform; 22 | 23 | public ModuleCreatorOptions() 24 | { 25 | IncludePhysBone = ModuleCreatorSettings.IncludePhysBone; 26 | IncludePhysBoneColider = ModuleCreatorSettings.IncludePhysBoneColider; 27 | IncludeConstraints = ModuleCreatorSettings.IncludeConstraints; 28 | SaveName = ""; 29 | UnpackPrefabToOrigin = ModuleCreatorSettings.UnpackPrefabToOrigin; 30 | RenameRootTransform = false; 31 | } 32 | } 33 | 34 | public class ModuleCreatorProcessor 35 | { 36 | public static GameObject CreateModule(GameObject target, ModuleCreatorOptions options) 37 | { 38 | var root = GetRoot(target); 39 | var renderer = target.GetComponent(); 40 | if (string.IsNullOrEmpty(options.SaveName)) options.SaveName = $"{root.name} {target.name}"; 41 | return CreateModuleImpl(root, new Renderer[]{ renderer }, options); 42 | } 43 | 44 | public static GameObject CreateModule(Renderer renderer, ModuleCreatorOptions options) 45 | { 46 | var root = GetRoot(renderer.gameObject); 47 | if (string.IsNullOrEmpty(options.SaveName)) options.SaveName = $"{root.name} {renderer.name}"; 48 | return CreateModuleImpl(root, new Renderer[]{ renderer }, options); 49 | } 50 | 51 | public static GameObject CreateModule(GameObject[] targets, ModuleCreatorOptions options) 52 | { 53 | if (targets.Length == 1) return CreateModule(targets[0], options); 54 | if (!TryGetCommonRoot(targets, out var root)) throw new InvalidOperationException("Please select the objects that have a common parent"); 55 | var renderers = targets.Select(t => t.GetComponent()).Where(r => r != null).ToArray(); 56 | if (string.IsNullOrEmpty(options.SaveName)) options.SaveName = $"{root.name} Parts"; 57 | return CreateModuleImpl(root, renderers, options); 58 | } 59 | 60 | public static GameObject CreateModule(Renderer[] renderers, ModuleCreatorOptions options) 61 | { 62 | if (renderers.Length == 1) return CreateModule(renderers[0], options); 63 | if (!TryGetCommonRoot(renderers.Select(t => t.gameObject), out var root)) throw new InvalidOperationException("Please select the objects that have a common parent"); 64 | if (string.IsNullOrEmpty(options.SaveName)) options.SaveName = $"{root.name} Parts"; 65 | return CreateModuleImpl(root, renderers, options); 66 | } 67 | 68 | private static GameObject CreateModuleImpl(GameObject root, Renderer[] renderers, ModuleCreatorOptions options) 69 | { 70 | var tmpRoot = GetTmpRoot(root); 71 | 72 | RemoveMissingScripts(tmpRoot); 73 | if (PrefabUtility.IsPartOfAnyPrefab(tmpRoot) && options.UnpackPrefabToOrigin) { 74 | UnpackPrefabToOrigin(tmpRoot); 75 | } 76 | 77 | var newRenderers = UnityUtils.GetCorrespondingComponents(root, tmpRoot, renderers).ToArray(); 78 | 79 | var context = new RendererDepedencyProviderContext(tmpRoot, newRenderers, options.IncludePhysBone, options.IncludePhysBoneColider, options.IncludeConstraints); 80 | var componentsToSave = new RendererDepedencyProvider(context).GetAllDependencies(); 81 | CleanUpHierarchy.CheckAndDeleteRecursive(tmpRoot, componentsToSave); 82 | 83 | if (options.RenameRootTransform) 84 | { 85 | RenamePBRootTransforms(tmpRoot); 86 | } 87 | 88 | var variantPath = AssetSaver.GeneratePrefabPath(root.name, options.SaveName); 89 | var variant = PrefabUtility.SaveAsPrefabAsset(tmpRoot, variantPath); 90 | Debug.Log("Saved prefab to " + variantPath); 91 | UnityEngine.Object.DestroyImmediate(tmpRoot, true); 92 | 93 | EditorGUIUtility.PingObject(variant); 94 | Selection.activeObject = variant; 95 | EditorUtility.FocusProjectWindow(); 96 | 97 | if (root.scene.IsValid()) 98 | { 99 | var instance = PrefabUtility.InstantiatePrefab(variant) as GameObject; 100 | SceneManager.MoveGameObjectToScene(instance, root.scene); 101 | EditorGUIUtility.PingObject(instance); 102 | Selection.activeGameObject = instance; 103 | return instance; 104 | } 105 | else 106 | { 107 | return variant; 108 | } 109 | } 110 | 111 | private static bool TryGetCommonRoot(IEnumerable objs, out GameObject commonRoot) 112 | { 113 | commonRoot = null; 114 | foreach (var obj in objs) 115 | { 116 | var root = GetRoot(obj); 117 | if (root == null) return false; 118 | if (commonRoot != null && root != commonRoot) { 119 | return false; 120 | } 121 | else { 122 | commonRoot = root; 123 | } 124 | } 125 | return true; 126 | } 127 | 128 | private static GameObject GetRoot(GameObject obj) 129 | { 130 | if (PrefabUtility.IsPartOfAnyPrefab(obj)) 131 | { 132 | return PrefabUtility.GetNearestPrefabInstanceRoot(obj); 133 | } 134 | else 135 | { 136 | return obj.transform.parent?.gameObject; 137 | } 138 | } 139 | 140 | private static GameObject GetTmpRoot(GameObject root) 141 | { 142 | GameObject tmp_root; 143 | // Prefab Stage 144 | if (PrefabUtility.IsPartOfPrefabAsset(root) || PrefabStageUtility.GetCurrentPrefabStage()?.IsPartOfPrefabContents(root) == true) 145 | { 146 | throw new NotSupportedException("Please execute on the object in the scene"); 147 | /* 148 | var scene = EditorSceneManager.GetActiveScene(); 149 | // nullが返される 150 | tmp_root = PrefabUtility.InstantiatePrefab(root, scene) as GameObject; 151 | */ 152 | } 153 | // Prefab Instance 154 | else if (PrefabUtility.IsPartOfPrefabInstance(root)) 155 | { 156 | Selection.activeObject = root; 157 | SceneView.lastActiveSceneView.Focus(); 158 | EditorWindow.focusedWindow.SendEvent(EditorGUIUtility.CommandEvent("Duplicate")); 159 | tmp_root = Selection.activeGameObject; 160 | } 161 | // Non Prefab 162 | else if (PrefabUtility.IsPartOfAnyPrefab(root)) 163 | { 164 | tmp_root = UnityEngine.Object.Instantiate(root); 165 | } 166 | else 167 | { 168 | throw new Exception("invalid prefab type"); 169 | } 170 | 171 | if (tmp_root == root) throw new Exception(); 172 | 173 | var transform = tmp_root.transform; 174 | transform.localPosition = Vector3.zero; 175 | //transform.localRotation = Quaternion.identity; 176 | //transform.localScale = Vector3.one; 177 | 178 | return tmp_root; 179 | } 180 | 181 | private static void RemoveMissingScripts(GameObject root) 182 | { 183 | int missings = 0; 184 | var components = root.GetComponentsInChildren(true); 185 | foreach (var component in components) 186 | { 187 | if (component == null) 188 | { 189 | UnityEngine.Object.DestroyImmediate(component); 190 | missings++; 191 | } 192 | } 193 | if (missings > 0) 194 | { 195 | Debug.Log($"Removed {missings} missing components"); 196 | } 197 | } 198 | 199 | private static void UnpackPrefabToOrigin(GameObject root) 200 | { 201 | while (true) 202 | { 203 | var parent = PrefabUtility.GetCorrespondingObjectFromSource(root); 204 | if (parent == null) throw new Exception(); 205 | if (PrefabUtility.IsPartOfModelPrefab(parent) || PrefabUtility.IsPartOfRegularPrefab(parent)) break; 206 | PrefabUtility.UnpackPrefabInstance(root, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); 207 | } 208 | } 209 | 210 | private static void RenamePBRootTransforms(GameObject root) 211 | { 212 | var physBoneTransforms = root.GetComponentsInChildren(true).Select(p => p.GetTarget()); 213 | var physBoneColliderTransforms = root.GetComponentsInChildren(true).Select(p => p.GetTarget()); 214 | 215 | var allTransforms = physBoneTransforms.Concat(physBoneColliderTransforms).ToHashSet(); 216 | foreach (var transform in allTransforms) 217 | { 218 | transform.name += ".1"; 219 | } 220 | } 221 | } 222 | } -------------------------------------------------------------------------------- /Editor/ModuleCreatorProcessor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d4cae0b464cd2134d98f2b6f7f0a40ac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/RendererDepedency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.Animations; 7 | using VRC.Dynamics; 8 | using VRC.SDK3.Dynamics.PhysBone.Components; 9 | 10 | namespace com.aoyon.modulecreator 11 | { 12 | internal struct RendererDepedencyProviderContext 13 | { 14 | public GameObject Root; 15 | public IEnumerable Renderers; 16 | public HashSet WeightedBones; 17 | 18 | public bool PhysBone; 19 | public bool PhysBoneCollider; 20 | public bool Constraints; 21 | 22 | public RendererDepedencyProviderContext(GameObject root, IEnumerable renderers, bool physBone = true, bool physBoneCollider = true, bool constraints = true) 23 | { 24 | Root = root; 25 | Renderers = renderers; 26 | 27 | WeightedBones = GetWeightedBones(renderers); 28 | 29 | PhysBone = physBone; 30 | PhysBoneCollider = physBoneCollider; 31 | Constraints = constraints; 32 | } 33 | 34 | private static HashSet GetWeightedBones(IEnumerable renderers) 35 | { 36 | var skinnedMeshRenderers = renderers.OfType(); 37 | var meshRenderers = renderers.OfType(); 38 | var weightedBones = UnityUtils.GetWeightedBones(skinnedMeshRenderers); 39 | weightedBones.UnionWith(meshRenderers.Select(m => m.transform)); 40 | return weightedBones; 41 | } 42 | } 43 | 44 | internal class RendererDepedencyProvider 45 | { 46 | private static IComponentDependency[] _providers; 47 | 48 | [InitializeOnLoadMethod] 49 | static void Init() 50 | { 51 | _providers = Utils.GetImplementClasses(); 52 | } 53 | 54 | private readonly RendererDepedencyProviderContext _context; 55 | 56 | public RendererDepedencyProvider(RendererDepedencyProviderContext context) 57 | { 58 | _context = context; 59 | } 60 | 61 | public HashSet GetAllDependencies() 62 | { 63 | var dependencies = new HashSet(); 64 | 65 | dependencies.UnionWith(_context.WeightedBones.Select(t => (Component)t).ToHashSet()); 66 | 67 | var collector = new RendererDepedencyCollector(dependencies); 68 | foreach (var provider in _providers) 69 | { 70 | if (!provider.IsEnabled(_context)) continue; 71 | provider.AddDependency(_context, collector); 72 | } 73 | 74 | return dependencies; 75 | } 76 | } 77 | 78 | internal class RendererDepedencyCollector 79 | { 80 | private readonly HashSet _holder; 81 | 82 | public RendererDepedencyCollector(HashSet holder) 83 | { 84 | _holder = holder; 85 | } 86 | 87 | public void AddDependency(Component componentToSave) 88 | { 89 | _holder.Add(componentToSave); 90 | } 91 | 92 | public void AddDependencies(IEnumerable componentsToSave) 93 | { 94 | _holder.UnionWith(componentsToSave); 95 | } 96 | } 97 | 98 | internal interface IComponentDependency 99 | { 100 | public bool IsEnabled(RendererDepedencyProviderContext context); 101 | public void AddDependency(RendererDepedencyProviderContext context, RendererDepedencyCollector collector); 102 | } 103 | 104 | internal class SkinnedMeshRendererDependency : IComponentDependency 105 | { 106 | public bool IsEnabled(RendererDepedencyProviderContext context) 107 | { 108 | return true; 109 | } 110 | 111 | public void AddDependency(RendererDepedencyProviderContext context, RendererDepedencyCollector collector) 112 | { 113 | var skinnedMeshRenderers = context.Renderers.OfType(); 114 | 115 | foreach (var skinnedMeshRenderer in skinnedMeshRenderers) 116 | { 117 | collector.AddDependency(skinnedMeshRenderer); 118 | if (skinnedMeshRenderer.rootBone != null) collector.AddDependency(skinnedMeshRenderer.rootBone); 119 | if (skinnedMeshRenderer.probeAnchor != null) collector.AddDependency(skinnedMeshRenderer.probeAnchor); 120 | } 121 | } 122 | } 123 | 124 | internal class MeshRendererDependency : IComponentDependency 125 | { 126 | public bool IsEnabled(RendererDepedencyProviderContext context) 127 | { 128 | return true; 129 | } 130 | 131 | public void AddDependency(RendererDepedencyProviderContext context, RendererDepedencyCollector collector) 132 | { 133 | var meshRenderers = context.Renderers.OfType(); 134 | 135 | foreach (var meshRenderer in meshRenderers) 136 | { 137 | collector.AddDependency(meshRenderer); 138 | var meshFilter = meshRenderer.GetComponent(); 139 | if (meshFilter != null) collector.AddDependency(meshFilter); 140 | if (meshRenderer.probeAnchor != null) collector.AddDependency(meshRenderer.probeAnchor); 141 | } 142 | } 143 | } 144 | 145 | internal class PhysBoneDependency : IComponentDependency 146 | { 147 | public bool IsEnabled(RendererDepedencyProviderContext context) 148 | { 149 | return context.PhysBone; 150 | } 151 | 152 | public void AddDependency(RendererDepedencyProviderContext context, RendererDepedencyCollector collector) 153 | { 154 | var physBones = context.Root.GetComponentsInChildren(true); 155 | 156 | foreach (VRCPhysBone physBone in physBones) 157 | { 158 | var weightedPBObjects = GetWeightedPhysBoneObjects(physBone, context.WeightedBones); 159 | 160 | // 有効なPhysBoneだった場合 161 | if (weightedPBObjects.Count > 0) 162 | { 163 | collector.AddDependency(physBone); 164 | collector.AddDependencies(weightedPBObjects.Select(t => (Component)t)); 165 | 166 | if (context.PhysBoneCollider) 167 | { 168 | foreach (VRCPhysBoneCollider collider in physBone.colliders) 169 | { 170 | if (collider == null) continue; 171 | collector.AddDependency(collider); 172 | collector.AddDependency(collider.GetTarget()); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | private static HashSet GetWeightedPhysBoneObjects(VRCPhysBone physBone, HashSet weightedBones) 180 | { 181 | var WeightedPhysBoneObjects = new HashSet(); 182 | 183 | var allchildren = UnityUtils.GetAllChildren(physBone.GetTarget()); 184 | 185 | foreach (Transform child in allchildren) 186 | { 187 | if (weightedBones.Contains(child)) 188 | { 189 | var result = new HashSet(); 190 | SingleChainRecursive(child, result); 191 | WeightedPhysBoneObjects.UnionWith(result); 192 | } 193 | } 194 | 195 | return WeightedPhysBoneObjects; 196 | } 197 | 198 | private static void SingleChainRecursive(Transform transform, HashSet result) 199 | { 200 | result.Add(transform); 201 | if (transform.childCount == 1) 202 | { 203 | Transform child = transform.GetChild(0); 204 | SingleChainRecursive(child, result); 205 | } 206 | } 207 | } 208 | 209 | internal class ConstraintDependency : IComponentDependency 210 | { 211 | public bool IsEnabled(RendererDepedencyProviderContext context) 212 | { 213 | return context.Constraints; 214 | } 215 | 216 | public void AddDependency(RendererDepedencyProviderContext context, RendererDepedencyCollector collector) 217 | { 218 | var root = context.Root.transform; 219 | var weightedBones = context.WeightedBones; 220 | 221 | var informations = new List(); 222 | 223 | // UnityConstraintsに関する情報を収集 224 | var unityConstraints = root.GetComponentsInChildren(true); 225 | foreach (var unityConstraint in unityConstraints) 226 | { 227 | var target = (unityConstraint as Component).transform; 228 | var sources = Enumerable.Range(0, unityConstraint.sourceCount) 229 | .Select(i => unityConstraint.GetSource(i).sourceTransform) 230 | .Where(s => s != null) 231 | .ToHashSet(); 232 | informations.Add(new ConstraintInformation(root, target, sources)); 233 | } 234 | 235 | // VRCConstraintsに関する情報を収集 236 | var vrcConstraints = root.GetComponentsInChildren(true); 237 | foreach (var vrcConstraint in vrcConstraints) 238 | { 239 | var target = vrcConstraint.GetTarget(); 240 | var souces = vrcConstraint.Sources 241 | .Select(s => s.SourceTransform) 242 | .Where(s => s != null) 243 | .ToHashSet(); 244 | informations.Add(new ConstraintInformation(root, target, souces)); 245 | } 246 | 247 | var validBones = weightedBones.SelectMany(b => GetParents(root, b)).ToHashSet(); 248 | 249 | float timeout = 10f; 250 | float startTime = UnityEngine.Time.realtimeSinceStartup; 251 | while (true) 252 | { 253 | // 有効なConstraint Sources 254 | var validSources = new HashSet(); 255 | foreach (var information in informations) 256 | { 257 | // 残すべきcontraintかどうかの判定 258 | if (information.TargetChildren.Overlaps(validBones)) 259 | { 260 | collector.AddDependency(information.Target); 261 | 262 | var sourceParents = information.Sources 263 | .SelectMany(s => s.SourceParents); 264 | 265 | collector.AddDependencies(sourceParents 266 | .Select(t => t.transform)); 267 | 268 | validSources.UnionWith(sourceParents); 269 | } 270 | } 271 | // Constraintを通しメッシュに影響するボーンが新規発見された場合 272 | // 追加した上で再度実行 273 | // 新規で見つからない場合は終了 274 | if (validSources.Except(validBones).Count() > 0) 275 | { 276 | validBones.UnionWith(validSources); 277 | } 278 | else 279 | { 280 | break; 281 | } 282 | 283 | // タイムアウト 284 | if (UnityEngine.Time.realtimeSinceStartup - startTime >= timeout) 285 | { 286 | throw new Exception("TraceConstraints: Timeout"); 287 | } 288 | } 289 | return; 290 | } 291 | 292 | class ConstraintInformation 293 | { 294 | public Transform Target; 295 | public HashSet TargetChildren; 296 | public HashSet Sources; 297 | 298 | public ConstraintInformation(Transform root, Transform target, HashSet sources) 299 | { 300 | Target = target; 301 | TargetChildren = UnityUtils.GetAllChildren(target) 302 | .ToHashSet(); 303 | Sources = sources 304 | .Select(s => new ConstraintSourceInformation( 305 | Source: s, 306 | SourceParents: GetParents(root, s) 307 | )).ToHashSet(); 308 | } 309 | } 310 | private static HashSet GetParents(Transform root, Transform target) 311 | { 312 | var parents = new HashSet(); 313 | var current = target; 314 | while (current != null && current != root) 315 | { 316 | parents.Add(current); 317 | current = current.parent; 318 | } 319 | if(current == root) 320 | { 321 | parents.Add(current); 322 | } 323 | return parents; 324 | } 325 | 326 | struct ConstraintSourceInformation 327 | { 328 | public Transform Source; 329 | public HashSet SourceParents; 330 | 331 | public ConstraintSourceInformation(Transform Source, HashSet SourceParents) 332 | { 333 | this.Source = Source; 334 | this.SourceParents = SourceParents; 335 | } 336 | } 337 | 338 | } 339 | } -------------------------------------------------------------------------------- /Editor/RendererDepedency.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3bc73c36894354459a3c98f8b64828a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Settings.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace com.aoyon.modulecreator 5 | { 6 | [FilePath("ProjectSettings/Packages/com.aoyon.modulecreator/settings.json", FilePathAttribute.Location.ProjectFolder)] 7 | public class ModuleCreatorSettings : ScriptableSingleton 8 | { 9 | [InitializeOnLoadMethod] 10 | private static void Init() 11 | { 12 | _ = instance; 13 | } 14 | private static void SetValue(ref T field, T value) 15 | { 16 | if (!Equals(field, value)) 17 | { 18 | field = value; 19 | instance.Save(true); 20 | } 21 | } 22 | 23 | [SerializeField] 24 | private bool includePhysBone = true; 25 | 26 | [SerializeField] 27 | private bool includePhysBoneColider = true; 28 | 29 | [SerializeField] 30 | private bool includeConstraints = true; 31 | 32 | [SerializeField] 33 | private bool unpackPrefabToOrigin = true; 34 | 35 | public static bool IncludePhysBone 36 | { 37 | get => instance.includePhysBone; 38 | set => SetValue(ref instance.includePhysBone, value); 39 | } 40 | 41 | public static bool IncludePhysBoneColider 42 | { 43 | get => instance.includePhysBoneColider; 44 | set => SetValue(ref instance.includePhysBoneColider, value); 45 | } 46 | 47 | public static bool IncludeConstraints 48 | { 49 | get => instance.includeConstraints; 50 | set => SetValue(ref instance.includeConstraints, value); 51 | } 52 | 53 | public static bool UnpackPrefabToOrigin 54 | { 55 | get => instance.unpackPrefabToOrigin; 56 | set => SetValue(ref instance.unpackPrefabToOrigin, value); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Editor/Settings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e9f467b5a5a0dc40ba6ab31a1b0f3e1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using VRC.Dynamics; 7 | 8 | namespace com.aoyon.modulecreator 9 | { 10 | internal static class Utils 11 | { 12 | public static T[] GetImplementClasses() where T : class 13 | { 14 | return TypeCache.GetTypesDerivedFrom() 15 | .Where(type => typeof(T).IsAssignableFrom(type) && !type.IsAbstract && !type.IsGenericTypeDefinition) 16 | .Select(type => Activator.CreateInstance(type) as T) 17 | .ToArray(); 18 | } 19 | } 20 | 21 | internal static class VRCExtensions 22 | { 23 | public static Transform GetTarget(this VRCPhysBoneBase physBone) => physBone.rootTransform == null ? physBone.transform : physBone.rootTransform; 24 | public static Transform GetTarget(this VRCPhysBoneColliderBase colider) => colider.rootTransform == null ? colider.transform : colider.rootTransform; 25 | public static Transform GetTarget(this VRCConstraintBase constraint) => constraint.TargetTransform == null ? constraint.transform : constraint.TargetTransform; 26 | } 27 | 28 | internal class UnityUtils 29 | { 30 | public static HashSet GetWeightedBones(IEnumerable skinnedMeshRenderers) 31 | { 32 | HashSet weightedBones = new (); 33 | foreach (var skinnedMeshRenderer in skinnedMeshRenderers) 34 | { 35 | BoneWeight[] boneWeights = skinnedMeshRenderer.sharedMesh.boneWeights; 36 | Transform[] bones = skinnedMeshRenderer.bones; 37 | 38 | foreach (BoneWeight boneWeight in boneWeights) 39 | { 40 | if (boneWeight.weight0 > 0) 41 | { 42 | Transform boneTransform = bones[boneWeight.boneIndex0]; 43 | if (boneTransform == null) 44 | { 45 | throw new InvalidOperationException($"missing weighted bone"); 46 | } 47 | weightedBones.Add(boneTransform); 48 | } 49 | if (boneWeight.weight1 > 0) 50 | { 51 | Transform boneTransform = bones[boneWeight.boneIndex1]; 52 | if (boneTransform == null) 53 | { 54 | throw new InvalidOperationException($"missing weighted bone"); 55 | } 56 | weightedBones.Add(boneTransform); 57 | } 58 | if (boneWeight.weight2 > 0) 59 | { 60 | Transform boneTransform = bones[boneWeight.boneIndex2]; 61 | if (boneTransform == null) 62 | { 63 | throw new InvalidOperationException($"missing weighted bone"); 64 | } 65 | weightedBones.Add(boneTransform); 66 | } 67 | if (boneWeight.weight3 > 0) 68 | { 69 | Transform boneTransform = bones[boneWeight.boneIndex3]; 70 | if (boneTransform == null) 71 | { 72 | throw new InvalidOperationException($"missing weighted bone"); 73 | } 74 | weightedBones.Add(boneTransform); 75 | } 76 | } 77 | 78 | Debug.Log($"Bones weighting {skinnedMeshRenderer.name}: {weightedBones.Count}/{skinnedMeshRenderer.bones.Length}"); 79 | } 80 | 81 | return weightedBones; 82 | } 83 | 84 | public static T GetCorrespondingComponent(GameObject root, GameObject new_root, T targetobj) where T : Component 85 | { 86 | return GetCorrespondingComponents(root, new_root, new T[] { targetobj }).First(); 87 | } 88 | 89 | public static IEnumerable GetCorrespondingComponents(GameObject root, GameObject new_root, IEnumerable targetobjs) where T : Component 90 | { 91 | var allChildren = GetAllChildren(root.transform); 92 | var newallChildren = GetAllChildren(new_root.transform); 93 | 94 | if (allChildren.Length != newallChildren.Length) 95 | { 96 | throw new InvalidOperationException("The number of children in the original and new objects does not match."); 97 | } 98 | 99 | return targetobjs.Select(obj => { 100 | var index = Array.IndexOf(allChildren, obj.transform); 101 | if (index == -1 || index >= newallChildren.Length) 102 | { 103 | throw new InvalidOperationException("Could not find corresponding transform in new root."); 104 | } 105 | return newallChildren[index].GetComponent(); 106 | }); 107 | } 108 | 109 | public static List GetChildren(Transform parent) 110 | { 111 | var children = new List(); 112 | foreach (Transform child in parent.transform) 113 | { 114 | children.Add(child); 115 | } 116 | return children; 117 | } 118 | 119 | public static Transform[] GetAllChildren(Transform target) 120 | { 121 | var children = new List(); 122 | 123 | foreach(Transform child in target) 124 | { 125 | children.Add(child); 126 | children.AddRange(GetAllChildren(child)); 127 | } 128 | 129 | return children.ToArray(); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Editor/Utils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b78c3d679bd449241be24316f62db1aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.aoyon.modulecreator.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.aoyon.modulecreator.Editor", 3 | "references": [ 4 | "VRC.SDK3A", 5 | "VRC.SDK3A.Editor", 6 | "VRC.SDKBase", 7 | "VRC.SDKBase.Editor" 8 | ], 9 | "includePlatforms": ["Editor"], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "autoReferenced": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "defineConstraints": [], 16 | "optionalUnityReferences": [] 17 | } -------------------------------------------------------------------------------- /Editor/com.aoyon.modulecreator.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e16ec67de28d5447ace8ac74bf2c1a6 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ao_425 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69222bbe73a448f43bd93b4007262a6e 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Module Creator 2 | ==== 3 | 4 | 開発段階のVRC向け改変ツールです 5 | 6 | アバターや衣装等からメッシュ単位でPrefabを生成します 7 | 8 | [VCC](https://tliks.github.io/vpm-repos/)から追加後オブジェクトのメニューから実行できます 9 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e0075ca853b48d4e808cea122618878 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.aoyon.module-creator", 3 | "displayName": "Module Creator", 4 | "version": "0.6.1", 5 | "unity": "2022.3", 6 | "description": "Output mesh unit for convenient modification and management", 7 | "author": { 8 | "name": "Ao_425", 9 | "url": "https://github.com/Tliks" 10 | }, 11 | "url": "https://github.com/Tliks/ModuleCreator", 12 | "changelogUrl": "https://github.com/Tliks/ModuleCreator/blob/main/CHANGELOG.md", 13 | "vpmDependencies": { 14 | "com.vrchat.avatars": ">=3.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f931b793ed5c7704b8e7c7b63527c648 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------