├── .gitattributes ├── .github └── workflows │ ├── release.yml │ └── update-upm.yml ├── .gitignore ├── .npmrc ├── .releaserc.json ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Editor.meta ├── Editor ├── GridWindow.cs ├── GridWindow.cs.meta ├── IModelDownloader.cs ├── IModelDownloader.cs.meta ├── ModelDescriptor.cs ├── ModelDescriptor.cs.meta ├── ModelDownloader.cs ├── ModelDownloader.cs.meta ├── SketchfabBrowser.Editor.asmdef ├── SketchfabBrowser.Editor.asmdef.meta ├── SketchfabBrowser.cs ├── SketchfabBrowser.cs.meta ├── res.meta └── res │ ├── sketchfab-logo.png │ └── sketchfab-logo.png.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── package.json ├── package.json.meta ├── res.meta └── res ├── model-browser.png └── model-browser.png.meta /.gitattributes: -------------------------------------------------------------------------------- 1 | ## Unity ## 2 | *.cs diff=csharp text 3 | *.cginc text 4 | *.shader text 5 | *.mat merge=unityyamlmerge eol=lf 6 | *.anim merge=unityyamlmerge eol=lf 7 | *.unity merge=unityyamlmerge eol=lf 8 | *.prefab merge=unityyamlmerge eol=lf 9 | *.physicsMaterial2D merge=unityyamlmerge eol=lf 10 | *.physicMaterial merge=unityyamlmerge eol=lf 11 | *.asset merge=unityyamlmerge eol=lf 12 | *.meta merge=unityyamlmerge eol=lf 13 | *.controller merge=unityyamlmerge eol=lf 14 | ## git-lfs ## 15 | #Image 16 | *.jpg filter=lfs diff=lfs merge=lfs -text 17 | *.jpeg filter=lfs diff=lfs merge=lfs -text 18 | *.png filter=lfs diff=lfs merge=lfs -text 19 | *.gif filter=lfs diff=lfs merge=lfs -text 20 | *.psd filter=lfs diff=lfs merge=lfs -text 21 | *.ai filter=lfs diff=lfs merge=lfs -text 22 | *.exr filter=lfs diff=lfs merge=lfs -text 23 | *.tga filter=lfs diff=lfs merge=lfs -text 24 | *.aif filter=lfs diff=lfs merge=lfs -text 25 | *.ttf filter=lfs diff=lfs merge=lfs -text 26 | *.tiff filter=lfs diff=lfs merge=lfs -text 27 | *.tif filter=lfs diff=lfs merge=lfs -text 28 | #Audio 29 | *.mp3 filter=lfs diff=lfs merge=lfs -text 30 | *.wav filter=lfs diff=lfs merge=lfs -text 31 | *.ogg filter=lfs diff=lfs merge=lfs -text 32 | #Video 33 | *.mp4 filter=lfs diff=lfs merge=lfs -text 34 | *.mov filter=lfs diff=lfs merge=lfs -text 35 | #3D Object 36 | *.FBX filter=lfs diff=lfs merge=lfs -text 37 | *.fbx filter=lfs diff=lfs merge=lfs -text 38 | *.blend filter=lfs diff=lfs merge=lfs -text 39 | *.obj filter=lfs diff=lfs merge=lfs -text 40 | # Libraries 41 | *.a filter=lfs diff=lfs merge=lfs -text 42 | *.aar filter=lfs diff=lfs merge=lfs -text 43 | *.dll filter=lfs diff=lfs merge=lfs -text 44 | *.so filter=lfs diff=lfs merge=lfs -text 45 | *.jar filter=lfs diff=lfs merge=lfs -text 46 | *.metalib filter=lfs diff=lfs merge=lfs -text 47 | #ETC 48 | *.pdf filter=lfs diff=lfs merge=lfs -text 49 | *.zip filter=lfs diff=lfs merge=lfs -text 50 | *.unitypackage filter=lfs diff=lfs merge=lfs -text 51 | *.rns filter=lfs diff=lfs merge=lfs -text 52 | *.reason filter=lfs diff=lfs merge=lfs -text 53 | *.lxo filter=lfs diff=lfs merge=lfs -text 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 📦 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | release: 9 | name: release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | - name: Release 16 | uses: cycjimmy/semantic-release-action@v3 17 | with: 18 | extra_plugins: | 19 | @semantic-release/changelog 20 | @semantic-release/git 21 | branch: main 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/update-upm.yml: -------------------------------------------------------------------------------- 1 | name: Publish 🚀 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | 9 | jobs: 10 | publish: 11 | name: Publish to OpenUPM 🚀 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: openupm/openupm-cli@v1.* 16 | with: 17 | args: publish -u ${{ secrets.NPM_USERNAME }} -p ${{ secrets.NPM_PASSWORD }} -e ${{ secrets.NPM_EMAIL }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Tt]emp/ 6 | /[Oo]bj/ 7 | /[Bb]uild/ 8 | /[Bb]uilds/ 9 | /[Ll]ogs/ 10 | /[Mm]emoryCaptures/ 11 | /[Pp]lugins/MultiPlay/ 12 | /[Uu]ser[Ss]ettings/ 13 | /[Aa]ssets/TextMesh*Pro/ 14 | 15 | # Asset meta data should only be ignored when the corresponding asset is also ignored 16 | !/[Aa]ssets/**/*.meta 17 | 18 | # Uncomment this line if you wish to ignore the asset store tools plugin 19 | # /[Aa]ssets/AssetStoreTools* 20 | 21 | # Autogenerated Jetbrains Rider plugin 22 | [Aa]ssets/Plugins/Editor/JetBrains* 23 | /.idea/ 24 | 25 | # Visual Studio cache directory 26 | .vs/ 27 | 28 | # Gradle cache directory 29 | .gradle/ 30 | 31 | # Autogenerated VS/MD/Consulo solution and project files 32 | ExportedObj/ 33 | .consulo/ 34 | *.csproj 35 | *.sln 36 | *.unityproj 37 | *.suo 38 | *.tmp 39 | *.user 40 | *.userprefs 41 | *.pidb 42 | *.booproj 43 | *.svd 44 | *.pdb 45 | *.mdb 46 | *.opendb 47 | *.VC.db 48 | 49 | # Unity3D generated meta files 50 | *.pidb.meta 51 | *.pdb.meta 52 | *.mdb.meta 53 | 54 | # Unity3D generated file on crash reports 55 | sysinfo.txt 56 | 57 | # Builds 58 | *.apk 59 | 60 | # Crashlytics generated file 61 | crashlytics-build.properties 62 | 63 | # Others 64 | *.bak 65 | *.idea 66 | *.vs 67 | Assets/TextMesh Pro/Examples & Extras/Resources/Fonts & Materials/Roboto-Bold SDF.asset 68 | Assets/TextMesh Pro/Examples & Extras/Resources/Fonts & Materials/Electronic Highway Sign SDF.asset 69 | Assets/TextMesh Pro/Examples & Extras/Resources/Fonts & Materials/Electronic Highway Sign SDF.asset 70 | 71 | Assets/[Ss]ketchfab Models/ 72 | Packages/com.unity.asset-store-tools/ 73 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=token_is_stored_locally @ ~/.npmrc 2 | @muammar-yacoob:registry=https://npm.pkg.github.com/ 3 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tagFormat": "v${version}", 3 | "plugins": [ 4 | ["@semantic-release/commit-analyzer", { "preset": "angular" }], 5 | "@semantic-release/release-notes-generator", 6 | ["@semantic-release/changelog", { "preset": "angular" }], 7 | ["@semantic-release/npm", { "npmPublish": false }], 8 | ["@semantic-release/git", { 9 | "assets": ["package.json", "CHANGELOG.md"], 10 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 11 | }], 12 | "@semantic-release/github" 13 | ] 14 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.0.1] - 2023-08-30 6 | 7 | ### Added 8 | 9 | - Initial release of the Unity Sketchfab Browser 10 | - Implementation of `IModelDownloader` for downloading Sketchfab models 11 | - `SketchfabBrowser` Editor window for browsing and searching models 12 | - Search functionality integrated into the Editor window 13 | - `GridPanel` for displaying search results 14 | - Asynchronous tasks using UniTask 15 | - Integrated Unity Package Manager (UPM) installation support -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c1d7a9b8c5f57a4e9a44ca51f26f094 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dd1e10725c748a442ad6ba88d221be30 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/GridWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace SparkGames.SketchfabBrowser.Editor 6 | { 7 | public class GridPanel 8 | { 9 | private bool showDetails; 10 | //if (searchThumbs == null || searchThumbs.Count == 0) return; 11 | 12 | int rowCount = 6; 13 | int columnCount = 2; 14 | 15 | float panelHeight = 150f; 16 | float padding = 2f; 17 | private GUIStyle hyperlinkStyle; 18 | private GUIStyle labelStyle; 19 | private Vector2 scrollPosition; 20 | 21 | public void Draw(float w, Model[] models, List searchThumbs) 22 | { 23 | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 24 | 25 | float panelWidth = (w - 100) / columnCount; 26 | Rect rect = new Rect(0, 0, panelWidth, 0); 27 | int thumbIndex = 0; 28 | for (int row = 0; row < rowCount; row++) 29 | { 30 | GUILayout.BeginHorizontal(); 31 | GUILayout.Space(padding); 32 | for (int col = 0; col < columnCount; col++) 33 | { 34 | if (thumbIndex >= searchThumbs.Count) 35 | { 36 | GUILayout.EndHorizontal(); 37 | EditorGUILayout.EndScrollView(); 38 | return; 39 | } 40 | 41 | var m = models[thumbIndex]; 42 | var icon = searchThumbs[thumbIndex].thumb; 43 | 44 | Rect panelRect = GUILayoutUtility.GetRect(panelWidth, panelHeight); 45 | 46 | labelStyle ??= new GUIStyle(GUI.skin.label) { normal = { textColor = Color.white } , alignment = TextAnchor.LowerCenter}; 47 | hyperlinkStyle ??= new GUIStyle(GUI.skin.label) { normal = { textColor = Color.cyan } , alignment = TextAnchor.LowerCenter}; 48 | 49 | // Background 50 | //EditorGUI.DrawRect(panelRect, Color.black); 51 | 52 | // Thumbnail 53 | Rect imageRect = new Rect(panelRect.x, panelRect.y, panelRect.width, panelHeight - 20f); 54 | 55 | if (GUI.Button(imageRect, GUIContent.none, GUIStyle.none)) 56 | { 57 | showDetails = !showDetails; 58 | } 59 | GUI.DrawTexture(imageRect, icon, ScaleMode.ScaleToFit); 60 | 61 | // Button 62 | Rect buttonRect = new Rect(panelRect.x, panelRect.y + panelRect.height - 20f, imageRect.width, 20f); 63 | 64 | // Description 65 | if (showDetails) 66 | { 67 | EditorGUI.DrawRect(panelRect, new Color(0,0,0,0.6f)); 68 | Rect labelRect = new Rect(panelRect.x, panelRect.y, panelRect.width, 20f); 69 | labelRect.y += (m.animationCount == 0) ? 25 : 15; 70 | 71 | GUI.Label(labelRect, m.name, labelStyle); 72 | labelRect.y += 20; 73 | GUI.Label(labelRect, m.license.label, labelStyle); 74 | labelRect.y += 20; 75 | GUI.Label(labelRect, "Vertices: "+m.vertexCount.ToString("N0"), labelStyle); 76 | 77 | if(m.animationCount > 0) 78 | { 79 | labelRect.y += 20; 80 | GUI.Label(labelRect, "Animations: " + m.animationCount, labelStyle); 81 | } 82 | 83 | if (GUI.Button(buttonRect, "More")) Application.OpenURL(m.viewerUrl); 84 | } 85 | else 86 | { 87 | if (m.IsDownloading == false) 88 | { 89 | 90 | string buttonText = m.price == 0 ? "Download" : $"Buy ${m.price}"; 91 | //GUI.enabled = !SketchfabBrowser.Instance.CurrentModel.IsDownloading; 92 | 93 | if(m.IsDownloaded) 94 | { 95 | buttonText = "Downloaded"; 96 | GUI.enabled = false; 97 | } 98 | if (GUI.Button(buttonRect, buttonText)) 99 | { 100 | _ = ModelDownloader.Instance.DownloadModel(m, onDownloadProgress: (p) => m.DownloadProgress = p); 101 | } 102 | GUI.enabled = true; 103 | } 104 | else 105 | { 106 | DrawProgressBar(m.DownloadProgress, buttonRect); 107 | } 108 | 109 | GUI.enabled = true; 110 | } 111 | 112 | thumbIndex++; 113 | } 114 | GUILayout.EndHorizontal(); 115 | rect.height += panelHeight; 116 | } 117 | EditorGUILayout.EndScrollView(); 118 | } 119 | 120 | 121 | private void DrawProgressBar(float percent, Rect buttonRect) 122 | { 123 | string percentString = (percent * 100).ToString("N0") + "%"; 124 | EditorGUI.ProgressBar(buttonRect, percent, percentString); 125 | //Debug.Log(percentString); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Editor/GridWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b64fc22ea4ffb114ca9a77e6c6bc0644 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/IModelDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Cysharp.Threading.Tasks; 3 | 4 | namespace SparkGames.SketchfabBrowser.Editor 5 | { 6 | public interface IModelDownloader 7 | { 8 | UniTask DownloadModel(Model model, Action onDownloadProgress = null); 9 | 10 | string ApiToken { get; } 11 | void SetToken(string apiToken); 12 | } 13 | } -------------------------------------------------------------------------------- /Editor/IModelDownloader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 692ea603c2edb6a4585fab22d9d71809 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ModelDescriptor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace SparkGames.SketchfabBrowser.Editor 4 | { 5 | [System.Serializable] 6 | public class PageModels 7 | { 8 | public Model[] results; 9 | public string next; 10 | public string previous; 11 | } 12 | 13 | [System.Serializable] 14 | public class Pagination 15 | { 16 | public string next; 17 | } 18 | 19 | [System.Serializable] 20 | public class Model 21 | { 22 | public string name; 23 | public string description; 24 | public string uri; // URL of the model's page 25 | public Thumbnails thumbnails; // URLs and images of the model's thumbnails 26 | public bool isDownloadable; 27 | public string viewerUrl; 28 | public int vertexCount; 29 | public int textureCount; 30 | public int materialCount; 31 | public int faceCount; 32 | public int animationCount; 33 | 34 | public float price; 35 | public string updatedAt; 36 | public License license; 37 | public string uid; 38 | public bool IsDownloading; 39 | public float DownloadProgress; 40 | public bool IsDownloaded; 41 | } 42 | 43 | [System.Serializable] 44 | public class Thumbnails 45 | { 46 | public Thumbnail[] images; 47 | 48 | [System.Serializable] 49 | public class Thumbnail 50 | { 51 | public string url; 52 | } 53 | } 54 | 55 | [System.Serializable] 56 | public class License 57 | { 58 | public string label; 59 | } 60 | 61 | [System.Serializable] 62 | public class AccountInfo 63 | { 64 | public string username; 65 | } 66 | 67 | [System.Serializable] 68 | public class ModelDownloadInfo 69 | { 70 | public ModelFormat glb; 71 | public ModelFormat gltf; 72 | public ModelFormat source; 73 | 74 | [System.Serializable] 75 | public class ModelFormat 76 | { 77 | public string url; 78 | public int size; 79 | public int expires; 80 | } 81 | } 82 | 83 | public enum ModelFormatExtension 84 | { 85 | gltf, 86 | glb 87 | } 88 | 89 | public class SearchThumb 90 | { 91 | public readonly string uid; 92 | public readonly Texture2D thumb; 93 | 94 | public SearchThumb(string modelUid, Texture2D texture2D) 95 | { 96 | uid = modelUid; 97 | thumb = texture2D; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Editor/ModelDescriptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46b1777801804ab4e998473fe75e2e04 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ModelDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Cysharp.Threading.Tasks; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using UnityEngine.Networking; 10 | using Object = UnityEngine.Object; 11 | 12 | namespace SparkGames.SketchfabBrowser.Editor 13 | { 14 | public class ModelDownloader : IModelDownloader 15 | { 16 | private string apiToken; 17 | private string downloadsFullPath; 18 | private string downloadsRelativePath; 19 | 20 | private Model currentModel; 21 | private const string SketchfabTokenKey = "SketchfabTokenKey"; 22 | private const string SketchfabDownloadsPath = "SketchfabDownloadsPath"; 23 | public string ApiToken => apiToken; 24 | 25 | private static ModelDownloader instance; 26 | public static ModelDownloader Instance => instance ??= new ModelDownloader(); 27 | 28 | public void SetDownloadPath() 29 | { 30 | apiToken = PlayerPrefs.GetString(SketchfabTokenKey, "your-sketchfab-api-token"); //https://sketchfab.com/settings/password 31 | downloadsFullPath = PlayerPrefs.GetString(SketchfabDownloadsPath, Path.Combine(Application.dataPath,"Sketchfab Models")); 32 | downloadsRelativePath = downloadsFullPath.Replace(Application.dataPath, "Assets"); 33 | Debug.Log($"Download path set to: {downloadsFullPath}"); 34 | } 35 | 36 | public void SetToken(string apiToken) 37 | { 38 | this.apiToken = apiToken; 39 | PlayerPrefs.SetString(SketchfabTokenKey, apiToken); 40 | PlayerPrefs.Save(); 41 | } 42 | 43 | public async UniTask DownloadModel(Model model, Action onDownloadProgress = null) 44 | { 45 | model.IsDownloading = true; 46 | currentModel = await DescribeModel(model.uid); 47 | Debug.Log($"Downloading {currentModel.name}..."); 48 | 49 | string downloadRequest = $"https://api.sketchfab.com/v3/models/{model.uid}/download"; 50 | using var request = UnityWebRequest.Get(downloadRequest); 51 | request.SetRequestHeader("Authorization", $"Token {apiToken}"); 52 | await request.SendWebRequest(); 53 | if (request.result == UnityWebRequest.Result.Success) 54 | { 55 | if (request.responseCode == 401) throw new UnauthorizedAccessException("Invalid API token"); 56 | 57 | var downloadInfo = JsonUtility.FromJson(request.downloadHandler.text); 58 | await DownloadModelFromUrl(downloadInfo.gltf.url, onDownloadProgress); 59 | model.IsDownloaded = true; 60 | } 61 | else 62 | { 63 | Debug.LogError("Error: " + request.error); 64 | } 65 | model.IsDownloading = false; 66 | } 67 | 68 | private async UniTask DescribeModel(string modelId) 69 | { 70 | string downloadUrl = $"https://api.sketchfab.com/v3/models/{modelId}"; 71 | using var request = UnityWebRequest.Get(downloadUrl); 72 | request.SetRequestHeader("Authorization", $"Token {apiToken}"); 73 | await request.SendWebRequest(); 74 | if (request.result == UnityWebRequest.Result.Success) 75 | { 76 | if (request.responseCode == 401) throw new UnauthorizedAccessException("Invalid API token"); 77 | 78 | var model = JsonUtility.FromJson(request.downloadHandler.text); 79 | return model; 80 | } 81 | else 82 | { 83 | Debug.LogError("Error: " + request.error); 84 | } 85 | 86 | return default; 87 | } 88 | 89 | private async UniTask DownloadModelFromUrl(string url, Action onDownloadProgress = null) 90 | { 91 | using var request = UnityWebRequest.Get(url); 92 | var operation = request.SendWebRequest(); 93 | 94 | while (!operation.isDone) 95 | { 96 | onDownloadProgress?.Invoke(request.downloadProgress); 97 | await UniTask.DelayFrame(1); 98 | } 99 | 100 | if (request.result == UnityWebRequest.Result.Success) 101 | { 102 | byte[] modelBytes = request.downloadHandler.data; 103 | 104 | string fileName = currentModel.name + $".zip"; 105 | 106 | Directory.CreateDirectory(downloadsFullPath); 107 | string savePath = Path.Combine(downloadsFullPath, fileName); 108 | File.WriteAllBytes(savePath, modelBytes); 109 | await Unzip(savePath); 110 | } 111 | else 112 | { 113 | Debug.LogError("Download failed. " + request.error); 114 | } 115 | } 116 | 117 | 118 | private async Task Unzip(string savePath, Action onUnpackProgress = null) 119 | { 120 | string unpackPath = $"{downloadsRelativePath}/{currentModel.name}"; 121 | if (Directory.Exists(unpackPath)) Directory.Delete(unpackPath, true); 122 | Directory.CreateDirectory(unpackPath); 123 | 124 | try 125 | { 126 | await UniTask.RunOnThreadPool(() => { ZipFile.ExtractToDirectory(savePath, unpackPath); }); 127 | //AssetDatabase.Refresh(); 128 | } 129 | catch (IOException e) 130 | { 131 | Debug.LogError("Failed to unzip the model: " + e.Message); 132 | } 133 | 134 | File.Delete(savePath); 135 | Debug.Log($"Model is downloaded to: " + savePath.Replace(".zip", "").Replace("\\", "/")); 136 | 137 | AssetDatabase.Refresh(); 138 | 139 | var targetFilePath = Directory.EnumerateFiles(unpackPath).FirstOrDefault(); 140 | if (targetFilePath != null) 141 | { 142 | var targetObject = AssetDatabase.LoadAssetAtPath(targetFilePath); 143 | if (targetObject != null) 144 | { 145 | Selection.activeObject = targetObject; 146 | EditorGUIUtility.PingObject(targetObject); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | -------------------------------------------------------------------------------- /Editor/ModelDownloader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1713ad0618574f244a7619c3ecae7bab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SketchfabBrowser.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SparkGames.Sketchfab.Editor", 3 | "rootNamespace": "SparkGames", 4 | "references": [ 5 | "GUID:f51ebe6a0ceec4240a699833d6309b23", 6 | "GUID:dfdc04dd4a53c5d4ba1e6ad417d35948" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Editor/SketchfabBrowser.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0325e9d71168d12439714297d7977c25 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/SketchfabBrowser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Cysharp.Threading.Tasks; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using UnityEngine.Networking; 8 | 9 | namespace SparkGames.SketchfabBrowser.Editor 10 | { 11 | public class SketchfabBrowser : EditorWindow 12 | { 13 | private string modelId = "564e02a97528499388ca00d3c6bdb044"; 14 | string searchKewordInputControl = "Cupcake"; 15 | 16 | private string apiToken; 17 | private bool connected; 18 | private string accountName; 19 | public Model CurrentModel = new Model(); 20 | private Texture2D windowIcon; 21 | private GUIStyle hyperlinkStyle; 22 | private Texture2D thumb; 23 | private bool moreInfo; 24 | private PageModels currentPageModels; 25 | private string searchKeyword; 26 | 27 | public PageModels pageModels; 28 | private List searchThumbs = new(); 29 | private bool isSearching; 30 | 31 | private Vector2 scrollPosition; 32 | private GridPanel panelDrawer; 33 | 34 | 35 | 36 | [MenuItem("Assets/Sketchfab Browser")] 37 | public static void ShowWindow() 38 | { 39 | SketchfabBrowser window = GetWindow(); 40 | window.titleContent = new GUIContent("Sketchfab Browser", window.windowIcon); 41 | window.minSize = new Vector2(500, 500); 42 | window.maxSize = new Vector2(1000, 1000); 43 | 44 | } 45 | 46 | private void OnEnable() 47 | { 48 | windowIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/Package/Editor/res/sketchfab-logo.png", typeof(Texture2D)); 49 | titleContent.image = windowIcon; 50 | panelDrawer = new GridPanel(); 51 | apiToken = ModelDownloader.Instance?.ApiToken; 52 | } 53 | 54 | private void OnGUI() 55 | { 56 | if (Application.isPlaying) 57 | { 58 | EditorGUILayout.HelpBox("Unavailable in play mode.", MessageType.Error); 59 | return; 60 | } 61 | 62 | if (!connected) 63 | { 64 | DrawConnectionUI(); 65 | } 66 | else 67 | { 68 | 69 | EditorGUILayout.HelpBox($"Connected as {accountName}",MessageType.Info); 70 | //GUILayout.Label($"Connected as {accountName}", EditorStyles.boldLabel); 71 | 72 | DrawSearchUI(); 73 | 74 | if (pageModels?.results?.Length > 0 && !isSearching) 75 | { 76 | DisplayResults(); 77 | return; 78 | } 79 | } 80 | } 81 | 82 | 83 | private void DrawSearchUI() 84 | { 85 | GUILayout.Space(20); 86 | 87 | GUILayout.BeginHorizontal(); 88 | 89 | GUILayout.Label("Search Keyword:", GUILayout.Width(Screen.width * 0.2f)); 90 | GUI.SetNextControlName(searchKewordInputControl); 91 | searchKeyword = EditorGUILayout.TextField(searchKeyword, GUILayout.Width(Screen.width * 0.56f)); 92 | 93 | bool canSearch = !isSearching && !string.IsNullOrEmpty(searchKeyword); 94 | 95 | using (new EditorGUI.DisabledScope(!canSearch)) 96 | { 97 | if (GUILayout.Button("Search", GUILayout.Width(Screen.width * 0.2f)) || 98 | GUI.GetNameOfFocusedControl() == searchKewordInputControl && 99 | Event.current.keyCode == KeyCode.Return) 100 | { 101 | Search24(searchKeyword).Forget(); 102 | } 103 | GUILayout.Space(2); 104 | } 105 | 106 | GUILayout.EndHorizontal(); 107 | 108 | 109 | bool canNavigatePages = !isSearching && pageModels != null; 110 | 111 | if (canNavigatePages) 112 | { 113 | GUILayout.BeginHorizontal(); 114 | 115 | if(string.IsNullOrEmpty(pageModels?.previous)) GUI.enabled = false; 116 | if (GUILayout.Button("Previous",GUILayout.Width(Screen.width * 0.2f))) 117 | { 118 | Search24(searchKeyword, pageModels?.previous).Forget(); 119 | } 120 | 121 | GUI.enabled = true; 122 | //GUILayout.Label("", GUILayout.Width(Screen.width * 0.6f)); 123 | GUILayout.FlexibleSpace(); 124 | 125 | if(string.IsNullOrEmpty(pageModels?.next)) GUI.enabled = false; 126 | if (GUILayout.Button("Next",GUILayout.Width(Screen.width * 0.2f))) 127 | { 128 | Search24(searchKeyword, after: pageModels?.next).Forget(); 129 | } 130 | GUILayout.Space(2); 131 | GUI.enabled = true; 132 | GUILayout.EndHorizontal(); 133 | } 134 | 135 | if (pageModels != null && !isSearching) 136 | { 137 | string resultMsg = pageModels.results.Length < 24 ? $"{pageModels.results.Length} models found." : "Showing 24 models."; 138 | GUILayout.Label(resultMsg, EditorStyles.boldLabel); 139 | } 140 | else if (isSearching) 141 | { 142 | GUILayout.Label("Searching...", EditorStyles.boldLabel); 143 | } 144 | else if (pageModels == null) 145 | { 146 | EditorGUILayout.HelpBox($"No models found with the keyword {searchKeyword}.",MessageType.Info); 147 | 148 | } 149 | 150 | if (Event.current.type == EventType.Repaint) GUI.FocusControl(searchKewordInputControl); 151 | } 152 | 153 | 154 | 155 | private void DisplayResults() 156 | { 157 | //scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); 158 | panelDrawer.Draw(position.width, pageModels.results, searchThumbs); 159 | //EditorGUILayout.EndScrollView(); 160 | } 161 | 162 | 163 | 164 | private void DrawConnectionUI() 165 | { 166 | hyperlinkStyle ??= new GUIStyle(GUI.skin.label) { normal = { textColor = Color.cyan } }; 167 | 168 | string tokenFieldName = "apiTokenField"; 169 | GUI.SetNextControlName(tokenFieldName); 170 | apiToken = EditorGUILayout.TextField("API Token", apiToken); 171 | 172 | if (GUILayout.Button("Get your API Token", hyperlinkStyle)) 173 | { 174 | Application.OpenURL("https://sketchfab.com/settings/password"); 175 | } 176 | 177 | if (string.IsNullOrEmpty(apiToken) || apiToken.Length != 32) 178 | { 179 | GUI.enabled = false; 180 | } 181 | 182 | if (GUILayout.Button("Connect to Sketchfab")) 183 | { 184 | GUI.FocusControl(null); 185 | ConnectToSketchfab().Forget(); 186 | } 187 | 188 | GUI.enabled = true; 189 | 190 | if (GUI.GetNameOfFocusedControl() == tokenFieldName && Event.current.keyCode == KeyCode.Return) 191 | { 192 | GUI.FocusControl(null); 193 | ConnectToSketchfab().Forget(); 194 | } 195 | } 196 | 197 | private async UniTaskVoid GetThumbnail() 198 | { 199 | thumb = await DownloadImage(CurrentModel.thumbnails.images[0].url); 200 | Repaint(); 201 | } 202 | 203 | async UniTaskVoid ConnectToSketchfab() 204 | { 205 | await Connect(apiToken); 206 | CurrentModel = null; 207 | } 208 | 209 | async UniTask Connect(string token) 210 | { 211 | string url = "https://api.sketchfab.com/v3/me"; 212 | using (var request = UnityWebRequest.Get(url)) 213 | { 214 | request.SetRequestHeader("Authorization", $"Token {token}"); 215 | try 216 | { 217 | await request.SendWebRequest(); 218 | if (request.result == UnityWebRequest.Result.Success) 219 | { 220 | connected = true; 221 | AccountInfo accountInfo = JsonUtility.FromJson(request.downloadHandler.text); 222 | accountName = accountInfo.username; 223 | Repaint(); 224 | 225 | ModelDownloader.Instance.SetToken(token); 226 | ModelDownloader.Instance.SetDownloadPath(); 227 | } 228 | else 229 | { 230 | Debug.LogError("Failed to connect to Sketchfab: " + request.error); 231 | } 232 | } 233 | catch (Exception) 234 | { 235 | Debug.LogError("Failed to connect to Sketchfab: " + request.error); 236 | } 237 | } 238 | } 239 | 240 | 241 | async UniTaskVoid FetchInfo() => await FetchModelInfo(modelId); 242 | //async UniTaskVoid Download() => await DownloadModel(modelId, CurrentModel.name); 243 | 244 | async UniTask Search24(string keyword, string after = null) 245 | { 246 | if (isSearching) return; 247 | isSearching = true; 248 | GUI.FocusControl(null); 249 | 250 | keyword = keyword.Trim().Replace(" ", ","); 251 | string searchRequest = 252 | $"https://api.sketchfab.com/v3/search?type=models&downloadable=true&purchasable=true&tags={keyword}&name={keyword}&description={keyword}&sort_by=-likeCount&per_page=24&after={after}"; 253 | using (var request = UnityWebRequest.Get(searchRequest)) 254 | { 255 | request.SetRequestHeader("Authorization", $"Token {apiToken}"); 256 | await request.SendWebRequest(); 257 | 258 | if (request.result != UnityWebRequest.Result.Success) 259 | { 260 | Debug.LogError("Error: " + request.error); 261 | isSearching = false; 262 | return; 263 | } 264 | 265 | pageModels = JsonUtility.FromJson(request.downloadHandler.text); 266 | //Debug.Log($"Search Finished with {pageModels.results.Length} results!"); 267 | GUI.FocusControl(searchKeyword); 268 | isSearching = false; 269 | await LoadSearchThumbs(); 270 | } 271 | 272 | isSearching = false; 273 | Repaint(); 274 | } 275 | 276 | private async Task LoadSearchThumbs() 277 | { 278 | if (pageModels.results.Length > 0) 279 | { 280 | searchThumbs.Clear(); 281 | foreach (var model in pageModels.results) 282 | { 283 | if (model.thumbnails.images.Length > 2) 284 | { 285 | var thumb = await DownloadImage(model.thumbnails.images[3].url); 286 | if (thumb != null) 287 | { 288 | searchThumbs.Add(new SearchThumb(model.uid, thumb)); 289 | } 290 | else 291 | { 292 | Debug.LogError("Failed to download thumbnail for model: " + model.uid); 293 | } 294 | } 295 | } 296 | } 297 | 298 | isSearching = false; 299 | } 300 | 301 | 302 | public async UniTask FetchModelInfo(string modelId) 303 | { 304 | string modelInfoUrl = $"https://api.sketchfab.com/v3/models/{modelId}"; 305 | using (var request = UnityWebRequest.Get(modelInfoUrl)) 306 | { 307 | await request.SendWebRequest(); 308 | 309 | if (request.result == UnityWebRequest.Result.Success) 310 | { 311 | thumb = null; 312 | CurrentModel = JsonUtility.FromJson(request.downloadHandler.text); 313 | } 314 | else 315 | { 316 | Debug.LogError("Failed to fetch model metadata: " + request.error); 317 | } 318 | } 319 | } 320 | 321 | async UniTask DownloadImage(string url) 322 | { 323 | if (!string.IsNullOrEmpty(url)) 324 | { 325 | using (var request = UnityWebRequestTexture.GetTexture(url)) 326 | { 327 | await request.SendWebRequest(); 328 | if (request.result == UnityWebRequest.Result.Success) 329 | { 330 | return DownloadHandlerTexture.GetContent(request); 331 | } 332 | else 333 | { 334 | Debug.LogError("Failed to download image: " + request.error); 335 | return null; 336 | } 337 | } 338 | } 339 | else 340 | { 341 | Debug.LogError("Thumbnail URL is empty or null."); 342 | return default; 343 | } 344 | } 345 | } 346 | } -------------------------------------------------------------------------------- /Editor/SketchfabBrowser.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6cd9fd77774f86542bdff8d0942fe5f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/res.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd351e717d58ca04181a1b63d14f9dff 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/res/sketchfab-logo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:89cf6f9170f0223b1d2e3603951c3372de7c50edb4ea5e4541dfa01de0020551 3 | size 16283 4 | -------------------------------------------------------------------------------- /Editor/res/sketchfab-logo.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f9e16b26fe8953543bafe1524740bbe5 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 0 41 | wrapV: 0 42 | wrapW: 0 43 | nPOTScale: 1 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 0 56 | spriteTessellationDetail: -1 57 | textureType: 0 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: Server 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | spriteSheet: 110 | serializedVersion: 2 111 | sprites: [] 112 | outline: [] 113 | physicsShape: [] 114 | bones: [] 115 | spriteID: 116 | internalID: 0 117 | vertices: [] 118 | indices: 119 | edges: [] 120 | weights: [] 121 | secondaryTextures: [] 122 | nameFileIdTable: {} 123 | mipmapLimitGroupName: 124 | pSDRemoveMatte: 0 125 | userData: 126 | assetBundleName: 127 | assetBundleVariant: 128 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Muammar Yacoob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c03828b7b9a60c41b0373f8533ce5eb 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Sketchfab Browser 2 | 3 | ## Overview 4 | The Unity Sketchfab Browser is a Unity Editor extension that allows developers to search, preview, and import 3D models from Sketchfab directly without leaving the Unity Editor. 5 | 6 | ## Features 7 | - **Search Functionality**: Quickly find models based on keywords. 8 | - **Preview**: View detailed previews of models before importing. 9 | - **Asynchronous Tasks**: Supports simultaneous downloads. 10 | 11 | ## Installation ## 12 | In Unity Package Manager (UPM) Add Package from git URL:
13 | https://github.com/muammar-yacoob/unity-sketchfab-browser.git

