Jak stworzyć niestandardowy interfejs użytkownika?

Oglądałem ten film na temat tworzenia niestandardowej karty interfejsu użytkownika w języku Python oraz w wiersz from bpy.types import Menu, Panel, UIList, widzę nazwy obiektów, które wyglądają, jakby można ich było użyć do tworzenia różnych elementów interfejsu użytkownika. Czy bpy.types zawiera wszystkie obiekty, których ludzie używają, gdy chcą tworzyć niestandardowe elementy interfejsu użytkownika?

Komentarze

  • Typ to klasa, która może służyć jako szablon dla dodatkowych klas w drodze dziedziczenia, np class MyPanel(bpy.types.Panel): blender.org/api/blender_python_api_current/bpy.types.html . Proponuję przejrzeć ” Edytor tekstu > szablony > python > Ui * ” przykłady dołączone do blendera.

Odpowiedź

Projektowanie interfejsu użytkownika lub dodatku jest w zasadzie połączeniem dostarczania właściwości i dziedziczenia wbudowanych klas typu (panel, operator , Menu itp.).


Właściwości

Zacznij od zdefiniowania najpierw swoich Właściwości . Właściwości są w zasadzie „typami danych” i mogą być wyświetlane w interfejsie użytkownika w celu podstawowej interakcji użytkownika. Pamiętaj, że możesz uzyskać dostęp do wartości każdej właściwości z niemal każdego miejsca. Aby wypełnić pełną listę w konsoli, użyj metody Pythona „s dir() na bpy.props :

tutaj wprowadź opis obrazu Kliknij, aby powiększyć

Wygląd właściwości

Definicja właściwości

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

Zwróć uwagę, że od Blendera 2.8x właściwości należy przypisać do zmiennych u zaśpiewaj pojedynczy dwukropek : zamiast zwykłego operatora przypisania = jak w Blenderze 2.7x lub starszych wersjach Blender.


Typy

Zawartość bpy.types to szablony klas stworzone do dziedziczenia.

Panel

Panele są wszędzie w Blenderze, więc jest to najbardziej podstawowy element interfejsu użytkownika. panel, który będzie używany, jest zdefiniowany przez bl_space_type . Interfejs Blendera jest „kontekstowy”, więc możesz zdefiniować bl_context , aby ustawić panel w jednym odpowiednim trybie (tryb obiektowy, tryb edycji itp.) .

tutaj wprowadź opis obrazu

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) 

Panele podrzędne

tutaj wprowadź opis obrazu

Od Blendera 2.8x możemy mieć podpanele , przypisując określony panel (nadrzędny) do 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() 

Zobacz też: Edytor tekstu> Szablony> Python > Panel UI .

Operator

Operator to najważniejszy element do zrozumienia. Możesz wyświetlać „operatory” jako Przyciski i po zarejestrowaniu, a także wywoływać je z dowolnego miejsca przez bpy.ops.IDNAME(). W ten sam sposób projektowany jest blender, wszystkie prawdziwe przyciski są „operatorami” pod maską, głównie napisanymi w C, ale następnie udostępnionymi w Pythonie. Zobacz też: Edytor tekstu> Szablony> Python > Operator … .

tutaj wprowadź opis obrazu

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

Szablon klasy operatora zawiera predefiniowane metody , w rzeczywistości poll, invoke, execute, draw, modal i cancel, których można używać razem z niestandardowymi właściwościami dla wszystkich różnych rodzajów operacji, a także w celu zapewnienia interakcji z użytkownikiem. Bardziej kompletny przykład operatora :

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

Więcej informacji: Jak wywołać okno dialogowe potwierdzenia? (dla niezwykle niebezpiecznych operatorów ).

Menu

W przypadku menu niestandardowego zdefiniuj / dziedzicz klasę menu . Dodaj swoje operatory i właściwości do draw() działającego poprawnie (zobacz też: Edytor tekstu> Szablony> Python> Szablony menu UI) .

tutaj wprowadź opis obrazu

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) 

Możesz także narysować przycisk wywołujący menu bez deklarowania żadnego dodatkowego operatora przez layout.operator("wm.call_menu").name="bl_idname"

Podmenu

Aby uzyskać podmenu, wywołaj drugie z menu nadrzędnego przez layout.menu(bl_idname) .

tutaj wprowadź opis obrazu

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

Wszystko w jednym

Podczas tworzenia dodatku zwykle wymaganych jest wiele właściwości. Dla lepszej organizacji możesz utworzyć „klasę ustawień” za pomocą PropertyGroup . Pamiętaj, że od Blendera 2.8x właściwości powinny być przypisane za pomocą pojedynczego dwukropka : zamiast zwykłego operatora przypisania =.

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

Również od modułu Blender 2.8x / rejestracja klasy została zmieniona , aby zapobiec konfliktom nazw. bpy.utils.register_module(__name__) nie jest już dostępny, więc zasadniczo musisz zarejestrować / wyrejestrować każdą klasę osobno lub w pętli (sprawdzona metoda):

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) 

Uwaga , że oprócz starego nazewnictwa konwencji musisz także dodać separator , na przykład _OT_, _MT_ lub _PT_ do nazwy klasy na podstawie odziedziczonego typu klasy (Operator, Menu, Panel).


Szablon dodatku

Poniższy dodatek dodaje panel niestandardowy do Tool Shelf w 3D View i drukuje bieżące „wartości użytkownika” wszystkich dostosowanych właściwości do konsoli:

tutaj wprowadź opis obrazu

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

Streszczenie:

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

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


Bardziej złożone przykłady

Komentarze

  • O Boże, to było naprawdę dokładne! ' jest dużo więcej w interfejsie użytkownika Pythona niż tylko bpy.types, a potem haha. Dzięki!
  • To jest zadziwiająco zwięzły i przejrzysty przegląd, który świetnie się sprawdzi jako część dokumentacji. Zajęło mi to tygodnie na wielu, wielu przykładach.
  • Znacznie lepsze niż oficjalny dokument. Uratowałeś mój dzień!

Odpowiedź

zmodyfikowana wersja – dla blendera 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() 

Niektóre zmiany wprowadzone dla wersji blendera 2.80:

  • Właściwości są teraz adnotacjami przypisanymi do :, a nie =
  • to teraz „UI”, a nie „TOOLS”
  • Procedura rejestracji została zmieniona:
    • Użyj bpy.utils.register_class(), a nie register_module()

Więcej informacji: python api blender 2.80

Komentarze

  • OMG. Dziękuję bardzo. Uczę się Pythona w blenderze. Dużo koduję w VB.net & C #. Walczył z interfejsem użytkownika. To bardzo dobrze to wyjaśniło i
  • cześć, chciałbym wiedzieć, do czego odnosi się scene.my_tool? ponieważ nie widziałem, gdzie zostało zdefiniowane?
  • teraz rozumiem, więc mogę zarejestrować właściwość wskaźnika do przechowywania, powiedzmy inne właściwości?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *