0
$\begingroup$

I have the below addon installed in Blender 3.3. I get AttributeError:'Mesh' object has no attribute 'subdiv_prop' on line 137 when I click the 'Project Image' button. How do I resolve this?

enter image description here

enter image description here

bl_info = {
    "name": "Tools",
    "description": "Tools",
    "author": "Tools",
    "version": (1, 0, 1),
    "blender": (3, 3, 0),
    "location": "View3D > 'N' Panel > PRT",
    "warning": "",
    "wiki_url": "",
    "doc_url": "",
    "tracker_url": "",
    "category": "3D View"
}


###############   IMPORTS
import bpy
from bpy.utils import previews
import os
import math
from bpy.props import *
import bmesh



###############   USER PANEL
class SNA_PT_Create_a_Product_Render(bpy.types.Panel):
    bl_label = "Create a Product Render"
    bl_idname = "SNA_PT_Create_a_Product_Render"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = 'PRT'
    bl_order = 0

    @classmethod
    def poll(cls, context):
        return True

    def draw_header(self, context):
        try:
            layout = self.layout
        except Exception as exc:
            print(str(exc) + " | Error in Create a Product Promo panel header")

    def draw(self, context):
        try:
            layout = self.layout
            scene = context.scene
            obj = context.object
            
            box = layout.box()
            box.enabled = True
            box.alert = False
            box.scale_x = 1.0
            box.scale_y = 1.0
            box.label(text=r"IMPORT ASSETS FROM OTHER FILES",icon= 'MESH_MONKEY')
            row = box.row(align=True)
            row.enabled = True
            row.alert = False
            row.scale_x = 1.0
            row.scale_y = 1.0
            op = row.operator("sna.append_model",text=r"Append Model",emboss=True,depress=False,icon_value=0)
            op = row.operator("sna.link_model",text=r"Link Model",emboss=True,depress=False,icon_value=0)
            row = box.row(align=True)
            row.enabled = True
            row.alert = False
            row.scale_x = 1.0
            row.scale_y = 1.0
            op = row.operator("sna.import_fbx",text=r"Import FBX",emboss=True,depress=False,icon_value=0)
            op = row.operator("sna.import_obj",text=r"Import OBJ",emboss=True,depress=False,icon_value=0)
            box = layout.box()
            box.enabled = True
            box.alert = False
            box.scale_x = 1.0
            box.scale_y = 1.0
            box.label(text=r"ADD DECAL / LABEL",icon= 'FILE_IMAGE')
            row = box.row(align=True)
            row.enabled = True
            row.alert = False
            row.scale_x = 1.0
            row.scale_y = 1.0
            op = row.operator("sna.import_image",text=r"Import Image",emboss=True,depress=False,icon_value=0)
            row = layout.column()
            row.alert = False if scene.shrink_target else True
            box.label(text=r"Project Image to a Mesh")
            op = box.prop(scene, "shrink_target")
            row = layout.column()
            op = box.operator(SNA_OT_Projector_Operator.bl_idname)
            box = layout.box()
            box.enabled = True
            box.alert = False
            box.scale_x = 1.0
            box.scale_y = 1.0
            box.label(text=r"MORE TOOLS",icon= 'TOOL_SETTINGS')
            row = box.row(align=True)
            row.prop(bpy.context.active_object,'is_shadow_catcher',icon_value=0,text=r"Turn Selected Object Into A Shadow Catcher",emboss=True,toggle=False,invert_checkbox=False,)
            box = layout.box()
            box.enabled = True
            box.alert = False
            box.scale_x = 1.0
            box.scale_y = 1.0
            box.label(text=r"RENDER YOUR PRODUCT",icon= 'RESTRICT_RENDER_OFF')
            op = box.operator("sna.render_image",text=r"Render Image",emboss=True,depress=False,icon_value=0)
            op = box.operator("sna.render_animation",text=r"Render Animation",emboss=True,depress=False,icon_value=0)
            box = layout.box()
            box.enabled = True
            box.alert = True
            box.scale_x = 1.0
            box.scale_y = 1.0
            box.label(text=r"NEED MORE TOOLS FOR YOUR PROJECTS?",icon= 'SHADERFX')
            row = box.row(align=True)
            row.operator("wm.url_open", text="See More Addons").url = ""
            row.operator("wm.url_open", text="Request A Tool").url = ""            
        except Exception as exc:
            print(str(exc) + " | Error in Create a Product Promo panel")



