[{"data":1,"prerenderedAt":230},["ShallowReactive",2],{"\u002F2020-09-26-blender-plugin-note":3},{"id":4,"title":5,"body":6,"date":207,"description":12,"extension":208,"meta":209,"navigation":212,"path":224,"seo":225,"stem":226,"tags":227,"__hash__":229},"blogs\u002F_legacy\u002F2020\u002F2020-09-26-blender-plugin-note.md","Blender插件制作笔记",{"type":7,"value":8,"toc":196},"minimark",[9,13,16,19,22,27,30,39,42,45,48,51,62,65,68,74,77,80,83,89,92,96,99,102,109,112,115,118,121,124,130,133,140,143,156,159,165,168,171,174,177,180,184,187,193],[10,11,12],"p",{},"这里记录的都是Blender相关的，和Python基础错误关系不大。",[10,14,15],{},"当前的blender版本为2.83.5 。",[10,17,18],{},"暂时打算停留在lts版本上，主干的开发速度太快了，跟不上节奏",[10,20,21],{},"blender的api文档有些难以查询，有的时候想要特定的功能反而直接求助于搜索引擎比较快……",[23,24,26],"h2",{"id":25},"ui禁用","UI禁用",[10,28,29],{},"对于有些UI需要禁用而不是不绘制的，直接操作UI的enabled属性就可以了。",[10,31,32,33],{},"UILayeout相关的文档可以在这里看到：",[34,35,36],"a",{"href":36,"rel":37},"https:\u002F\u002Fdocs.blender.org\u002Fapi\u002Fcurrent\u002Fbpy.types.UILayout.html",[38],"nofollow",[23,40,41],{"id":41},"类型注册",[10,43,44],{},"所有的blender相关的类型必须经过register_class才能使用，要留意在插件解除注册的时候同时unregister_class。",[10,46,47],{},"对于需要注册的属性也同样，属性可以注册在Object上，这样每个物品都会有。注册在Scene上的属性只有Scene会有。",[10,49,50],{},"从别人的插件里面看到，把class放到一个列表里面统一操作的方式比较好：",[52,53,58],"pre",{"className":54,"code":56,"language":57},[55],"language-text","def register():\n    from bpy.utils import register_class\n    for cls in classes:\n        register_class(cls)\n\ndef unregister():\n    from bpy.utils import unregister_class\n    for cls in reversed(classes):\n        unregister_class(cls)\n","text",[59,60,56],"code",{"__ignoreMap":61},"",[23,63,64],{"id":64},"物品选择",[10,66,67],{},"对于想要选择的物品，在UI中想要限定类型的话，需要在注册PointerProperty的时候指定",[52,69,72],{"className":70,"code":71,"language":57},[55],"map_target: bpy.props.PointerProperty(\n    type=bpy.types.Object,\n    name=\"FollowTarget\",\n    poll=lambda self, obj: obj.type == 'ARMATURE' and obj != bpy.context.object\n    )\n",[59,73,71],{"__ignoreMap":61},[10,75,76],{},"这样的话，在进行属性显示时使用layout.prop就能只选择ARMATURE类型了。",[23,78,79],{"id":79},"属性",[10,81,82],{},"属性的变更需要注册update",[52,84,87],{"className":85,"code":86,"language":57},[55],"source_follow_rotation: BoolProperty(\n    update=OnSourceFollowTypeChange, default=True)\n\n\n",[59,88,86],{"__ignoreMap":61},[10,90,91],{},"update传入时的self就是这个属性本身，很方便。",[23,93,95],{"id":94},"uilist","UIList",[10,97,98],{},"ui基本上按照固定的流程来操作就不会有问题。",[10,100,101],{},"遇到问题比较多的是UIList，这个文档描述有些不清晰，导致使用上很多靠猜，而Python又没有类型检查，真的很苦恼。",[10,103,104,105],{},"官方的文档在这里：",[34,106,107],{"href":107,"rel":108,"title":107},"https:\u002F\u002Fdocs.blender.org\u002Fapi\u002Fcurrent\u002Fbpy.types.UIList.html",[38],[10,110,111],{},"主要的问题是对初次接触的人很不友好，如果你和我一样对Python也不是很熟悉的话会一头雾水。原因是第一个例子太简单，而第二个例子太复杂。",[10,113,114],{},"其实，对于UIList而言，一般只要重载draw_item就可以了，在里面可以和其他UI一样执行绘制。",[10,116,117],{},"如果需要过滤功能的话，默认的就可以了。但是如果你需要过滤的属性不是name的话，才需要重写filter_items，不然过滤会不成功。",[10,119,120],{},"如果需要添加一些自定义的过滤的话，才需要最复杂的处理：额外的重载draw_filter来绘制额外的属性，在filter_itmes根据属性进行过滤。",[10,122,123],{},"列表示例：",[52,125,128],{"className":126,"code":127,"language":57},[55],"# ListBox\n# Show list of retarget cell\nclass QM_UL_ControlCell(UIList):\n    \"\"\"\n    List box for retarget cells, this is the list in side panel\n    \"\"\"\n    # Constants (flags)\n    # Be careful not to shadow FILTER_ITEM!\n    VGROUP_EMPTY = 1 \u003C\u003C 0\n\n    # Custom properties, saved with .blend file.\n    use_filter_name_reverse: bpy.props.BoolProperty(name=\"Reverse Name\", default=False, options=set(),\n                                                    description=\"Reverse name filtering\")\n\n    use_order_name: bpy.props.BoolProperty(name=\"Name\", default=False, options=set(),\n                                           description=\"Sort groups by their name (case-insensitive)\")\n\n    use_filter_linked: bpy.props.BoolProperty(name=\"Linked\", default=False, options=set(),\n                                              description=\"Filter linked only\")\n\n    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):\n        row = layout.row()\n\n        subrow = row.row(align=True)\n        subrow.prop(item, \"source_name\", text=\"\", emboss=False)\n\n        subrow = subrow.row(align=True)\n        if len(item.target_name) > 0:\n            subrow.prop(item, \"source_follow_location\", text=\"\",\n                        toggle=True, icon=\"CON_LOCLIKE\")\n            subrow.prop(item, \"source_follow_rotation\", text=\"\",\n                        toggle=True, icon=\"CON_ROTLIKE\")\n\n    def invoke(self, context, event):\n        pass\n\n    def draw_filter(self, context, layout):\n        # Nothing much to say here, it's usual UI code...\n        row = layout.row()\n\n        subrow = row.row(align=True)\n        subrow.prop(self, \"filter_name\", text=\"\")\n        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'\n        subrow.prop(self, \"use_filter_name_reverse\", text=\"\", icon=icon)\n\n        icon = 'LINKED' if self.use_filter_linked else 'UNLINKED'\n        subrow.prop(self, \"use_filter_linked\", text=\"\", icon=icon)\n\n        subrow = layout.row(align=True)\n        subrow.label(text=\"Order by:\")\n        subrow.prop(self, \"use_order_name\", toggle=True)\n\n    # Filter\n    # 1. we use [source_name] not [name]\n    # 2. provide filter for targeted cell\n    def filter_items(self, context, data, propname):\n        statelist = getattr(data, propname)\n        helper_funcs = bpy.types.UI_UL_list\n\n        # Default return values.\n        flt_flags = []\n        flt_neworder = []\n\n        # Filtering by name\n        if self.filter_name:\n            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, statelist, \"source_name\",\n                                                          reverse=self.use_filter_name_reverse)\n        if not flt_flags:\n            flt_flags = [self.bitflag_filter_item] * len(statelist)\n\n        # Filtering cell with target\n        if self.use_filter_linked:\n            for i, item in enumerate(statelist):\n                if len(item.target_name) == 0:\n                    flt_flags[i] &= ~self.bitflag_filter_item\n\n        # Reorder by name\n        if self.use_order_name:\n            flt_neworder = helper_funcs.sort_items_by_name(\n                statelist, \"source_name\")\n\n        return flt_flags, flt_neworder\n\n",[59,129,127],{"__ignoreMap":61},[10,131,132],{},"最终结果是这样的：",[10,134,135],{},[136,137],"img",{"alt":138,"src":139},"image","\u002Fwp-content\u002Fuploads\u002F2020\u002F09\u002Fimage_thumb-24.png",[10,141,142],{},"主要起作用的其实就是",[144,145,146,150,153],"ul",{},[147,148,149],"li",{},"draw_item: 绘制单个列表",[147,151,152],{},"draw_filter: 绘制下面的过滤区域，这个不指定也会有一个默认的",[147,154,155],{},"filter_item: 用于实际对列表的元素进行过滤",[10,157,158],{},"在列表绘制时可以指定列表元素和选中的index：",[52,160,163],{"className":161,"code":162,"language":57},[55],"layout.template_list(\"QM_UL_ControlCell\", \"\",\n    qm_state, \"quickmap_celllist\",\n    qm_state, \"quickmap_celllist_index\"\n    )\n",[59,164,162],{"__ignoreMap":61},[10,166,167],{},"这样就可以在选中指定列表元素时根据index的update来得到通知了。",[23,169,170],{"id":170},"文件读写",[10,172,173],{},"python对json的支持比较好，读写很方便。",[10,175,176],{},"文件本身需要让operator继承ExportHelper和ImportHelper才能分别进行blender封装好的文件操作。",[10,178,179],{},"本身很简单，网上随便就能找到例子，这里就不列出占位置了。",[23,181,183],{"id":182},"collection","Collection",[10,185,186],{},"这部分操作有点迷，在网上抄了一段代码直接用就好了：",[52,188,191],{"className":189,"code":190,"language":57},[55],"# Collection Operate\ndef make_collection(collection_name):\n    if collection_name in bpy.data.collections:  # Does the collection already exist?\n        return bpy.data.collections[collection_name]\n    else:\n        new_collection = bpy.data.collections.new(collection_name)\n        bpy.context.scene.collection.children.link(\n            new_collection)  # Add the new collection under a parent\n        return new_collection\n",[59,192,190],{"__ignoreMap":61},[10,194,195],{},"主要是collection的注册关系需要留意，不过别人封好了就不用管了~",{"title":61,"searchDepth":197,"depth":198,"links":199},2,3,[200,201,202,203,204,205,206],{"id":25,"depth":197,"text":26},{"id":41,"depth":197,"text":41},{"id":64,"depth":197,"text":64},{"id":79,"depth":197,"text":79},{"id":94,"depth":197,"text":95},{"id":170,"depth":197,"text":170},{"id":182,"depth":197,"text":183},"2020-09-26","md",{"layout":210,"status":211,"published":212,"author":213,"author_login":215,"author_email":216,"wordpress_id":217,"wordpress_url":218,"date_gmt":219,"excerpt":220},"post","publish",true,{"display_name":214,"login":215,"email":216,"url":61},"风铃","flinkor","flinkor@foxmail.com",3062,"\u002F?p=3062","2020-09-26 13:07:30 +0000",{"type":7,"value":221},[222],[10,223,12],{},"\u002F2020-09-26-blender-plugin-note",{"title":5,"description":12},"_legacy\u002F2020\u002F2020-09-26-blender-plugin-note",[228],"Blender","vSpYGo9iGu31hKuBi00WOCDKGYNSEyUQhAZypHcrca8",1788763177477]