/// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="importBaseDir">
        /// The base project directory for saving the imported
        /// Unity assets (e.g. "Assets/Imported/MyModel").
        /// </param>
        public EditorGltfImportCache(string importBaseDir)
        {
            Textures  = new SerializedAssetList <Texture2D>(SerializeTexture);
            Materials = new SerializedAssetList <Material>(SerializeMaterial);
            Meshes    = new SerializedAssetList <List <KeyValuePair <Mesh, Material> > >
                            (SerializeMesh);

            // create directory structure for imported assets

            _importBaseDir = importBaseDir;
            Directory.CreateDirectory(
                UnityPathUtil.GetAbsolutePath(_importBaseDir));

            _importTexturesDir = Path.Combine(_importBaseDir, "Textures");
            Directory.CreateDirectory(
                UnityPathUtil.GetAbsolutePath(_importTexturesDir));

            _importMaterialsDir = Path.Combine(_importBaseDir, "Materials");
            Directory.CreateDirectory(
                UnityPathUtil.GetAbsolutePath(_importMaterialsDir));

            _importMeshesDir = Path.Combine(_importBaseDir, "Meshes");
            Directory.CreateDirectory(
                UnityPathUtil.GetAbsolutePath(_importMeshesDir));
        }
Пример #2
0
        /// <summary>
        /// Save the input mesh to disk as a set of Unity .asset
        /// files and return a new mesh. The input mesh is a list
        /// mesh primitives, where each primitive is a KeyValuePair
        /// of a Mesh and a Material.  The returned mesh (i.e. list of primitives)
        /// is the same as the input list, except that the Mesh
        /// for each primitive has been replaced by one that is backed
        /// by a Unity .asset file.  These Mesh objects know about
        /// their backing .asset file and will automatically sync
        /// in-memory changes to the Mesh to disk. (For further
        /// info, see the Unity documentation for AssetDatabase.)
        /// </summary>
        /// <param name="mesh">
        /// The mesh (list of mesh primitives) to be serialized to disk.
        /// </param>
        /// <returns>
        /// A new mesh (list of mesh primitives) that is backed by a
        /// set of .asset files (one per mesh primitive).
        /// </returns>
        protected List <KeyValuePair <Mesh, Material> > SerializeMesh(
            int index, List <KeyValuePair <Mesh, Material> > mesh)
        {
            Directory.CreateDirectory(UnityPathUtil.GetAbsolutePath(_importMeshesDir));

            for (int i = 0; i < mesh.Count; ++i)
            {
                Mesh     primitiveMesh     = mesh[i].Key;
                Material primitiveMaterial = mesh[i].Value;

                string basename = String.Format("{0}.asset", primitiveMesh.name);
                string path     = Path.Combine(_importMeshesDir, basename);

                // Serialize the mesh to disk as a Unity asset.
                //
                // Note: The primitiveMaterial does not need
                // to be serialized here, since that has already
                // been done during the earlier material-importing
                // step.

                AssetDatabase.CreateAsset(primitiveMesh, path);
                AssetDatabase.Refresh();
                primitiveMesh = (Mesh)AssetDatabase.LoadAssetAtPath(
                    path, typeof(Mesh));

                mesh[i] = new KeyValuePair <Mesh, Material>(
                    primitiveMesh, primitiveMaterial);
            }

            return(mesh);
        }
Пример #3
0
        /// <summary>
        /// Save the given AnimationClip to disk as a Unity asset
        /// and return a new AnimationClip. The returned AnimationClip
        /// is the same as the original, except that it knows
        /// about the .anim file that backs it and will automatically
        /// synchronize in-memory changes to disk. (For further
        /// info, see the Unity documentation for AssetDatabase.)
        /// </summary>
        /// <param name="clip">
        /// The AnimationClip to be serialized to disk
        /// </param>
        /// <returns>
        /// A new AnimationClip that is backed by a .anim file
        /// </returns>
        private AnimationClip SerializeAnimationClip(int index, AnimationClip clip)
        {
            Directory.CreateDirectory(UnityPathUtil.GetAbsolutePath(_importAnimationsDir));

            string basename = string.Format("{0}.anim", clip.name);
            string path     = Path.Combine(_importAnimationsDir, basename);

            AssetDatabase.CreateAsset(clip, path);
            AssetDatabase.Refresh();
            clip = (AnimationClip)AssetDatabase.LoadAssetAtPath(
                path, typeof(AnimationClip));

            return(clip);
        }
Пример #4
0
        /// <summary>
        /// Save the given material to disk as a Unity asset
        /// and return a new Material. The returned Material
        /// is the same as the original, except that it knows
        /// about the .mat file that backs it and will automatically
        /// synchronize in-memory changes to disk. (For further
        /// info, see the Unity documentation for AssetDatabase.)
        /// </summary>
        /// <param name="material">
        /// The material to be serialized to disk
        /// </param>
        /// <returns>
        /// A new Material that is backed by a .mat file
        /// </returns>
        protected Material SerializeMaterial(int index, Material material)
        {
            Directory.CreateDirectory(UnityPathUtil.GetAbsolutePath(_importMaterialsDir));

            string basename = String.Format("{0}.mat", material.name);
            string path     = Path.Combine(_importMaterialsDir, basename);

            AssetDatabase.CreateAsset(material, path);
            AssetDatabase.Refresh();
            material = (Material)AssetDatabase.LoadAssetAtPath(
                path, typeof(Material));

            return(material);
        }
        /// <summary>
        /// Save the given texture to disk as a Unity asset and
        /// return a new Texture2D. The returned Texture2D
        /// is the same as the original, except that it
        /// knows about the asset file that backs it and will
        /// automatically synchronize in-memory changes to disk.
        /// (For further info, see the Unity documentation for
        /// AssetDatabase.)
        /// </summary>
        /// <param name="texture">
        /// The texture to be serialized to disk.
        /// </param>
        /// <returns>
        /// A new Texture2D that is backed by an asset file.
        /// </returns>
        protected Texture2D SerializeTexture(int index, Texture2D texture)
        {
            // Unity's Texture2D.LoadImage() method imports
            // .png/.jpg images upside down, so flip it
            // right side up again.
            texture = TextureUtil.FlipTexture(texture);

            string basename = String.Format("texture_{0}.png", index);
            string pngPath  = Path.Combine(_importTexturesDir, basename);

            byte[] pngData = texture.EncodeToPNG();
            File.WriteAllBytes(UnityPathUtil.GetAbsolutePath(pngPath), pngData);

            AssetDatabase.Refresh();
            texture = (Texture2D)AssetDatabase.LoadAssetAtPath(
                pngPath, typeof(Texture2D));

            return(texture);
        }
        /// <summary>
        /// Callback that is invoked for each item (file or folder) that
        /// is currently visible in the left/right panes of the Project Browser.
        /// </summary>
        /// <param name="guid"></param>
        /// <param name="selectionRect"></param>
        private static void ProjectItemOnGUI(string guid, Rect selectionRect)
        {
            Event @event = Event.current;

            // Get a reference to the Project Browser window that the user last
            // interacted with. (It is possible to have multiple Project Browsers
            // open at the same time.)
            //
            // Note: There is no Unity API for doing this, so we have to use
            // reflection to access private/internal members.

            var projectBrowser = ProjectBrowserExtensions
                                 .GetLastInteractedProjectBrowser();

            // Get the position of the Project Browser window, in screen coordinates.

            Vector2 projectBrowserPosition = ((EditorWindow)projectBrowser).position.position;

            // For each event, we build a mapping of items -> rects. This mapping
            // is needed to determine if our drop target is located inside a tree
            // area or a list area, and to determine if the drop target is outside
            // of any items (e.g. dropping into an empty area of the files pane).
            //
            // I use `@event.rawType` here instead of `@event.type` because
            // `@event.type` is set to `EventType.Ignore` when the mouse is located
            // outside of the tree/list area containing the current item
            // (identified by `guid`).

            if (@event.rawType != _currentEventType)
            {
                _currentEventType = @event.rawType;

                List <ItemRect> temp = _itemRectsLastFrame;
                _itemRectsLastFrame = _itemRectsThisFrame;
                _itemRectsThisFrame = temp;
                _itemRectsThisFrame.Clear();
            }

            Vector2 localMousePosition  = Event.current.mousePosition;
            Vector2 globalMousePosition = GUIUtility.GUIToScreenPoint(localMousePosition);

            // Record guid => screen rect mapping for the current item.

            Rect globalRect = new Rect(
                GUIUtility.GUIToScreenPoint(selectionRect.position),
                selectionRect.size);

            // Note: Non-assets in the Project Browser (e.g. "Favorites", "Packages")
            // have empty GUIDs.

            if (guid.Length > 0)
            {
                _itemRectsThisFrame.Add(new ItemRect {
                    guid = guid, rect = globalRect
                });
            }

            // Note: When file(s) are dragged onto the right pane, the left pane
            // will have `@event.type == EventType.Ignore` and `@event.rawType ==
            // EventType.DragPerform`. (And likewise for items in the right pane
            // when file(s) are dragged onto the left pane.) I'm not 100% sure why
            // this happens, but I think it because the mouse events are outside
            // the active GUI clip area (e.g. `GUI.BeginClip()`,
            // `GUI.BeginScrollRect()`).

            if (@event.type == EventType.DragPerform || @event.rawType == EventType.DragPerform)
            {
                // Determine the target directory for the GLTF import (i.e.
                // directory where the model prefab and associated assets will be
                // created), based on the drag-and-drop target in the Project
                // Browser.
                //
                // If the GLTF file(s) are dragged onto a directory, import into
                // that directory. If the GLTF file(s) are dragged onto a file,
                // import into the parent directory of that file.  If the GLTF
                // file(s) are dragged onto an empty area, import into the
                // directory that is currently selected in the left pane.

                string       dragTargetGuid = null;
                DropPosition dropPosition   = DropPosition.UponItem;
                for (int i = 0; i < _itemRectsLastFrame.Count; ++i)
                {
                    Rect   itemRect = _itemRectsLastFrame[i].rect;
                    string itemGuid = _itemRectsLastFrame[i].guid;

                    if (itemRect.Contains(globalMousePosition))
                    {
                        ProjectBrowserExtensions.ItemType itemType
                            = ProjectBrowserExtensions.GetItemType(itemRect);

                        string itemPath = UnityPathUtil.NormalizePathSeparators(
                            AssetDatabase.GUIDToAssetPath(itemGuid));

                        // TreeItem areas support dropping files between
                        // vertically adjacent folders/files (as indicated by a
                        // horizontal blue line in the UI), whereas
                        // list areas do not.
                        if (itemType == ProjectBrowserExtensions.ItemType.TreeItem)
                        {
                            // The height of the target region at the top/bottom of
                            // an item rect that corresponds to dropping files
                            // between items.
                            //
                            // This value is hardcoded to match Unity's internal
                            // value for `TreeViewGUI.k_HalfDropBetweenHeight`. For
                            // Unity's own implementation of this logic, see
                            // `TreeViewDragging.TryGetDropPosition`.
                            const float halfDropBetweenHeight = 4f;

                            if (globalMousePosition.y <= itemRect.yMin + halfDropBetweenHeight)
                            {
                                dropPosition = DropPosition.AboveItem;
                            }
                            else if (globalMousePosition.y >= itemRect.yMax - halfDropBetweenHeight)
                            {
                                dropPosition = DropPosition.BelowItem;
                            }
                            else
                            {
                                dropPosition = DropPosition.UponItem;
                            }
                        }

                        // The easy case: we are dropping file(s) directly onto a file/folder
                        // (rather than between two vertically adjacent files/folders).

                        if (dropPosition == DropPosition.UponItem)
                        {
                            dragTargetGuid = itemGuid;
                            break;
                        }

                        if (dropPosition == DropPosition.BelowItem)
                        {
                            // Special case: If we are dropping below the last item
                            // in a tree/list, then the drop target should be the
                            // parent folder of the target item.
                            //
                            // Note: In "Two Column Layout", `_itemRectsLastFrame`
                            // stores both tree items and list items in the same
                            // list. As a result, the current item (i) may be in
                            // the left pane (i.e. folder tree) while the next item
                            // (i + 1) is in the right pane (i.e. files list). In
                            // this case, we are not really dropping between items,
                            // but we are rather dropping after the last item in
                            // the folder tree.

                            ProjectBrowserExtensions.ItemType nextItemType
                                = ProjectBrowserExtensions.ItemType.TreeItem;

                            if (i + 1 < _itemRectsLastFrame.Count)
                            {
                                Rect nextItemRect = _itemRectsLastFrame[i + 1].rect;
                                nextItemType = ProjectBrowserExtensions
                                               .GetItemType(nextItemRect);
                            }

                            if (i + 1 >= _itemRectsLastFrame.Count ||
                                nextItemType != itemType)
                            {
                                // Special case: user is not allowed to drop into the
                                // parent folder of "Assets", so do nothing.
                                if (itemPath == "Assets")
                                {
                                    return;
                                }

                                string itemParentPath = Path.GetDirectoryName(itemPath);
                                dragTargetGuid = AssetDatabase.AssetPathToGUID(itemParentPath);

                                break;
                            }

                            // If the next item is a child file/folder of the
                            // current item, then the current item should be the
                            // drag target.  Otherwise, the current and next
                            // items are siblings and the drag target should
                            // be their shared parent folder.

                            string nextItemGuid = _itemRectsLastFrame[i + 1].guid;

                            string nextItemPath = UnityPathUtil.NormalizePathSeparators(
                                AssetDatabase.GUIDToAssetPath(nextItemGuid));

                            if (UnityPathUtil.GetParentDir(nextItemPath) == itemPath)
                            {
                                dragTargetGuid = itemGuid;
                                break;
                            }
                            else
                            {
                                dragTargetGuid = AssetDatabase.AssetPathToGUID(
                                    UnityPathUtil.GetParentDir(itemPath));
                                break;
                            }
                        }

                        if (dropPosition == DropPosition.AboveItem)
                        {
                            // If we are dropping above the topmost item in a
                            // tree/list.
                            //
                            // Note: This should never happen except when the user
                            // is drops files above the root "Assets" folder.  In
                            // the case that a subset of items is currently being
                            // shown in a scroll window, the tree/list window will
                            // automatically scroll up when the user hovers the
                            // mouse above the top visible item.

                            if (i == 0)
                            {
                                return;
                            }

                            // If the prev item is parent folder of the
                            // current item, then the previous item should be the
                            // drag target.  Otherwise, the current and previous
                            // items are siblings and the drag target should
                            // be their shared parent folder.

                            string prevItemGuid = _itemRectsLastFrame[i - 1].guid;

                            string prevItemPath = UnityPathUtil.NormalizePathSeparators(
                                AssetDatabase.GUIDToAssetPath(prevItemGuid));

                            if (UnityPathUtil.GetParentDir(itemPath) == prevItemPath)
                            {
                                dragTargetGuid = prevItemGuid;
                                break;
                            }
                            else
                            {
                                dragTargetGuid = AssetDatabase.AssetPathToGUID(
                                    UnityPathUtil.GetParentDir(itemPath));
                                break;
                            }
                        }
                    }
                }

                if (dragTargetGuid == null)
                {
                    // The user dragged the GLTF file(s) onto an empty area of the
                    // Project Browser. Use the currently selected folder in the
                    // left pane (i.e. folder tree) as the import directory.

                    dragTargetGuid = AssetDatabase.AssetPathToGUID(
                        ProjectBrowserExtensions.GetSelectedProjectFolder());
                }

                string dragTargetProjectPath = AssetDatabase.GUIDToAssetPath(dragTargetGuid);
                string dragTargetPath        = UnityPathUtil.GetAbsolutePath(dragTargetProjectPath);

                // Invoke user-defined drag-and-drop callbacks

                OnDragAndDrop?.Invoke(dragTargetPath, DragAndDrop.paths);
            }
        }