Exemple #1
0
    /// <summary>
    /// Exports and uploads the saber via adb
    /// </summary>
    /// <param name="saber"></param>
    void ExportAndUpload(SaberReferences saber)
    {
        string saberName = saber.descriptor.objectName + "." + extension;
        string path      = EditorUtility.SaveFilePanel("Save " + extension + " file", "", saberName, extension);

        if (path == "")
        {
            EditorUtility.DisplayDialog("Exportation Failed!", "Invalid path", "OK");
            return;
        }

        string folderPath = Path.GetDirectoryName(path) + "/";

        if (exporterConfig.ipAddress != "")
        {
            QosmeticUtils.adb("connect " + exporterConfig.ipAddress + ":5555");
        }

        QosmeticUtils.adb("shell am force-stop com.beatgames.beatsaber");

        ExportSaberFile(saber, folderPath);

        QosmeticUtils.adb("push \"" + folderPath + saberName + "\" \"" + questPath + saberName + "\"");
        QosmeticUtils.adb("shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity");

        EditorUtility.DisplayDialog("Exportation Successful!", "If the game did not turn off or the saber is not showing up, make sure your quest is connected through adb", "OK");
        EditorUtility.RevealInFinder(folderPath + saberName);
    }
    void ExportAllAndUpload()
    {
        string path = EditorUtility.SaveFolderPanel("Select folder to export all ." + extension + " files", "", "");

        if (path == "")
        {
            EditorUtility.DisplayDialog("Export Failed", "Path was invalid", "OK");
            return;
        }

        path += "/";
        List <string> skippedWalls         = new List <string>();
        bool          wallsWereSkipped     = false;
        bool          atLeastOneSuccessful = false;

        if (exporterConfig.ipAddress != "")
        {
            QosmeticUtils.adb("connect " + exporterConfig.ipAddress + ":5555");
        }
        QosmeticUtils.adb("shell am force-stop com.beatgames.beatsaber");

        foreach (var wall in questwalls)
        {
            bool disableExport = shouldDisableExport(wall);

            if (!disableExport)
            {
                atLeastOneSuccessful = true;
                ExportWallFile(wall, path);
                string wallName = wall.descriptor.objectName + "." + extension;
                QosmeticUtils.adb("push\"" + path + wallName + "\" \"" + questPath + wallName + "\"");
            }
            else
            {
                wallsWereSkipped = true;
                skippedWalls.Add(wall.descriptor.objectName);
            }
        }

        if (wallsWereSkipped)
        {
            string message = "These Walls were skipped due to exporter issues: \n";
            foreach (var wallName in skippedWalls)
            {
                message += wallName + "\n";
            }
            EditorUtility.DisplayDialog("Sabers Were skipped...", message, "OK");
        }
        else
        {
            EditorUtility.DisplayDialog("Wall exports successful!", "Exported all walls successfully!", "OK");
        }
        if (atLeastOneSuccessful)
        {
            QosmeticUtils.adb("shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity");
            EditorUtility.RevealInFinder(path);
        }
    }
