Skip to main content
added 949 characters in body
Source Link
batFINGER
  • 85.2k
  • 10
  • 114
  • 244
import bpy
from bpy.props import PointerProperty, BoolProperty
from bpy.types import PropertyGroup

class SaunaCloudPanel:
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Sauna"
    @classmethod
    def poll(cls, context):
        ao = context.active_object
        return ao and ao.type == 'MESH'

class SAUNA_PT_Wall(SaunaCloudPanel, bpy.types.Panel):
    bl_idname = "SAUNA_PT_Wall"
    bl_label = "Wall"

    def draw(self, context):
    
        layout = self.layout
        sc = context.scene.saunacloud
        ob = context.active_object
        mat = ob.active_material
        layout.context_pointer_set("material", mat)
        layout.context_pointer_set("sauna_cloud_material", sc.material)
        box = layout.box()
        box.prop(ob, "name")
        col = box.column()
        col.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=5)
        col.operator("object.material_slot_add")
        if mat:
            col.prop(mat, "is_sauna_cloud")
        
        
        #col.prop(sc,  'material', text=getattr(sc.material, "name", "None"))
        col.template_ID(sc, "material")
        


class SAUNA_Global_Props(PropertyGroup):
    def poll_material(self, material):
        print("poll", material.name, material.is_sauna_cloud)
        return material.is_sauna_cloud


    def update_material(self, context):
        scene = self.id_data
        sc = self
        ob = context.object
        mat = self.material
        # debug print
        print(f"Update object: {ob.name} with material: {mat.name}")
        ob.active_material = mat
        


    material : PointerProperty(
        type=bpy.types.Material,
        poll=poll_material,
        update=update_material,
        )          

classes = (
            SAUNA_Global_Props,
            SAUNA_PT_Wall,
        )

def draw_sc_material_checkbox(self, context):
    mat = getattr(context, "material", None)
    self.layout.label(text=mat.name if mat else "None")
    if mat:
        self.layout.prop(mat, "is_sauna_cloud")

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.saunacloud = bpy.props.PointerProperty(type=SAUNA_Global_Props)
    bpy.types.Material.is_sauna_cloud = BoolProperty()
    bpy.types.MATERIAL_PT_preview.prepend(draw_sc_material_checkbox)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.MATERIAL_PT_preview.remove(draw_sc_material_checkbox)

if __name__ == "__main__":
    register()

To answer the question, in an update or items method self is the propertygroup and self.id_data the ID object down the chain it belongs to. Scene in this case.

To get the enum value in either

self.matlist

Consider defining an enum and using same on material and object to poll instead of relying on naming convention.

enum = EnumProperty(... our types ...)

bpy.types.Material.sauna_type = enum
bpy.types.Object.sauna_type = enum

You can't set properties of ID objects in a draw method.

In the update method, to do same as above

ob.active_material = bpy.data.materials.get(sc.matname)

however IMO far better to poll a pointer. Import from bpy import context into the poll method to get context object. eg using matching enums,

def material_poll(self, material):
    from bpy import context
    ob = context.object
    return material.sauna_type == getattr(ob, "sauna_type", None) 

or via name slicing method used in question

import bpy
from bpy.props import PointerProperty, BoolProperty
from bpy.types import PropertyGroup

class SaunaCloudPanel:
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Sauna"
    @classmethod
    def poll(cls, context):
        ao = context.active_object
        return ao and ao.type == 'MESH'

class SAUNA_PT_Wall(SaunaCloudPanel, bpy.types.Panel):
    bl_idname = "SAUNA_PT_Wall"
    bl_label = "Wall"

    def draw(self, context):
    
        layout = self.layout
        sc = context.scene.saunacloud
        ob = context.active_object
        mat = ob.active_material
        layout.context_pointer_set("material", mat)
        layout.context_pointer_set("sauna_cloud_material", sc.material)
        box = layout.box()
        box.prop(ob, "name")
        col = box.column()
        col.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=5)
        col.operator("object.material_slot_add")
        if mat:
            col.prop(mat, "is_sauna_cloud")
        
        
        #col.prop(sc,  'material', text=getattr(sc.material, "name", "None"))
        col.template_ID(sc, "material")
        