###############   OPERATORS
class SNA_OT_Projector_Operator(bpy.types.Operator):
    """Project Image onto Mesh"""
    bl_idname = "mesh.custom_subdiv"
    bl_label = "Project Image"
    bl_options = {'REGISTER', 'UNDO'}
    
    subd_levels: IntProperty(default=30, options={'HIDDEN', 'SKIP_SAVE'})

    @classmethod
    def poll(cls, context):
        return context.scene.shrink_target != context.active_object and context.active_object.type=='MESH' and context.mode=='OBJECT'

    def execute(self, context):
        obj = context.object
        ob = context.object
        me = ob.data
        
        if me.subdiv_prop >= self.subd_levels:
            self.report({'INFO'}, "Maximum reached")
            return {'CANCELLED'}
        else:
            shrink_mod = None
            for m in obj.modifiers:
                if m.type == 'SHRINKWRAP':
                    shrink_mod = m
            if shrink_mod is None:
                shrink_mod = obj.modifiers.new(name="Projector", type='SHRINKWRAP')
            shrink_mod.offset = 0.01
            shrink_mod.target = context.scene.shrink_target
            if shrink_mod.target is None:
                self.report({"ERROR"},"Select a Target object above then click 'Project Image' button again")
                return {'CANCELLED'}
            bm = bmesh.new()
            bm.from_mesh(me)
            bmesh.ops.subdivide_edges(bm, edges=bm.edges, cuts=self.subd_levels, use_grid_fill=True)
            bm.to_mesh(me)
            me.update()
            self.report({'INFO'}, "{} levels applied".format(self.subd_levels))
            me.subdiv_prop =+ self.subd_levels
            return {'FINISHED'}



