Python API, changes to type registration in 2.8 #52599
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset System
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Viewport & EEVEE
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Asset Browser Project
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Module
Viewport & EEVEE
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Severity
High
Severity
Low
Severity
Normal
Severity
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
5 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#52599
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Recently D2774 was committed to master which replaces list lookups with a hash.
This exposed naming collisions in add-ons, at first I thought it reasonable to rename classes to account for this however considering how many 3rd party addons there are, it's not practical.
This raises the question of why we have a global name-space for registered classes at all (currently in
bpy.types
) at all.This tasks outlines changes to registration that avoid problems with add-on naming collisions, and keeps class registration manageable.
Note: none of these changes apply to 2.7x releases.
General Proposal
STRUCT_RUNTIME
) types are added tobpy.types
.For known types - access them from the module that defines them (as you would in any other kind of class in Python).
For introspection/scanning for all types use
bpy.types.*.__subclasses__()
(needed for generating docs for eg).No checks for naming collisions are performed but...
Each type has a unique identifier that can collide. (for example, two operators can't have the same
bl_idname
, two RenderEngine's ... UIList's etc)On collision with dynamic types with matching ID's. The new types will overwrite the old ones.
While it might be good to change this to prevent accidents, it's been working for years, it's handy for Python developers who want to re-run their scripts to try modified behavior, we could make this more strict so scripts don't accidentally clobber eachother's ID's, this would be better to handle separately.
Option 1) Exception for Compatibility (now in master)
From searching over add-ons I only found only one case of an add-on referencing it's own class via
bpy.types
so thats easily resolved.
The issue is there are classes that are accessed to extend Blender, mainly menu's but also panels and headers.
We could update scripts to replace
bpy.types.INFO_MT_file_export
withbl_ui.space_info.INFO_MT_file_export
,but this is going to break a lot of scripts and exposes Blender's internal module layout.
Currently register-able types are:
I'm proposing
[Menu, Panel, Header, UIList, Operator]
continue to be accessible frombpy.types
under theirbl_idname
(not Python class-name).so scripts can use them to manipulate the interface.
We'll have to ensure
bl_idname
conventions are followed*_MT_*
,*_PT_*
,*_HT_*
,*_UL_*
,*_OT_*
.(this is going to break some scripts, but means using a global name-space won't collide with different types).
Option 2) Expose general types via
bpy.types.*.find(...)
Instead of keeping some classes in
bpy.types
, we could use an function. eg:bpy.types.INFO_MT_file_export
would be accessed asbpy.types.Menu.find("INFO_MT_file_export")
This has the advantage that we don't need to be strict about naming, it also simplifies the code (not having to track public/private structs).
The main disadvantage is scripts will need to be updated, however it will be quite straightforward and nearly all scripts will need some updates for 2.8x anyway.
Update: submitted patch: D2816 (option 1)
Changed status to: 'Open'
Added subscribers: @ideasman42, @mont29, @Sergey
Python API, changes to type registrationto Python API, changes to type registration in 2.8Added subscriber: @JacquesLucke
Here are a few ideas that I have on the topic:
For every register-able type Blender could store a hash table (
bl_idname -> cls
).So whenever you call
bpy.utils.register_class(MyClass)
, the type is inserted into the corresponding hash table.Registered types can than be accessed like this:
bpy.types.TYPE.get(bl_idname)
This would eliminate the need for strange
bl_idname
conventions which I personally do not like at all.The only naming rule that should be enforced (imo) is that
bl_idname
s should be valid Python identifiers. This also makes the system more future proof if we decide to use a different access pattern later on.I'm not sure if it is good that
Operator
s are handled in a special way (thebl_idname
needs exactly one.
somewhere in the middle, while on both sides are valid Python identifiers) but I guess that can't really be changed easily.We could also have
bpy.types.TYPE.get_all()
to get all registered subclasses of a type. The problem withTYPE.__subclasses__()
is that there can be subclasses that are not registered.Just wanted to share a few different ideas on the reloading issue as well although I'm not fully convinced by them.
register_class(cls, group = "")
. This group string (which does not have to be set by the developer) is stored in the hash table next to the class. The hash table would then look like this:bl_idname -> (cls, group)
. Whenever a new type is registered, the group is checked. If it is equal,cls
is replaced, otherwise an exception is raised. Types that are not part of a group (group = ""
) cannot be reloaded at all. Thegroup
should usually be the addon name, but can also be just a random string. It only has to stay constant within one execution of Blender.(btw: Also
Node
is register-able)Added subscriber: @satishgoda1
We have per type hash maps for almost all registerable types already (except the new PropertyGroup).
While writing the proposal I considered supporting:
The main reason against this is it would break a lot of scripts, (anything that touches the file-menu - so import/exporters).
For 2.8x perhaps this is OK, I'd rather avoid large breakages for minimal gains.
We need operators names the way they are so they can be accessed as
bpy.ops.xxx.xxx()
.Scripts can check the subclass is registered by calling
cls.is_registered()
.Not sure about each type needing a group, in general though Im not against something like what you're suggesting. I just rather not make that change part of this proposal.
The benefit of using
bpy.types.Menu.find(NAME)
overbl_ui.space_info.NAME
is that the first already contains the type information. That means thatNAME
does not have to contain any type information anymore. In my opinion this is highly preferable (works well with having onebl_idname
namespace per type instead of a global namespace).I think updating existing scripts can mostly be automated here as well. By using
bl_ui.space_info.NAME
NAME
stil has to be unique across multiple types. Maybe I also understood that wrong, I didn't know about thebl_ui
module before.Ah good to know. I haven't found that in the api docs.
Alright, that can be discussed later.
My main incentive here is that I don't want to use these
__MT__
, ... conventions. :D@JacquesLucke, While
bl_ui
is a regular Python module it's currently not documented as part of the API, I'd rather not expose it publicly.Added the option for an accessor function as
Option 2
in the proposal.I’d go for option 1 (“I'd rather avoid large breakages for minimal gains.” -> 100% agree!).
And strict (naming) conventions are actually a big +1 from my side, if we had had them in the first place we would not have hit those collisions issues at all. It’s pretty stupid to write two public classes with same name, at best it's confusing, and unless you have a real strict and good control over your scopes it will bite you at one point or the other. Python coders tend to be sloppy, see quality of code in many existing addons, losing conventions will only makes things worse.
@ideasman42 scripts like i18n messages extraction etc. are already using
bpy.types.*.__subclasses__()
trick anyway, at least due to stupid operators (iirc, their definition generates two classes with same name, one for operator, one for its properties, and inbpy.types.foo
you only have access to the propertygroup one, or something like that).Added subscriber: @VukGardasevic
I'm supporting conventions in naming.
bl_idname
and the registered class name should follow the same pattern, i.e. the bl_idname and the class name can be guessed correctly from each other. Plus the class name should reflect the internal structure one.Currently while editing the an add-on, in the class overview, there can be different naming conventions inside the same script. This makes comprehension, debugging difficult, not to mention accessing them from search.
Example from contrib (one add-on that had a naming collision)
In that example class
RemuevePropiedades
is an Operator,View3DMCPanel
is a stub class for inheritance.The problem is as something is not an clear simple rule that is mandatory, people don't think it is important and they spent very little time thinking about the naming conventions. The result is a mess (for bl_idname some add-ons use context like
object.some_operator
, some use an name of add-on or an abbreviation of it liketerrain.some_operator
,opr.some_operator
,sky.dyn
or something remotely related to the functionality likeadd.some_operator
).Still, everything depends on extensiveness of other API changes. If most of the add-ons end up breaking ( for instance accessing selection, layers are changed, add-ons relying on Blender Internal etc.) then it makes sense to impose naming rules during the refactor/rewrite of them. If the changes only affect a smaller number of add-ons, then some other solutions are also legitimate (allowing similar naming as today as an exemption to the rule for third party add-ons).
Also +1 for strict naming conventions. :)
I'm not against naming conventions. However I'm against knowledge duplication in code. Eg: when I already say that a specific class is a Panel, then I don't need to put that information into the identifier.
If you want that the identifier and class name can be guessed from each other, there just should not be both but only one string in the first place.
I agree that the operator categories are a mess in many addons. I think there are multiple reasons for that:
I'm not saying that the current operator naming "rules" are bad but that they don't work well for larger addons. I'm not sure yet what the best way is to solve this.
True, if you are the only person who writes addons. As an addon developer you don't have control over other addon developers.
Addons also need access to that data, so the two statements don't work well together. (or not?)
Speaking from an addon developer perspective (I don't know anything about the internals):
Blender is responsible for making sure that addon developers can feel save and that naming collisions with other addons are impossible. And by that I mean that it should not be enforced by convention but by data structure.
This would be the ideal goal we should strive for. None of the proposals above archieve this goal yet. I don't know if it is possible to archieve this goal for Blender 2.8, if not, we should try to get close IMO.
Changed status from 'Open' to: 'Resolved'
Committed (Option 1)
636baa598a
&0bbae3f3f6
Currently you do, because it's accessed via
bpy.types
, moving elsewhere could work but we opted for Option 1.This is the case, Python developers don't need to define an
bl_idname
in this case the class name will be used.We could have a convention that all addons use
ADDON_ID_##_own_name
,effectively a (category, name) tuple.
Right, so a convention would be helpful.
Not sure what you mean. Addon have access to it via
bpy.types
.There are many areas in computing where avoiding collisions is done by conventions - commands in your $PATH, Python module names, key bindings between your OS and applications... etc.
I see what you're saying, but blender is not just a python library, all the logic has C internals and is defined in C. This means in some cases Python API follows C conventions.
While its possible to have an extra level of categories - this is a big change - then every reference to an operator would need to be "category.subcategory.toolname" or so.
At the moment I think its not very practical, and we can manage the problem using conventions.
I was wondering, how are the types selected that require
_MT_
, ... in thebl_idname
? Because atm this convention only exists for some types, right? Most of the types you mention in your initial post don't require this (yet), correct?I'm not sure how a convention helps here. It might even increase the likelyhood that two developers name different operators the same.
If the add-on module name is included in the name not so much since it has to be unique for it to be activated properly (without the duplicates warning).
Also on the other side, having a prefix like that, it'll make search and replace much easier. :)
Having to think only about an unique name of the module to be used inside the prefix is less of a possible surface area for collisions.
Of course bl_idname complicates things a bit.
Let's say the the prefix is :
ADDON_ID_MATERIALS_UTILS_add_material
what the bl_idname would be? Maybematerials_utils.add_material
?Some of the concerns can be addressed by developer tool / add-on where it can be easily enabled by the user and do a one button check for collisions.
That could be part of the package manager - like an option for checking for conflicts > output to a file like system info.