Exemple #3
0
    /// <summary>
    /// Runs the code to display the bmbf input options in the GUI
    /// </summary>
    /// <param name="saber"></param>
    void BMBFConfigDisplay(SaberReferences saber)
    {
        QosmeticUtils.addIfNull(saber.bmbfmod);

        saber.bmbfmod.components[0].type = "FileCopyMod";
        saber.bmbfmod.components[0].targetRootedPathAndFileName = questPath + "testSaber." + extension;

        if (saber.advancedInfo)
        {
            saber.bmbfmod.coverImageFilename = EditorGUILayout.TextField("Cover image name", saber.bmbfmod.coverImageFilename);
        }
        else if (saber.bmbfmod.coverImageFilename == "" && exporterConfig.defaultCoverImageFileName != "")
        {
            saber.bmbfmod.coverImageFilename = exporterConfig.defaultCoverImageFileName;
        }
        saber.bmbfmod.icon = saber.bmbfmod.coverImageFilename;

        if (saber.advancedInfo)
        {
            saber.bmbfmod.version = EditorGUILayout.TextField("Saber version", saber.bmbfmod.version);
        }
        else if (saber.bmbfmod.version == "" && exporterConfig.defaultVersion != "")
        {
            saber.bmbfmod.version = exporterConfig.defaultVersion;
        }

        if (saber.advancedInfo)
        {
            saber.bmbfmod.links.pageLink = EditorGUILayout.TextField("Page link", saber.bmbfmod.links.pageLink);
        }
        else if (saber.bmbfmod.links.pageLink == "" && exporterConfig.defaultPageLink != "")
        {
            saber.bmbfmod.links.pageLink = exporterConfig.defaultPageLink;
        }

        saber.bmbfmod.gameVersion    = EditorGUILayout.TextField("Game version", saber.bmbfmod.gameVersion);
        saber.bmbfmod.description[0] = EditorGUILayout.TextField("Description", saber.bmbfmod.description[0]);
        saber.bmbfmod.platform       = "Quest";
        saber.bmbfmod.name           = EditorGUILayout.TextField("Saber name", saber.bmbfmod.name);
        saber.bmbfmod.author         = EditorGUILayout.TextField("Author name", saber.bmbfmod.author);
        saber.bmbfmod.category       = "Saber";

        saber.bmbfmod.components[0].sourceFileName = saber.bmbfmod.name + "." + extension;

        string modID = saber.bmbfmod.name + saber.bmbfmod.author + "V" + saber.bmbfmod.version;

        modID = QosmeticUtils.sanitizeString(modID);

        saber.bmbfmod.id = modID;
    }
Exemple #4
0
    void OnGUI()
    {
        GUILayout.Label("Object cleanup at export config", EditorStyles.boldLabel);
        exporterConfig.removeLightsAtExport  = EditorGUILayout.ToggleLeft("Remove Lights at export", exporterConfig.removeLightsAtExport);
        exporterConfig.removeCamerasAtExport = EditorGUILayout.ToggleLeft("Remove Cameras at export", exporterConfig.removeCamerasAtExport);
        GUILayout.Space(5);

        /*
         * GUILayout.Label("Default information config", EditorStyles.boldLabel);
         * exporterConfig.defaultVersion = EditorGUILayout.TextField("Default Version", exporterConfig.defaultVersion);
         * exporterConfig.defaultPageLink = EditorGUILayout.TextField("Default Page Link", exporterConfig.defaultPageLink);
         * exporterConfig.defaultCoverImageFileName = EditorGUILayout.TextField("Default Cover Image File Name", exporterConfig.defaultCoverImageFileName);
         * GUILayout.Space(5);
         */

        GUILayout.Label("Quest IP Address for file upload", EditorStyles.boldLabel);

        exporterConfig.ipAddress = EditorGUILayout.TextField("IP address", exporterConfig.ipAddress);

        GUILayout.Space(5);
        GUILayout.Label("Misc.", EditorStyles.boldLabel);

        exporterConfig.forceAllowExport = EditorGUILayout.ToggleLeft("Force Allow Export", exporterConfig.forceAllowExport);
        GUILayout.Space(5);
        exporterConfig.allowOwnCamera = EditorGUILayout.ToggleLeft("Allow Own Camera (add QosmeticsCam script to it)", exporterConfig.allowOwnCamera);
        GUILayout.Space(5);
        exporterConfig.setCamLayerOnExport = EditorGUILayout.ToggleLeft("Set cam layer on export (only export is visible)", exporterConfig.setCamLayerOnExport);
        GUILayout.Space(5);

        if (GUILayout.Button("Save config"))
        {
            string json = JsonUtility.ToJson(exporterConfig, true);
            File.WriteAllText(exporterConfigPath, json);
            EditorUtility.DisplayDialog("Config Saved", "For changes to show up in your exports, close and reopen the exporter unity window (not the entire unity project)", "OK");
            OnFocus();
        }
        GUILayout.Label("If you want to upload files wirelessly, press this button every time you reboot your quest:", EditorStyles.boldLabel);
        GUILayout.Label("Make sure that when you click this button, your quest is connected with a usb cable", EditorStyles.boldLabel);
        if (GUILayout.Button("Connect quest wirelessly"))
        {
            QosmeticUtils.adb("adb tcpip 5555");
        }
    }
