Esempio n. 1
0
        /// <summary>
        /// Callback that is invoked when external file(s) are
        /// dragged-and-dropped into the Project Browser.
        /// </summary>
        private static void HandleDragAndDrop(string targetPath, string[] droppedPaths)
        {
            // Hold the Control key or Command key while
            // dragging-and-dropping a .gltf/.glb/.zip to
            // copy the file into the project without
            // performing an automatic glTF import.

            if (Event.current.control || Event.current.command)
            {
                return;
            }

            // Read current import options from Piglet Options window.
            //
            // Note: The SaveAssets/Refresh calls ensure that any changes
            // made in the Piglet Options window are saved out to disk
            // first.

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            _pigletOptions = Resources.Load <PigletOptions>("PigletOptions");

            // Do nothing if drag-and-drop glTF import has been disabled by the user

            if (!_pigletOptions.EnableDragAndDropImport)
            {
                return;
            }

            // If `targetPath` is a regular file and not a directory, use
            // the parent directory as the import directory.

            string importDir = Directory.Exists(targetPath)
                ? targetPath : Path.GetDirectoryName(targetPath);

            importDir = UnityPathUtil.NormalizePathSeparators(importDir);

            // Exclude files that don't have .gltf/.glb extension.
            //
            // Note: I would prefer to pass skipped files through to Unity
            // for default drag-and-drop handling, but that does not seem
            // to be possible because `DragAndDrop.paths` is read-only.

            List <string> acceptedPaths = new List <string>();

            foreach (string path in DragAndDrop.paths)
            {
                // Don't trigger automatic glTF import when we are dragging
                // a .gltf/.glb/.zip file from within the Unity project folder.
                //
                // When the source file is inside the Unity project folder,
                // the Unity drag-and-drop machinery will report a relative path
                // starting with "Assets/".

                if (path.StartsWith("Assets/"))
                {
                    continue;
                }

                string _path = path.ToLower();

                if (_path.EndsWith(".gltf") || _path.EndsWith(".glb"))
                {
                    acceptedPaths.Add(path);
                }

                else if (_path.EndsWith(".zip") && ZipUtil.ContainsGltfFile(path))
                {
                    acceptedPaths.Add(path);
                }
            }

            if (acceptedPaths.Count > 0)
            {
                // Run GLTF import(s) in the background.

                StartImport(acceptedPaths, importDir);

                // Consume the `DragPerform` event, so that Unity's
                // default drag-and-drop handling, which
                // simply copies the file(s) into the target Assets
                // folder, is not performed.

                Event.current.Use();
            }
        }
        /// <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);
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Coroutine to import GLTF files with Piglet's EditorGltfImporter.
        /// The string value returned via the IEnumerator is the target directory
        /// for the current import, so that files from an aborted/canceled import
        /// can be easily cleaned up.
        /// </summary>
        private static IEnumerator <string> ImportCoroutine(List <string> gltfPaths, string baseImportDir)
        {
            foreach (string gltfPath in gltfPaths)
            {
                string gltfBasename      = Path.GetFileName(gltfPath);
                string gltfBasenameNoExt = Path.GetFileNameWithoutExtension(gltfPath);

                bool abortImport = false;

                // callback for updating progress during glTF import
                void OnProgress(GltfImportStep type, int count, int total)
                {
                    ProgressLog.Instance.OnImportProgress(type, count, total);

                    abortImport = EditorUtility.DisplayCancelableProgressBar(
                        $"Importing {gltfBasename}...",
                        ProgressLog.Instance.GetProgressMessage(),
                        (float)count / total);
                }

                string importPath = UnityPathUtil.NormalizePathSeparators(
                    Path.Combine(baseImportDir, gltfBasenameNoExt));
                string importProjectPath = UnityPathUtil.GetProjectPath(importPath);

                if ((Directory.Exists(importPath) || File.Exists(importPath)) &&
                    _pigletOptions.PromptBeforeOverwritingFiles)
                {
                    if (!EditorUtility.DisplayDialog(
                            "Warning!",
                            $"Overwrite \"{importProjectPath}\"?",
                            "OK", "Cancel"))
                    {
                        yield break;
                    }

                    FileUtil.DeleteFileOrDirectory(importPath);
                    AssetDatabase.Refresh();
                }

                GltfImportTask importTask =
                    EditorGltfImporter.GetImportTask(gltfPath, importPath,
                                                     _pigletOptions.ImportOptions);

                importTask.OnProgress = OnProgress;

                GameObject importedPrefab = null;
                importTask.OnCompleted = (prefab) => importedPrefab = prefab;

                // restart import timer at zero
                ProgressLog.Instance.StartImport();

                while (true)
                {
                    if (abortImport)
                    {
                        importTask.Abort();
                        EditorUtility.ClearProgressBar();
                        yield break;
                    }

                    try
                    {
                        if (!importTask.MoveNext())
                        {
                            break;
                        }
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e);

                        EditorUtility.ClearProgressBar();
                        EditorUtility.DisplayDialog("Import Failed",
                                                    String.Format("Import of {0} failed. "
                                                                  + "See Unity console log for details.", gltfBasename),
                                                    "OK");

                        yield break;
                    }

                    yield return(importPath);
                }

                // Before modifying the selection, store a handle to
                // the transform of the currently selected game object (if any).

                Transform selectedTransform = Selection.activeTransform;

                // Select the prefab file in the Project Browser.
                if (_pigletOptions.SelectPrefabAfterImport)
                {
                    Selection.activeObject = importedPrefab;
                    yield return(importPath);
                }

                if (_pigletOptions.AddPrefabToScene)
                {
                    // If we are currently in Prefab Mode, exit
                    // back to the main scene hierarchy view.
                    //
                    // Note: Prefab Mode was introduced in Unity 2018.3.
#if UNITY_2018_3_OR_NEWER
                    if (StageUtility.GetCurrentStageHandle()
                        != StageUtility.GetMainStageHandle())
                    {
                        StageUtility.GoToMainStage();
                    }
#endif

                    GameObject instance = (GameObject)PrefabUtility
                                          .InstantiatePrefab(importedPrefab);

                    // parent the prefab instance to the currently
                    // selected GameObject (if any)
                    if (selectedTransform != null)
                    {
                        instance.transform.parent = selectedTransform;
                    }

                    if (_pigletOptions.SelectPrefabInScene)
                    {
                        Selection.activeGameObject = instance;
                        yield return(importPath);
                    }
                }

                if (_pigletOptions.OpenPrefabAfterImport)
                {
                    AssetDatabase.OpenAsset(importedPrefab);

                    // Note: This is the best method I could find
                    // for automatically centering the prefab in
                    // the scene view. For further info, see
                    // https://answers.unity.com/questions/813814/framing-objects-via-script-in-the-unity-editor.html
                    SceneView.FrameLastActiveSceneView();
                }

                EditorUtility.ClearProgressBar();
            }
        }