1
$\begingroup$

My question is basically the title. I have to create an object first before my custom N panel shows up. I want to be able to make it (or some fields within it) visible even if no object is in the scene.

Here is my code:

bl_info = {
    "name": "Cultmethod Sandbox",
    "author": "Jay",
    "version": (0, 0, 1),
    "blender": (2, 83, 0),
    "location": "3D View",
    "description": "Cultmethod Sandbox",
    "category": "Development"
}
 
import bpy
import colorsys
import bmesh

from bpy.props import (StringProperty,
                       BoolProperty,
                       IntProperty,
                       FloatProperty,
                       FloatVectorProperty,
                       EnumProperty,
                       PointerProperty,
                       )
from bpy.types import (Panel,
                       Menu,
                       Operator,
                       PropertyGroup,
                       )

# ------------------------------------------------------------------------
#    Operators
# ------------------------------------------------------------------------

class CULT_SetUpSuzanne(Operator):
    bl_label = "Place a Suzanne"
    bl_idname = "object.create_suzanne"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bpy.ops.mesh.primitive_monkey_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
        bpy.ops.object.subdivision_set(level=3, relative=False)
        bpy.ops.object.shade_smooth()
        #bpy.context.space_data.context = 'MODIFIER'
        bpy.ops.object.modifier_apply(modifier="Subdivision")
        bpy.ops.transform.translate(value=(0, 0, 1.25323), orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(False, False, True), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False)
        return {'FINISHED'}


class CULT_ClearParentKeep(Operator):
    bl_label = "Clear Parent and Keep Transform"
    bl_idname = "object.clear_parent_keep"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        sel_objs = [obj for obj in bpy.context.selected_objects]
        for obj in sel_objs:
            bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
        return {'FINISHED'}


class CULT_ClearAnimData(Operator):
    bl_label = "Clear Animation Data"
    bl_idname = "object.clear_anim_data"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        sel_objs = [obj for obj in bpy.context.selected_objects]
        #bpy.ops.object.select_all(action='DESELECT')
        for obj in sel_objs:
            bpy.ops.anim.keyframe_clear_v3d()
        return {'FINISHED'}

class CULT_ResetViewportDisplay(Operator):
    bl_label = "Reset Viewport Color"
    bl_idname = "object.reset_viewport_disp"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        selected_obj = bpy.context.selected_objects
        active_obj = bpy.context.active_object

        for x in selected_obj:
            bpy.ops.object.select_all(action='DESELECT')
            x.select_set(True)
            bpy.context.view_layer.objects.active = x
            if x.type == 'MESH':
                for mat in x.data.materials:
                    color = colorsys.hsv_to_rgb(0, 0, 0.906)
                    color4 = (color[0], color[1], color[2], 1)
                    try:
                        mat.diffuse_color = color4
                    except:
                        print("Can\'t change viewport material color")
            
        # Select again objects
        for j in selected_obj:
            j.select_set(True)
        
        bpy.context.view_layer.objects.active = active_obj

        return {'FINISHED'}

class CULT_RecalcNormalsSelected(Operator):
    bl_label = "Recalculate Normals Outside"
    bl_idname = "object.recalc_normals_selected"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bm = bmesh.new()
        meshes = set(o.data for o in context.selected_objects if o.type == 'MESH')
        for mesh in meshes:
            bm.from_mesh(mesh)
            bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
            bm.to_mesh(mesh)
            bm.clear()
            mesh.update()

        bm.free()

        return {'FINISHED'}


# ------------------------------------------------------------------------
#    Panel in Object Mode
# ------------------------------------------------------------------------

class OBJECT_PT_CustomPanel(Panel):
    bl_label = "Cultmethod"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = "VIEW_3D"   
    bl_region_type = "UI"
    bl_category = "Cultmethod"
    bl_context = "objectmode"   


    @classmethod
    def poll(self,context):
        return context.object is not None

    def draw(self, context):
        layout = self.layout
        scene = context.scene

        layout.operator("object.clear_anim_data")
        layout.operator("object.reset_viewport_disp")
        layout.operator("object.clear_parent_keep")
        layout.operator("object.recalc_normals_selected")
        layout.operator("object.create_suzanne")

# ------------------------------------------------------------------------
#    Registration
# ------------------------------------------------------------------------

classes = (
    CULT_ClearParentKeep,
    CULT_ClearAnimData,
    CULT_ResetViewportDisplay,
    CULT_RecalcNormalsSelected,
    CULT_SetUpSuzanne,
    OBJECT_PT_CustomPanel
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)


if __name__ == "__main__":
    register()

Any help at all would be hugely appreciated!

$\endgroup$
2
$\begingroup$

TL;DR Remove the poll function if you want the panel to be displayed at all times.


It seems that you've unintentionally used a poll function for your OBJECT_PT_CustomPanel panel. The poll function for panels and operators is used to check if the execute function is allowed to be run. If poll returns true it can be executed, otherwise not. For panels this has the effect that they aren't drawn when poll returns false.

The implementation of OBJECT_PT_CustomPanel contains the following poll function.

    @classmethod
    def poll(self,context):
        return context.object is not None

It checks if there is currently any object referenced by bpy.context.object. If there are no objects in the scene or you've deleted the previously active one, then its value is None and thus the return value is false. Therefore, the panel will not be displayed. If you don't want this behavior and always display the panel, remove the poll function. This is equivalent to having a poll function that always returns true.

$\endgroup$
3
  • $\begingroup$ Thanks so much for that! Going to implement it now. $\endgroup$
    – Jay
    Jul 3 at 6:46
  • $\begingroup$ Is there a way to implement an independent poll function for each operator so that a specific feature grays out or disappears if it isn't relevant? Or is that not the correct way to do this? $\endgroup$
    – Jay
    Jul 3 at 22:21
  • $\begingroup$ @Jay Sure, that's possible when you are implementing your own operators that are displayed in the panel. You can define the poll for them however you like as well. $\endgroup$
    – Robert Gützkow
    Jul 4 at 7:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.