Exemple #5
0
    /// <summary>
    /// Exports a complete Zip package ready to be loaded into bmbf
    /// </summary>
    /// <param name="saber"></param>
    void ExportSaberZip(SaberReferences saber, string path = "")
    {
        /*
         * if (EditorUserBuildSettings.activeBuildTarget.ToString() != "Android")
         * {
         *  EditorUtility.DisplayDialog("Exportation Failed!", "Your projects build target is not Android.", "OK");
         *  return;
         * }
         */
        bool batch = path != "";

        GameObject saberObject = saber.gameObject;
        TextAsset  config      = new TextAsset(JsonUtility.ToJson(saber.config, true));

        string    bmbfmodJson = JsonUtility.ToJson(saber.bmbfmod, true);
        TextAsset bmbfmod     = new TextAsset(bmbfmodJson);

        string    descriptorString = JsonUtility.ToJson(saber.descriptor, true);
        TextAsset descriptor       = new TextAsset(descriptorString);

        string zipname = saber.bmbfmod.name + "V" + saber.bmbfmod.version + ".zip";

        zipname = QosmeticUtils.sanitizeString(zipname);

        if (!batch)
        {
            path = EditorUtility.SaveFilePanel("Save " + "." + extension + " zip", "", zipname, "zip");
        }
        else
        {
            path += zipname;
        }

        if (path == "")
        {
            EditorUtility.DisplayDialog("Exportation Failed!", "Invalid path", "OK");
            return;
        }

        string fileName   = saber.bmbfmod.name + "." + extension;
        string folderPath = Path.GetDirectoryName(path);

        string workingDir = Application.temporaryCachePath + "/QuestSaber";

        if (File.Exists(path))
        {
            File.Delete(path);
        }
        if (!Directory.Exists(workingDir))
        {
            Directory.CreateDirectory(workingDir);
        }

        if (exporterConfig.removeCamerasAtExport)
        {
            QosmeticUtils.PurgeCameras(saberObject);
        }
        if (exporterConfig.removeLightsAtExport)
        {
            QosmeticUtils.PurgeLights(saberObject);
        }

        Selection.activeObject = saberObject;
        EditorUtility.SetDirty(saber);
        EditorSceneManager.MarkSceneDirty(saberObject.scene);
        EditorSceneManager.SaveScene(saberObject.scene);

        GameObject camObject;
        Camera     exportCam;

        if (exporterConfig.allowOwnCamera) // manual cam
        {
            camObject = cam.gameObject;
            exportCam = cam;
        }
        else // automatic cam:
        {
            camObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
            exportCam = camObject.AddComponent <Camera>();
            exportCam.gameObject.name         = "Camera";
            exportCam.transform.localPosition = new Vector3(0.6f, 0.4f, 0.05f);
            exportCam.transform.rotation      = Quaternion.Euler(25f, -70f, 0.0f);
            exportCam.clearFlags      = CameraClearFlags.SolidColor;
            exportCam.nearClipPlane   = 0.01f;
            exportCam.backgroundColor = new Color(0.0f, 0.0f, 25.0f / 256.0f);
        }
        exportCam.nearClipPlane = 0.01f;

        int oldCamMask    = exportCam.cullingMask;
        int oldObjectMask = saber.gameObject.layer;
        int newMask       = 31;
        int newCamMask    = 1 << 31;

        // moved prefab forward so that the layermask isnt f****d up on the actual exported file
        PrefabUtility.SaveAsPrefabAsset(Selection.activeGameObject as GameObject, "Assets/" + prefabName + ".prefab");

        if (exporterConfig.setCamLayerOnExport)
        {
            exportCam.cullingMask = newCamMask;
            QosmeticUtils.SetLayerRecursively(exportCam.gameObject, newMask);
            QosmeticUtils.SetLayerRecursively(saber.gameObject, newMask);
        }

        Texture2D thumbnail = QosmeticUtils.getTexture2D(exportCam, 1024, 1024);

        //Start actually constructing the bundle


        AssetDatabase.CreateAsset(config, "Assets/config.asset");
        AssetDatabase.CreateAsset(bmbfmod, "Assets/BMBFmod.asset");
        AssetDatabase.CreateAsset(thumbnail, "Assets/thumbnail.asset");
        AssetDatabase.CreateAsset(descriptor, "Assets/descriptor.asset");

        AssetBundleBuild assetBundleBuild = default(AssetBundleBuild);

        assetBundleBuild.assetNames = new string[]
        {
            "Assets/" + prefabName + ".prefab",
            "Assets/config.asset",
            "Assets/BMBFmod.asset",
            "Assets/thumbnail.asset",
            "Assets/descriptor.asset"
        };

        assetBundleBuild.assetBundleName = fileName;

        BuildTargetGroup selectedBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
        BuildTarget      activeBuildTarget        = EditorUserBuildSettings.activeBuildTarget;

        //Build Bundle
        BuildPipeline.BuildAssetBundles(workingDir, new AssetBundleBuild[] { assetBundleBuild }, 0, BuildTarget.Android);
        EditorPrefs.SetString("currentBuildingAssetBundlePath", folderPath);
        EditorUserBuildSettings.SwitchActiveBuildTarget(selectedBuildTargetGroup, activeBuildTarget);

        thumbnail = QosmeticUtils.getTexture2D(exportCam, 1024, 512);

        Directory.CreateDirectory(workingDir + "/tempzip");

        QosmeticUtils.exportImage(thumbnail, workingDir + "/tempzip/" + saber.bmbfmod.coverImageFilename);
        File.Move(workingDir + "/" + fileName, workingDir + "/tempzip/" + fileName);
        File.WriteAllText(workingDir + "/tempzip/bmbfmod.json", bmbfmodJson);

        QosmeticUtils.createZipFromFolder(workingDir + "/tempzip", path);

        //Asset cleanup
        AssetDatabase.DeleteAsset("Assets/" + prefabName + ".prefab");
        AssetDatabase.DeleteAsset("Assets/config.asset");
        AssetDatabase.DeleteAsset("Assets/BMBFmod.asset");
        AssetDatabase.DeleteAsset("Assets/thumbnail.asset");
        AssetDatabase.DeleteAsset("Assets/descriptor.asset");

        if (!exporterConfig.allowOwnCamera)
        {
            DestroyImmediate(camObject);
        }

        clearTemp(workingDir);

        AssetDatabase.Refresh();

        if (exporterConfig.setCamLayerOnExport)
        {
            if (exporterConfig.allowOwnCamera)
            {
                exportCam.cullingMask = oldCamMask;
            }
            if (exporterConfig.allowOwnCamera)
            {
                QosmeticUtils.SetLayerRecursively(exportCam.gameObject, oldObjectMask);
            }
            QosmeticUtils.SetLayerRecursively(saber.gameObject, oldObjectMask);
        }

        if (!batch)
        {
            EditorUtility.DisplayDialog("Exportation Successful!", "Exportation Successful!", "OK");
            EditorUtility.RevealInFinder(path);
        }
    }
