Would anybody be willing to comment on my (short) addon code?

Hello everybody,

I created my first (simple but, to me, very useful) addon. Since I think other people might find it useful I’d like to publish it somewhere./ But since I’m an absolute beginner in Blender-land I don’t want to spread bad code :slight_smile:

Would someone be willing to check out my (simple, 1 file ) add on and comment on it/ point out obvious errors and the like?

The add on adds an operator to blender to project the current active camera background image onto the mesh, and it also adds an extra button to the ‘options’ panel when texture painting.

Things I’d like to know:

  • in bl_info there should also be a “location” field. But I have no clue how to know what to put there.
  • in the operator class I used “camera.projectcamerabackgroundimage” for the bl_id. I just made this up on the spot and it seems to work, but I don’t really have a clue if the “camera.” part is correct, and what it means.
  • for bl_options in the operator I used {“UNDO”}, should I also add {“REGISTER”}, it’s not really clear to me what that means. Also, can I just set the “UNDO” option without implementing anything for it? Why is it even optional?
  • anything other which is bad/wrong :slight_smile:

Known issues

  • The (already existing) ‘Apply Camera Image’ gives a nice ‘missing textures detected’ warning if there is no target texture. I couldn’t be arsed to add that because Then I’d need to find out how to detect that situation and it took me long enough to get this far. Maybe later.
Code
bl_info = {
    "name" : "Project active camera background image.",
    "author" : "Martijn Versteegh",
    "version" : (1, 0),
    "blender" : (2, 80, 0),
    "description" : "Project the active camera background image onto the mesh from the camera.",
    "warning" :"",
    "doc_url" : "",
    "tracker_url" : "",
    "category" : "Paint",
}


import bpy

class ProjectActiveCameraBackgroundImage(bpy.types.Operator) :
    """Project the active Camera's Background Image onto the current mesh."""
    bl_idname = "camera.projectcamerabackgroundimage"
    bl_label = "Project Active Camera Background Image"
    bl_options = { "UNDO" }
    
    
    
    def execute(self, context) :
        if context.mode != 'PAINT_TEXTURE' :
            return { "CANCELLED" }
        
        if not context.scene.camera :
            return { "CANCELLED" }
        
        camera = context.scene.camera
        if camera.data.background_images.items() :
            bgimage = camera.data.background_images[0]
        else:
            return { "CANCELLED" }
        bpy.ops.paint.project_image(image=bgimage.image.name)
        return { "FINISHED" }
    #end execute
    
    def invoke(self, context, events) :
        return self.execute(context)
    #end invoke()
    
    @classmethod
    def poll(cls, context) :
        if not context.mode == 'PAINT_TEXTURE':
            return False
        if not context.scene.camera :
            return False
        
        if context.scene.camera.data.background_images.items() :
            return True
        
        return False
    #end poll
#end class


def draw_button(self, context) :
    layout = self.layout
    layout.operator("camera.projectcamerabackgroundimage", text = "Apply Camera BG Image")

def register() :
    bpy.utils.register_class(ProjectActiveCameraBackgroundImage)
    bpy.types.VIEW3D_PT_tools_imagepaint_options_external.append(draw_button)
#end register()    

def unregister() :
    bpy.types.VIEW3D_PT_tools_imagepaint_options_external.remove(draw_button)
    bpy.utils.unregister_class(ProjectActiveCameraBackgroundImage)
#end unregister

#if we are run as a script, register out class    
if __name__ == "__main__":
    #unregister()
    register()

unrelated to your actual code, you can use three backticks and get a properly syntax formatted text block here on devtalk.

my comments/suggestions are inline, but keep in mind that writing code is a bit like writing prose- there is a lot of subjectivity involved so at the end of the day you have to pick and choose the things you like.

bl_info = {
    "name" : "Project active camera background image.",
    "author" : "Martijn Versteegh",
    "version" : (1, 0),
    "blender" : (2, 80, 0),
    "description" : "Project the active camera background image onto the mesh from the camera.",
    "warning" :"",
    "doc_url" : "",
    "tracker_url" : "",
    "category" : "Paint",
}

# pep-8 would recommend that you have imports at the top of your script, above any 
# locally scoped variables (such as the bl_info dictionary)
import bpy

