/// Returns true on success. /// Returns false and shows a user-visible error on failure. static public bool InitializeDirectoryWithUserError( string directoryName, string failureMessage = null) { string err = null; try { if (Directory.Exists(directoryName)) { return(true); } Directory.CreateDirectory(directoryName); } catch (System.UnauthorizedAccessException e) { err = e.Message; } catch (IOException e) { err = e.Message; } if (failureMessage == null) { failureMessage = "Failed to create directory"; } if (err != null) { OutputWindowScript.Error(failureMessage, err); return(false); } return(true); }
/// Main entry point public static bool Export(string outputFile, string format, string fbxVersion = null) { using (var G = new FbxExportGlobals(outputFile)) { int fmt = G.m_manager.GetIOPluginRegistry().FindWriterIDByDescription(format); if (!G.m_exporter.Initialize(outputFile, fmt, G.m_ioSettings)) { OutputWindowScript.Error("FBX export failed", "Could not initialize exporter"); return(false); } if (!String.IsNullOrEmpty(fbxVersion)) { G.m_exporter.SetFileExportVersion(new FbxString(fbxVersion)); } G.m_scene = FbxScene.Create(G.m_manager, "scene"); if (G.m_scene == null) { OutputWindowScript.Error("FBX export failed", "Could not initialize scene"); return(false); } String version = string.Format("{0}.{1}", App.Config.m_VersionNumber, App.Config.m_BuildStamp); FbxDocumentInfo info = FbxDocumentInfo.Create(G.m_manager, "DocInfo"); info.Original_ApplicationVendor.Set(new FbxString(App.kDisplayVendorName)); info.Original_ApplicationName.Set(new FbxString(App.kAppDisplayName)); info.Original_ApplicationVersion.Set(new FbxString(version)); info.LastSaved_ApplicationVendor.Set(new FbxString(App.kDisplayVendorName)); info.LastSaved_ApplicationName.Set(new FbxString(App.kAppDisplayName)); info.LastSaved_ApplicationVersion.Set(new FbxString(version)); // The toolkit's FBX parser is too simple to be able to read anything but // the UserData/Properties70 node, so add the extra info as a custom property var stringType = info.Original_ApplicationVersion.GetPropertyDataType(); var prop = FbxProperty.Create(info.Original, stringType, "RequiredToolkitVersion"); prop.SetString(FbxUtils.kRequiredToolkitVersion); G.m_scene.SetDocumentInfo(info); G.m_scene.GetGlobalSettings().SetSystemUnit(FbxSystemUnit.m); try { WriteObjectsAndConnections2(G); G.m_exporter.Export(G.m_scene); } catch (InvalidOperationException e) { OutputWindowScript.Error("FBX export failed", e.Message); return(false); } catch (IOException e) { OutputWindowScript.Error("FBX export failed", e.Message); return(false); } return(true); } }
// Emits user-visible error on failure protected IEnumerator SpawnModelCoroutine(string reason) { if (m_Model == null) { // Same as calling Model.RequestModelPreload -> RequestModelLoadInternal, except // this won't ignore the request if the load-into-memory previously failed. App.PolyAssetCatalog.RequestModelLoad(m_PacAsset.AssetId, reason); m_LoadingOverlay.SetActive(true); } // It is possible from this section forward that the user may have moved on to a different page // on the Poly panel, which is why we use a local copy of 'model' rather than m_Model. string assetId = m_PacAsset.AssetId; Model model; // A model in the catalog will become non-null once the gltf has been downloaded or is in the // cache. while ((model = App.PolyAssetCatalog.GetModel(assetId)) == null) { yield return(null); } // We only want to disable the loading overlay if the button is still referring to the same // asset. if (assetId == m_PacAsset.AssetId) { m_LoadingOverlay.SetActive(false); } // A model becomes valid once the gltf has been successfully read into a Unity mesh. if (!model.m_Valid) { // The model might be in the "loaded with error" state, but it seems harmless to try again. // If the user keeps clicking, we'll keep trying. yield return(model.LoadFullyCoroutine(reason)); Debug.Assert(model.m_Valid || model.Error != null); } if (!model.m_Valid) { // TODO: Is there a reason not to do this unconditionally? if (assetId == m_PacAsset.AssetId) { RefreshModelButton(); } OutputWindowScript.Error($"Couldn't load model: {model.Error?.message}", model.Error?.detail); } else { SpawnValidModel(model); } }
/// The directory must already have been created public bool WriteToDisk() { // First, iterate over everything that needs to be written to disk and verify it's valid. // If any invalid elements are found, it's likely due to a download failure and we will abort // the entire process. if (m_RootElement.assetBytes == null) { return(false); } ulong requiredDiskSpace = (ulong)m_RootElement.assetBytes.Length; for (int j = 0; j < m_ResourceElements.Count; ++j) { if (m_ResourceElements[j].assetBytes == null) { // Download failed on one of the elements. return(false); } else { requiredDiskSpace += (ulong)m_ResourceElements[j].assetBytes.Length; } } // Next, check to see if we have enough disk space to write all the files. string assetDir = App.PolyAssetCatalog.GetCacheDirectoryForAsset(m_AssetId); if (!FileUtils.HasFreeSpace(assetDir, requiredDiskSpace / (1024 * 1024))) { OutputWindowScript.Error(String.Format("Out of disk space! {0} {1}", requiredDiskSpace, m_AssetId)); return(false); } // // Next, begin writing to disk, remembering each file written to make the operation atomic. // var written = new List <string>(); if (!Directory.Exists(assetDir)) { UnityEngine.Debug.LogErrorFormat("Caller did not create directory for me: {0}", assetDir); } string rootFilePath = Path.Combine(assetDir, GetPolySanitizedFilePath(m_RootElement.filePath)); if (!FileUtils.InitializeDirectory(Path.GetDirectoryName(rootFilePath)) || !FileUtils.WriteBytesIgnoreExceptions(m_RootElement.assetBytes, rootFilePath)) { return(false); } written.Add(rootFilePath); // Write all resources to disk for (int j = 0; j < m_ResourceElements.Count; ++j) { string filePath = Path.Combine(assetDir, GetPolySanitizedFilePath(m_ResourceElements[j].filePath)); if (!FileUtils.InitializeDirectory(Path.GetDirectoryName(filePath)) || !FileUtils.WriteBytesIgnoreExceptions(m_ResourceElements[j].assetBytes, filePath)) { RemoveFiles(written); return(false); } written.Add(filePath); } return(true); }
private ExportResults ExportHelper( SceneStatePayload payload, string outputFile, bool binary, bool doExtras, int gltfVersion, bool allowHttpUri) { // TODO: Ownership of this temp directory is sloppy. // Payload and export share the same dir and we assume that the exporter: // 1. will not write files whose names conflict with payload's // 2. will clean up the entire directory when done // This works, as long as the payload isn't used for more than one export (it currently isn't) using (var exporter = new GlTF_ScriptableExporter(payload.temporaryDirectory, gltfVersion)) { exporter.AllowHttpUri = allowHttpUri; try { m_exporter = exporter; exporter.G.binary = binary; exporter.BeginExport(outputFile); exporter.SetMetadata(payload.generator, copyright: null); if (doExtras) { SetExtras(exporter, payload); } if (payload.env.skyCubemap != null) { // Add the skybox texture to the export. string texturePath = ExportUtils.GetTexturePath(payload.env.skyCubemap); string textureFilename = Path.GetFileName(texturePath); exporter.G.extras["TB_EnvironmentSkybox"] = ExportFileReference.CreateLocal(texturePath, textureFilename); } WriteObjectsAndConnections(exporter, payload); string[] exportedFiles = exporter.EndExport(); return(new ExportResults { success = true, exportedFiles = exportedFiles, numTris = exporter.NumTris }); } catch (InvalidOperationException e) { OutputWindowScript.Error("glTF export failed", e.Message); // TODO: anti-pattern. Let the exception bubble up so caller can log it properly // Actually, InvalidOperationException is now somewhat expected in experimental, since // the gltf exporter does not check IExportableMaterial.SupportsDetailedMaterialInfo. // But we still want the logging for standalone builds. Debug.LogException(e); return(new ExportResults { success = false }); } catch (IOException e) { OutputWindowScript.Error("glTF export failed", e.Message); return(new ExportResults { success = false }); } finally { payload.Destroy(); // The lifetime of ExportGlTF, GlTF_ScriptableExporter, and GlTF_Globals instances // is identical. This is solely to be pedantic. m_exporter = null; } } }
void LoadModel() { // Clean up existing model if (m_ModelInstance != null) { GameObject.Destroy(m_ModelInstance.gameObject); } // Early out if we don't have a model to clone. // This can happen if model loading is deferred. if (m_Model == null || m_Model.m_ModelParent == null) { return; } m_ModelInstance = Instantiate(m_Model.m_ModelParent); m_ModelInstance.gameObject.SetActive(true); m_ModelInstance.parent = this.transform; Coords.AsLocal[m_ModelInstance] = TrTransform.identity; float maxExtent = 2 * Mathf.Max(m_Model.m_MeshBounds.extents.x, Mathf.Max(m_Model.m_MeshBounds.extents.y, m_Model.m_MeshBounds.extents.z)); float size; if (maxExtent == 0.0f) { // If we created a widget with a model that doesn't have geo, we won't have calculated a // bounds worth much. In that case, give us a default size. size = 1.0f; } else { size = kInitialSizeMeters_RS * App.METERS_TO_UNITS / maxExtent; } m_InitSize_CS = size / Coords.CanvasPose.scale; // Models are created in the main canvas. Cache model layer in case it's overridden later. HierarchyUtils.RecursivelySetLayer(transform, App.Scene.MainCanvas.gameObject.layer); m_BackupLayer = m_ModelInstance.gameObject.layer; // Set a new batchId on this model so it can be picked up in GPU intersections. m_BatchId = GpuIntersector.GetNextBatchId(); HierarchyUtils.RecursivelySetMaterialBatchID(m_ModelInstance, m_BatchId); WidgetManager.m_Instance.AddWidgetToBatchMap(this, m_BatchId); Vector3 ratios = GetBoundsRatios(m_Model.m_MeshBounds); m_ContainerBloat.x = Mathf.Max(0, m_MinContainerRatio - ratios.x); m_ContainerBloat.y = Mathf.Max(0, m_MinContainerRatio - ratios.y); m_ContainerBloat.z = Mathf.Max(0, m_MinContainerRatio - ratios.z); m_ContainerBloat /= m_MinContainerRatio; // Normalize for the min ratio. m_ContainerBloat *= m_MaxBloat / App.Scene.Pose.scale; // Apply bloat to appropriate axes. m_BoxCollider.size = m_Model.m_MeshBounds.size + m_ContainerBloat; m_BoxCollider.transform.localPosition = m_Model.m_MeshBounds.center; InitSnapGhost(m_Model.m_ModelParent, m_ModelInstance); // Remove previous model vertex recording. WidgetManager.m_Instance.AdjustModelVertCount(-m_NumVertsTrackedByWidgetManager); m_NumVertsTrackedByWidgetManager = 0; m_ObjModelScript = GetComponentInChildren <ObjModelScript>(); m_ObjModelScript.Init(); if (m_ObjModelScript.NumMeshes == 0) { OutputWindowScript.Error("No usable geometry in model"); } else { m_NumVertsTrackedByWidgetManager = m_ObjModelScript.GetNumVertsInMeshes(); WidgetManager.m_Instance.AdjustModelVertCount(m_NumVertsTrackedByWidgetManager); } if (m_Model.IsCached()) { m_Model.RefreshCache(); } }
public IEnumerator ShareVideo(string filename) { m_UploadsInProgress++; if (!App.GoogleIdentity.LoggedIn) { // Need user to login yield return(App.GoogleIdentity.ReauthorizeAsync().AsIeNull()); } FileInfo fileInfo = new FileInfo(filename); long filesize = fileInfo.Length; if (filesize == 0) { OutputWindowScript.Error("Video processing failed"); m_UploadsInProgress--; yield break; } string uploadUrl = null; using (UnityWebRequest www = InitResumableUpload(filename, filesize)) { yield return(App.GoogleIdentity.Authenticate(www).AsIeNull()); yield return(www.SendWebRequest()); if (www.isNetworkError) { OutputWindowScript.Error("Network error"); m_UploadsInProgress--; yield break; } if (www.responseCode == 200) { uploadUrl = www.GetResponseHeader("Location"); } else if (www.responseCode == 401) { OutputWindowScript.Error("Authorization failed"); m_UploadsInProgress--; yield break; } else if (www.responseCode == 403) { OutputWindowScript.Error( "Upload forbidden.\nYou may need to create a YouTube channel first."); m_UploadsInProgress--; yield break; } else { OutputWindowScript.Error(String.Format("Error pushing to YouTube: {0}", www.responseCode)); m_UploadsInProgress--; yield break; } } if (uploadUrl == null) { OutputWindowScript.Error( "Upload failed.\nYou may need to create a YouTube channel first."); m_UploadsInProgress--; yield break; } m_UploadStream = new FileStream(filename, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[filesize]; m_UploadStream.Read(buffer, 0, (int)filesize); using (UnityWebRequest www = UnityWebRequest.Put(uploadUrl, buffer)) { www.SetRequestHeader("Content-Type", "video/*"); yield return(App.GoogleIdentity.Authenticate(www).AsIeNull()); yield return(www.SendWebRequest()); m_UploadStream.Close(); m_UploadStream = null; if (www.isNetworkError) { OutputWindowScript.Error("Network error"); m_UploadsInProgress--; yield break; } else if (www.responseCode >= 400) { // Some kind of error - extract the error message while (!www.downloadHandler.isDone) { yield return(null); } JObject json = JObject.Parse(www.downloadHandler.text); string message = json["message"].ToString(); OutputWindowScript.Error(String.Format("YouTube error: {0}", message)); m_UploadsInProgress--; yield break; } } App.OpenURL("http://www.youtube.com/my_videos"); OutputWindowScript.m_Instance.CreateInfoCardAtController(InputManager.ControllerName.Brush, m_UploadedMessage); m_UploadsInProgress--; }