Exemple #6
0
    void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("Quest Sabers", EditorStyles.boldLabel);

        if (GUILayout.Button("Go to saber scene"))
        {
            EditorSceneManager.OpenScene("Assets/scenes/questsabers.unity");
            OnFocus();
        }

        if (GUILayout.Button("Add New Saber Template"))
        {
            AddSaberTemplate();
        }
        GUILayout.EndHorizontal();

        GUILayout.Space(5);
        if (GUILayout.Button("Export all Active sabers"))
        {
            ExportAll();
        }
        if (GUILayout.Button("Export and Upload all Active sabers"))
        {
            ExportAllAndUpload();
        }

        GUILayout.Space(10);

        foreach (var saber in questsabers)
        {
            if (saber == null)
            {
                continue;
            }
            GUILayout.Label("GameObject : " + saber.gameObject.name, EditorStyles.boldLabel);
            saber.descriptorFolded = EditorGUILayout.BeginFoldoutHeaderGroup(saber.descriptorFolded, "Saber Descriptor");

            if (saber.descriptorFolded)
            {
                GUILayout.Space(5);
                DescriptorDisplay(saber);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            GUILayout.Space(5);

            saber.configFolded = EditorGUILayout.BeginFoldoutHeaderGroup(saber.configFolded, "Saber config");
            if (saber.configFolded)
            {
                SaberConfigDisplay(saber);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            EditorGUILayout.Space(10);
            bool disableExport = shouldDisableExport(saber);

            if (disableExport && exporterConfig.forceAllowExport)
            {
                GUILayout.Label("WARNING! force allow export is set to true so this saber file is unlikely to work correctly in game!", EditorStyles.boldLabel);
            }
            EditorGUI.BeginDisabledGroup(disableExport && !exporterConfig.forceAllowExport);
            if (GUILayout.Button("Export " + saber.descriptor.objectName + "." + extension))
            {
                ExportSaberFile(saber);
            }

            if (GUILayout.Button("Export and Upload " + saber.descriptor.objectName + "." + extension + " To Quest"))
            {
                ExportAndUpload(saber);
            }
            EditorGUI.EndDisabledGroup();

            if (QosmeticUtils.GetObjectBounds(saber.gameObject).extents.z * 2.0 > 2.0)
            {
                GUILayout.Label("The saber might be too long", EditorStyles.boldLabel);
            }

            if (QosmeticUtils.GetObjectBounds(saber.gameObject).extents.z * 2.0 < 0.5)
            {
                GUILayout.Label("The saber might be too short", EditorStyles.boldLabel);
            }

            if (QosmeticUtils.GetObjectBounds(saber.gameObject).extents.x * 2.0 > 1.0)
            {
                GUILayout.Label("The saber might be too large", EditorStyles.boldLabel);
            }

            if (debug)
            {
                if (GUILayout.Button("Log config json"))
                {
                    SetTrailConfigs(saber);

                    Debug.Log(JsonUtility.ToJson(saber.config, true));
                }
            }
        }
    }
    /// <summary>
    /// Exports just a qbloq file
    /// </summary>
    /// <param name="bloq"></param>
    void ExportBloqFile(BloqReferences bloq, string path = "")
    {
        bool batch = path != "";

        GameObject bloqObject = bloq.gameObject;
        TextAsset  config     = new TextAsset(JsonUtility.ToJson(bloq.config, true));

        //string bmbfmodJson = JsonUtility.ToJson(bloq.bmbfmod, true);
        //TextAsset bmbfmod = new TextAsset(bmbfmodJson);

        string    descriptorString = JsonUtility.ToJson(bloq.descriptor, true);
        TextAsset descriptor       = new TextAsset(descriptorString);

        string bloqName = bloq.descriptor.objectName + "." + extension;

        if (!batch)
        {
            path = EditorUtility.SaveFilePanel("Save " + extension + " file", "", bloqName, extension);
        }
        else
        {
            path += bloqName;
        }

        if (path == "")
        {
            EditorUtility.DisplayDialog("Exportation Failed!", "Invalid path", "OK");
            return;
        }

        string fileName   = Path.GetFileName(path);
        string folderPath = Path.GetDirectoryName(path);

        string workingDir = Application.temporaryCachePath + "/QuestBloq";

        if (File.Exists(path))
        {
            File.Delete(path);
        }
        if (!Directory.Exists(workingDir))
        {
            Directory.CreateDirectory(workingDir);
        }

        if (exporterConfig.removeCamerasAtExport)
        {
            QosmeticUtils.PurgeCameras(bloqObject);
        }
        if (exporterConfig.removeLightsAtExport)
        {
            QosmeticUtils.PurgeLights(bloqObject);
        }

        Selection.activeObject = bloqObject;
        EditorUtility.SetDirty(bloq);
        EditorSceneManager.MarkSceneDirty(bloqObject.scene);
        EditorSceneManager.SaveScene(bloqObject.scene);

        GameObject camObject;
        Camera     exportCam;

        if (exporterConfig.allowOwnCamera) // manual cam
        {
            camObject = cam.gameObject;
            exportCam = cam;
        }
        else // automatic cam:
        {
            camObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
            exportCam = camObject.AddComponent <Camera>();
            exportCam.gameObject.name         = "Camera";
            exportCam.transform.localPosition = new Vector3(2.2f, 2.6f, -6.4f);
            exportCam.transform.rotation      = Quaternion.Euler(20.0f, -20.0f, 0.0f);
            exportCam.clearFlags      = CameraClearFlags.SolidColor;
            exportCam.backgroundColor = new Color(0.0f, 0.0f, 25.0f / 256.0f);
        }
        exportCam.nearClipPlane = 0.01f;

        int oldCamMask    = exportCam.cullingMask;
        int oldObjectMask = bloq.gameObject.layer;
        int newMask       = 31;
        int newCamMask    = 1 << 31;

        // moved prefab forward so that the layermask isnt f****d up on the actual exported file
        PrefabUtility.SaveAsPrefabAsset(Selection.activeGameObject as GameObject, "Assets/" + prefabName + ".prefab");

        if (exporterConfig.setCamLayerOnExport)
        {
            exportCam.cullingMask = newCamMask;
            QosmeticUtils.SetLayerRecursively(exportCam.gameObject, newMask);
            QosmeticUtils.SetLayerRecursively(bloq.gameObject, newMask);
        }


        Texture2D thumbnail = bloq.thumbnail;

        if (!thumbnail)
        {
            thumbnail = QosmeticUtils.getTexture2D(exportCam, 1024, 1024);
        }


        //Start actually constructing the bundle

        AssetDatabase.CreateAsset(config, "Assets/config.asset");
        //AssetDatabase.CreateAsset(bmbfmod, "Assets/BMBFmod.asset");
        AssetDatabase.CreateAsset(thumbnail, "Assets/thumbnail.asset");
        AssetDatabase.CreateAsset(descriptor, "Assets/descriptor.asset");

        AssetBundleBuild assetBundleBuild = default(AssetBundleBuild);

        assetBundleBuild.assetNames = new string[]
        {
            "Assets/" + prefabName + ".prefab",
            "Assets/config.asset",
            //"Assets/BMBFmod.asset",
            "Assets/thumbnail.asset",
            "Assets/descriptor.asset"
        };

        assetBundleBuild.assetBundleName = fileName;

        BuildTargetGroup selectedBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
        BuildTarget      activeBuildTarget        = EditorUserBuildSettings.activeBuildTarget;

        //Build Bundle
        BuildPipeline.BuildAssetBundles(workingDir, new AssetBundleBuild[] { assetBundleBuild }, 0, BuildTarget.Android);
        EditorPrefs.SetString("currentBuildingAssetBundlePath", folderPath);
        EditorUserBuildSettings.SwitchActiveBuildTarget(selectedBuildTargetGroup, activeBuildTarget);

        //Asset cleanup
        AssetDatabase.DeleteAsset("Assets/" + prefabName + ".prefab");
        AssetDatabase.DeleteAsset("Assets/config.asset");
        //AssetDatabase.DeleteAsset("Assets/BMBFmod.asset");
        AssetDatabase.DeleteAsset("Assets/thumbnail.asset");
        AssetDatabase.DeleteAsset("Assets/descriptor.asset");
        if (!exporterConfig.allowOwnCamera)
        {
            DestroyImmediate(camObject);
        }

        File.Move(workingDir + "/" + fileName, path);
        AssetDatabase.Refresh();

        clearTemp(workingDir);

        if (exporterConfig.setCamLayerOnExport)
        {
            if (exporterConfig.allowOwnCamera)
            {
                exportCam.cullingMask = oldCamMask;
            }
            if (exporterConfig.allowOwnCamera)
            {
                QosmeticUtils.SetLayerRecursively(exportCam.gameObject, oldObjectMask);
            }
            QosmeticUtils.SetLayerRecursively(bloq.gameObject, oldObjectMask);
        }

        if (!batch)
        {
            EditorUtility.DisplayDialog("Exportation Successful!", "Exportation Successful!", "OK");
            EditorUtility.RevealInFinder(path);
        }
    }
    void OnGUI()
    {
        GUILayout.BeginHorizontal();

        GUILayout.Label("Quest Bloqs", EditorStyles.boldLabel);
        if (GUILayout.Button("Go to Bloq scene"))
        {
            EditorSceneManager.OpenScene("Assets/scenes/questnotes.unity");
            OnFocus();
        }

        if (GUILayout.Button("Add New Bloq Template"))
        {
            AddBloqTemplate();
        }

        GUILayout.EndHorizontal();

        GUILayout.Space(5);
        if (GUILayout.Button("Export all Active bloqs"))
        {
            ExportAll();
        }
        if (GUILayout.Button("Export and Upload all Active bloqs"))
        {
            ExportAllAndUpload();
        }

        GUILayout.Space(10);
        foreach (var bloq in questbloqs)
        {
            if (bloq == null)
            {
                continue;
            }
            GUILayout.Label("GameObject : " + bloq.gameObject.name, EditorStyles.boldLabel);
            bloq.descriptorFolded = EditorGUILayout.BeginFoldoutHeaderGroup(bloq.descriptorFolded, "Bloq Descriptor");

            if (bloq.descriptorFolded)
            {
                //bloq.advancedInfo = EditorGUILayout.ToggleLeft("Advanced BMBF mod info", bloq.advancedInfo);
                GUILayout.Space(5);
                DescriptorDisplay(bloq);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            GUILayout.Space(5);

            bloq.configFolded = EditorGUILayout.BeginFoldoutHeaderGroup(bloq.configFolded, "Bloq config");
            if (bloq.configFolded)
            {
                BloqConfigDisplay(bloq);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            EditorGUILayout.Space(10);
            bool disableExport = shouldDisableExport(bloq);
            if (disableExport && exporterConfig.forceAllowExport)
            {
                GUILayout.Label("WARNING! force allow export is set to true so this bloq file is unlikely to work correctly in game!", EditorStyles.boldLabel);
            }
            EditorGUI.BeginDisabledGroup(disableExport && !exporterConfig.forceAllowExport);
            if (GUILayout.Button("Export " + bloq.descriptor.objectName + "." + extension))
            {
                GetHasBomb(bloq);
                GetHasDebris(bloq);
                ExportBloqFile(bloq);
            }

            if (GUILayout.Button("Export and Upload " + bloq.descriptor.objectName + "." + extension))
            {
                GetHasBomb(bloq);
                GetHasDebris(bloq);
                ExportAndUpload(bloq);
            }
            EditorGUI.EndDisabledGroup();


            foreach (Transform child in bloq.transform)
            {
                if (QosmeticUtils.GetObjectBounds(child.gameObject).extents.x * 2.0 > 1.4)
                {
                    GUILayout.Label(child.gameObject.name + " might be too wide (x)", EditorStyles.boldLabel);
                }

                if (QosmeticUtils.GetObjectBounds(child.gameObject).extents.y * 2.0 > 1.4)
                {
                    GUILayout.Label(child.gameObject.name + " might be too tall (y)", EditorStyles.boldLabel);
                }

                if (QosmeticUtils.GetObjectBounds(child.gameObject).extents.z * 2.0 > 1.4)
                {
                    GUILayout.Label(child.gameObject.name + " might be too long (z)", EditorStyles.boldLabel);
                }
            }
        }
    }