Here are three major areas into which I tend to categorize parts of my scripts:
Operators, Properties, and Ui.
Depending on your needs, any single item of which can be made to entirely suffice. (Achieve goal using only ops, only props, or only ui) Usually a combination of all three, but not always necessary.
You can have operators, with no properties, and no ui.
You can have properties – and make use of them w/o necessarily needing an interface or full-fledged operators.
You could have an interface, such as a panel or menu, display whatever properties you wish, without needing to define any of said properties.
Thus I see these (at least) three distinct areas – The Operators, The Properties, and The User Interface.
Its best to keep them clear in your head while you work on them, lest tasks get mis-delegated to unsuited items.
Add to the list of code-areas, what I call ‘The Idiom’. It should be familiar by now:
if __name__ == '__main__':
Incidentally, I don't keep a lot of code snippets around. Besides this one.
So, in continuing to break standard blender scripting down into discrete areas, we have the registration area, at the bottom, and of course at the very top, the lines where you import bpy and optionally, other modules.
That gives us:
- User Interface
Remarks on 1 (importing)
There are a lot of different ways to do it, but the line:
from . import foo
seems to work for me, assuming foo.py lives in the same folder as the script doing the importing.
Remarks on 5 (Registration)
Things *can* get messy here, as a sign that you may be doing it wrong. To keep it clean, group properties together so you can register them all at once.
bar = bpy.props.BoolProperty()
baz = bpy.props.IntProperty()
bpy.types.Object.foo = bpy.props.PointerProperty(type=foo)
When you have a lot of properties, this method keeps the registration area from getting cluttered.
Remarks on 2 (Properties)
The update handlers are extremely useful. Their definitions obviously group with the properties they accompany, prefixed to the properties area.
Properties? Properties of what? Thise is a common issue. Yes, of course you can give tons of things properties, and you must use common sense in deciding what goes where. You can assign properties to the scene, objects, OPERATORS and... Well my point is that AFAIK, one doesn't give properties to UI classes. ( read: bpy.types.Panel )
Remarks on 4: (User Interface)
Not a lot of logic goes here. It is just 'layout'. You may use if blocks to control what gets drawn, but a @poll class method is better suited for that type of logic.
You'll see a lot of:
layout = self.layout
row = layout.row()
-- or --
For testing little bits of interface code in the console, I occasionally use a dirty little one-liner lambda and skip all the extras:
Functions such as these do not need to be registered, nor will they persist from session to session.
Remarks on 3 (Operators)
Operators can have properties and they can define their own interface code.
In basic form, skipping the Importation and Registration:
bl_label = ‘foo’
bl_idname = ‘bar.foo’
So you have a foo class. Nothing happening yet. Simply defining a class does nothing. Should do nothing. Register the class with blender and what happens? Look in bpy.types for your answer, and since this is an operator, bpy.ops also.
bpy.types.BAR_OT_foo where BAR is the uppercased name of the submodule or fake module-like class to which foo belongs, and _OT_ indicates that this is an operator type class.
bpy.ops.bar.foo where bar represents said submodule and foo is the 'operator' which 'belongs' to it.
If you wish to have more control over the naming of the class, you explicitly name your classes according to this convention, otherwise expect that blender will name registered classes according to this system.
The bl_idname 'bar.foo' contains enough information for blender to do this. It isn't obvious that one may use whatever name one might wish, in place of bar -- reading through example scripts, the majority use 'object' or 'mesh' or 'scene' as appropriate, but if you wish to group your operators according to your own 'fake module-like class', go right ahead. No need for the code to live all in one file or even addon, for them to all share in common one thing: their bl_idname have same prefix before the dot. (something unique to them, after the dot.)
In your interface code, to lay out a button for an operator:
To make button call with certain prop hardcoded in ui:
self.layout.operator('foo.bar').n = 1
an op with a prop:
bl_label = ‘foo’
bl_idname = ‘bar.foo’
n = bpy.props.IntProperty()
For an operator to use its draw method it has to get called. Panels do not 'draw what the operator says it wants to look like'. Panels don't particularly care what the operator does, much less what it looks like. Operators get buttons. There is a lot of flexibility for labels and icons and properties, but in the end there is just a button. The fact that operators can have draw method may lead one to wonder, HOW IS DRAW?
also, WHERE IS DRAW? there are various places where an active_operator should show up. Foremost, the left-side pane in the 3d view which opens by the letter t. at the bottom is where you always see the properties of the active operator.
Other spaces have similar tool panels as well.
The set of bl_options for the operator should contain 'REGISTER', first of all. Then when the operator is run, blender will know to put it in the place where operators can draw or are drawn. If there are any properties to the operator, they will automatically have associated ui elements created for them, so that there is no need to be explicit about how to draw. The draw method is for when you wish to customize the display of the operator, and it doesn't work any differently from how a panel's draw method works.