-
-
Save p2or/30b8b30c89871b8ae5c97803107fd494 to your computer and use it in GitHub Desktop.
| # ##### 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 ##### | |
| bl_info = { | |
| "name": "material-pointer-uilist-dev", | |
| "description": "", | |
| "author": "p2or", | |
| "version": (0, 2), | |
| "blender": (2, 80, 0), | |
| "location": "Text Editor", | |
| "warning": "", # used for warning icon and text in addons panel | |
| "wiki_url": "", | |
| "tracker_url": "", | |
| "category": "Development" | |
| } | |
| import bpy | |
| from bpy.props import (IntProperty, | |
| BoolProperty, | |
| StringProperty, | |
| CollectionProperty, | |
| PointerProperty) | |
| from bpy.types import (Operator, | |
| Panel, | |
| PropertyGroup, | |
| UIList) | |
| # ------------------------------------------------------------------- | |
| # Operators | |
| # ------------------------------------------------------------------- | |
| class CUSTOM_OT_actions(Operator): | |
| """Move items up and down, add and remove""" | |
| bl_idname = "custom.list_action" | |
| bl_label = "List Actions" | |
| bl_description = "Move items up and down, add and remove" | |
| bl_options = {'REGISTER'} | |
| action: bpy.props.EnumProperty( | |
| items=( | |
| ('UP', "Up", ""), | |
| ('DOWN', "Down", ""), | |
| ('REMOVE', "Remove", ""), | |
| ('ADD', "Add", ""))) | |
| def random_color(self): | |
| from mathutils import Color | |
| from random import random | |
| return Color((random(), random(), random())) | |
| def invoke(self, context, event): | |
| scn = context.scene | |
| idx = scn.custom_index | |
| try: | |
| item = scn.custom[idx] | |
| except IndexError: | |
| pass | |
| else: | |
| if self.action == 'DOWN' and idx < len(scn.custom) - 1: | |
| item_next = scn.custom[idx+1].name | |
| scn.custom.move(idx, idx+1) | |
| scn.custom_index += 1 | |
| info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1) | |
| self.report({'INFO'}, info) | |
| elif self.action == 'UP' and idx >= 1: | |
| item_prev = scn.custom[idx-1].name | |
| scn.custom.move(idx, idx-1) | |
| scn.custom_index -= 1 | |
| info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1) | |
| self.report({'INFO'}, info) | |
| elif self.action == 'REMOVE': | |
| item = scn.custom[scn.custom_index] | |
| mat = item.material | |
| if mat: | |
| mat_obj = bpy.data.materials.get(mat.name, None) | |
| if mat_obj: | |
| bpy.data.materials.remove(mat_obj, do_unlink=True) | |
| info = 'Item %s removed from scene' % (item) | |
| scn.custom.remove(idx) | |
| if scn.custom_index == 0: | |
| scn.custom_index = 0 | |
| else: | |
| scn.custom_index -= 1 | |
| self.report({'INFO'}, info) | |
| if self.action == 'ADD': | |
| item = scn.custom.add() | |
| item.id = len(scn.custom) | |
| item.material = bpy.data.materials.new(name="Material") | |
| item.name = item.material.name | |
| col = self.random_color() | |
| item.material.diffuse_color = (col.r, col.g, col.b, 1.0) | |
| scn.custom_index = (len(scn.custom)-1) | |
| info = '%s added to list' % (item.name) | |
| self.report({'INFO'}, info) | |
| return {"FINISHED"} | |
| class CUSTOM_OT_addBlendMaterials(Operator): | |
| """Add all materials of the current Blend-file to the UI list""" | |
| bl_idname = "custom.add_bmaterials" | |
| bl_label = "Add all available Materials" | |
| bl_description = "Add all available materials to the UI list" | |
| bl_options = {'REGISTER', 'UNDO'} | |
| @classmethod | |
| def poll(cls, context): | |
| return len(bpy.data.materials) | |
| def execute(self, context): | |
| scn = context.scene | |
| for mat in bpy.data.materials: | |
| if not context.scene.custom.get(mat.name): | |
| item = scn.custom.add() | |
| item.id = len(scn.custom) | |
| item.material = mat | |
| item.name = item.material.name | |
| scn.custom_index = (len(scn.custom)-1) | |
| info = '%s added to list' % (item.name) | |
| self.report({'INFO'}, info) | |
| return{'FINISHED'} | |
| class CUSTOM_OT_printItems(Operator): | |
| """Print all items and their properties to the console""" | |
| bl_idname = "custom.print_items" | |
| bl_label = "Print Items to Console" | |
| bl_description = "Print all items and their properties to the console" | |
| bl_options = {'REGISTER', 'UNDO'} | |
| reverse_order: BoolProperty( | |
| default=False, | |
| name="Reverse Order") | |
| @classmethod | |
| def poll(cls, context): | |
| return bool(context.scene.custom) | |
| def execute(self, context): | |
| scn = context.scene | |
| if self.reverse_order: | |
| for i in range(scn.custom_index, -1, -1): | |
| mat = scn.custom[i].material | |
| print ("Material:", mat,"-",mat.name, mat.diffuse_color) | |
| else: | |
| for item in scn.custom: | |
| mat = item.material | |
| print ("Material:", mat,"-",mat.name, mat.diffuse_color) | |
| return{'FINISHED'} | |
| class CUSTOM_OT_clearList(Operator): | |
| """Clear all items of the list and remove from scene""" | |
| bl_idname = "custom.clear_list" | |
| bl_label = "Clear List and Remove Materials" | |
| bl_description = "Clear all items of the list and remove from scene" | |
| bl_options = {'INTERNAL'} | |
| @classmethod | |
| def poll(cls, context): | |
| return bool(context.scene.custom) | |
| def invoke(self, context, event): | |
| return context.window_manager.invoke_confirm(self, event) | |
| def execute(self, context): | |
| if bool(context.scene.custom): | |
| # Remove materials from the scene | |
| for i in context.scene.custom: | |
| if i.material: | |
| mat_obj = bpy.data.materials.get(i.material.name, None) | |
| if mat_obj: | |
| info = 'Item %s removed from scene' % (i.material.name) | |
| bpy.data.materials.remove(mat_obj, do_unlink=True) | |
| # Clear the list | |
| context.scene.custom.clear() | |
| self.report({'INFO'}, "All materials removed from scene") | |
| else: | |
| self.report({'INFO'}, "Nothing to remove") | |
| return{'FINISHED'} | |
| # ------------------------------------------------------------------- | |
| # Drawing | |
| # ------------------------------------------------------------------- | |
| class CUSTOM_UL_items(UIList): | |
| def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): | |
| mat = item.material | |
| if self.layout_type in {'DEFAULT', 'COMPACT'}: | |
| split = layout.split(factor=0.3) | |
| split.label(text="Index: %d" % (index)) | |
| # static method UILayout.icon returns the integer value of the icon ID | |
| # "computed" for the given RNA object. | |
| split.prop(mat, "name", text="", emboss=False, icon_value=layout.icon(mat)) | |
| elif self.layout_type in {'GRID'}: | |
| layout.alignment = 'CENTER' | |
| layout.label(text="", icon_value=layout.icon(mat)) | |
| def invoke(self, context, event): | |
| pass | |
| class CUSTOM_PT_materialList(Panel): | |
| """Adds a custom panel to the TEXT_EDITOR""" | |
| bl_idname = 'TEXT_PT_my_panel' | |
| bl_space_type = "TEXT_EDITOR" | |
| bl_region_type = "UI" | |
| bl_category = "Dev" | |
| bl_label = "Custom Material List Demo" | |
| def draw(self, context): | |
| layout = self.layout | |
| scn = bpy.context.scene | |
| rows = 2 | |
| row = layout.row() | |
| row.template_list("CUSTOM_UL_items", "custom_def_list", scn, "custom", | |
| scn, "custom_index", rows=rows) | |
| col = row.column(align=True) | |
| col.operator(CUSTOM_OT_actions.bl_idname, icon='ADD', text="").action = 'ADD' | |
| col.operator(CUSTOM_OT_actions.bl_idname, icon='REMOVE', text="").action = 'REMOVE' | |
| col.separator() | |
| col.operator(CUSTOM_OT_actions.bl_idname, icon='TRIA_UP', text="").action = 'UP' | |
| col.operator(CUSTOM_OT_actions.bl_idname, icon='TRIA_DOWN', text="").action = 'DOWN' | |
| row = layout.row() | |
| row.template_list("CUSTOM_UL_items", "custom_grid_list", scn, "custom", | |
| scn, "custom_index", rows=2, type='GRID') | |
| row = layout.row() | |
| row.operator(CUSTOM_OT_addBlendMaterials.bl_idname, icon="NODE_MATERIAL") | |
| row = layout.row() | |
| col = row.column(align=True) | |
| row = col.row(align=True) | |
| row.operator(CUSTOM_OT_printItems.bl_idname, icon="LINENUMBERS_ON") | |
| row = col.row(align=True) | |
| row.operator(CUSTOM_OT_clearList.bl_idname, icon="X") | |
| # ------------------------------------------------------------------- | |
| # Collection | |
| # ------------------------------------------------------------------- | |
| class CUSTOM_PG_materialCollection(PropertyGroup): | |
| #name: StringProperty() -> Instantiated by default | |
| material: PointerProperty( | |
| name="Material", | |
| type=bpy.types.Material) | |
| # ------------------------------------------------------------------- | |
| # Register & Unregister | |
| # ------------------------------------------------------------------- | |
| classes = ( | |
| CUSTOM_OT_actions, | |
| CUSTOM_OT_addBlendMaterials, | |
| CUSTOM_OT_printItems, | |
| CUSTOM_OT_clearList, | |
| CUSTOM_UL_items, | |
| CUSTOM_PT_materialList, | |
| CUSTOM_PG_materialCollection | |
| ) | |
| def register(): | |
| from bpy.utils import register_class | |
| for cls in classes: | |
| register_class(cls) | |
| # Custom scene properties | |
| bpy.types.Scene.custom = CollectionProperty(type=CUSTOM_PG_materialCollection) | |
| bpy.types.Scene.custom_index = IntProperty() | |
| def unregister(): | |
| from bpy.utils import unregister_class | |
| for cls in reversed(classes): | |
| unregister_class(cls) | |
| del bpy.types.Scene.custom | |
| del bpy.types.Scene.custom_index | |
| if __name__ == "__main__": | |
| register() |
Hi @DB3D,
I guess that's not really a python thing you can control at the moment. Have a look at the Shape Keys list to see what's possible to implement. There is a Bool Property to set the actual state for each item and you can select multiple shape key values at once to edit them simultaneously, which should possible for a custom list as well (by default).
Cheers,
Christian
Hello @p2or,
how can I update list according to selection (to view only the materials of selected object)? Without any "add or "remove" operators.
Thanks!
Hi @ikakupa,
that's actually pretty easy to do since this is the behavior of the regular material list. Right-click the list (Material Properties), choose 'Edit Source' and just copy-paste the draw method of the CYCLES_PT_context_material class to your custom panel. Demo based on 'UI Panel Simple' template that comes with Blender:
import bpy
class HelloWorldPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Hello World Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
mat = context.material
ob = context.object
slot = context.material_slot
space = context.space_data
if ob:
is_sortable = len(ob.material_slots) > 1
rows = 1
if (is_sortable):
rows = 4
row = layout.row()
row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows)
col = row.column(align=True)
col.operator("object.material_slot_add", icon='ADD', text="")
col.operator("object.material_slot_remove", icon='REMOVE', text="")
col.menu("MATERIAL_MT_context_menu", icon='DOWNARROW_HLT', text="")
if is_sortable:
col.separator()
col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
'''
if ob.mode == 'EDIT':
row = layout.row(align=True)
row.operator("object.material_slot_assign", text="Assign")
row.operator("object.material_slot_select", text="Select")
row.operator("object.material_slot_deselect", text="Deselect")
'''
split = layout.split(factor=0.65)
if ob:
split.template_ID(ob, "active_material", new="material.new")
row = split.row()
if slot:
row.prop(slot, "link", text="")
else:
row.label()
elif mat:
split.template_ID(space, "pin_id")
split.separator()
def register():
bpy.utils.register_class(HelloWorldPanel)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
if __name__ == "__main__":
register()Cheers,
Christian
Thank you for your answer. Sorry, I misspelled object/objectS in my last comment. I want to view materials (and edit their name) of multiple selected objects simultaneously. I tried to change 'UI Panel Simple' template to achieve this, sadly my py skills are not good enough.
Objects are selected:
https://pasteboard.co/JTVzC2Vs.jpg