class SNA_OT_Append_Model(bpy.types.Operator):
    bl_idname = "sna.append_model"
    bl_label = "Append model"
    bl_description = "Import a model from another .blend file"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.wm.append('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of Append model")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of Append model")
        return self.execute(context)



class SNA_OT_Link_Model(bpy.types.Operator):
    bl_idname = "sna.link_model"
    bl_label = "Link model"
    bl_description = "Link a model from another .blend file"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.wm.link('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of New Operator")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of New Operator")
        return self.execute(context)



class SNA_OT_Import_Fbx(bpy.types.Operator):
    bl_idname = "sna.import_fbx"
    bl_label = "Import FBX"
    bl_description = "Import a model from a .FBX project file"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.import_scene.fbx('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of New Operator")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of New Operator")
        return self.execute(context)



class SNA_OT_Import_Obj(bpy.types.Operator):
    bl_idname = "sna.import_obj"
    bl_label = "Import OBJ"
    bl_description = "Import a model from a .OBJ project file"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.import_scene.obj('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of New Operator")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of New Operator")
        return self.execute(context)



class SNA_OT_Render_Image(bpy.types.Operator):
    bl_idname = "sna.render_image"
    bl_label = "Render image"
    bl_description = "Render your promo"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.render.render('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',animation=False,write_still=False,use_viewport=True,layer=r"",scene=r"",)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of Render image")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of Render image")
        return self.execute(context)



class SNA_OT_Render_Animation(bpy.types.Operator):
    bl_idname = "sna.render_animation"
    bl_label = "Render animation"
    bl_description = ""
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.ops.render.render('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',write_still=False,use_viewport=True,layer=r"",scene=r"",)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of Render animation")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of Render animation")
        return self.execute(context)



class SNA_OT_Import_Image(bpy.types.Operator):
    bl_idname = "sna.import_image"
    bl_label = "Import image"
    bl_description = "Import your label / decal / sticker"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def invoke(self, context, event):
        try:
            bpy.ops.preferences.addon_enable('INVOKE_DEFAULT' if False else 'EXEC_DEFAULT',module=r"io_import_images_as_planes",)
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of New Operator")
        return self.execute(context)
    
    def execute(self, context):
        try:
            bpy.ops.import_image.to_plane('INVOKE_DEFAULT' if True else 'EXEC_DEFAULT',)
        except Exception as exc:
            print(str(exc) + " | Error in execute function of New Operator")
        return {"FINISHED"}



class SNA_OT_Shadow_Catcher(bpy.types.Operator):
    bl_idname = "sna.shadow_catcher"
    bl_label = "Shadow Catcher"
    bl_description = ""
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        try:
            bpy.context.active_object.is_shadow_catcher = True
        except Exception as exc:
            print(str(exc) + " | Error in execute function of Shadow Catcher")
        return {"FINISHED"}

    def invoke(self, context, event):
        try:
            pass
        except Exception as exc:
            print(str(exc) + " | Error in invoke function of Shadow Catcher")
        return self.execute(context)



def target_poll(self, object):
    return object.type == 'MESH'



###############   REGISTER AND UNREGISTER ADDON
def register():
    bpy.utils.register_class(SNA_PT_Create_a_Product_Render)
    bpy.utils.register_class(SNA_OT_Append_Model)
    bpy.utils.register_class(SNA_OT_Link_Model)
    bpy.utils.register_class(SNA_OT_Import_Fbx)
    bpy.utils.register_class(SNA_OT_Import_Obj)
    bpy.utils.register_class(SNA_OT_Render_Image)
    bpy.utils.register_class(SNA_OT_Render_Animation)
    bpy.utils.register_class(SNA_OT_Import_Image)
    bpy.utils.register_class(SNA_OT_Shadow_Catcher)
    bpy.utils.register_class(SNA_OT_Projector_Operator)
    bpy.types.Scene.shrink_target = bpy.props.PointerProperty(
        name="Select Mesh",
        type=bpy.types.Object,
        poll=target_poll
    )


def unregister():
    bpy.utils.unregister_class(SNA_OT_Shadow_Catcher)
    bpy.utils.unregister_class(SNA_OT_Import_Image)
    bpy.utils.unregister_class(SNA_OT_Render_Animation)
    bpy.utils.unregister_class(SNA_OT_Render_Image)
    bpy.utils.unregister_class(SNA_OT_Import_Obj)
    bpy.utils.unregister_class(SNA_OT_Import_Fbx)
    bpy.utils.unregister_class(SNA_OT_Link_Model)
    bpy.utils.unregister_class(SNA_OT_Append_Model)
    bpy.utils.unregister_class(SNA_PT_Create_a_Product_Render)
    bpy.utils.unregister_class(SNA_OT_Projector_Operator)
    del bpy.types.Scene.shrink_target

$\endgroup$
1
  • $\begingroup$ See this topic. subdiv_prop is a custom property, which is not declared now in the addon. See the register and unregister functions of the related topic. $\endgroup$ Commented Sep 30, 2022 at 9:17

1 Answer 1

1
$\begingroup$

Your problem is that you haven't created the custom attribute for the bpy.types.Mesh object yet.

Similar to how you did here, in your register function:

bpy.types.Scene.shrink_target = bpy.props.PointerProperty(
        name="Select Mesh",
        type=bpy.types.Object,
        poll=target_poll
    )

You need to do somethings like:

class MeshSubdivProp(bpy.types.PropertyGroup):
    subdiv_prop: bpy.props.BoolProperty(default=False)
    other_prop: bpy.props.StringProperty()
    # etc


def register():
    ... # your other register code
    bpy.types.Mesh.subdiv_props = bpy.props.PointerProperty(type=MeshSubdivProp)

Then in the operator you can check if the mesh has been subdivided like:

def execute(self, context):
    custom_props = me.subdiv_props
    if custom_props.subdiv_prop:
        # your code

Or you can go simpler and create a pointer to a single property (e.g. IntProperty, BoolProperty, etc.) without the whole PropertyGroup.

$\endgroup$
2
  • $\begingroup$ Thanks for the response. I am new and learning python. On what lines do I place the following 2 codes? 1 def execute(self, context): custom_props = me.subdiv_props if custom_props.subdiv_prop: # your code and 2 class MeshSubdivProp(bpy.types.PropertyGroup): subdiv_prop: bpy.props.BoolProperty(default=False) other_prop: bpy.props.StringProperty() # etc $\endgroup$
    – 3vanse
    Commented Oct 2, 2022 at 9:07
  • $\begingroup$ Don't place those anywhere. These are just examples. Look at the register function of the example that @Sietse Brouwer posted. You need to register the property as a custom property of the mesh object. Like def register(): bpy.types.Mesh.subdiv_prop = bpy.props.BoolProperty(default=False) or something like that. In my example I created a PropertyGroup but you can use an IntProperty/BoolProperty like in the other example. $\endgroup$
    – Jakemoyo
    Commented Oct 3, 2022 at 10:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .