using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.XR.ARSubsystems; using UnityEngine; using UnityEngine.XR.ARKit; using UnityEngine.XR.ARSubsystems; #if UNITY_IOS using System.IO; using UnityEditor.iOS.Xcode; #endif namespace UnityEditor.XR.ARKit { /// /// Looks at all XRReferenceImageLibraries in the project and generates an AR Resource Group for each library, /// then inserts them into a new Xcode asset catalog called "ARReferenceImages". /// static class ARKitReferenceImageLibraryBuildProcessor { static IEnumerable> ResourceGroups() { // Create a resource group for each reference image library foreach (var library in ARKitBuildHelper.AssetsOfType()) { var resourceGroup = new ARResourceGroup(library.name + "_" + library.guid.ToUUIDString()); // Create a resource group for each library foreach (var referenceImage in library) { try { resourceGroup.AddResource(new ARReferenceImage(referenceImage)); } catch (ARReferenceImage.InvalidWidthException) { throw new BuildFailedException(string.Format("ARKit requires dimensions for all images. Reference image at index {0} named '{1}' in library '{2}' requires a non-zero width.", library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library))); } catch (ARReferenceImage.MissingTextureException) { throw new BuildFailedException(string.Format("Reference image at index {0} named '{1}' in library '{2}' is missing a texture.", library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library))); } catch (ARReferenceImage.BadTexturePathException) { throw new BuildFailedException(string.Format("Could not resolve texture path for reference image at index {0} named '{1}' in library '{2}'.", library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library))); } catch (ARReferenceImage.LoadTextureException e) { throw new BuildFailedException(string.Format("Could not load texture at path {0} for reference image at index {1} named '{2}' in library '{3}'.", e.path, library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library))); } catch (ARReferenceImage.TextureNotExportableException) { throw new BuildFailedException(string.Format( "Reference image at index {0} named '{1}' in library '{2}' could not be exported. " + "ARKit can directly use a texture's source asset if it is a JPG or PNG. " + "For all other formats, the texture must be exported to PNG, which requires the texture to be readable and uncompressed. " + "Change the Texture Import Settings or use a JPG or PNG.", library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library))); } catch { Debug.LogErrorFormat("Failed to generate reference image at index {0} named '{1}' in library '{2}'.", library.indexOf(referenceImage), referenceImage.name, AssetDatabase.GetAssetPath(library)); throw; } } yield return (resourceGroup, library); } } // Fail the build if any of the reference images are invalid class Preprocessor : IPreprocessBuildWithReport, ARBuildProcessor.IPreprocessBuild { public int callbackOrder => 0; static void BuildAssets() { var assets = AssetDatabase.FindAssets($"t:{nameof(XRReferenceImageLibrary)}"); var index = 0; var minimumDeploymentTarget = new Version(11, 3); foreach (var (resourceGroup, library) in ResourceGroups()) { index++; EditorUtility.DisplayProgressBar( $"Compiling {nameof(XRReferenceImageLibrary)} ({index} of {assets.Length})", $"{AssetDatabase.GetAssetPath(library)} ({library.count} image{(library.count == 1 ? "" : "s")})", (float)index / assets.Length); // Do not change this name. It must match the native call to referenceImagesInGroupNamed. resourceGroup.name = "ARReferenceImages"; // Convert the resource group to a 'car' (compiled asset catalog) file library.SetDataForKey(ARKitPackageInfo.identifier, resourceGroup.ToCar(minimumDeploymentTarget)); } EditorUtility.ClearProgressBar(); } void ARBuildProcessor.IPreprocessBuild.OnPreprocessBuild(PreprocessBuildEventArgs eventArgs) { if (eventArgs.activeLoadersForBuildTarget?.OfType().Any() == false) return; BuildAssets(); } void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report) { #if UNITY_XR_ARKIT_LOADER_ENABLED && UNITY_IOS if (report.summary.platform != BuildTarget.iOS) return; BuildAssets(); #endif // UNITY_XR_ARKIT_LOADER_ENABLED } } class Postprocessor : IPostprocessBuildWithReport { public int callbackOrder => 0; public void OnPostprocessBuild(BuildReport report) { #if UNITY_XR_ARKIT_LOADER_ENABLED && UNITY_IOS if (report.summary.platform != BuildTarget.iOS) return; var buildOutputPath = report.summary.outputPath; // Read in the PBXProject var project = new PBXProject(); var pbxProjectPath = PBXProject.GetPBXProjectPath(buildOutputPath); project.ReadFromString(File.ReadAllText(pbxProjectPath)); // Create a new asset catalog var assetCatalog = new XcodeAssetCatalog("ARReferenceImages"); // Generate resource groups and add each one to the asset catalog foreach (var (resourceGroup, library) in ResourceGroups()) { // Only add libraries where we don't already have the data if (!library.dataStore.ContainsKey(ARKitPackageInfo.identifier)) { assetCatalog.AddResourceGroup(resourceGroup); } } // Don't create empty asset catalogs if (assetCatalog.count == 0) return; // Create the asset catalog on disk assetCatalog.WriteAndAddToPBXProject(project, buildOutputPath); // Write out the updated Xcode project file File.WriteAllText(pbxProjectPath, project.WriteToString()); #endif } } } }