Thanks,
Irakli
Hi @ikakupa,
aha, need to investigate, not sure. I don't think there is an easy way to get that due to the nature of the UI list. I suggest ask a question on https://blender.stackexchange.com/ or https://devtalk.blender.org/ if you need a solution rather quick.
Cheers,
Christian
Thank you Christian, I am going to give it a try and post it on Devtalk.
Hi @ikakupa,
is it possible to show only "non-greasepencil-materials" in the list?
I tried it with a simple "if material.is_grease_pencil: return" in the draw_item-function, but then I get empty slots in the list...

Thank you very much,
Sergej
Hi @squindt,
If you don't want to show grease pencil materials I wouldn't even add them to the PropertyGroup:
class CUSTOM_OT_addBlendMaterials(Operator):
"""Add all materials of the current Blend-file to the UI list"""
bl_idname = "custom.add_bmaterials"
bl_label = "Add all available Materials"
bl_description = "Add all available materials to the UI list"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return len(bpy.data.materials)
def execute(self, context):
scn = context.scene
materials = [m for m in bpy.data.materials if not m.is_grease_pencil]
for mat in materials:
if not context.scene.custom.get(mat.name):
item = scn.custom.add()
...
Cheers,
Christian
Sorry @p2or,
I forgot to mention, that I am trying to populate the list without an operator but automatically by referencing "bpy.data" as the "dataptr" and "materials" as propname arguments in the template_list-function. That way every new material which is created is automatically in the custom list, but unfortunately also the grease pencil materials. Is there a possibility to prevent this?
Thank you very much for the fast response! :)
Sergej
Hi @squindt,
see e.g. How to populate UIList with all material slot in scene? @batFINGER's code displays all materials used the scene (even using pointers), simple and clean IMHO.
Cheers, Christian
Hi @p2or,
thank you very much! That is exactly what I need!
I saw this example of @batFINGER before, but didn't consider it as a solution for my problem because in his demo-GIF he pushed a button to repopulate the list after he created new materials. Now I tried it myself and the materials got added automatically, but I don't understand why :D

hello @p2or
is it possible to have multiples selected slots at the same time with this GUI ?