├── 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 |
--------------------------------------------------------------------------------