class SAUNA_Global_Props(PropertyGroup):
    def poll_material(self, material):
        print("poll", material.name, material.is_sauna_cloud)
        return material.is_sauna_cloud


    def update_material(self, context):
        scene = self.id_data
        sc = self
        ob = context.object
        mat = self.material
        # debug print
        print(f"Update object: {ob.name} with material: {mat.name}")
        ob.active_material = mat
        


    material : PointerProperty(
        type=bpy.types.Material,
        poll=poll_material,
        update=update_material,
        )          

classes = (
            SAUNA_Global_Props,
            SAUNA_PT_Wall,
        )

def draw_sc_material_checkbox(self, context):
    mat = getattr(context, "material", None)
    self.layout.label(text=mat.name if mat else "None")
    if mat:
        self.layout.prop(mat, "is_sauna_cloud")

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.saunacloud = bpy.props.PointerProperty(type=SAUNA_Global_Props)
    bpy.types.Material.is_sauna_cloud = BoolProperty()
    bpy.types.MATERIAL_PT_preview.prepend(draw_sc_material_checkbox)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.MATERIAL_PT_preview.remove(draw_sc_material_checkbox)

if __name__ == "__main__":
    register()
import bpy
from bpy.props import PointerProperty, BoolProperty
from bpy.types import PropertyGroup

class SaunaCloudPanel:
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Sauna"
    @classmethod
    def poll(cls, context):
        ao = context.active_object
        return ao and ao.type == 'MESH'

class SAUNA_PT_Wall(SaunaCloudPanel, bpy.types.Panel):
    bl_idname = "SAUNA_PT_Wall"
    bl_label = "Wall"

    def draw(self, context):
    
        layout = self.layout
        sc = context.scene.saunacloud
        ob = context.active_object
        mat = ob.active_material
        layout.context_pointer_set("material", mat)
        layout.context_pointer_set("sauna_cloud_material", sc.material)
        box = layout.box()
        box.prop(ob, "name")
        col = box.column()
        col.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=5)
        col.operator("object.material_slot_add")
        if mat:
            col.prop(mat, "is_sauna_cloud")
        
        
        #col.prop(sc,  'material', text=getattr(sc.material, "name", "None"))
        col.template_ID(sc, "material")
        


class SAUNA_Global_Props(PropertyGroup):
    def poll_material(self, material):
        return material.is_sauna_cloud


    def update_material(self, context):
        scene = self.id_data
        sc = self
        ob = context.object
        mat = self.material
        # debug print
        print(f"Update object: {ob.name} with material: {mat.name}")
        ob.active_material = mat
        


    material : PointerProperty(
        type=bpy.types.Material,
        poll=poll_material,
        update=update_material,
        )          

classes = (
            SAUNA_Global_Props,
            SAUNA_PT_Wall,
        )

def draw_sc_material_checkbox(self, context):
    mat = getattr(context, "material", None)
    self.layout.label(text=mat.name if mat else "None")
    if mat:
        self.layout.prop(mat, "is_sauna_cloud")

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.saunacloud = bpy.props.PointerProperty(type=SAUNA_Global_Props)
    bpy.types.Material.is_sauna_cloud = BoolProperty()
    bpy.types.MATERIAL_PT_preview.prepend(draw_sc_material_checkbox)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.MATERIAL_PT_preview.remove(draw_sc_material_checkbox)

if __name__ == "__main__":
    register()

To answer the question, in an update or items method self is the propertygroup and self.id_data the ID object down the chain it belongs to. Scene in this case.

To get the enum value in either

self.matlist

Consider defining an enum and using same on material and object to poll instead of relying on naming convention.

enum = EnumProperty(... our types ...)

bpy.types.Material.sauna_type = enum
bpy.types.Object.sauna_type = enum

You can't set properties of ID objects in a draw method.

In the update method, to do same as above

