Je regardais cette vidéo sur la création dun onglet dinterface utilisateur personnalisé à laide de Python, et dans la ligne from bpy.types import Menu, Panel, UIList
, je vois les noms des objets qui semblent pouvoir tous être utilisés pour créer différents éléments dinterface utilisateur. bpy.types
contient-il tous les objets que les gens utilisent lorsquils souhaitent créer des éléments dinterface utilisateur personnalisés?
Commentaires
Réponse
La conception dune interface utilisateur ou dun module complémentaire est essentiellement une combinaison de la fourniture de propriétés et de lhéritage de classes de types intégrées (panneau, opérateur , Menu etc.).
Propriétés
Commencez par définir vos Propriétés en premier. Les propriétés sont essentiellement des « types de données » et peuvent être affichées dans linterface utilisateur pour une interaction utilisateur de base. Notez que vous pouvez accéder à la valeur de chaque propriété de presque partout. Pour remplir une liste complète dans la console, utilisez la méthode python « s dir()
sur bpy.props
:
Apparence de la propriété
- Définissez un
BoolProperty
pour une « case à cocher » - Définissez un
FloatProperty
ouIntegerProperty
pour obtenir un « Slider » - Définir un
StringProperty
pour chaque type de « caractère Entrée utilisateur » ou « Chemins daccès aux fichiers » - Définissez un
EnumProperty
pour obtenir un « Menu déroulant »
…
Définition de propriété
from bpy.props import (StringProperty, BoolProperty, IntProperty, FloatProperty, EnumProperty, ) my_bool : BoolProperty( name="Enable or Disable", description="A bool property", default = False ) my_int : IntProperty( name = "Set a value", description="A integer property", default = 23, min = 10, max = 100 ) ...
Notez quà partir de Blender 2.8x , propriétés doit être affecté aux variables u chantez un simple deux-points :
au lieu de lopérateur daffectation habituel =
comme dans Blender 2.7x ou les versions antérieures de Blender.
Types
Le contenu de bpy.types
sont des modèles de classe conçus pour lhéritage.
Panel
Les panneaux sont partout dans Blender, donc cest lélément le plus basique de linterface utilisateur. Où le panneau qui va être utilisé est défini par bl_space_type
. Linterface de Blenders est « contextuelle » afin que vous puissiez définir bl_context
pour obtenir le panneau dans un mode respectif (mode objet, mode dédition, etc.) .
class HelloWorldPanel(bpy.types.Panel): bl_idname = "OBJECT_PT_hello_world" bl_label = "Hello World" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" def draw(self, context): self.layout.label(text="Hello World") bpy.utils.register_class(HelloWorldPanel)
Sous-panneaux
À partir de Blender 2.8x, nous pouvons avoir sous-panneaux en attribuant un certain panneau (parent) à bl_parent_id
:
import bpy class HelloWorldPanel: bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Tools" bl_options = {"DEFAULT_CLOSED"} class HELLO_PT_World1(HelloWorldPanel, bpy.types.Panel): bl_idname = "HELLO_PT_World1" bl_label = "Panel 1" def draw(self, context): layout = self.layout layout.label(text="This is the main panel.") class HELLO_PT_World2(HelloWorldPanel, bpy.types.Panel): bl_parent_id = "HELLO_PT_World1" bl_label = "Panel 2" def draw(self, context): layout = self.layout layout.label(text="First Sub Panel of Panel 1.") class HELLO_PT_World3(HelloWorldPanel, bpy.types.Panel): bl_parent_id = "HELLO_PT_World1" bl_label = "Panel 3" def draw(self, context): layout = self.layout layout.label(text="Second Sub Panel of Panel 1.") classes = ( HELLO_PT_World1, HELLO_PT_World2, HELLO_PT_World3 ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in classes: bpy.utils.unregister_class(cls) if __name__ == "__main__": register()
Voir aussi: Éditeur de texte> Modèles> Python > Panneau UI .
Opérateur
L Opérateur est lélément le plus important à comprendre. Vous pouvez afficher les « opérateurs » sous forme de boutons et une fois enregistrés et vous pouvez lappeler de partout via bpy.ops.IDNAME()
. C « est aussi la façon dont Blender est conçu, tous les vrais boutons sont des » opérateurs « sous le capot, principalement écrits en C mais ensuite exposés à python. Voir aussi: Editeur de texte> Modèles> Python > Opérateur … .
class HelloWorldMinimal(bpy.types.Operator): bl_idname = "wm.hello_world" bl_label = "Minimal Operator" def execute(self, context): # Report "Hello World" to the Info Area self.report({"INFO"}, "Hello World") return {"FINISHED"} bpy.utils.register_class(HelloWorldMinimal) # test call the operator bpy.ops.wm.hello_world()
Le modèle de classe Operator est fourni avec des méthodes prédéfinies , en fait poll
, invoke
, execute
, draw
, modal
et cancel
qui peuvent être utilisés avec des propriétés personnalisées pour tous les types dopérations et aussi pour permettre une interaction avec lutilisateur. Exemple plus complet dun opérateur :
class HelloWorld(bpy.types.Operator): bl_idname = "wm.hello_world" bl_label = "Minimal Operator" bl_options = {"REGISTER"} # Operator user properties, should be assigned using a single colon : # instead of using an equal sign = in Blender 2.8 report_flag: bpy.props.BoolProperty( name = "Report", default = True) @classmethod # Will never run when poll returns false def poll(cls, context): return context.object def invoke(self, context, event): # Used for user interaction wm = context.window_manager return wm.invoke_props_dialog(self) def draw(self, context): # Draw options (typically displayed in the tool-bar) row = self.layout row.prop(self, "report_flag", text="Report Hello World") def execute(self, context): # Runs by default if self.report_flag: self.report({"INFO"}, "Hello World") else: print ("Hello World") return {"FINISHED"} bpy.utils.register_class(HelloWorld) # For interaction, pass "INVOKE_DEFAULT" when calling # the operator, this way invoke runs before execute method bpy.ops.wm.hello_world("INVOKE_DEFAULT")
Lectures complémentaires: Comment faire appeler une boîte de dialogue de confirmation? (pour les opérateurs extrêmement dangereux ).
Menu
Pour un menu personnalisé, définissez / héritez dune Classe de menu . Ajoutez correctement vos opérateurs et propriétés à la fonction draw()
(voir aussi: Éditeur de texte> Modèles> Python> Modèles de menu dinterface utilisateur) .
class SimpleCustomMenu(bpy.types.Menu): bl_label = "Simple Custom Menu" bl_idname = "OBJECT_MT_simple_custom_menu" def draw(self, context): layout = self.layout layout.operator("wm.open_mainfile") layout.operator("wm.save_as_mainfile") # The menu can also be called from scripts bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)
Vous pouvez également dessiner un bouton pour appeler le menu sans déclarer dopérateur supplémentaire par layout.operator("wm.call_menu").name="bl_idname"
Sous-menu
Pour obtenir un sous-menu, appelez le second dans le menu parent via layout.menu(bl_idname)
.
class MyCustomMenu(bpy.types.Menu): bl_label = "First Menu" bl_idname = "OBJECT_MT_custom_menu" def draw(self, context): layout = self.layout layout.label(text="Hello First Menu!", icon="WORLD_DATA") # call the second custom menu layout.menu("OBJECT_MT_sub_menu", icon="COLLAPSEMENU") class MyCustomSubMenu(bpy.types.Menu): bl_label = "Sub Menu" bl_idname = "OBJECT_MT_sub_menu" def draw(self, context): layout = self.layout layout.label(text="Hello Second Menu!", icon="WORLD_DATA") # call another predefined menu layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map" # draw a button within the panel to call the first menu class OBJECT_PT_my_panel(bpy.types.Panel): ... def draw(self, context): layout.operator("wm.call_menu", text="Call My Menu").name = "OBJECT_MT_custom_menu" ...
Tout-en-un
Lors de la création dun module complémentaire, de nombreuses propriétés sont généralement requises. Pour une meilleure organisation, vous pouvez créer une « classe de paramètres » en utilisant un PropertyGroup . Notez quà partir de Blender 2.8x , les propriétés doivent être attribuées en utilisant un simple deux-points :
au lieu de lopérateur daffectation habituel =
.
2,7x
class MySettings(PropertyGroup): my_bool = BoolProperty() my_int = IntProperty() my_float = FloatProperty() ...
2,8x
class MySettings(PropertyGroup): my_bool: BoolProperty() my_int: IntProperty() my_float: FloatProperty() ...
Aussi, à partir du module Blender 2.8x / lenregistrement de la classe a été modifié pour éviter les conflits de nom. bpy.utils.register_module(__name__)
nest plus disponible, vous devez donc enregistrer / désenregistrer chaque classe séparément ou dans une boucle (meilleure pratique):
classes = ( WM_OT_HelloWorld, OBJECT_PT_CustomPanel, ) def register(): from bpy.utils import register_class for cls in classes: register_class(cls) def unregister(): from bpy.utils import unregister_class for cls in reversed(classes): unregister_class(cls)
Notez quen plus de lancien nom conventions , vous devez également ajouter un séparateur comme _OT_
, _MT_
ou _PT_
au nom de votre classe en fonction du type de classe hérité (Operator
, Menu
, Panel
).
Modèle de module complémentaire
Le module complémentaire suivant ajoute un panneau personnalisé dans Tool Shelf de la Vue 3D et imprime les « valeurs utilisateur » actuelles de toutes les propriétés personnalisées sur la console:
Blender 2.7x
bl_info = { "name": "Add-on Template", "description": "", "author": "", "version": (0, 0, 2), "blender": (2, 70, 0), "location": "3D View > Tools", "warning": "", # used for warning icon and text in addons panel "wiki_url": "", "tracker_url": "", "category": "Development" } import bpy from bpy.props import (StringProperty, BoolProperty, IntProperty, FloatProperty, EnumProperty, PointerProperty, ) from bpy.types import (Panel, Operator, PropertyGroup, ) # ------------------------------------------------------------------------ # Scene Properties # ------------------------------------------------------------------------ class MySettings(PropertyGroup): my_bool = BoolProperty( name="Enable or Disable", description="A bool property", default = False ) my_int = IntProperty( name = "Int Value", description="A integer property", default = 23, min = 10, max = 100 ) my_float = FloatProperty( name = "Float Value", description = "A float property", default = 23.7, min = 0.01, max = 30.0 ) my_string = StringProperty( name="User Input", description=":", default="", maxlen=1024, ) my_enum = EnumProperty( name="Dropdown:", description="Apply Data to attribute.", items=[ ("OP1", "Option 1", ""), ("OP2", "Option 2", ""), ("OP3", "Option 3", ""), ] ) # ------------------------------------------------------------------------ # Operators # ------------------------------------------------------------------------ class WM_OT_HelloWorld(bpy.types.Operator): bl_idname = "wm.hello_world" bl_label = "Print Values Operator" def execute(self, context): scene = context.scene mytool = scene.my_tool # print the values to the console print("Hello World") print("bool state:", mytool.my_bool) print("int value:", mytool.my_int) print("float value:", mytool.my_float) print("string value:", mytool.my_string) print("enum state:", mytool.my_enum) return {"FINISHED"} # ------------------------------------------------------------------------ # Menus # ------------------------------------------------------------------------ class OBJECT_MT_CustomMenu(bpy.types.Menu): bl_idname = "object.custom_menu" bl_label = "Select" def draw(self, context): layout = self.layout # Built-in example operators layout.operator("object.select_all", text="Select/Deselect All").action = "TOGGLE" layout.operator("object.select_all", text="Inverse").action = "INVERT" layout.operator("object.select_random", text="Random") # ------------------------------------------------------------------------ # Panel in Object Mode # ------------------------------------------------------------------------ class OBJECT_PT_CustomPanel(Panel): bl_idname = "object.custom_panel" bl_label = "My Panel" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" bl_category = "Tools" bl_context = "objectmode" @classmethod def poll(self,context): return context.object is not None def draw(self, context): layout = self.layout scene = context.scene mytool = scene.my_tool layout.prop(mytool, "my_bool") layout.prop(mytool, "my_enum", text="") layout.prop(mytool, "my_int") layout.prop(mytool, "my_float") layout.prop(mytool, "my_string") layout.operator("wm.hello_world") layout.menu(OBJECT_MT_CustomMenu.bl_idname, text="Presets", icon="SCENE") layout.separator() # ------------------------------------------------------------------------ # Registration # ------------------------------------------------------------------------ def register(): bpy.utils.register_module(__name__) bpy.types.Scene.my_tool = PointerProperty(type=MySettings) def unregister(): bpy.utils.unregister_module(__name__) del bpy.types.Scene.my_tool if __name__ == "__main__": register()
Gist:
https://gist.github.com/p2or/a00bdde9f2751940717a404cf977dd01
Blender 2.8x
bl_info = { "name": "Add-on Template", "description": "", "author": "p2or", "version": (0, 0, 3), "blender": (2, 80, 0), "location": "3D View > Tools", "warning": "", # used for warning icon and text in addons panel "wiki_url": "", "tracker_url": "", "category": "Development" } import bpy from bpy.props import (StringProperty, BoolProperty, IntProperty, FloatProperty, FloatVectorProperty, EnumProperty, PointerProperty, ) from bpy.types import (Panel, Menu, Operator, PropertyGroup, ) # ------------------------------------------------------------------------ # Scene Properties # ------------------------------------------------------------------------ class MyProperties(PropertyGroup): my_bool: BoolProperty( name="Enable or Disable", description="A bool property", default = False ) my_int: IntProperty( name = "Int Value", description="A integer property", default = 23, min = 10, max = 100 ) my_float: FloatProperty( name = "Float Value", description = "A float property", default = 23.7, min = 0.01, max = 30.0 ) my_float_vector: FloatVectorProperty( name = "Float Vector Value", description="Something", default=(0.0, 0.0, 0.0), min= 0.0, # float max = 0.1 ) my_string: StringProperty( name="User Input", description=":", default="", maxlen=1024, ) my_path: StringProperty( name = "Directory", description="Choose a directory:", default="", maxlen=1024, subtype="DIR_PATH" ) my_enum: EnumProperty( name="Dropdown:", description="Apply Data to attribute.", items=[ ("OP1", "Option 1", ""), ("OP2", "Option 2", ""), ("OP3", "Option 3", ""), ] ) # ------------------------------------------------------------------------ # Operators # ------------------------------------------------------------------------ class WM_OT_HelloWorld(Operator): bl_label = "Print Values Operator" bl_idname = "wm.hello_world" def execute(self, context): scene = context.scene mytool = scene.my_tool # print the values to the console print("Hello World") print("bool state:", mytool.my_bool) print("int value:", mytool.my_int) print("float value:", mytool.my_float) print("string value:", mytool.my_string) print("enum state:", mytool.my_enum) return {"FINISHED"} # ------------------------------------------------------------------------ # Menus # ------------------------------------------------------------------------ class OBJECT_MT_CustomMenu(bpy.types.Menu): bl_label = "Select" bl_idname = "OBJECT_MT_custom_menu" def draw(self, context): layout = self.layout # Built-in operators layout.operator("object.select_all", text="Select/Deselect All").action = "TOGGLE" layout.operator("object.select_all", text="Inverse").action = "INVERT" layout.operator("object.select_random", text="Random") # ------------------------------------------------------------------------ # Panel in Object Mode # ------------------------------------------------------------------------ class OBJECT_PT_CustomPanel(Panel): bl_label = "My Panel" bl_idname = "OBJECT_PT_custom_panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Tools" bl_context = "objectmode" @classmethod def poll(self,context): return context.object is not None def draw(self, context): layout = self.layout scene = context.scene mytool = scene.my_tool layout.prop(mytool, "my_bool") layout.prop(mytool, "my_enum", text="") layout.prop(mytool, "my_int") layout.prop(mytool, "my_float") layout.prop(mytool, "my_float_vector", text="") layout.prop(mytool, "my_string") layout.prop(mytool, "my_path") layout.operator("wm.hello_world") layout.menu(OBJECT_MT_CustomMenu.bl_idname, text="Presets", icon="SCENE") layout.separator() # ------------------------------------------------------------------------ # Registration # ------------------------------------------------------------------------ classes = ( MyProperties, WM_OT_HelloWorld, OBJECT_MT_CustomMenu, OBJECT_PT_CustomPanel ) def register(): from bpy.utils import register_class for cls in classes: register_class(cls) bpy.types.Scene.my_tool = PointerProperty(type=MyProperties) def unregister(): from bpy.utils import unregister_class for cls in reversed(classes): unregister_class(cls) del bpy.types.Scene.my_tool if __name__ == "__main__": register()
Contenu essentiel: https://gist.github.com/p2or/2947b1aa89141caae182526a8fc2bc5a
Exemples plus complexes
- Quelle interface utilisateur fonctionnerait pour choisir parmi une longue, longue liste?
- aperçus personnalisés du script dans un menu
- Créer une interface similaire à la zone de liste des matériaux
Commentaires
- Bon sang, cétait vraiment complet! Il y a ‘ beaucoup plus dans Python UI que juste bpy.types alors haha. Merci!
- Ceci est un aperçu incroyablement concis et clair qui serait formidable dans le cadre de la documentation. Il ma fallu des semaines pour comprendre cela sur de très nombreux exemples.
- Bien mieux quun document officiel. Vous avez sauvé ma journée!
Réponse
version modifiée – pour blender 2.80 !!!
# https://blender.stackexchange.com/q/57306/3710 # https://blender.stackexchange.com/q/79779/3710 # # modified for blender 2.80 # last modification: 2019-09-12 -- add custom-preferences panel -- Emanuel Rumpf -- bl_info = { "name": "Add-on Template", "description": "", "author": "", "version": (0, 0, 2), "blender": (2, 80, 0), "location": "3D View > Tools", "warning": "", # used for warning icon and text in addons panel "wiki_url": "", "tracker_url": "", "category": "Development" } """ This is an addon - template for blender 2.80 Use it as base for new addons. -- Some changes made for blender 2.80 version (from 2.79): - Properties are annotations now, assigned with : not = - bl_region_type now is "UI" not "TOOLS" - Registration procedure changed: Use bpy.utils.register_class() not register_module() More information see: python api blender 2.80 """ import bpy #import collections #import importlib #import mathutils #import math from bpy.utils import ( register_class, unregister_class ) from bpy.props import ( StringProperty, BoolProperty, IntProperty, FloatProperty, FloatVectorProperty, EnumProperty, PointerProperty, ) from bpy.types import ( Panel, AddonPreferences, Operator, PropertyGroup, ) # this must match the addon name, use "__package__" # when defining this in a submodule of a python package. addon_name = __name__ # when single file #addon_name = __package__ # when file in package # ------------------------------------------------------------------------ # settings in addon-preferences panel # ------------------------------------------------------------------------ # panel update function for PREFS_PT_MyPrefs panel def _update_panel_fnc (self, context): # # load addon custom-preferences print( addon_name, ": update pref.panel function called" ) # main_panel = OBJECT_PT_my_panel # main_panel .bl_category = context .preferences.addons[addon_name] .preferences.tab_label # re-register for update unregister_class( main_panel ) register_class( main_panel ) class PREFS_PT_MyPrefs( AddonPreferences ): """ Custom Addon Preferences Panel - in addon activation panel - menu / edit / preferences / add-ons """ bl_idname = addon_name tab_label: StringProperty( name="Tab Label", description="Choose a label-name for the panel tab", default="New Addon", update=_update_panel_fnc ) def draw(self, context): layout = self.layout row = layout.row() col = row.column() col.label(text="Tab Label:") col.prop(self, "tab_label", text="") # ------------------------------------------------------------------------ # properties visible in the addon-panel # ------------------------------------------------------------------------ class PG_MyProperties (PropertyGroup): my_bool : BoolProperty( name="Enable or Disable", description="A bool property", default = False ) my_int : IntProperty( name = "Int Value", description="A integer property", default = 23, min = 10, max = 100 ) my_float : FloatProperty( name = "Float Value", description = "A float property", default = 23.7, min = 0.01, max = 30.0 ) my_float_vector : FloatVectorProperty( name = "Float Vector Value", description="Something", default=(0.0, 0.0, 0.0), min= 0.0, # float max = 0.1 ) my_string : StringProperty( name="User Input", description=":", default="", maxlen=1024, ) my_enum : EnumProperty( name="Dropdown:", description="Apply Data to attribute.", items=[ ("OP1", "Option 1", ""), ("OP2", "Option 2", ""), ("OP3", "Option 3", ""), ] ) # ------------------------------------------------------------------------ # operators # ------------------------------------------------------------------------ class OT_HelloWorldOperator (bpy.types.Operator): bl_idname = "wm.hello_world" bl_label = "Print Values Operator" def execute(self, context): scene = context.scene mytool = scene.my_tool # print the values to the console print("Hello World") print("bool state:", mytool.my_bool) print("int value:", mytool.my_int) print("float value:", mytool.my_float) print("string value:", mytool.my_string) print("enum state:", mytool.my_enum) return {"FINISHED"} # ------------------------------------------------------------------------ # menus # ------------------------------------------------------------------------ class MT_BasicMenu (bpy.types.Menu): bl_idname = "OBJECT_MT_select_test" bl_label = "Select" def draw(self, context): layout = self.layout # built-in example operators layout.operator("object.select_all", text="Select/Deselect All").action = "TOGGLE" layout.operator("object.select_all", text="Inverse").action = "INVERT" layout.operator("object.select_random", text="Random") # ------------------------------------------------------------------------ # addon - panel -- visible in objectmode # ------------------------------------------------------------------------ class OBJECT_PT_my_panel (Panel): bl_idname = "OBJECT_PT_my_panel" bl_label = "My Panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Tool" # note: replaced by preferences-setting in register function bl_context = "objectmode" # def __init(self): # super( self, Panel ).__init__() # bl_category = bpy.context.preferences.addons[__name__].preferences.category @classmethod def poll(self,context): return context.object is not None def draw(self, context): layout = self.layout scene = context.scene mytool = scene.my_tool layout.prop( mytool, "my_bool") layout.prop( mytool, "my_enum", text="") layout.prop( mytool, "my_int") layout.prop( mytool, "my_float") layout.prop( mytool, "my_float_vector", text="") layout.prop( mytool, "my_string") layout.operator( "wm.hello_world") layout.menu( "OBJECT_MT_select_test", text="Presets", icon="SCENE") # ------------------------------------------------------------------------ # register and unregister # ------------------------------------------------------------------------ classes = ( PG_MyProperties, # OT_HelloWorldOperator, MT_BasicMenu, OBJECT_PT_my_panel, # PREFS_PT_MyPrefs, ) def register(): # for cls in classes: register_class(cls) # bpy.types.Scene.my_tool = PointerProperty(type=PG_MyProperties) # def unregister(): # for cls in reversed(classes): unregister_class(cls) # del bpy.types.Scene.my_tool # remove PG_MyProperties if __name__ == "__main__": pass #register()
Quelques modifications apportées pour la version blender 2.80:
- Les propriétés sont désormais des annotations, attribuées avec
:
et non=
-
bl_region_type
est désormais « UI » et non « TOOLS » - La procédure denregistrement a été modifiée:
- Utilisez
bpy.utils.register_class()
pasregister_module()
- Utilisez
Plus dinformations: python api blender 2.80
Commentaires
- OMG. Merci beaucoup. Japprends le python dans Blender. Je code beaucoup dans VB.net & C #. Se débattait avec les trucs de linterface utilisateur Cela la très bien expliqué et
- bonjour, je voudrais savoir à quoi
scene.my_tool
faisait référence? parce que je nai pas vu où il a été défini pour la première fois? - oh maintenant je comprends, donc je dois enregistrer une propriété de pointeur pour stocker, disons dautres propriétés?
class MyPanel(bpy.types.Panel):
blender.org/api/blender_python_api_current/bpy.types.html . Ma suggestion est de regarder à travers l » Éditeur de texte > templates > python > Ui * » exemples fournis avec blender.