Extensions:2.6/Py/API Changes

提供: wiki
< Extensions:2.6‎ | Py
2018年6月29日 (金) 05:53時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

API Changes

This page shall give addon developers advice on how to update their scripts to make them work again after major changes to the Blender Python API.

If your specific problem isn't covered here, feel free to inform the community about it in the forum.

You may also have a look at the auto-generated API change log for minor things that changed - but keep in mind that it may be incomplete!


2.66

Template List (r53355)

UI's template_list() was refactored to be much more flexible and give real control over items' drawing. A new RNA type was introduced for this, bpy.types.UIList, making lists independent from Panels. This UIList has a draw_item() callback which allows to customize items' drawing from python, that all addons can now use.

If your addon uses the old API for a simple list, it's easy to update:

# OLD
template_list(bpy.context.scene, "your_collection", bpy.context.scene, "your_collection_idx")

# NEW
template_list('UI_UL_list', 'your_collection_id', bpy.context.scene, "your_collection", bpy.context.scene, "your_collection_idx")

For simple lists, it isn't necessary to derive a custom class from UIList type. Instead, simply put 'UI_UL_list' (for a default drawing method for the list items) as first argument and a list ID string as second. Chose something unique for the ID, preferably a brief description of what your list contains. If another script used the default 'UI_UL_list' and the same ID (maybe a blank string), Blender would not be able to distinguish between both lists!


For in-depth explanation, e.g. how to create custom draw_item callbacks, see the API docs: UIList, UILayout.template_list.


Region Draw Callbacks (r53207)

Region drawing callbacks were changed to work much closer to how blender manages them internally. The change was necessary to fix bug T30740.

To update old code, you need to replace the Region.callback_add() and Region.callback_remove() calls. The new way of adding/removing a draw callback is to call a Space type's draw_handler_add() and draw_handler_remove() functions. A common Space type is the 3D View, bpy.types.SpaceView3D. For a list of Space types, see subclasses in API docs - bpy.types.Space.


The deprecated callback_add() expected 3 arguments, whereas draw_handler_add() requires 4. You now need to specify the Space's region type (string) as 3rd parameter. It is also required on callback removal:

# OLD
self._handle = context.region.callback_add(draw_callback_px, (self, context), 'POST_PIXEL')
context.region.callback_remove(self._handle)

# NEW
CLASSNAME._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
bpy.types.SpaceView3D.draw_handler_remove(CLASSNAME._handle, 'WINDOW')
# To store the handle, the class should be used rather than the class instance (you may use self.__class__),
# so we can access the variable from outside, e.g. to remove the drawback on unregister()


Below you find a comparison of the old and the new API usage:

class ScreencastKeysStatus(bpy.types.Operator):
    bl_idname = "view3d.screencast_keys"
    bl_label = "Screencast Keys"
    bl_description = "Display keys pressed in the 3D View"

    _handle = None
    _timer = None














    def modal(self, context, event):
        # ...

        if not context.window_manager.screencast_keys_keys:
            # stop script
            context.window_manager.event_timer_remove(self._timer)
            context.region.callback_remove(self._handle)
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def cancel(self, context):
        if context.window_manager.screencast_keys_keys:
            context.window_manager.event_timer_remove(self._timer)
            context.region.callback_remove(self._handle)
            context.window_manager.screencast_keys_keys = False
        return {'CANCELLED'}

    def invoke(self, context, event):
        # ...
                self._handle = context.region.callback_add(draw_callback_px,
                    (self, context), 'POST_PIXEL')
                self._timer = context.window_manager.event_timer_add(0.075,
                    context.window)

# ...
def unregister():

# ...
class ScreencastKeysStatus(bpy.types.Operator):
    bl_idname = "view3d.screencast_keys"
    bl_label = "Screencast Keys"
    bl_description = "Display keys pressed in the 3D View"

    _handle = None
    _timer = None

    @staticmethod
    def handle_add(self, context):
        ScreencastKeysStatus._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
        ScreencastKeysStatus._timer = context.window_manager.event_timer_add(0.075, context.window)

    @staticmethod
    def handle_remove(context):
        if ScreencastKeysStatus._handle is not None:
            context.window_manager.event_timer_remove(ScreencastKeysStatus._timer)
            bpy.types.SpaceView3D.draw_handler_remove(ScreencastKeysStatus._handle, 'WINDOW')
        ScreencastKeysStatus._handle = None
        ScreencastKeysStatus._timer = None

    def modal(self, context, event):
        # ...

        if not context.window_manager.screencast_keys_keys:
            # stop script

            ScreencastKeysStatus.handle_remove(context)
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def cancel(self, context):
        if context.window_manager.screencast_keys_keys:

            ScreencastKeysStatus.handle_remove(context)
            context.window_manager.screencast_keys_keys = False
        return {'CANCELLED'}

    def invoke(self, context, event):
        # ...



                ScreencastKeysStatus.handle_add(self, context)

# ...
def unregister():
    ScreencastKeysStatus.handle_remove(bpy.context)
# ...


Note: If you don't use an event timer, remove the argument "context" from handle_remove(), in staticmethod definition and in the call inside unregister(). Or omit the static methods and call the draw_handler_* functions directly, as seen in the Operator Modal Draw template.

Restricted Context

To avoid errors on accessing blend-file data while addons register() / unregister(), Blender now restricts access to bpy.context and bpy.data.

This is done because there is no assurance that the data loaded when the addon is registered will be active or even exist when the user accesses operators the addon defines.

If you see an exception like this, then the addon needs to be updated to access the context during execution rather then on registration:

AttributeError: '_RestrictContext' object has no attribute 'scene'

Bad code example:

def register():
    bpy.types.Scene.my_prop = bpy.props.StringProperty()
    bpy.context.scene.my_prop = "foobar" # can't access bpy.context here!

Such initialization needs to be done when an operator runs the first time. It's also possible to provide a special init operator, shown as button in a panel.

Init operator example:

import bpy

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

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

        # If Scene.my_prop wasn't created in register() or removed, draw a note
        if not hasattr(context.scene, "my_prop"):
            layout.label("Scene does not have a property 'my_prop'")

        # If it has no longer the default property value, draw a label with icon
        elif context.scene.my_prop != 'default value':
            layout.label("my_prop = " + context.scene.my_prop, icon="FILE_TICK")

        # It has the default property value, draw a label with no icon
        else:
            layout.label("my_prop = " + context.scene.my_prop)

        layout.operator(InitMyPropOperator.bl_idname, text=InitMyPropOperator.bl_label)

class InitMyPropOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "scene.init_my_prop"
    bl_label = "Init my_prop"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        if context.scene.my_prop != "initialized":
            context.scene.my_prop = "initialized"
            self.__class__.bl_label = "Change my_prop"
        else:
            context.scene.my_prop = "foobar"
            self.__class__.bl_label = self.bl_label
        return {'FINISHED'}


def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.my_prop = bpy.props.StringProperty(default="default value")

def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.my_prop

if __name__ == "__main__":
    register()

Also see: mailing list thread


2.67

Node Identifiers

The type enum property of nodes is no longer the primary way of identifying node types. Instead nodes now have a bl_idname property (like other registerable types such as operators, menus, etc.). Also see: Python Nodes - API Changes

The bl_idname must be passed to the nodetree.nodes.new method in particular for identifying the node type. The old type property still exists, but should be avoided as much as possible. Scripters are encouraged to convert their use of explicit identifiers to the new system. The full list of which enum type maps to which bl_idname can be found below (most names are pretty obvious though). A quick way of getting the correct identifier is typing

node.bl_idname

in the Blender console.

Shader Nodes
type bl_idname
ADD_SHADER ShaderNodeAddShader
AMBIENT_OCCLUSION ShaderNodeAmbientOcclusion
ATTRIBUTE ShaderNodeAttribute
BACKGROUND ShaderNodeBackground
BRIGHTCONTRAST ShaderNodeBrightContrast
BSDF_ANISOTROPIC ShaderNodeBsdfAnisotropic
BSDF_DIFFUSE ShaderNodeBsdfDiffuse
BSDF_GLASS ShaderNodeBsdfGlass
BSDF_GLOSSY ShaderNodeBsdfGlossy
BSDF_REFRACTION ShaderNodeBsdfRefraction
BSDF_TRANSLUCENT ShaderNodeBsdfTranslucent
BSDF_TRANSPARENT ShaderNodeBsdfTransparent
BSDF_VELVET ShaderNodeBsdfVelvet
BUMP ShaderNodeBump
CAMERA ShaderNodeCameraData
COMBRGB ShaderNodeCombineRGB
CURVE_RGB ShaderNodeRGBCurve
CURVE_VEC ShaderNodeVectorCurve
EMISSION ShaderNodeEmission
FRESNEL ShaderNodeFresnel
GAMMA ShaderNodeGamma
GEOMETRY ShaderNodeGeometry
GROUP ShaderNodeGroup
HAIR_INFO ShaderNodeHairInfo
HOLDOUT ShaderNodeHoldout
HUE_SAT ShaderNodeHueSaturation
INVERT ShaderNodeInvert
LAYER_WEIGHT ShaderNodeLayerWeight
LIGHT_FALLOFF ShaderNodeLightFalloff
LIGHT_PATH ShaderNodeLightPath
MAPPING ShaderNodeMapping
MATERIAL ShaderNodeMaterial
MATERIAL_EXT ShaderNodeExtendedMaterial
MATH ShaderNodeMath
MIX_RGB ShaderNodeMixRGB
MIX_SHADER ShaderNodeMixShader
NEW_GEOMETRY ShaderNodeNewGeometry
NORMAL ShaderNodeNormal
NORMAL_MAP ShaderNodeNormalMap
OBJECT_INFO ShaderNodeObjectInfo
OUTPUT ShaderNodeOutput
OUTPUT_LAMP ShaderNodeOutputLamp
OUTPUT_MATERIAL ShaderNodeOutputMaterial
OUTPUT_WORLD ShaderNodeOutputWorld
PARTICLE_INFO ShaderNodeParticleInfo
RGB ShaderNodeRGB
RGBTOBW ShaderNodeRGBToBW
SCRIPT ShaderNodeScript
SEPRGB ShaderNodeSeparateRGB
SQUEEZE ShaderNodeSqueeze
SUBSURFACE_SCATTERING ShaderNodeSubsurfaceScattering
TANGENT ShaderNodeTangent
TEXTURE ShaderNodeTexture
TEX_BRICK ShaderNodeTexBrick
TEX_CHECKER ShaderNodeTexChecker
TEX_COORD ShaderNodeTexCoord
TEX_ENVIRONMENT ShaderNodeTexEnvironment
TEX_GRADIENT ShaderNodeTexGradient
TEX_IMAGE ShaderNodeTexImage
TEX_MAGIC ShaderNodeTexMagic
TEX_MUSGRAVE ShaderNodeTexMusgrave
TEX_NOISE ShaderNodeTexNoise
TEX_SKY ShaderNodeTexSky
TEX_VORONOI ShaderNodeTexVoronoi
TEX_WAVE ShaderNodeTexWave
VALTORGB ShaderNodeValToRGB
VALUE ShaderNodeValue
VECT_MATH ShaderNodeVectorMath


Compositor Nodes
type bl_idname
ALPHAOVER CompositorNodeAlphaOver
BILATERALBLUR CompositorNodeBilateralblur
BLUR CompositorNodeBlur
BOKEHBLUR CompositorNodeBokehBlur
BOKEHIMAGE CompositorNodeBokehImage
BOXMASK CompositorNodeBoxMask
BRIGHTCONTRAST CompositorNodeBrightContrast
CHANNEL_MATTE CompositorNodeChannelMatte
CHROMA_MATTE CompositorNodeChromaMatte
COLORBALANCE CompositorNodeColorBalance
COLORCORRECTION CompositorNodeColorCorrection
COLOR_MATTE CompositorNodeColorMatte
COLOR_SPILL CompositorNodeColorSpill
COMBHSVA CompositorNodeCombHSVA
COMBRGBA CompositorNodeCombRGBA
COMBYCCA CompositorNodeCombYCCA
COMBYUVA CompositorNodeCombYUVA
COMPOSITE CompositorNodeComposite
CROP CompositorNodeCrop
CURVE_RGB CompositorNodeCurveRGB
CURVE_VEC CompositorNodeCurveVec
DBLUR CompositorNodeDBlur
DEFOCUS CompositorNodeDefocus
DESPECKLE CompositorNodeDespeckle
DIFF_MATTE CompositorNodeDiffMatte
DILATEERODE CompositorNodeDilateErode
DISPLACE CompositorNodeDisplace
DISTANCE_MATTE CompositorNodeDistanceMatte
DOUBLEEDGEMASK CompositorNodeDoubleEdgeMask
ELLIPSEMASK CompositorNodeEllipseMask
FILTER CompositorNodeFilter
FLIP CompositorNodeFlip
GAMMA CompositorNodeGamma
GLARE CompositorNodeGlare
GROUP CompositorNodeGroup
HUECORRECT CompositorNodeHueCorrect
HUE_SAT CompositorNodeHueSat
ID_MASK CompositorNodeIDMask
IMAGE CompositorNodeImage
INPAINT CompositorNodeInpaint
INVERT CompositorNodeInvert
KEYING CompositorNodeKeying
KEYINGSCREEN CompositorNodeKeyingScreen
LENSDIST CompositorNodeLensdist
LEVELS CompositorNodeLevels
LUMA_MATTE CompositorNodeLumaMatte
MAP_RANGE CompositorNodeMapRange
MAP_UV CompositorNodeMapUV
MAP_VALUE CompositorNodeMapValue
MASK CompositorNodeMask
MATH CompositorNodeMath
MIX_RGB CompositorNodeMixRGB
MOVIECLIP CompositorNodeMovieClip
MOVIEDISTORTION CompositorNodeMovieDistortion
NORMAL CompositorNodeNormal
NORMALIZE CompositorNodeNormalize
OUTPUT_FILE CompositorNodeOutputFile
PIXELATE CompositorNodePixelate
PREMULKEY CompositorNodePremulKey
RGB CompositorNodeRGB
RGBTOBW CompositorNodeRGBToBW
ROTATE CompositorNodeRotate
SCALE CompositorNodeScale
SEPHSVA CompositorNodeSepHSVA
SEPRGBA CompositorNodeSepRGBA
SEPYCCA CompositorNodeSepYCCA
SEPYUVA CompositorNodeSepYUVA
SETALPHA CompositorNodeSetAlpha
SPLITVIEWER CompositorNodeSplitViewer
STABILIZE2D CompositorNodeStabilize
SWITCH CompositorNodeSwitch
TEXTURE CompositorNodeTexture
TIME CompositorNodeTime
TONEMAP CompositorNodeTonemap
TRACKPOS CompositorNodeTrackPos
TRANSFORM CompositorNodeTransform
TRANSLATE CompositorNodeTranslate
VALTORGB CompositorNodeValToRGB
VALUE CompositorNodeValue
VECBLUR CompositorNodeVecBlur
VIEWER CompositorNodeViewer
ZCOMBINE CompositorNodeZcombine


