public void Manual_PlanarProjection()
    {
        //Select faces

        List <Face> selectedFaces = new List <Face>();

        selectedFaces.Add(m_cube.faces[2]);
        selectedFaces.Add(m_cube.faces[4]);
        selectedFaces.Add(m_cube.faces[5]);
        MeshSelection.SetSelection(m_cube.gameObject);
        m_cube.SetSelectedFaces(selectedFaces);
        MeshSelection.OnObjectSelectionChanged();

        foreach (Face f in selectedFaces)
        {
            Assert.That(f.manualUV, Is.EqualTo(false));
        }

        //Select faces
        UVEditor.instance.Menu_SetManualUV();

        foreach (Face f in selectedFaces)
        {
            Assert.That(f.manualUV, Is.EqualTo(true));
        }

        //Modify those faces
        Vector2 minimalUV = UVEditor.instance.UVSelectionMinimalUV();

        Assert.That(minimalUV, !Is.EqualTo(UVEditor.LowerLeft));

        UVEditor.instance.Menu_PlanarProject();
        minimalUV = UVEditor.instance.UVSelectionMinimalUV();
        Assert.That(minimalUV, Is.EqualTo(UVEditor.LowerLeft));
    }
    public void SelectMaterial_WithNullMaterial()
    {
        //Make first faces selected
        ProBuilderMesh mesh = selectables[0];

        MeshSelection.SetSelection(mesh.gameObject);
        mesh.AddToFaceSelection(3);
        MeshSelection.OnObjectSelectionChanged();

        UnityEditor.ProBuilder.Actions.SelectMaterial selectMaterial = new UnityEditor.ProBuilder.Actions.SelectMaterial();
        var oldValue = selectMaterial.m_RestrictToSelectedObjects.value;

        selectMaterial.m_RestrictToSelectedObjects.value = false;
        selectMaterial.PerformAction();
        selectMaterial.m_RestrictToSelectedObjects.value = oldValue;

        //We need to force the object selection change here to ensure that MeshSelection reflect the result
        //of the action, which typically would notify the MeshSelection asynchronously.
        MeshSelection.OnObjectSelectionChanged();

        Assert.That(MeshSelection.selectedObjectCount, Is.EqualTo(1));
        Assert.That(mesh.selectedFaceCount, Is.EqualTo(3));
        Assert.That(mesh.selectedFaceIndexes.IndexOf(3), !Is.EqualTo(-1));
        Assert.That(mesh.selectedFaceIndexes.IndexOf(4), !Is.EqualTo(-1));
        Assert.That(mesh.selectedFaceIndexes.IndexOf(5), !Is.EqualTo(-1));
        Assert.That(selectables[1].selectedFaceCount, Is.EqualTo(0));
    }
        public void DuplicateFaces_ToSubmesh()
        {
            //Make first faces selected
            ProBuilderMesh mesh = selectables[0];

            Assume.That(mesh.faces, Is.Not.Null);
            Face        face          = selectables[0].faces[0];
            List <Face> selectedFaces = new List <Face>();

            selectedFaces.Add(face);
            mesh.SetSelectedFaces(selectedFaces);
            Assert.AreEqual(mesh.selectedFaceCount, 1);
            MeshSelection.SetSelection(mesh.gameObject);
            MeshSelection.OnObjectSelectionChanged();

            UnityEditor.ProBuilder.Actions.DuplicateFaces duplicateFaces = new UnityEditor.ProBuilder.Actions.DuplicateFaces();
            ProBuilderSettings.Set <UnityEditor.ProBuilder.Actions.DuplicateFaces.DuplicateFaceSetting>("DuplicateFaces.target", UnityEditor.ProBuilder.Actions.DuplicateFaces.DuplicateFaceSetting.Submesh);
            duplicateFaces.DoAction();

            //All selectable object should have all faces selected
            Assert.AreEqual(selectables[0].faces.Count, 7);

            Assert.AreEqual(MeshSelection.selectedObjectCount, 1);
            Assert.AreEqual(UnityEditor.Selection.objects[0], mesh.gameObject);
        }
예제 #4
0
        public override ActionResult DoAction()
        {
            GameObject     go   = new GameObject();
            PolyShape      poly = go.AddComponent <PolyShape>();
            ProBuilderMesh pb   = poly.gameObject.AddComponent <ProBuilderMesh>();

            pb.CreateShapeFromPolygon(poly.m_Points, poly.extrude, poly.flipNormals);
            EditorUtility.InitObject(pb);

            // Special case - we don't want to reset the grid pivot because we rely on it to set the active plane for
            // interaction, regardless of whether snapping is enabled or not.
            if (ProGridsInterface.SnapEnabled() || ProGridsInterface.GridVisible())
            {
                Vector3 pivot;
                if (ProGridsInterface.GetPivot(out pivot))
                {
                    go.transform.position = pivot;
                }
            }
            MeshSelection.SetSelection(go);
            UndoUtility.RegisterCreatedObjectUndo(go, "Create Poly Shape");
            poly.polyEditMode = PolyShape.PolyEditMode.Path;


            return(new ActionResult(ActionResult.Status.Success, "Create Poly Shape"));
        }
예제 #5
0
    public void CollapseVertices_SelectedSharedVertices_ActionEnabled()
    {
        // check that selecting two shared vertices will enable collapse vertices
        Assert.That(m_PBMesh, Is.Not.Null);

        var sharedVertices = m_PBMesh.sharedVerticesInternal;

        Assert.That(sharedVertices, Is.Not.Null);
        Assert.That(sharedVertices.Length, Is.GreaterThanOrEqualTo(2));

        var selectedVertices = sharedVertices[0].Union(sharedVertices[1]);

        Assert.That(selectedVertices.Count(), Is.GreaterThan(1));

        // Set the selected vertices to two different shared vertices (collapsable)
        m_PBMesh.SetSelectedVertices(selectedVertices);
        Assert.That(m_PBMesh.selectedIndexesInternal.Length, Is.EqualTo(selectedVertices.Count()));

        MeshSelection.SetSelection(m_PBMesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        UnityEditor.ProBuilder.Actions.CollapseVertices collapseVertices = new UnityEditor.ProBuilder.Actions.CollapseVertices();

        Assert.That(collapseVertices.enabled, Is.True);
    }
        public void DuplicateFaces_ToObject()
        {
            //Make first faces selected
            ProBuilderMesh mesh = selectables[0];

            Assume.That(mesh.faces, Is.Not.Null);
            Face        face          = selectables[0].faces[0];
            List <Face> selectedFaces = new List <Face>();

            selectedFaces.Add(face);
            mesh.SetSelectedFaces(selectedFaces);
            Assert.AreEqual(mesh.selectedFaceCount, 1);
            MeshSelection.SetSelection(mesh.gameObject);
            MeshSelection.OnObjectSelectionChanged();


            UnityEditor.ProBuilder.Actions.DuplicateFaces duplicateFaces = new UnityEditor.ProBuilder.Actions.DuplicateFaces();
            ProBuilderSettings.Set <UnityEditor.ProBuilder.Actions.DuplicateFaces.DuplicateFaceSetting>("DuplicateFaces.target", UnityEditor.ProBuilder.Actions.DuplicateFaces.DuplicateFaceSetting.GameObject);
            duplicateFaces.DoAction();

            //selectable object should keep all faces selected
            Assert.AreEqual(selectables[0].faces.Count, 6);

            Assert.AreEqual(MeshSelection.selectedObjectCount, 1);
            Assert.AreNotEqual(UnityEditor.Selection.objects[0], mesh.gameObject);

            //This needs to be called explicitly in the case of unit test so that the internal representation of ProBuilder MeshSelection
            //gets updated prior to accessing it
            MeshSelection.OnObjectSelectionChanged();
            ProBuilderMesh newMesh = MeshSelection.activeMesh;

            Assert.AreEqual(newMesh.faces.Count, 1);
        }
예제 #7
0
    public void SelectFaces_WithoutColor()
    {
        Setup();

        //Make first faces selected
        ProBuilderMesh mesh = selectables[0];

        Assert.IsNotNull(mesh.faces);
        Face        face          = selectables[0].faces[0];
        List <Face> selectedFaces = new List <Face>();

        selectedFaces.Add(face);
        mesh.SetSelectedFaces(selectedFaces);
        Assert.AreEqual(mesh.selectedFaceCount, 1);
        MeshSelection.SetSelection(mesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        foreach (var currObject in selectables)
        {
            //Validate that prior not all faces are selected
            Assert.AreNotEqual(currObject.selectedFacesInternal.Length, 6);
        }

        UnityEditor.ProBuilder.Actions.SelectVertexColor selectColorAction = new UnityEditor.ProBuilder.Actions.SelectVertexColor();
        selectColorAction.DoAction();

        foreach (var currObject in selectables)
        {
            //All selectable object should have all faces selected
            Assert.AreEqual(currObject.selectedFacesInternal.Length, 6);
        }
    }
예제 #8
0
        protected override ActionResult PerformActionImplementation()
        {
            ProBuilderEditor.selectMode = SelectMode.Object;
            MeshSelection.SetSelection((GameObject)null);

            m_Tool = ScriptableObject.CreateInstance <DrawShapeTool>();
            ToolManager.SetActiveTool(m_Tool);

            MenuAction.onPerformAction         += ActionPerformed;
            ToolManager.activeToolChanging     += LeaveTool;
            ProBuilderEditor.selectModeChanged += OnSelectModeChanged;

            return(new ActionResult(ActionResult.Status.Success, "Draw Shape Tool Starts"));
        }
    public void CollapseVertices_SelectedSharedVertices_ActionEnabled()
    {
        Assume.That(m_PBMesh, Is.Not.Null);

        MeshSelection.SetSelection(m_PBMesh.gameObject);
        int[] vertexSelection = new[] { 0, 1, 2, 3 };
        m_PBMesh.SetSelectedVertices(vertexSelection);

        Assume.That(m_PBMesh.selectedIndexesInternal, Is.EquivalentTo(vertexSelection));
        Assume.That(MeshSelection.selectedVertexCount, Is.EqualTo(vertexSelection.Length));

        var collapseAction = new UnityEditor.ProBuilder.Actions.CollapseVertices();

        Assert.That(collapseAction.enabled, Is.True);
    }
예제 #10
0
        protected override ActionResult PerformActionImplementation()
        {
            GameObject go     = new GameObject();
            var        bezier = go.AddComponent <BezierShape>();

            go.GetComponent <MeshRenderer>().sharedMaterial = EditorMaterialUtility.GetUserMaterial();
            bezier.Init();
            bezier.Refresh();
            EditorUtility.InitObject(bezier.GetComponent <ProBuilderMesh>());
            MeshSelection.SetSelection(go);
            UndoUtility.RegisterCreatedObjectUndo(go, "Create Bezier Shape");
            bezier.isEditing = true;

            return(new ActionResult(ActionResult.Status.Success, "Create Bezier Shape"));
        }
    public void Setup()
    {
        // make sure the ProBuilder window is open
        if (ProBuilderEditor.instance == null)
        {
            ProBuilderEditor.MenuOpenWindow();
        }

        Assume.That(ProBuilderEditor.instance, Is.Not.Null);

        m_PBMesh = ShapeGenerator.CreateShape(ShapeType.Plane);
        MeshSelection.SetSelection(m_PBMesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        m_PreviousSelectMode        = ProBuilderEditor.selectMode;
        ProBuilderEditor.selectMode = SelectMode.Object;
    }
예제 #12
0
    public void Setup()
    {
        // make sure the ProBuilder window is open
        if (ProBuilderEditor.instance == null)
        {
            ProBuilderEditor.MenuOpenWindow();
        }

        Assume.That(ProBuilderEditor.instance, Is.Not.Null);

        m_PBMesh = ShapeFactory.Instantiate(typeof(UnityEngine.ProBuilder.Shapes.Plane));
        MeshSelection.SetSelection(m_PBMesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        m_PreviousSelectMode        = ProBuilderEditor.selectMode;
        ProBuilderEditor.selectMode = SelectMode.Object;
    }
        protected override ActionResult PerformActionImplementation()
        {
            if (!CanCreateNewPolyShape())
            {
                return(new ActionResult(ActionResult.Status.Canceled, "Canceled Create Poly Shape"));
            }

            GameObject go = new GameObject("PolyShape");

            UndoUtility.RegisterCreatedObjectUndo(go, "Create Poly Shape");
            PolyShape      poly = Undo.AddComponent <PolyShape>(go);
            ProBuilderMesh pb   = Undo.AddComponent <ProBuilderMesh>(go);

            pb.CreateShapeFromPolygon(poly.m_Points, poly.extrude, poly.flipNormals);
            EditorUtility.InitObject(pb);

            // Special case - we don't want to reset the grid pivot because we rely on it to set the active plane for
            // interaction, regardless of whether snapping is enabled or not.
            if (ProGridsInterface.SnapEnabled() || ProGridsInterface.GridVisible())
            {
                Vector3 pivot;
                if (ProGridsInterface.GetPivot(out pivot))
                {
                    go.transform.position = pivot;
                }
            }
            MeshSelection.SetSelection(go);
            poly.polyEditMode = PolyShape.PolyEditMode.Path;

            ProBuilderEditor.selectMode = SelectMode.Object;

            m_Tool = ScriptableObject.CreateInstance <PolyShapeTool>();
            ((PolyShapeTool)m_Tool).polygon = poly;
            ToolManager.SetActiveTool(m_Tool);

            Undo.RegisterCreatedObjectUndo(m_Tool, "Open PolyShape Tool");

            MenuAction.onPerformAction         += ActionPerformed;
            ToolManager.activeToolChanging     += LeaveTool;
            ProBuilderEditor.selectModeChanged += OnSelectModeChanged;

            MeshSelection.objectSelectionChanged += OnObjectSelectionChanged;

            return(new ActionResult(ActionResult.Status.Success, "Create Poly Shape"));
        }
예제 #14
0
    public void SelectFaces_WithColor()
    {
        Setup(true /*with color*/);

        //Make first faces selected
        ProBuilderMesh mesh = selectables[0];

        Assert.IsNotNull(mesh.faces);
        Face        face          = selectables[0].faces[0];
        List <Face> selectedFaces = new List <Face>();

        selectedFaces.Add(face);
        mesh.SetSelectedFaces(selectedFaces);
        Assert.AreEqual(mesh.selectedFaceCount, 1);
        MeshSelection.SetSelection(mesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        //Validate that prior only a face on first cube is selected
        Assert.AreEqual(selectables[0].selectedFacesInternal.Length, 1);
        Assert.AreEqual(selectables[1].selectedFacesInternal.Length, 0);

        UnityEditor.ProBuilder.Actions.SelectVertexColor selectColorAction = new UnityEditor.ProBuilder.Actions.SelectVertexColor();
        selectColorAction.DoAction();

        //Validate that after a face is selected on both cube
        Color[] colors0 = selectables[0].colorsInternal;
        Color[] colors1 = selectables[1].colorsInternal;

        Assert.AreEqual(selectables[0].selectedFacesInternal.Length, 1);
        Assert.AreEqual(selectables[1].selectedFacesInternal.Length, 1);

        int[] tris0 = selectables[0].selectedFacesInternal[0].distinctIndexesInternal;
        int[] tris1 = selectables[1].selectedFacesInternal[0].distinctIndexesInternal;
        Assert.AreEqual(tris0.Length, tris1.Length);

        //Validate that the face match
        for (int n = 0; n < tris0.Length; n++)
        {
            Assert.AreEqual(colors0[tris0[n]], colors1[tris1[n]]);
            Assert.AreEqual(colors0[tris0[n]], faceColors[0]);
        }
    }
예제 #15
0
    public static void DetachFaceUndoTest()
    {
        var cube      = ShapeFactory.Instantiate <Cube>();
        var duplicate = UnityEngine.Object.Instantiate(cube.gameObject).GetComponent <ProBuilderMesh>();

        duplicate.MakeUnique();

        // Select the mesh
        MeshSelection.SetSelection(cube.gameObject);
        MeshSelection.OnObjectSelectionChanged();
        Assume.That(MeshSelection.selectedObjectCount, Is.EqualTo(1));

        // Select a face
        cube.SetSelectedFaces(new Face[] { cube.facesInternal[0] });
        Assume.That(cube.selectedFacesInternal.Length, Is.EqualTo(1));

        // Perform `Detach Faces` action
        var detachAction = new DetachFaces();
        var result       = detachAction.PerformAction();

        Assume.That(result.status, Is.EqualTo(ActionResult.Status.Success));

        UnityEditor.Undo.PerformUndo();

        // this is usually caught by UndoUtility
        cube.InvalidateCaches();

        cube.ToMesh();
        cube.Refresh();

        // After undo, previously edited mesh should match the duplicate
        TestUtility.AssertAreEqual(duplicate.mesh, cube.mesh);

        UnityEngine.Object.DestroyImmediate(cube.gameObject);
        UnityEngine.Object.DestroyImmediate(duplicate.gameObject);
    }
예제 #16
0
    public void CollapseVertices_SelectSharedVertex_ActionDisabled()
    {
        Assert.That(m_PBMesh, Is.Not.Null);

        var sharedVertices = m_PBMesh.sharedVerticesInternal;

        Assert.That(sharedVertices, Is.Not.Null);
        Assert.That(sharedVertices.Length, Is.GreaterThanOrEqualTo(1));

        var sharedVertex = sharedVertices[0];

        Assert.That(sharedVertex.Count, Is.GreaterThan(1));

        // Set the selected vertices to all vertices belonging to a single shared vertex
        m_PBMesh.SetSelectedVertices(sharedVertex);
        Assert.That(m_PBMesh.selectedIndexesInternal.Length, Is.EqualTo(sharedVertex.Count));

        MeshSelection.SetSelection(m_PBMesh.gameObject);
        MeshSelection.OnObjectSelectionChanged();

        UnityEditor.ProBuilder.Actions.CollapseVertices collapseVertices = new UnityEditor.ProBuilder.Actions.CollapseVertices();

        Assert.That(collapseVertices.enabled, Is.False);
    }
예제 #17
0
        public override ActionResult DoAction()
        {
            Vector3 scale = new Vector3(
                (m_MirrorAxes & MirrorSettings.X) > 0 ? -1f : 1f,
                (m_MirrorAxes & MirrorSettings.Y) > 0 ? -1f : 1f,
                (m_MirrorAxes & MirrorSettings.Z) > 0 ? -1f : 1f);

            bool duplicate = (m_MirrorAxes & MirrorSettings.Duplicate) > 0;

            List <GameObject> res = new List <GameObject>();

            foreach (ProBuilderMesh pb in MeshSelection.topInternal)
            {
                res.Add(Mirror(pb, scale, duplicate).gameObject);
            }

            MeshSelection.SetSelection(res);

            ProBuilderEditor.Refresh();

            return(res.Count > 0 ?
                   new ActionResult(ActionResult.Status.Success, string.Format("Mirror {0} {1}", res.Count, res.Count > 1 ? "Objects" : "Object")) :
                   new ActionResult(ActionResult.Status.NoChange, "No Objects Selected"));
        }
예제 #18
0
        static ActionResult DuplicateFacesToObject()
        {
            int duplicatedFaceCount      = 0;
            List <GameObject> duplicated = new List <GameObject>();

            foreach (ProBuilderMesh mesh in MeshSelection.topInternal)
            {
                if (mesh.selectedFaceCount < 1)
                {
                    continue;
                }

                var primary = mesh.selectedFaceIndexes;
                duplicatedFaceCount += primary.Count;

                List <int> inverse = new List <int>();

                for (int i = 0; i < mesh.facesInternal.Length; i++)
                {
                    if (!primary.Contains(i))
                    {
                        inverse.Add(i);
                    }
                }

                ProBuilderMesh copy = Object.Instantiate(mesh.gameObject, mesh.transform.parent).GetComponent <ProBuilderMesh>();
                EditorUtility.SynchronizeWithMeshFilter(copy);

                if (copy.transform.childCount > 0)
                {
                    for (int i = copy.transform.childCount - 1; i > -1; i--)
                    {
                        Object.DestroyImmediate(copy.transform.GetChild(i).gameObject);
                    }

                    foreach (var child in mesh.transform.GetComponentsInChildren <ProBuilderMesh>())
                    {
                        EditorUtility.SynchronizeWithMeshFilter(child);
                    }
                }

                Undo.RegisterCreatedObjectUndo(copy.gameObject, "Duplicate Selection");

                copy.DeleteFaces(inverse);
                copy.Rebuild();
                copy.Optimize();
                mesh.ClearSelection();
                copy.ClearSelection();
                copy.SetSelectedFaces(copy.faces);

                copy.gameObject.name = GameObjectUtility.GetUniqueNameForSibling(mesh.transform.parent, mesh.gameObject.name);
                duplicated.Add(copy.gameObject);
            }

            MeshSelection.SetSelection(duplicated);
            ProBuilderEditor.Refresh();

            if (duplicatedFaceCount > 0)
            {
                return(new ActionResult(ActionResult.Status.Success, "Duplicate " + duplicatedFaceCount + " faces to new Object"));
            }

            return(new ActionResult(ActionResult.Status.Failure, "No Faces Selected"));
        }
예제 #19
0
    public static IEnumerator ExtrudeOrthogonally_OneElementManyTimes_NoYOffsetAccumulates()
    {
        // Generate single face plane
        var pb = ShapeGenerator.GeneratePlane(PivotLocation.Center, 1f, 1f, 0, 0, Axis.Up);

        try
        {
            pb.transform.position = Vector3.zero;
            pb.transform.rotation = Quaternion.identity;

            ProBuilderEditor.MenuOpenWindow();
            EditorApplication.ExecuteMenuItem("Window/General/Scene");

            var sceneView = UnityEngine.Resources.FindObjectsOfTypeAll <UnityEditor.SceneView>()[0];
            sceneView.orthographic = true;
            sceneView.drawGizmos   = false;
            sceneView.pivot        = new Vector3(0, 0, 0);
            sceneView.rotation     = Quaternion.AngleAxis(90f, Vector3.right);
            sceneView.size         = 2.0f;
            sceneView.Focus();

            var e = new Event();
            e.type = EventType.MouseEnterWindow;
            sceneView.SendEvent(e);

            Assume.That(pb.facesInternal.Length, Is.EqualTo(1));
            var face = pb.facesInternal[0];

            // Select face
            var selectedFaces = new List <Face>();
            selectedFaces.Add(face);
            Tools.current = Tool.Move;
            ProBuilderEditor.toolManager.SetSelectMode(SelectMode.Face);
            pb.SetSelectedFaces(selectedFaces);
            MeshSelection.SetSelection(pb.gameObject);

            // Center mouse position
            var bounds   = SceneView.focusedWindow.rootVisualElement.worldBound;
            var mousePos = (bounds.size * 0.5f + bounds.position) + new Vector2Int(1, 1);

            e = new UnityEngine.Event()
            {
                type          = EventType.MouseDown,
                mousePosition = mousePos,
                modifiers     = EventModifiers.None,
                clickCount    = 1,
                delta         = Vector2.zero,
            };
            sceneView.SendEvent(e);

            e = new UnityEngine.Event()
            {
                type          = EventType.MouseUp,
                mousePosition = mousePos,
                modifiers     = EventModifiers.None,
                clickCount    = 0,
                delta         = Vector2.zero,
            };
            sceneView.SendEvent(e);

            yield return(null);

            const int k_ExtrudeCount = 100;
            for (int i = 0; i < k_ExtrudeCount; i++)
            {
                // Press down at the center of the face
                e = new UnityEngine.Event()
                {
                    type          = EventType.MouseDown,
                    mousePosition = mousePos,
                    modifiers     = EventModifiers.None,
                    clickCount    = 1,
                    delta         = Vector2.zero,
                };
                sceneView.SendEvent(e);

                // Do lateral 1px drag and release
                var mouseDelta = new Vector2(i % 2 == 0 ? 1f : -1f, 0f);
                mousePos += mouseDelta;

                e.type          = EventType.MouseDrag;
                e.mousePosition = mousePos;
                e.modifiers     = EventModifiers.Shift;
                e.clickCount    = 0;
                e.delta         = mouseDelta;
                sceneView.SendEvent(e);

                e.type          = EventType.MouseUp;
                e.mousePosition = mousePos;
                e.delta         = Vector2.zero;
                sceneView.SendEvent(e);

                yield return(null);
            }

            // Check that our face count is correct after all extrusions
            Assume.That(pb.facesInternal.Length, Is.EqualTo(k_ExtrudeCount * 4 + 1));

            // We should have the last extruded face in selection
            var postExtrudeSelectedFaces = pb.GetSelectedFaces();
            Assume.That(postExtrudeSelectedFaces.Length, Is.EqualTo(1));
            var lastExtrudedFace = postExtrudeSelectedFaces[0];
            var faceVertices     = pb.GetVertices(lastExtrudedFace.indexes);

            // After many orthogonal extrusions, the last face should still be at y=0 coordinate
            for (int i = 0; i < faceVertices.Length; i++)
            {
                Assert.That(faceVertices[i].position.y, Is.EqualTo(0f));
            }
        }
        finally
        {
            UObject.DestroyImmediate(pb.gameObject);
        }
    }
        static ActionResult DetachFacesToObject()
        {
            int detachedFaceCount      = 0;
            List <GameObject> detached = new List <GameObject>();

            foreach (ProBuilderMesh mesh in MeshSelection.topInternal)
            {
                if (mesh.selectedFaceCount < 1 || mesh.selectedFaceCount == mesh.facesInternal.Length)
                {
                    continue;
                }

                var primary = mesh.selectedFaceIndexes;
                detachedFaceCount += primary.Count;

                List <int> inverse = new List <int>();

                for (int i = 0; i < mesh.facesInternal.Length; i++)
                {
                    if (!primary.Contains(i))
                    {
                        inverse.Add(i);
                    }
                }

                ProBuilderMesh copy = Object.Instantiate(mesh.gameObject, mesh.transform.parent).GetComponent <ProBuilderMesh>();
                EditorUtility.SynchronizeWithMeshFilter(copy);

#if !UNITY_2018_3_OR_NEWER
                // if is prefab, break connection and destroy children
                if (EditorUtility.IsPrefabInstance(copy.gameObject) || EditorUtility.IsPrefabAsset(copy.gameObject))
                {
                    PrefabUtility.DisconnectPrefabInstance(copy.gameObject);
                }
#endif

                if (copy.transform.childCount > 0)
                {
                    for (int i = copy.transform.childCount - 1; i > -1; i--)
                    {
                        Object.DestroyImmediate(copy.transform.GetChild(i).gameObject);
                    }

                    foreach (var child in mesh.transform.GetComponentsInChildren <ProBuilderMesh>())
                    {
                        EditorUtility.SynchronizeWithMeshFilter(child);
                    }
                }

                Undo.RegisterCreatedObjectUndo(copy.gameObject, "Detach Selection");

                mesh.DeleteFaces(primary);
                copy.DeleteFaces(inverse);

                mesh.Rebuild();
                copy.Rebuild();

                mesh.Optimize();
                copy.Optimize();

                mesh.ClearSelection();
                copy.ClearSelection();

                copy.SetSelectedFaces(copy.faces);

                copy.gameObject.name = GameObjectUtility.GetUniqueNameForSibling(mesh.transform.parent, mesh.gameObject.name);;
                detached.Add(copy.gameObject);
            }

            MeshSelection.SetSelection(detached);
            ProBuilderEditor.Refresh();

            if (detachedFaceCount > 0)
            {
                return(new ActionResult(ActionResult.Status.Success, "Detach " + detachedFaceCount + " faces to new Object"));
            }

            return(new ActionResult(ActionResult.Status.Failure, "No Faces Selected"));
        }