14 | Or get the latest [Unity Package Installer](../../releases)
15 | 16 | After installation, you will see the Sketchfab browser option under `[Assets > Sketchfab Browser]` in the Unity editor top menu bar. 17 | 18 | ### Model Browser 19 | [![Model Inspector](./res/model-browser.png)] 20 | 21 | ## Usage 22 | 1. Open the Sketchfab Browser from `Assets > Sketchfab Browser`. 23 | 2. Enter your Sketchfab API token when prompted. 24 | 3. Use the search bar to find models by keyword. 25 | 4. Browse through models and click on them to see more details. 26 | 5. Click `Download` or `Buy` to add the model to your Unity project. 27 | 28 | ## Support and Contribution 29 | Found this useful? A quick ⭐️ is much appreciated and could help you and others find the repo easier. 30 | For issues, feature requests, or contributions, please open an issue or pull request on GitHub. 31 | Here's a quick overview -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bafd79d42b99f4c41855af639560eabb 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.sparkgames.sketchfabbrowser", 3 | "version": "1.0.1", 4 | "displayName": "Sketchfab Browser", 5 | "description": "Browse & Fetch models from Sketchfab.com", 6 | "unity": "2020.1", 7 | "keywords": ["Sketchfab","Unity","Model","3D", "Browser"], 8 | "author": { 9 | "name": "Muammar Yacoob", 10 | "email": "muammar.yacoob@gmail.com", 11 | "url": "https://panettonegames.com" 12 | }, 13 | "license": "MIT", 14 | "repository": { 15 | "url": "https://github.com/muammar-yacoob/Unity-Sketchfab-Browser.git" 16 | }, 17 | "dependencies": { 18 | "com.unity.modules.jsonserialize": "1.0.0", 19 | "com.unity.modules.unitywebrequest": "1.0.0", 20 | "com.cysharp.unitask": "2.3.1", 21 | "com.atteneder.gltfast": "5.0.4" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8d2e75389d93d949b65b990e1d07ac0 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /res.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d8f55ab85973994096fa5f94694f0b6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /res/model-browser.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d5a1e17a687e306bafb895d0761526961721c8a71a01baf9f59062876e8205d5 3 | size 310668 4 | -------------------------------------------------------------------------------- /res/model-browser.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6cb10b053671f394d9e63505f3148fee 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 0 41 | wrapV: 0 42 | wrapW: 0 43 | nPOTScale: 1 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 0 56 | spriteTessellationDetail: -1 57 | textureType: 0 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: Server 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | spriteSheet: 110 | serializedVersion: 2 111 | sprites: [] 112 | outline: [] 113 | physicsShape: [] 114 | bones: [] 115 | spriteID: 116 | internalID: 0 117 | vertices: [] 118 | indices: 119 | edges: [] 120 | weights: [] 121 | secondaryTextures: [] 122 | nameFileIdTable: {} 123 | mipmapLimitGroupName: 124 | pSDRemoveMatte: 0 125 | userData: 126 | assetBundleName: 127 | assetBundleVariant: 128 | --------------------------------------------------------------------------------