Texture Nodes
type bl_idname
AT TextureNodeAt
BRICKS TextureNodeBricks
CHECKER TextureNodeChecker
COMPOSE TextureNodeCompose
COORD TextureNodeCoordinates
CURVE_RGB TextureNodeCurveRGB
CURVE_TIME TextureNodeCurveTime
DECOMPOSE TextureNodeDecompose
DISTANCE TextureNodeDistance
GROUP TextureNodeGroup
HUE_SAT TextureNodeHueSaturation
IMAGE TextureNodeImage
INVERT TextureNodeInvert
MATH TextureNodeMath
MIX_RGB TextureNodeMixRGB
OUTPUT TextureNodeOutput
RGBTOBW TextureNodeRGBToBW
ROTATE TextureNodeRotate
SCALE TextureNodeScale
TEXTURE TextureNodeTexture
TEX_BLEND TextureNodeTexBlend
TEX_CLOUDS TextureNodeTexClouds
TEX_DISTNOISE TextureNodeTexDistNoise
TEX_MAGIC TextureNodeTexMagic
TEX_MARBLE TextureNodeTexMarble
TEX_MUSGRAVE TextureNodeTexMusgrave
TEX_NOISE TextureNodeTexNoise
TEX_STUCCI TextureNodeTexStucci
TEX_VORONOI TextureNodeTexVoronoi
TEX_WOOD TextureNodeTexWood
TRANSLATE TextureNodeTranslate
VALTONOR TextureNodeValToNor
VALTORGB TextureNodeValToRGB
VIEWER TextureNodeViewer

The tables above were generated with a python script. This may need to be rerun in case nodes are added and missing in these lists:

import bpy

trees = [
    ('ShaderNodeTree', 'ShaderNode', 'Shader Nodes'),
    ('CompositorNodeTree', 'CompositorNode', 'Compositor Nodes'),
    ('TextureNodeTree', 'TextureNode', 'Texture Nodes'),
    ]

for tree_idname, node_idname, type_category in trees:
    treetype = getattr(bpy.types, tree_idname)
    nodetype = getattr(bpy.types, node_idname)
#    print(treetype, nodetype)
    if not treetype or not nodetype:
        continue
    tree = bpy.data.node_groups.new("test", tree_idname)

    node_map = {}

    for c in nodetype.__subclasses__():
#        print(c)
        if hasattr(c, "bl_rna"):
            try:
                node = tree.nodes.new(c.bl_rna.identifier)
                node_map[node.type] = node.bl_idname
#                print("%s -> %s" % (node.type, c.bl_rna.identifier))
                shader_tree.nodes.remove(node)
            except:
                pass

    sorted_node_map = sorted(node_map.items(), key=lambda item: item[0])
    table = "{| {{Css/prettytable|75%%|talign=left}}\n| '''type''' || '''bl_idname'''\n|-\n%s|}" \
            % "|-\n".join("| %s || %s\n" % (key, value) for key, value in sorted_node_map)

    print("\n\n=====%s=====\n" % type_category)
    print(table)

2.70

Node Socket in_out (r60660)

This was an enum property with 2 modes 'IN' and 'OUT'. Now there is a boolean property is_output instead that can be used in the same way:

old:

socket.in_out == 'IN'  # is it an input socket?
socket.in_out == 'OUT' # is it an output socket?

new:

not socket.is_output   # is it an input socket?
socket.is_output       # is it an output socket?

Python Node Output Drawing (r60661)

Node output sockets now use the same button drawing function as input sockets, instead of always displaying just the label. This means pynodes scripters have to make a small adjustment in order to draw sockets in the established way, i.e. only showing labels for outputs:

     def draw(self, context, layout, node, text):
-        if self.is_linked:
+        if self.is_output or self.is_linked:
             layout.label(text)
         else:
             # draw full property buttons here