# this is a doozy of a class name. what about a shorter alternative: ProjectCameraBackground?
class ProjectActiveCameraBackgroundImage(bpy.types.Operator) :
    """Project the active Camera's Background Image onto the current mesh."""

    # doozy of a class name produces a doozy of an operator idname, and the word
    # camera makes a double appearance. how about camera.project_background?
    bl_idname = "camera.projectcamerabackgroundimage"

    # here's where you can use that long class name, now if there were any doubts about
    # what your class does, it's explained here. and if this isn't enough space to do it, there's
    # always bl_description (which you are currently not using).
    bl_label = "Project Active Camera Background Image"

    bl_options = { "UNDO" }
    
  
    def execute(self, context) :
        # context.mode can never be anything besides PAINT_TEXTURE because you have a poll()
        # classmethod that will prevent it from happening. This can be removed.
        if context.mode != 'PAINT_TEXTURE' :
            return { "CANCELLED" }
        
        # same here:
        if not context.scene.camera :
            return { "CANCELLED" }
        
        camera = context.scene.camera
        # pep-8 would not approve of an unecessary whitespace before your colon:
        if camera.data.background_images.items() : 
            bgimage = camera.data.background_images[0]
        else:
            return { "CANCELLED" }
        bpy.ops.paint.project_image(image=bgimage.image.name)
        return { "FINISHED" }
    #end execute
    
    def invoke(self, context, events) :
        return self.execute(context)
    #end invoke()
    
    @classmethod
    def poll(cls, context) :
        # this is the syntactic equivilent to a double-negative.
        # instead, try this:
        # if context.mode != 'PAINT_TEXTURE':
        if not context.mode == 'PAINT_TEXTURE':
            return False

        # watch that extra white space! it's a good idea to install a linter to prevent little issues like this
        # from sneaking up and causing an error:
        if not context.scene.camera :
            return False
        
        # and if you wonder why I keep mentioning it- it's because Python is very whitespace sensitive.
        # it may not matter in this specific situation, but in others it can potentially change the scope of
        # your variables, cause compile errors, etc.
        if context.scene.camera.data.background_images.items() :
            return True
        
        return False
    #end poll
#end class


def draw_button(self, context) :
    layout = self.layout
    layout.operator("camera.projectcamerabackgroundimage", text = "Apply Camera BG Image")

def register() :
    bpy.utils.register_class(ProjectActiveCameraBackgroundImage)
    bpy.types.VIEW3D_PT_tools_imagepaint_options_external.append(draw_button)
#end register()    

def unregister() :
    bpy.types.VIEW3D_PT_tools_imagepaint_options_external.remove(draw_button)
    bpy.utils.unregister_class(ProjectActiveCameraBackgroundImage)
#end unregister

#if we are run as a script, register out class    
if __name__ == "__main__":
    #unregister()
    register()

hope this helps!

oh- and to answer a few of your questions:

in bl_info there should also be a “location” field. But I have no clue how to know what to put there.

This is optional, it’s the location where the user could find your addon after they installed it. IE) in the mesh menu, or something along those lines.

in the operator class I used “camera.projectcamerabackgroundimage” for the bl_id. I just made this up on the spot and it seems to work, but I don’t really have a clue if the “camera.” part is correct, and what it means.

the operator idname can be whatever you want it to be. you can make your own up, or even hijack existing built-in operator idnames (which I wouldn’t recommend unless you have a reason for doing so). If your operator does stuff with a camera, camera is as good a prefix as any- though one might argue that all cameras are part of view3d, so view3d.whatever might make more sense, but ultimately it’s your call.

for bl_options in the operator I used {“UNDO”}, should I also add {“REGISTER”}, it’s not really clear to me what that means. Also, can I just set the “UNDO” option without implementing anything for it? Why is it even optional?

without REGISTER your addon will not work with the “repeat last action” behavior in Blender. Typically, if your operator is something a user might want to do over and over again with the same settings, use REGISTER, otherwise skip it. A good example would be duplicate/move. the user duplicates an object and moves it 3m, then uses the “repeat” behavior to make multiple copies that are all 3m away from each other. for your addon it’s probably not necessary.

1 Like

Thanks a lot! Exactly what I was looking for.

I knew there was something :slight_smile: . Pity it’s not listed in the icons in the top row of the post editor.

I’m not sure what ‘doozy’ means (English is not my first language) but I guess you think it’s too verbose :smiley:

This is one of those subjective things. I myself very much prefer overly verbose class names. That’s what tab completion is for after all :slight_smile: But The ‘Camera’ might be a bit redundant indeed. I’ll make it slightly shorter.

The long operator id is probably good to make shorter, since that gets exposed.

I’m not really a python coder, I copied the style with the extra space before the colon from examples. I’ll change it to be pep8 compliant. I’ll have a look at the pep8 style, that might be useful to do :slight_smile:

I’ll experiment with the ‘repeat last action’. It might be useful in this operator.

Thanks a lot for your comments! I’ll take your comments to heart.

What would be a good place to post such an add-on? is there an official
add-on directory somewhere?

regards,

nope, not really- most people just put their addons on github and then start a thread over on blenderartists.org with a link.