├── requirements.txt ├── TextureBrowser ├── Resources │ └── icon128.png ├── TextureBrowser.uplugin └── Content │ └── Python │ ├── init_unreal.py │ └── texture_browser.py ├── README.md └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | unreal-qt 2 | -------------------------------------------------------------------------------- /TextureBrowser/Resources/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hannesdelbeke/texture-browser-unreal-plugin/HEAD/TextureBrowser/Resources/icon128.png -------------------------------------------------------------------------------- /TextureBrowser/TextureBrowser.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "0.1", 5 | "FriendlyName": "TextureBrowser", 6 | "Description": "Browser textures in Unreal, e.g. search for icons included in the Unreal installation", 7 | "Category": "tool", 8 | "CreatedBy": "Hannes", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": true, 15 | "IsExperimentalVersion": true, 16 | "Installed": false, 17 | "Plugins": [ 18 | { 19 | "Name": "PythonScriptPlugin", 20 | "Enabled": true 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /TextureBrowser/Content/Python/init_unreal.py: -------------------------------------------------------------------------------- 1 | # code in init_unreal.py wil run on startup if the plugin is enabled 2 | import unreal 3 | 4 | 5 | def create_script_editor_button(): 6 | """Add a tool button to the tool bar""" 7 | 8 | section_name = 'Plugins' 9 | se_command = 'import texture_browser;texture_browser.show()' # todo replace with your code 10 | label = 'texture browser' 11 | tooltip = "browser textures in unreal" 12 | 13 | menus = unreal.ToolMenus.get() 14 | level_menu_bar = menus.find_menu('LevelEditor.LevelEditorToolBar.PlayToolBar') 15 | level_menu_bar.add_section(section_name=section_name, label=section_name) 16 | 17 | entry = unreal.ToolMenuEntry(type=unreal.MultiBlockType.TOOL_BAR_BUTTON) 18 | entry.set_label(label) 19 | entry.set_tool_tip(tooltip) 20 | entry.set_icon('EditorStyle', 'Symbols.SearchGlass') 21 | entry.set_string_command( 22 | type=unreal.ToolMenuStringCommandType.PYTHON, 23 | custom_type=unreal.Name(''), # not sure what this is 24 | string=se_command 25 | ) 26 | level_menu_bar.add_menu_entry(section_name, entry) 27 | menus.refresh_all_widgets() 28 | 29 | 30 | create_script_editor_button() 31 | ## TODO add code to add to menu 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Engine resource browser 2 | 3 | | dark style | default style | 4 | | -- | -- | 5 | | | | 6 | 7 | Browse the Unreal installation folder for default resources (icons, shapes, fonts) to use in your qt-tools. 8 | - Filter by file type 9 | - Search by name 10 | - Copy the path 11 | - Pure Python, no compiling, code also works outside Unreal. 12 | 13 | ## Install 14 | 15 | ### Install using Plugget (recommended) 16 | Plugget automatically installs this tool & it's dependencies in the correct location. 17 | - Install the [plugget unreal](https://github.com/plugget/plugget-unreal) plugin 18 | - Search texture browser and click install. 19 | - restart Unreal 20 | 21 | ### Install manuallly 22 | - pip install the Python dependencies to `...\MyProject\Content\Python\Lib\site-packages` (you can use `--target` with pip install) 23 | - PySide2 24 | - unreal-qt (optional) 25 | - Just copy the folder in your Unreal project's plugin folder `...\MyProject\Plugins\texture-browser` 26 | - Restart Unreal 27 | 28 | ## Dark mode 29 | To not worry about QApp managegement, and automatically style in Unreal dark mode, you can use [unreal_qt](https://github.com/hannesdelbeke/unreal_qt) 30 | 31 | ### Similar projects 32 | GitHub repos: 33 | - [SlateIconBrowser](https://github.com/sirjofri/SlateIconBrowser) C++ icon browser, requires compiling 34 | - [unreal-engine-editor-icons](https://github.com/EpicKiwi/unreal-engine-editor-icons) thumnails & names of all editor icons in your browser 35 | - [maya-qt-img-resource-browser](https://github.com/leocov-dev/maya-qt-img-resource-browser) Similar, but for Maya 36 | 37 | Reference 38 | - Unreal's [content browser docs](https://docs.unrealengine.com/4.26/en-US/Basics/ContentBrowser/UI/) are a good reference 39 | 40 | 41 | ## community 42 | - Unreal forum [thread](https://forums.unrealengine.com/t/free-icon-font-browser-plugin/1215762) 43 | 44 | If this tool is helpfull give it a ⭐ on the [GitHub](https://github.com/hannesdelbeke/texture-browser-unreal-plugin) page at the top right🙏 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /TextureBrowser/Content/Python/texture_browser.py: -------------------------------------------------------------------------------- 1 | """ 2 | an icon browser for the unreal engine project resources 3 | """ 4 | from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools 5 | from PySide2.QtWidgets import QApplication, QLabel, QDialog, QWidget, QProgressBar 6 | from pathlib import Path 7 | 8 | 9 | # create a widget showing all icons in the icon folder 10 | class IconWidget(QWidget): 11 | def __init__(self, *args): 12 | super(IconWidget, self).__init__(*args) 13 | 14 | layout = QtWidgets.QVBoxLayout(self) 15 | 16 | # ' tabs 17 | self.tab_widget = QtWidgets.QTabWidget() 18 | image_types = ["png", "bmp", "svg", "tps", "ttf"] 19 | 20 | self.lists = [] 21 | for img_type in image_types: 22 | thumbnail_list = QtWidgets.QListWidget(self) 23 | thumbnail_list.setResizeMode(QtWidgets.QListWidget.Adjust) 24 | if img_type not in ["tps", "ttf"]: 25 | thumbnail_list.setViewMode(QtWidgets.QListView.IconMode) 26 | thumbnail_list.setIconSize(QtCore.QSize(64, 64)) 27 | 28 | has_a_stylesheet = thumbnail_list.styleSheet() == "" 29 | if has_a_stylesheet: 30 | thumbnail_list.setStyleSheet("background-color: rgb(50, 50, 50); color: rgb(255, 255, 255);") 31 | 32 | thumbnail_list.setDragEnabled(False) 33 | 34 | self.lists.append(thumbnail_list) 35 | 36 | tab = QtWidgets.QWidget() 37 | tab.layout = QtWidgets.QVBoxLayout() 38 | tab.setLayout(tab.layout) 39 | 40 | tab.layout.addWidget(thumbnail_list) 41 | 42 | self.tab_widget.addTab(tab, f"{img_type}") 43 | 44 | # connect click 45 | thumbnail_list.itemClicked.connect(self.click_icon) 46 | 47 | # connect tab change 48 | self.tab_widget.currentChanged.connect(self.search_current_tab) 49 | 50 | # textfield to search for icons 51 | self.search_field = QtWidgets.QLineEdit() 52 | self.search_field.textChanged.connect(self.search) 53 | layout.addWidget(self.search_field) 54 | self.setLayout(layout) 55 | layout.addWidget(self.tab_widget) 56 | 57 | self.selected_field = QtWidgets.QLineEdit() 58 | layout.addWidget(self.selected_field) 59 | 60 | # # Loading bar 61 | # self.loading_bar = QProgressBar(self) 62 | # self.loading_bar.setMinimum(0) 63 | # self.loading_bar.setMaximum(1) 64 | # layout.addWidget(self.loading_bar) 65 | 66 | # Load icons 67 | self.load_icons() 68 | 69 | def load_icons(self): 70 | content_path = Path(r"C:\Program Files\Epic Games\UE_5.0\Engine\Content\\") 71 | image_types = ["png", "bmp", "svg", "tps", "ttf"] 72 | 73 | total_icons = 0 74 | 75 | # Count the total number of icons 76 | for img_type in image_types: 77 | icon_paths = content_path.glob(f"**/*.{img_type}") 78 | icon_count = sum(1 for _ in icon_paths) 79 | total_icons += icon_count 80 | 81 | try: 82 | import unreal 83 | 84 | nr_of_steps = total_icons 85 | with unreal.ScopedSlowTask(nr_of_steps, "loading icons") as slow_task: 86 | slow_task.make_dialog(True) 87 | for x in self.load_icons_iter(image_types=image_types, content_path=content_path): 88 | if slow_task.should_cancel(): 89 | break 90 | slow_task.enter_progress_frame(1, f"loaded icon {x}/{nr_of_steps}") 91 | # do the thing 92 | except: 93 | for _ in self.load_icons_iter(image_types=image_types, content_path=content_path): 94 | pass 95 | 96 | 97 | def load_icons_iter(self, image_types, content_path): 98 | 99 | loaded_icons = 0 100 | 101 | # Load icons and update the progress 102 | for index, img_type in enumerate(image_types): 103 | thumbnail_list = self.lists[index] 104 | 105 | icon_paths = content_path.glob(f"**/*.{img_type}") 106 | icon_count = 0 107 | for thumbnail_path in icon_paths: 108 | icon = QtGui.QIcon(str(thumbnail_path)) 109 | name = "" 110 | if img_type in ["tps", "ttf"]: 111 | name = thumbnail_path.stem 112 | item = QtWidgets.QListWidgetItem(icon, name) 113 | item.setToolTip(str(thumbnail_path)) 114 | thumbnail_list.addItem(item) 115 | icon_count += 1 116 | 117 | loaded_icons += 1 118 | yield loaded_icons 119 | 120 | if icon_count == 0: 121 | print(f"no icons found for {img_type}") 122 | continue 123 | 124 | # Update the tab label with the icon count 125 | self.tab_widget.setTabText(index, f"{img_type}({icon_count})") 126 | 127 | def search_current_tab(self, index): 128 | # get tab from index 129 | tab = self.tab_widget.widget(index) 130 | self.search(self.search_field.text()) 131 | 132 | def search(self, text=None): 133 | if text is None: 134 | text = self.search_field.text() 135 | 136 | active_tab = self.tab_widget.currentWidget() 137 | active_list = active_tab.layout.itemAt(0).widget() 138 | 139 | for i in range(active_list.count()): 140 | item = active_list.item(i) 141 | if text.lower() in item.toolTip().lower(): 142 | item.setHidden(False) 143 | else: 144 | item.setHidden(True) 145 | 146 | def click_icon(self, item): 147 | self.selected_field.setText(item.toolTip()) 148 | 149 | 150 | window = None 151 | 152 | 153 | def show(): 154 | global window 155 | window = IconWidget() 156 | window.resize(1000, 800) 157 | window.show() 158 | 159 | # app = QApplication([]) 160 | import unreal 161 | unreal.parent_external_window_to_slate(window.winId()) 162 | # app.exec_() 163 | 164 | 165 | show() 166 | --------------------------------------------------------------------------------