Hvordan oprettes et brugerdefineret brugergrænseflade?

Jeg så denne video for at lave en brugerdefineret UI-fane ved hjælp af Python og i linjen from bpy.types import Menu, Panel, UIList, jeg ser navnene på objekter, der ser ud som om de alle kan bruges til at lave forskellige brugergrænsefladeelementer. Indeholder bpy.types alle de objekter, som folk bruger, når de vil lave brugerdefinerede brugergrænseflade-elementer?

Kommentarer

  • En type er en klasse, der kan bruges som en skabelon til yderligere klasser ved arv, f.eks class MyPanel(bpy.types.Panel): blender.org/api/blender_python_api_current/bpy.types.html . Mit forslag er at se gennem ” Teksteditor > skabeloner > python > Ui * ” eksempler, der følger med blender.

Svar

Design af en brugergrænseflade eller tilføjelse er grundlæggende en kombination af levering af egenskaber og arv af indbyggede type klasser (Panel, operatør , Menu osv.).


Egenskaber

Start med først at definere dine Egenskaber . Egenskaber er dybest set “datatyper” og kan vises i brugergrænsefladen for grundlæggende brugerinteraktion. Bemærk, at du kan få adgang til værdien for hver ejendom næsten overalt. For at udfylde en komplet liste i konsollen skal du bruge python “s dir() -metoden på bpy.props :

indtast billedebeskrivelse her Klik for at forstørre

Ejendomsudseende

Definition af egenskab

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 ) ... 

Bemærk, at fra og med Blender 2.8x , egenskaber skal tildeles variabler u syng et enkelt kolon : i stedet for den sædvanlige tildelingsoperator = som i Blender 2.7x eller ældre versioner af Blender.


Typer

Indholdet af bpy.types er klasseskabeloner bygget til arv.

Panel

Paneler er overalt i Blender, så det er det mest basale element i brugergrænsefladen. panelet skal bruges er defineret af bl_space_type . Blanders grænseflade er “kontekstafhængig”, så du kan definere bl_context for at få panelet i en respektiv tilstand (Objekttilstand, redigeringstilstand osv.) .

indtast billedbeskrivelse her

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) 

Underpaneler

indtast billedbeskrivelse her

Fra Blender 2.8x kan vi have underpaneler ved at tildele et bestemt panel (overordnet) til 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() 

Se også: Teksteditor> Skabeloner> Python > UI-panel .

Operatør

Operatør er den vigtigste bit at forstå. Du kan vise “operatorer” som Knapper og en gang registreret, og du kan ringe til det overalt via bpy.ops.IDNAME(). Det er også den måde, som blender er designet til, alle ægte knapper er “operatører” under emhætten, for det meste skrevet i C men derefter udsat for python. Se også: Teksteditor> Skabeloner> Python > Operatør … .

indtast billedbeskrivelse her

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() 

Operatørklasseskabelonen leveres med foruddefinerede metoder , faktisk poll, invoke, execute, draw, modal og cancel, som kan bruges langs brugerdefinerede egenskaber til alle forskellige slags operationer og også til at give brugerinteraktion. Mere komplet eksempel på en operatør :

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") 

Yderligere læsning: Sådan gør du kalde en dialogboks til bekræftelse? (for enormt farlige operatører ).

Menu

For en brugerdefineret menu skal du definere / arve en Menuklasse . Føj dine operatører og egenskaber til draw() -funktionen korrekt (se også: Teksteditor> Skabeloner> Python> UI-menuskabeloner) .

indtast billedebeskrivelse her

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) 

Du kan også tegne en knap for at ringe til menuen uden at erklære nogen ekstra operatør af layout.operator("wm.call_menu").name="bl_idname"

Undermenu

For at få en undermenu skal du ringe til den anden i overordnet menu via layout.menu(bl_idname) .

indtast billedebeskrivelse her

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" ... 

Alt i en

Når du opretter en tilføjelse, kræves der normalt mange egenskaber. For en bedre organisation kan du oprette en “indstillingsklasse” ved hjælp af en PropertyGroup . Bemærk det fra Blender 2.8x , egenskaber skal tildeles ved hjælp af et enkelt kolon : i stedet for den sædvanlige tildelingsoperator =.

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() ... 

Også fra Blender 2.8x-modul / klasse registrering er ændret for at forhindre navnekonflikter. bpy.utils.register_module(__name__) er ikke tilgængelig mere, så du dybest set skal registrere / afregistrere hver klasse separat eller inden for en loop (bedste praksis):

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) 

Bemærk at der ud over den gamle navngivning konventioner skal du også tilføje en seperator som _OT_, _MT_ eller _PT_ til navnet på din klasse baseret på den arvede klassetype (Operator, Menu, Panel).


Skabelon til tilføjelse

Følgende tilføjelse tilføjer et brugerdefineret panel til Værktøjshylde i 3D-visning og udskriver de aktuelle “brugerværdier” for alle brugerdefinerede egenskaber til konsollen:

indtast billedbeskrivelse her

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() 

Gist: https://gist.github.com/p2or/2947b1aa89141caae182526a8fc2bc5a


Mere komplekse eksempler

Kommentarer

  • Godhed, dette var virkelig grundigt! Der er ‘ langt mere til Python UI end bare bpy.types, så haha. Tak!
  • Dette er en utrolig kortfattet og klar oversigt, der ville være fantastisk som en del af dokumentet. Det tog mig uger at finde ud af dette på mange, mange eksempler.
  • Meget bedre end officielt dokument. Du reddede min dag!

Svar

ændret version – til 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() 

Nogle ændringer foretaget for blender 2.80 version:

  • Egenskaber er nu kommentarer, tildelt : ikke =
  • bl_region_type er nu “UI” ikke “TOOLS”
  • Registreringsproceduren er ændret:
    • Brug bpy.utils.register_class() ikke register_module()

Flere oplysninger: python api blender 2,80

Kommentarer

  • OMG. Mange tak. Jeg lærer python i blender. Jeg koder meget i VB.net & C #. Kæmpede med UI-tingene. Dette forklarede det meget godt, og
  • hej, jeg vil gerne vide, hvad scene.my_tool henviste til? fordi jeg ikke kunne se, hvor det blev defineret første gang?
  • Åh nu får jeg det, så jeg hv at registrere en pointeregenskab for at gemme, siger nogle andre egenskaber?

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *