add virtual ImporterContext.PreprocessAsync

This commit is contained in:
ousttrue
2025-04-16 15:03:48 +09:00
parent e36c43207a
commit 21f96a5bac
11 changed files with 363 additions and 252 deletions

View File

@@ -81,6 +81,14 @@ namespace UniGLTF
_storage = storage;
MigrationFlags = migrationFlags;
// Version Compatibility
RestoreOlderVersionValues(json, GLTF);
foreach (var image in GLTF.images)
{
image.uri = PrepareImageUri(image.uri);
}
// init
if (Chunks != null)
{
@@ -97,6 +105,47 @@ namespace UniGLTF
_UriCache.Clear();
}
private static void RestoreOlderVersionValues(string Json, glTF GLTF)
{
var parsed = UniJSON.JsonParser.Parse(Json);
for (int i = 0; i < GLTF.images.Count; ++i)
{
if (string.IsNullOrEmpty(GLTF.images[i].name))
{
try
{
var extraName = parsed["images"][i]["extra"]["name"].Value.GetString();
if (!string.IsNullOrEmpty(extraName))
{
GLTF.images[i].name = extraName;
}
}
catch (Exception)
{
// do nothing
}
}
}
}
static string PrepareImageUri(string uri)
{
if (string.IsNullOrEmpty(uri))
{
return uri;
}
if (uri.StartsWith("./"))
{
// skip
uri = uri.Substring(2);
}
// %20 to ' ' etc...
var unescape = Uri.UnescapeDataString(uri);
return unescape;
}
public static GltfData CreateFromExportForTest(ExportingGltfData data)
{
return CreateFromGltfDataForTest(data.Gltf, data.BinBytes);

View File

@@ -14,7 +14,7 @@ namespace UniGLTF
{
public readonly bool IsAssetImport;
private readonly ImporterContextSettings _settings;
public ITextureDescriptorGenerator TextureDescriptorGenerator { get; protected set; }
public IMaterialDescriptorGenerator MaterialDescriptorGenerator { get; protected set; }
public TextureFactory TextureFactory { get; }
@@ -95,6 +95,9 @@ namespace UniGLTF
MeasureTime = new ImporterContextSpeedLog().MeasureTime;
}
// 前処理
await PreprocessAsync();
if (GLTF.extensionsRequired != null)
{
var sb = new List<string>();
@@ -142,6 +145,21 @@ namespace UniGLTF
return instance;
}
/// <summary>
/// GlbLowLevelParser.Parse からこちらに移動
/// </summary>
/// <returns></returns>
protected virtual Task PreprocessAsync()
{
RenameUtil.FixMeshNameUnique(GLTF);
RenameUtil.FixTextureNameUnique(GLTF);
RenameUtil.FixMaterialNameUnique(GLTF);
RenameUtil.FixNodeName(GLTF);
RenameUtil.FixAnimationNameUnique(GLTF);
return Task.CompletedTask;
}
public virtual async Task LoadAnimationAsync(IAwaitCaller awaitCaller)
{
if (GLTF.animations != null && GLTF.animations.Any())

View File

@@ -13,9 +13,6 @@ namespace UniGLTF
/// </summary>
public sealed class GlbLowLevelParser
{
public static readonly string UniqueFixResourceSuffix = "__UNIGLTF__DUPLICATED__";
private static readonly Regex _removeUniqueFixResourceSuffix = new Regex($@"^(.+){UniqueFixResourceSuffix}(\d+)$");
private readonly string _path;
private readonly byte[] _binary;
@@ -79,191 +76,9 @@ namespace UniGLTF
throw new UniGLTFException("unknown gltf version {0}", GLTF.asset.version);
}
// Version Compatibility
RestoreOlderVersionValues(json, GLTF);
FixMeshNameUnique(GLTF);
foreach (var image in GLTF.images)
{
image.uri = PrepareUri(image.uri);
}
FixTextureNameUnique(GLTF);
FixMaterialNameUnique(GLTF);
FixNodeName(GLTF);
FixAnimationNameUnique(GLTF);
return new GltfData(path, json, GLTF, chunks, storage, migrationFlags);
}
private static void FixMeshNameUnique(glTF GLTF)
{
var used = new HashSet<string>();
foreach (var mesh in GLTF.meshes)
{
if (string.IsNullOrEmpty(mesh.name))
{
// empty
mesh.name = "mesh_" + Guid.NewGuid().ToString("N");
used.Add(mesh.name);
}
else
{
var lower = mesh.name.ToLower();
if (used.Contains(lower))
{
// rename
var uname = lower + "_" + Guid.NewGuid().ToString("N");
mesh.name = uname;
lower = uname;
}
used.Add(lower);
}
}
}
private static void RenameImageFromTexture(glTF GLTF, int i)
{
foreach (var texture in GLTF.textures)
{
if (texture.source == i)
{
if (!string.IsNullOrEmpty(texture.name))
{
GLTF.images[i].name = texture.name;
return;
}
}
}
}
/// <summary>
/// image.uri を前理
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
public static string PrepareUri(string uri)
{
if (string.IsNullOrEmpty(uri))
{
return uri;
}
if (uri.StartsWith("./"))
{
// skip
uri = uri.Substring(2);
}
// %20 to ' ' etc...
var unescape = Uri.UnescapeDataString(uri);
return unescape;
}
/// <summary>
/// gltfTexture.name を Unity Asset 名として運用する。
/// ユニークである必要がある。
/// </summary>
private static void FixTextureNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (var textureIdx = 0; textureIdx < GLTF.textures.Count; ++textureIdx)
{
var gltfTexture = GLTF.textures[textureIdx];
if (gltfTexture.source.HasValidIndex())
{
var gltfImage = GLTF.images[gltfTexture.source.Value];
if (!string.IsNullOrEmpty(gltfImage.uri) && !gltfImage.uri.StartsWith("data:"))
{
// from image uri
gltfTexture.name = Path.GetFileNameWithoutExtension(gltfImage.uri);
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
// use image name
gltfTexture.name = gltfImage.name;
}
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
gltfTexture.name = $"texture_{textureIdx}";
}
gltfTexture.name = FixNameUnique(used, gltfTexture.name);
}
}
private static void FixMaterialNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (var materialIdx = 0; materialIdx < GLTF.materials.Count; ++materialIdx)
{
var material = GLTF.materials[materialIdx];
if (string.IsNullOrEmpty(material.name))
{
material.name = $"material_{materialIdx}";
}
material.name = FixNameUnique(used, material.name);
}
}
/// <summary>
/// rename empty name to $"{index}"
/// </summary>
private static void FixNodeName(glTF GLTF)
{
for (var i = 0; i < GLTF.nodes.Count; ++i)
{
var node = GLTF.nodes[i];
if (string.IsNullOrWhiteSpace(node.name))
{
node.name = $"{i}";
}
}
}
private static void FixAnimationNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < GLTF.animations.Count; ++i)
{
var animation = GLTF.animations[i];
if (string.IsNullOrEmpty(animation.name))
{
animation.name = $"animation_{i}";
}
animation.name = FixNameUnique(used, animation.name);
}
}
private static void RestoreOlderVersionValues(string Json, glTF GLTF)
{
var parsed = UniJSON.JsonParser.Parse(Json);
for (int i = 0; i < GLTF.images.Count; ++i)
{
if (string.IsNullOrEmpty(GLTF.images[i].name))
{
try
{
var extraName = parsed["images"][i]["extra"]["name"].Value.GetString();
if (!string.IsNullOrEmpty(extraName))
{
GLTF.images[i].name = extraName;
}
}
catch (Exception)
{
// do nothing
}
}
}
}
public static void AppendImageExtension(glTFImage texture, string extension)
{
@@ -272,38 +87,5 @@ namespace UniGLTF
texture.name = texture.name + extension;
}
}
public static string FixNameUnique(HashSet<string> used, string originalName)
{
if (used.Add(originalName))
{
return originalName;
}
var duplicatedIdx = 2;
while (true)
{
var newName = $"{originalName}{UniqueFixResourceSuffix}{duplicatedIdx++}";
if (used.Add(newName))
{
return newName;
}
}
}
public static bool TryGetOriginalName(string name, out string originalName)
{
var match = _removeUniqueFixResourceSuffix.Match(name);
if (match.Success)
{
originalName = match.Groups[1].Value;
return true;
}
else
{
originalName = name;
return false;
}
}
}
}

View File

@@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace UniGLTF
{
public static class RenameUtil
{
public static readonly string UniqueFixResourceSuffix = "__UNIGLTF__DUPLICATED__";
private static readonly Regex _removeUniqueFixResourceSuffix = new Regex($@"^(.+){UniqueFixResourceSuffix}(\d+)$");
public static string FixNameUnique(HashSet<string> used, string originalName)
{
if (used.Add(originalName))
{
return originalName;
}
var duplicatedIdx = 2;
while (true)
{
var newName = $"{originalName}{UniqueFixResourceSuffix}{duplicatedIdx++}";
if (used.Add(newName))
{
return newName;
}
}
}
/// <summary>
/// GlbLowLevelParser.FixNameUnique で付与した Suffix を remove
/// </summary>
public static void FixName(glTF gltf)
{
var regex = new Regex($@"{UniqueFixResourceSuffix}\d+$");
foreach (var gltfImages in gltf.images)
{
if (regex.IsMatch(gltfImages.name))
{
gltfImages.name = regex.Replace(gltfImages.name, string.Empty);
}
}
foreach (var gltfMaterial in gltf.materials)
{
if (regex.IsMatch(gltfMaterial.name))
{
gltfMaterial.name = regex.Replace(gltfMaterial.name, string.Empty);
}
}
foreach (var gltfAnimation in gltf.animations)
{
if (regex.IsMatch(gltfAnimation.name))
{
gltfAnimation.name = regex.Replace(gltfAnimation.name, string.Empty);
}
}
}
[Obsolete]
public static bool TryGetOriginalName(string name, out string originalName)
{
var match = _removeUniqueFixResourceSuffix.Match(name);
if (match.Success)
{
originalName = match.Groups[1].Value;
return true;
}
else
{
originalName = name;
return false;
}
}
public static void FixMeshNameUnique(glTF GLTF)
{
var used = new HashSet<string>();
foreach (var mesh in GLTF.meshes)
{
if (string.IsNullOrEmpty(mesh.name))
{
// empty
mesh.name = "mesh_" + Guid.NewGuid().ToString("N");
used.Add(mesh.name);
}
else
{
var lower = mesh.name.ToLower();
if (used.Contains(lower))
{
// rename
var uname = lower + "_" + Guid.NewGuid().ToString("N");
mesh.name = uname;
lower = uname;
}
used.Add(lower);
}
}
}
[Obsolete]
public static void RenameImageFromTexture(glTF GLTF, int i)
{
foreach (var texture in GLTF.textures)
{
if (texture.source == i)
{
if (!string.IsNullOrEmpty(texture.name))
{
GLTF.images[i].name = texture.name;
return;
}
}
}
}
/// <summary>
/// gltfTexture.name を Unity Asset 名として運用する。
/// ユニークである必要がある。
/// </summary>
public static void FixTextureNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (var textureIdx = 0; textureIdx < GLTF.textures.Count; ++textureIdx)
{
var gltfTexture = GLTF.textures[textureIdx];
if (gltfTexture.source.HasValidIndex())
{
var gltfImage = GLTF.images[gltfTexture.source.Value];
if (!string.IsNullOrEmpty(gltfImage.uri) && !gltfImage.uri.StartsWith("data:"))
{
// from image uri
gltfTexture.name = Path.GetFileNameWithoutExtension(gltfImage.uri);
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
// use image name
gltfTexture.name = gltfImage.name;
}
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
gltfTexture.name = $"texture_{textureIdx}";
}
gltfTexture.name = FixNameUnique(used, gltfTexture.name);
}
}
public static void FixMaterialNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (var materialIdx = 0; materialIdx < GLTF.materials.Count; ++materialIdx)
{
var material = GLTF.materials[materialIdx];
if (string.IsNullOrEmpty(material.name))
{
material.name = $"material_{materialIdx}";
}
material.name = FixNameUnique(used, material.name);
}
}
/// <summary>
/// rename empty name to $"{index}"
/// </summary>
public static void FixNodeName(glTF GLTF)
{
for (var i = 0; i < GLTF.nodes.Count; ++i)
{
var node = GLTF.nodes[i];
if (string.IsNullOrWhiteSpace(node.name))
{
node.name = $"{i}";
}
}
}
public static void FixAnimationNameUnique(glTF GLTF)
{
// NOTE: Windows FileSystem は大文字小文字の違いは同名ファイルとして扱ってしまうため, IgnoreCase で評価する.
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < GLTF.animations.Count; ++i)
{
var animation = GLTF.animations[i];
if (string.IsNullOrEmpty(animation.name))
{
animation.name = $"animation_{i}";
}
animation.name = FixNameUnique(used, animation.name);
}
}
public static void FixNodeNameUnique(glTF GLTF)
{
var m_uniqueNameSet = new HashSet<string>();
int counter = 1;
for (var i = 0; i < GLTF.nodes.Count; ++i)
{
RenameIfDupName(GLTF.nodes, i, m_uniqueNameSet, ref counter);
}
}
static void RenameIfDupName(List<glTFNode> nodes, int index, HashSet<string> m_uniqueNameSet, ref int m_counter)
{
var t = nodes[index];
if (!m_uniqueNameSet.Contains(t.name))
{
m_uniqueNameSet.Add(t.name);
return;
}
var parent = nodes.FirstOrDefault(x => x.children.Contains(index));
if (parent != null && t.children.Length == 0)
{
/// AvatarBuilder:BuildHumanAvatar で同名の Transform があるとエラーになる。
///
/// AvatarBuilder 'GLTF': Ambiguous Transform '32/root/torso_1/torso_2/torso_3/torso_4/torso_5/torso_6/torso_7/neck_1/neck_2/head/ENDSITE' and '32/root/torso_1/torso_2/torso_3/torso_4/torso_5/torso_6/torso_7/l_shoulder/l_up_arm/l_low_arm/l_hand/ENDSITE' found in hierarchy for human bone 'Head'. Transform name mapped to a human bone must be unique.
/// UnityEngine.AvatarBuilder:BuildHumanAvatar (UnityEngine.GameObject,UnityEngine.HumanDescription)
/// UniHumanoid.AvatarDescription:CreateAvatar (UnityEngine.Transform)
///
/// 主に BVH の EndSite 由来の GameObject 名が重複することへの対策
/// ex: parent-ENDSITE
var newName = $"{parent.name}-{t.name}";
if (!m_uniqueNameSet.Contains(newName))
{
UniGLTFLogger.Warning($"force rename !!: {t.name} => {newName}");
t.name = newName;
m_uniqueNameSet.Add(newName);
return;
}
}
// 連番
for (int i = 0; i < 100; ++i)
{
// ex: name.1
var newName = $"{t.name}{m_counter++}";
if (!m_uniqueNameSet.Contains(newName))
{
UniGLTFLogger.Warning($"force rename: {t.name} => {newName}");
t.name = newName;
m_uniqueNameSet.Add(newName);
return;
}
}
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ded08d75fb22a94f80265d4403d5b3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -366,36 +366,7 @@ namespace UniGLTF
GltfTextureExporter.PushGltfTexture(_data, unityTexture, colorSpace, m_textureSerializer);
}
FixName(_gltf);
}
/// <summary>
/// GlbLowLevelParser.FixNameUnique で付与した Suffix を remove
/// </summary>
public static void FixName(glTF gltf)
{
var regex = new Regex($@"{GlbLowLevelParser.UniqueFixResourceSuffix}\d+$");
foreach (var gltfImages in gltf.images)
{
if (regex.IsMatch(gltfImages.name))
{
gltfImages.name = regex.Replace(gltfImages.name, string.Empty);
}
}
foreach (var gltfMaterial in gltf.materials)
{
if (regex.IsMatch(gltfMaterial.name))
{
gltfMaterial.name = regex.Replace(gltfMaterial.name, string.Empty);
}
}
foreach (var gltfAnimation in gltf.animations)
{
if (regex.IsMatch(gltfAnimation.name))
{
gltfAnimation.name = regex.Replace(gltfAnimation.name, string.Empty);
}
}
RenameUtil.FixName(_gltf);
}
#endregion
}

