493 lines
14 KiB
Python
493 lines
14 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
bl_info = {
|
|
"name": "Icon Viewer",
|
|
"description": "Click an icon to copy its name to the clipboard",
|
|
"author": "roaoao",
|
|
"version": (1, 4, 1),
|
|
"blender": (3, 4, 0),
|
|
"location": "Text Editor > Dev Tab > Icon Viewer",
|
|
"doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html",
|
|
"category": "Development",
|
|
}
|
|
|
|
import bpy
|
|
import math
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
StringProperty,
|
|
)
|
|
|
|
DPI = 72
|
|
POPUP_PADDING = 10
|
|
PANEL_PADDING = 44
|
|
WIN_PADDING = 32
|
|
ICON_SIZE = 20
|
|
HISTORY_SIZE = 100
|
|
HISTORY = []
|
|
|
|
|
|
def ui_scale():
|
|
prefs = bpy.context.preferences.system
|
|
return prefs.dpi / DPI
|
|
|
|
|
|
def prefs():
|
|
return bpy.context.preferences.addons[__name__].preferences
|
|
|
|
|
|
class Icons:
|
|
def __init__(self, is_popup=False):
|
|
self._filtered_icons = None
|
|
self._filter = ""
|
|
self.filter = ""
|
|
self.selected_icon = ""
|
|
self.is_popup = is_popup
|
|
|
|
@property
|
|
def filter(self):
|
|
return self._filter
|
|
|
|
@filter.setter
|
|
def filter(self, value):
|
|
if self._filter == value:
|
|
return
|
|
|
|
self._filter = value
|
|
self.update()
|
|
|
|
@property
|
|
def filtered_icons(self):
|
|
if self._filtered_icons is None:
|
|
self._filtered_icons = []
|
|
icon_filter = self._filter.upper()
|
|
self.filtered_icons.clear()
|
|
pr = prefs()
|
|
|
|
icons = bpy.types.UILayout.bl_rna.functions[
|
|
"prop"].parameters["icon"].enum_items.keys()
|
|
for icon in icons:
|
|
if icon == 'NONE' or \
|
|
icon_filter and icon_filter not in icon or \
|
|
not pr.show_brush_icons and "BRUSH_" in icon and \
|
|
icon != 'BRUSH_DATA' or \
|
|
not pr.show_matcap_icons and "MATCAP_" in icon or \
|
|
not pr.show_event_icons and (
|
|
"EVENT_" in icon or "MOUSE_" in icon
|
|
) or \
|
|
not pr.show_colorset_icons and "COLORSET_" in icon:
|
|
continue
|
|
self._filtered_icons.append(icon)
|
|
|
|
return self._filtered_icons
|
|
|
|
@property
|
|
def num_icons(self):
|
|
return len(self.filtered_icons)
|
|
|
|
def update(self):
|
|
if self._filtered_icons is not None:
|
|
self._filtered_icons.clear()
|
|
self._filtered_icons = None
|
|
|
|
def draw(self, layout, num_cols=0, icons=None):
|
|
if icons:
|
|
filtered_icons = reversed(icons)
|
|
else:
|
|
filtered_icons = self.filtered_icons
|
|
|
|
column = layout.column(align=True)
|
|
row = column.row(align=True)
|
|
row.alignment = 'CENTER'
|
|
|
|
selected_icon = self.selected_icon if self.is_popup else \
|
|
bpy.context.window_manager.clipboard
|
|
col_idx = 0
|
|
for i, icon in enumerate(filtered_icons):
|
|
p = row.operator(
|
|
IV_OT_icon_select.bl_idname, text="",
|
|
icon=icon, emboss=icon == selected_icon)
|
|
p.icon = icon
|
|
p.force_copy_on_select = not self.is_popup
|
|
|
|
col_idx += 1
|
|
if col_idx > num_cols - 1:
|
|
if icons:
|
|
break
|
|
col_idx = 0
|
|
if i < len(filtered_icons) - 1:
|
|
row = column.row(align=True)
|
|
row.alignment = 'CENTER'
|
|
|
|
if col_idx != 0 and not icons and i >= num_cols:
|
|
for _ in range(num_cols - col_idx):
|
|
row.label(text="", icon='BLANK1')
|
|
|
|
if not filtered_icons:
|
|
row.label(text="No icons were found")
|
|
|
|
|
|
class IV_Preferences(bpy.types.AddonPreferences):
|
|
bl_idname = __name__
|
|
|
|
panel_icons = Icons()
|
|
popup_icons = Icons(is_popup=True)
|
|
|
|
def update_icons(self, context):
|
|
self.panel_icons.update()
|
|
self.popup_icons.update()
|
|
|
|
def set_panel_filter(self, value):
|
|
self.panel_icons.filter = value
|
|
|
|
panel_filter: StringProperty(
|
|
description="Filter",
|
|
default="",
|
|
get=lambda s: s.panel_icons.filter,
|
|
set=set_panel_filter,
|
|
options={'TEXTEDIT_UPDATE'})
|
|
show_panel_icons: BoolProperty(
|
|
name="Show Icons",
|
|
description="Show icons", default=True)
|
|
show_history: BoolProperty(
|
|
name="Show History",
|
|
description="Show history", default=True)
|
|
show_brush_icons: BoolProperty(
|
|
name="Show Brush Icons",
|
|
description="Show brush icons", default=True,
|
|
update=update_icons)
|
|
show_matcap_icons: BoolProperty(
|
|
name="Show Matcap Icons",
|
|
description="Show matcap icons", default=True,
|
|
update=update_icons)
|
|
show_event_icons: BoolProperty(
|
|
name="Show Event Icons",
|
|
description="Show event icons", default=True,
|
|
update=update_icons)
|
|
show_colorset_icons: BoolProperty(
|
|
name="Show Colorset Icons",
|
|
description="Show colorset icons", default=True,
|
|
update=update_icons)
|
|
copy_on_select: BoolProperty(
|
|
name="Copy Icon On Click",
|
|
description="Copy icon on click", default=True)
|
|
close_on_select: BoolProperty(
|
|
name="Close Popup On Click",
|
|
description=(
|
|
"Close the popup on click.\n"
|
|
"Not supported by some windows (User Preferences, Render)"
|
|
),
|
|
default=False)
|
|
auto_focus_filter: BoolProperty(
|
|
name="Auto Focus Input Field",
|
|
description="Auto focus input field", default=True)
|
|
show_panel: BoolProperty(
|
|
name="Show Panel",
|
|
description="Show the panel in the Text Editor", default=True)
|
|
show_header: BoolProperty(
|
|
name="Show Header",
|
|
description="Show the header in the Python Console",
|
|
default=True)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
row.scale_y = 1.5
|
|
row.operator(IV_OT_icons_show.bl_idname)
|
|
|
|
row = layout.row()
|
|
|
|
col = row.column(align=True)
|
|
col.label(text="Icons:")
|
|
col.prop(self, "show_matcap_icons")
|
|
col.prop(self, "show_brush_icons")
|
|
col.prop(self, "show_colorset_icons")
|
|
col.prop(self, "show_event_icons")
|
|
col.separator()
|
|
col.prop(self, "show_history")
|
|
|
|
col = row.column(align=True)
|
|
col.label(text="Popup:")
|
|
col.prop(self, "auto_focus_filter")
|
|
col.prop(self, "copy_on_select")
|
|
if self.copy_on_select:
|
|
col.prop(self, "close_on_select")
|
|
|
|
col = row.column(align=True)
|
|
col.label(text="Panel:")
|
|
col.prop(self, "show_panel")
|
|
if self.show_panel:
|
|
col.prop(self, "show_panel_icons")
|
|
|
|
col.separator()
|
|
col.label(text="Header:")
|
|
col.prop(self, "show_header")
|
|
|
|
|
|
class IV_PT_icons(bpy.types.Panel):
|
|
bl_space_type = "TEXT_EDITOR"
|
|
bl_region_type = "UI"
|
|
bl_label = "Icon Viewer"
|
|
bl_category = "Dev"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@staticmethod
|
|
def tag_redraw():
|
|
wm = bpy.context.window_manager
|
|
if not wm:
|
|
return
|
|
|
|
for w in wm.windows:
|
|
for a in w.screen.areas:
|
|
if a.type == 'TEXT_EDITOR':
|
|
for r in a.regions:
|
|
if r.type == 'UI':
|
|
r.tag_redraw()
|
|
|
|
def draw(self, context):
|
|
pr = prefs()
|
|
row = self.layout.row(align=True)
|
|
if pr.show_panel_icons:
|
|
row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
|
|
else:
|
|
row.operator(IV_OT_icons_show.bl_idname)
|
|
row.operator(
|
|
IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
|
|
|
|
_, y0 = context.region.view2d.region_to_view(0, 0)
|
|
_, y1 = context.region.view2d.region_to_view(0, 10)
|
|
region_scale = 10 / abs(y0 - y1)
|
|
|
|
num_cols = max(
|
|
1,
|
|
(context.region.width - PANEL_PADDING) //
|
|
math.ceil(ui_scale() * region_scale * ICON_SIZE))
|
|
|
|
col = None
|
|
if HISTORY and pr.show_history:
|
|
col = self.layout.column(align=True)
|
|
pr.panel_icons.draw(col.box(), num_cols, HISTORY)
|
|
|
|
if pr.show_panel_icons:
|
|
col = col or self.layout.column(align=True)
|
|
pr.panel_icons.draw(col.box(), num_cols)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return prefs().show_panel
|
|
|
|
|
|
class IV_OT_panel_menu_call(bpy.types.Operator):
|
|
bl_idname = "iv.panel_menu_call"
|
|
bl_label = ""
|
|
bl_description = "Menu"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def menu(self, menu, context):
|
|
pr = prefs()
|
|
layout = menu.layout
|
|
layout.prop(pr, "show_panel_icons")
|
|
layout.prop(pr, "show_history")
|
|
|
|
if not pr.show_panel_icons:
|
|
return
|
|
|
|
layout.separator()
|
|
layout.prop(pr, "show_matcap_icons")
|
|
layout.prop(pr, "show_brush_icons")
|
|
layout.prop(pr, "show_colorset_icons")
|
|
layout.prop(pr, "show_event_icons")
|
|
|
|
def execute(self, context):
|
|
context.window_manager.popup_menu(self.menu, title="Icon Viewer")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class IV_OT_icon_select(bpy.types.Operator):
|
|
bl_idname = "iv.icon_select"
|
|
bl_label = ""
|
|
bl_description = "Select the icon"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
icon: StringProperty()
|
|
force_copy_on_select: BoolProperty()
|
|
|
|
def execute(self, context):
|
|
pr = prefs()
|
|
pr.popup_icons.selected_icon = self.icon
|
|
if pr.copy_on_select or self.force_copy_on_select:
|
|
context.window_manager.clipboard = self.icon
|
|
self.report({'INFO'}, self.icon)
|
|
|
|
if pr.close_on_select and IV_OT_icons_show.instance:
|
|
IV_OT_icons_show.instance.close()
|
|
|
|
if pr.show_history:
|
|
if self.icon in HISTORY:
|
|
HISTORY.remove(self.icon)
|
|
if len(HISTORY) >= HISTORY_SIZE:
|
|
HISTORY.pop(0)
|
|
HISTORY.append(self.icon)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class IV_OT_icons_show(bpy.types.Operator):
|
|
bl_idname = "iv.icons_show"
|
|
bl_label = "Icon Viewer"
|
|
bl_description = "Icon viewer"
|
|
bl_property = "filter_auto_focus"
|
|
|
|
instance = None
|
|
|
|
def set_filter(self, value):
|
|
prefs().popup_icons.filter = value
|
|
|
|
def set_selected_icon(self, value):
|
|
if IV_OT_icons_show.instance:
|
|
IV_OT_icons_show.instance.auto_focusable = False
|
|
|
|
filter_auto_focus: StringProperty(
|
|
description="Filter",
|
|
get=lambda s: prefs().popup_icons.filter,
|
|
set=set_filter,
|
|
options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
|
|
filter: StringProperty(
|
|
description="Filter",
|
|
get=lambda s: prefs().popup_icons.filter,
|
|
set=set_filter,
|
|
options={'TEXTEDIT_UPDATE'})
|
|
selected_icon: StringProperty(
|
|
description="Selected Icon",
|
|
get=lambda s: prefs().popup_icons.selected_icon,
|
|
set=set_selected_icon)
|
|
|
|
def get_num_cols(self, num_icons):
|
|
return round(1.3 * math.sqrt(num_icons))
|
|
|
|
def draw_header(self, layout):
|
|
pr = prefs()
|
|
header = layout.box()
|
|
header = header.split(factor=0.75) if self.selected_icon else \
|
|
header.row()
|
|
row = header.row(align=True)
|
|
row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
|
|
row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
|
|
row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
|
|
row.prop(pr, "show_event_icons", text="", icon='HAND')
|
|
row.separator()
|
|
|
|
row.prop(
|
|
pr, "copy_on_select", text="",
|
|
icon='COPYDOWN', toggle=True)
|
|
if pr.copy_on_select:
|
|
sub = row.row(align=True)
|
|
if bpy.context.window.screen.name == "temp":
|
|
sub.alert = True
|
|
sub.prop(
|
|
pr, "close_on_select", text="",
|
|
icon='RESTRICT_SELECT_OFF', toggle=True)
|
|
row.prop(
|
|
pr, "auto_focus_filter", text="",
|
|
icon='OUTLINER_DATA_FONT', toggle=True)
|
|
row.separator()
|
|
|
|
if self.auto_focusable and pr.auto_focus_filter:
|
|
row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
|
|
else:
|
|
row.prop(self, "filter", text="", icon='VIEWZOOM')
|
|
|
|
if self.selected_icon:
|
|
row = header.row()
|
|
row.prop(self, "selected_icon", text="", icon=self.selected_icon)
|
|
|
|
def draw(self, context):
|
|
pr = prefs()
|
|
col = self.layout
|
|
self.draw_header(col)
|
|
|
|
history_num_cols = int(
|
|
(self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
|
|
num_cols = min(
|
|
self.get_num_cols(len(pr.popup_icons.filtered_icons)),
|
|
history_num_cols)
|
|
|
|
subcol = col.column(align=True)
|
|
|
|
if HISTORY and pr.show_history:
|
|
pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
|
|
|
|
pr.popup_icons.draw(subcol.box(), num_cols)
|
|
|
|
def close(self):
|
|
bpy.context.window.screen = bpy.context.window.screen
|
|
|
|
def check(self, context):
|
|
return True
|
|
|
|
def cancel(self, context):
|
|
IV_OT_icons_show.instance = None
|
|
IV_PT_icons.tag_redraw()
|
|
|
|
def execute(self, context):
|
|
if not IV_OT_icons_show.instance:
|
|
return {'CANCELLED'}
|
|
IV_OT_icons_show.instance = None
|
|
|
|
pr = prefs()
|
|
if self.selected_icon and not pr.copy_on_select:
|
|
context.window_manager.clipboard = self.selected_icon
|
|
self.report({'INFO'}, self.selected_icon)
|
|
pr.popup_icons.selected_icon = ""
|
|
|
|
IV_PT_icons.tag_redraw()
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
pr = prefs()
|
|
pr.popup_icons.selected_icon = ""
|
|
pr.popup_icons.filter = ""
|
|
IV_OT_icons_show.instance = self
|
|
self.auto_focusable = True
|
|
|
|
num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
|
|
self.width = int(min(
|
|
ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
|
|
context.window.width - WIN_PADDING))
|
|
|
|
return context.window_manager.invoke_props_dialog(
|
|
self, width=self.width)
|
|
|
|
def draw_console_header(self, context):
|
|
if not prefs().show_header:
|
|
return
|
|
self.layout.operator(IV_OT_icons_show.bl_idname)
|
|
|
|
classes = (
|
|
IV_PT_icons,
|
|
IV_OT_panel_menu_call,
|
|
IV_OT_icon_select,
|
|
IV_OT_icons_show,
|
|
IV_Preferences,
|
|
)
|
|
|
|
|
|
def register():
|
|
if bpy.app.background:
|
|
return
|
|
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
bpy.types.CONSOLE_HT_header.append(draw_console_header)
|
|
|
|
|
|
def unregister():
|
|
if bpy.app.background:
|
|
return
|
|
|
|
bpy.types.CONSOLE_HT_header.remove(draw_console_header)
|
|
|
|
for cls in classes:
|
|
bpy.utils.unregister_class(cls)
|