ob.active_material = bpy.data.materials.get(sc.matname)

however IMO far better to poll a pointer. Import from bpy import context into the poll method to get context object. eg using matching enums,

def material_poll(self, material):
    from bpy import context
    ob = context.object
    return material.sauna_type == getattr(ob, "sauna_type", None) 

or via name slicing method used in question

Source Link
batFINGER
  • 85.2k
  • 10
  • 114
  • 244

Ditch the Enum.

enter image description here

AFAIC Using an enum property to list the materials has been superseded by using a pointer property to the ID object type. In this case a material.

Custom search data to UILayout.prop_search

Search custom properties in objects on the scene (Python)

Have used this option here. Given the property group a material property, which is a pointer to blend material, or None if not set.

An update method on this property sets the objects active material to the sauna specific one chosen.

Added a boolean property to material type to flag (poll)) it as a "Sauna Cloud" material. The only materials that poll will have this set as true.

A select menu is drawn by the UI using a UILayout.prop(...) (commented out) or with the ID template. (Note a custom operator to add material can be set here, I took out the generic add material_)

Other alterations.

Drew the context objects material slots in the panel and an option to add a new one. Further to this would be to define your own UIList to add the "Is sauna material" checkbox in the list, add a filter etc.

Displayed the is sauna material checkbox, in panel and prepended it to Material Preview panel, by way of example.

Set the context members material (context.material only exists in the PROPERTIES area, otherwise needs to be set for material operators) which can used in your addons custom operators, with buttons in this layout, as

mat = context.material
sc_mat = context.sauna_cloud_material

or in hindsight sc = context.sauna_cloud.

Changed the namespace from SC to SAUNA and would go as far as to say even SAUNA_CLOUD The naming convention, _PT_ for panel type. is all about limiting name clashes.

import bpy
from bpy.props import PointerProperty, BoolProperty
from bpy.types import PropertyGroup

class SaunaCloudPanel:
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Sauna"
    @classmethod
    def poll(cls, context):
        ao = context.active_object
        return ao and ao.type == 'MESH'

class SAUNA_PT_Wall(SaunaCloudPanel, bpy.types.Panel):
    bl_idname = "SAUNA_PT_Wall"
    bl_label = "Wall"

    def draw(self, context):
    
        layout = self.layout
        sc = context.scene.saunacloud
        ob = context.active_object
        mat = ob.active_material
        layout.context_pointer_set("material", mat)
        layout.context_pointer_set("sauna_cloud_material", sc.material)
        box = layout.box()
        box.prop(ob, "name")
        col = box.column()
        col.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=5)
        col.operator("object.material_slot_add")
        if mat:
            col.prop(mat, "is_sauna_cloud")
        
        
        #col.prop(sc,  'material', text=getattr(sc.material, "name", "None"))
        col.template_ID(sc, "material")
        


class SAUNA_Global_Props(PropertyGroup):
    def poll_material(self, material):
        print("poll", material.name, material.is_sauna_cloud)
        return material.is_sauna_cloud


    def update_material(self, context):
        scene = self.id_data
        sc = self
        ob = context.object
        mat = self.material
        # debug print
        print(f"Update object: {ob.name} with material: {mat.name}")
        ob.active_material = mat
        


    material : PointerProperty(
        type=bpy.types.Material,
        poll=poll_material,
        update=update_material,
        )          

classes = (
            SAUNA_Global_Props,
            SAUNA_PT_Wall,
        )

def draw_sc_material_checkbox(self, context):
    mat = getattr(context, "material", None)
    self.layout.label(text=mat.name if mat else "None")
    if mat:
        self.layout.prop(mat, "is_sauna_cloud")

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.saunacloud = bpy.props.PointerProperty(type=SAUNA_Global_Props)
    bpy.types.Material.is_sauna_cloud = BoolProperty()
    bpy.types.MATERIAL_PT_preview.prepend(draw_sc_material_checkbox)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.MATERIAL_PT_preview.remove(draw_sc_material_checkbox)

if __name__ == "__main__":
    register()