483cdf6e91
Caused by rB4b20eebb85cc. Apparently the Addon misused the API though and it is not allowed to register multiple headers, instead, we need to append to the existing one. Maniphest Tasks: T82453 Differential Revision: https://developer.blender.org/D9579
511 lines
15 KiB
Python
511 lines
15 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
|
|
bl_info = {
|
|
"name": "Icon Viewer",
|
|
"description": "Click an icon to copy its name to the clipboard",
|
|
"author": "roaoao",
|
|
"version": (1, 4, 0),
|
|
"blender": (2, 80, 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 * prefs.pixel_size / 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 = 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)
|