View File

@@ -36,6 +36,13 @@ namespace VRM
_springBoneRuntime = springboneRuntime ?? new Vrm0XSpringBoneDefaultRuntime();
}
protected override Task PreprocessAsync()
{
base.PreprocessAsync();
RenameUtil.FixNodeNameUnique(GLTF);
return Task.CompletedTask;
}
#region OnLoad
protected override async Task OnLoadHierarchy(IAwaitCaller awaitCaller, Func<string, IDisposable> MeasureTime)
{

View File

@@ -95,7 +95,7 @@ namespace UniVRM10
// data.GLTF.textures は前処理によりユニーク性がある
// unique な名前を振り出す
var used = new HashSet<string>(data.GLTF.textures.Select(x => x.name));
var uniqueName = GlbLowLevelParser.FixNameUnique(used, UniqueThumbnailName);
var uniqueName = RenameUtil.FixNameUnique(used, UniqueThumbnailName);
value = GltfTextureImporter.CreateSrgbFromOnlyImage(data, imageIndex, uniqueName, gltfImage.uri);
return true;

View File

@@ -235,10 +235,9 @@ namespace UniVRM10
}
// Fix Duplicated name
gltfExporter.FixName(Storage.Gltf);
RenameUtil.FixName(Storage.Gltf);
}
/// <summary>
/// VRMコンポーネントのエクスポート
/// </summary>

View File

@@ -79,6 +79,13 @@ namespace UniVRM10
m_springboneRuntime = springboneRuntime;
}
protected override Task PreprocessAsync()
{
base.PreprocessAsync();
RenameUtil.FixNodeNameUnique(GLTF);
return Task.CompletedTask;
}
static void AssignHumanoid(List<VrmLib.Node> nodes, UniGLTF.Extensions.VRMC_vrm.HumanBone humanBone, VrmLib.HumanoidBones key)
{
if (nodes == null)

View File

@@ -26,6 +26,13 @@ namespace UniVRM10
m_vrma = GetExtension(Data);
}
protected override Task PreprocessAsync()
{
base.PreprocessAsync();
RenameUtil.FixNodeNameUnique(GLTF);
return Task.CompletedTask;
}
private static VRMC_vrm_animation GetExtension(GltfData data)
{
if (data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions)