New Py UI creation style for Blender

Paste to “Scripting” workspace and Alt+P

this ~120 LoC of Python code implements Add>templates>Python> UI Panel; Menu; Icon(thumbnail grid)

Why can’t blender’s Data-UI codes turn to this declarative way?

@blPanel("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  ui.op("wm.open_mainfile"),
  ui.op("wm.save_as_mainfile"),

  ui.op("object.shade_smooth"),
  ui.l(text="Hello world!", icon='WORLD_DATA'),
  ui.menus("object.select_by_type",property="type", text="all Type"),
  ui.op("wm.call_menu", text="Unwrap", e=[vaset(name = "VIEW3D_MT_uv_map")] ))

截图 2022-06-19 22-38-58

alternative to:

class CustomMenu(bpy.types.Menu):
    bl_label = "Custom Menu"
    bl_idname = "OBJECT_MT_custom_menu"

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

        layout.operator("wm.open_mainfile")
        layout.operator("wm.save_as_mainfile").copy = True

        layout.operator("object.shade_smooth")

        layout.label(text="Hello world!", icon='WORLD_DATA')

        # use an operator enum property to populate a sub-menu
        layout.operator_menu_enum("object.select_by_type",
                                  property="type",
                                  text="Select All by Type",
                                  )

        # call another menu
        layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map"

full:

import bpy
import bpy.utils as U

def each(a,f):
  for x in a:f(x)
apart1=lambda f:lambda *a,**kw:lambda x1:f(*a,x1,**kw)
vaset=lambda **kw:lambda o: each(kw.items(),lambda x: setattr(o,*x))
def vagetr(f,fw):
  class D:
    def __getattribute__(o,k):return f(k)
    def __setattr__(o,k,v):fw((k,v))
  return D()
def also(x,f):f(x);return x

class _ui:
  kt=dict(l="label",r="row",op="operator",col="column", # ui.r(..)(x)=x.row(..)
    menus="operator_menu_enum",menuIcon="operator_enum")
  pOf=lambda o:vagetr(lambda k:ui.prop(o,k), _propDefOn(o) ) # _ui.pOf(x).k=ui.prop(x,"k")
  def __call__(o, ui,*a):
    for x in a: o.l(text=x)(ui) if isinstance(x,str) else x(ui)
    return ui
  def __getattribute__(o,k):
      def aUi(*a,e=[],**kw):return lambda e0: o(getattr(e0,_ui.kt.get(k,k)) (*a,**kw), *e) #^14
      return aUi
ui=_ui(); plug=[]
def register():each(plug,U.register_class)
def unregister():each(plug,U.unregister_class)

@apart1
def blPanel(name,k,f, kUI="PROPERTIES WINDOW",fin=None):
  class _:
    bl_label = name;bl_idname = k
    def draw(o,c):f(o.layout,c)
    def unregister(o):fin() if fin else 0
  if -1!= (i:=k.find("_MT_")):
    class A(_,bpy.types.Menu): show=lambda:(bpy.ops.wm.call_menu_pie if k.startswith("PIE",i+4) else bpy.ops.wm.call_menu)(name=k)
  else:
    i=k.index("_PT_"); k0,k1=kUI.split()
    class A(_,bpy.types.Panel):
      bl_region_type = k1
      bl_space_type = k0; bl_context = k[:i].lower()
  plug.append(A);return A

Tys={set:"Enum",list:"Collection",str:"String",object:"Pointer", bool:0,int:0,float:0}
for k,v in Tys.items():Tys[k]=getattr(bpy.props,f"{v if v else k.__name__.capitalize()}Property")
@apart1
def _propDefOn(o,kv):
  T=getattr(bpy.types,o.bl_rna.identifier)
  k,arg=kv
  def ty(t,tt,s="",v0=None):
    return Tys[t](subtype=tt, description=s, default=v0) if t!=set else Tys[t](items=tt)
  setattr(T,k, ty(*arg) )


# samples
@blPanel("Hello World Panel","OBJECT_PT_hello")
def lay(o,c, br=ui.r()):
  it=c.object
  ui(o, br,
  ui.l(text="Hello world!", icon='WORLD_DATA'), br,
  "Active object is: " + it.name, br,
  ui.prop(it,"name"), br,
  ui.op("mesh.primitive_cube_add"))

@blPanel("Hello wo", "OBJECT_PT_helo")
def lay(o,c):
    S=_ui.pOf(c.scene)
    ui(o, ui.r(e=["fdfd",ui.op("wm.open_mainfile"), ui.op("render.render")]), S.frame_current )

@blPanel("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  ui.op("wm.open_mainfile"),
  ui.op("wm.save_as_mainfile"),

  ui.op("object.shade_smooth"),
  ui.l(text="Hello world!", icon='WORLD_DATA'),
  ui.menus("object.select_by_type",property="type", text="all Type"),
  ui.op("wm.call_menu", text="Unwrap", e=[vaset(name = "VIEW3D_MT_uv_map")] ))

@blPanel("Select Mode","VIEW3D_MT_PIE_template")
def myPie(o,c):
  ui.menu_pie(e=[ui.menuIcon("mesh.select_mode", "type")]) (o)

@blPanel("Layout Demo","SCENE_PT_demo")
def lay(o,c):
  S=_ui.pOf(c.scene); uis=[S.frame_start,S.frame_end];ren=ui.op("render.render")
  ui(o,
  " Simple Row:",ui.r(e=uis),
  " Aligned Row:",ui.r(align=True,e=uis),
  ui.split(e=[
    ui.col(e=["Column One:", *uis]),
    ui.col(align=True,e=["Column Two:", *uis])
  ]),
  "Big Button:",
  ui.r(e=[ren, vaset(scale_y = 3.0)]),
  "Different button sizes:",
  ui.r(align=True,e=[ren, ui.r(e=[vaset(scale_x = 2.0),ren]) ,ren]) )


import bpy.utils.previews as UP
# dynamic icon(>= custom icon)
wm=bpy.context.window_manager
previewers={"main":also(UP.new(), vaset(my_previews_dir = "",my_previews = ()))}
def filterImg(fp,po):
  import os
  if fp == po.my_previews_dir: return po.my_previews

  print("Scanning directory: %s" %fp); res=[]
  if fp and os.path.exists(fp):
    # Scan the directory for png files
    fps = [x for x in os.listdir(fp) if x.lower().endswith(".png")]
    for i, name in enumerate(fps):
      icon = po.get(name)or po.load(name, os.path.join(fp, name), 'IMAGE') #don't use both get()&has()
      res.append((name, name, "", icon.icon_id, i))

  po.my_previews_dir = fp; po.my_previews = res #^fn:L1
  return res

@blPanel("Previews Example Panel","OBJECT_PT_previews", fin=lambda: each(previewers.values(), UP.remove))
def lay(o,c):
  k="my_previews"
  ui(o, ui.prop(wm,k+"_dir"), ui.template_icon_view(wm,k), ui.prop(wm,k))

ty=_ui.pOf(wm)
ty.my_previews_dir=(str,'DIR_PATH', "Folder Path","")
ty.my_previews=(set,lambda o,c:filterImg(wm.my_previews_dir,previewers["main"]) if o!=None else [])

register()
myMenu.show();myPie.show()

minimal:

import bpy

apart1=lambda f:lambda *a,**kw:lambda x1:f(*a,x1,**kw)
vaset=lambda **kw:lambda o: each(kw.items(),lambda x: setattr(o,*x))

class _ui:
  kt=dict(l="label",r="row",op="operator",col="column", # ui.r(..)(x)=x.row(..)
    menus="operator_menu_enum",menuIcon="operator_enum")
  def __call__(o, ui,*a):
    for x in a: o.l(text=x)(ui) if isinstance(x,str) else x(ui)
    return ui
  def __getattribute__(o,k):
      def aUi(*a,e=[],**kw):return lambda e0: o(getattr(e0,_ui.kt.get(k,k)) (*a,**kw), *e) #^14
      return aUi
ui=_ui(); plug=[]

@apart1
def blPanel(name,k,f, kUI="PROPERTIES WINDOW",fin=None):
  class _:
    bl_label = name;bl_idname = k
    def draw(o,c):f(o.layout,c)
    def unregister(o):fin() if fin else 0
  if -1!= (i:=k.find("_MT_")):
    class A(_,bpy.types.Menu): show=lambda:(bpy.ops.wm.call_menu_pie if k.startswith("PIE",i+4) else bpy.ops.wm.call_menu)(name=k)
  else:
    i=k.index("_PT_"); k0,k1=kUI.split()
    class A(_,bpy.types.Panel):
      bl_region_type = k1
      bl_space_type = k0; bl_context = k[:i].lower()
  plug.append(A);return A


@blPanel("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  ui.op("wm.open_mainfile"),
  ui.op("wm.save_as_mainfile"),

  ui.op("object.shade_smooth"),
  ui.l(text="Hello world!", icon='WORLD_DATA'),
  ui.menus("object.select_by_type",property="type", text="all Type"),
  ui.op("wm.call_menu", text="Unwrap", e=[vaset(name = "VIEW3D_MT_uv_map")] ))

myMenu.show()

Python is not my jam, but reading code is, while you try to make a case for an api change you do so in the worst way possible by changing the actual function names to something much less readable

  • are o and c clearer than self and context ?
  • is .l clearer than label?
  • is .op clearer than Operator?
  • menus is plural but does a single menu only
  • is e=[vaset(name = "VIEW3D_MT_uv_map")] really better than blah.name = "VIEW3D_MT_uv_map"?

not really readability issue but still a difference between the two snippets

  • .copy = True for save_as_mainfile appears to have gone missing , so it’s not even an apples to apples comparison

all these things reduced the readability by so much that any improvements on the API side will completely get lost in your proposal. Making a proposal for an api change is great, but highlight what’s better about it! make it shine! don’t make things worse in other areas

3 Likes
  1. It’s not “apple to apples” comparison, @bl def is 2x easy to write than list.append (w/o implicit self) style UI tree, and less C-Like
  2. We should not suppose APIs are for "zero-knowledge newcomers, they should read docs, they draw UIs already in their mind, and that’s why shortnames is good to remember, rather than “make code seems human-readable but NO GOOD at refactoring”
  3. self is fully a junk as OOP varaible, only Python use it as arg0 to mimic “Function-style” structure.
    I know it’s widely approved, We may avoid to mention self when creating UI. context is another junk so Android make it on this, Blender don’t have good OOP data-inherience, but there’s “context override”… so it’s impossible to keep it global
    EDIT: 3.2 provides ctx.temp_override, instead of passing ctxt everywhere
    ref: UILayout
  4. yes, much clearer
ui(self, # indented, better than always flat
  #row=layout.row() *repeat-assign N times
  ui.label(text="Hello world!", icon='WORLD_DATA'),
  "Active object is: " + it.name, #row.label(text="Active object is: " + it.name)
  ui.prop(it,"name"),
  "!mesh.primitive_cube_add") #row.operator("mesh.primitive_cube_add")
  1. operator_menu_enum is not concrete, it’s a menu or a enum (choice list)?
  2. It’s required in ui.row(align=True, e=["label:", "!mesh.op" ]) (rather than layout.“append”) style, and now op as button:
@bl("","VIEW3D_MT_uv_map") # ID
def mUvMap():pass
@bl("","wm.save_as_mainfile")
def opSave():pass

@bl("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  ["!wm.open_mainfile",opSave.ref(arg=dict(copy=True)) ], #row
  "!object.shade_smooth",
  ui.label(text="Hello world!", icon='WORLD_DATA'),
  ui.menus("object.select_by_type",property="type", text="all Type"),
  mUvMap.show(text="Unwrap") ) # clearer?

new:

import bpy
import bpy.utils as U
import functools as F

def each(a,f):
  for x in a:f(x)
def also(x,f):f(x);return x
apart1=lambda f:lambda *a,**kw:lambda x1:f(*a,x1,**kw)

class ui_:
  kt=dict(l="label",r="row",op="operator",col="column", # ui.r(..)(x)=x.row(..)
    menus="operator_menu_enum",menuIcon="operator_enum")
  kFmt=lambda s: " ".join(x.capitalize()for x in s.split("_")[1:])
  pOf=lambda o:vagetr(lambda k:ui.prop(o,k, text=ui_.kFmt(k)), _propDefOn(o) ) # ui_.pOf(x).k=ui.prop(x,"k")
  def __call__(o, ui,*a):
    for x in a: (o.op(x[1:]) if x[0]=='!' else o.l(text=x))(ui) if isinstance(x,str) else (o.r(e=x)if isinstance(x,list) else x)(ui)
    return ui
  def __getattribute__(o,k):
    def aUi(*a,e=[],**kw):return lambda e0: o(getattr(e0,ui_.kt.get(k,k)) (*a,**kw), *e) #^17
    return aUi
ui=ui_(); plug=[]
def register():each(plug,U.register_class)
def unregister():each(plug,U.unregister_class)

def _opCaller(k,mkUI=False,**kw): # f=ui_.op("wm.menu"); f():do, f(text=):UI
  def do(arg={},**e):
    return ui.op(k,e=[vaset(**kw,**arg)],**e) if e or mkUI else F.reduce(getattr, k.split('.'), bpy.ops)(**kw,**arg)
  return do
ui_.op=_opCaller
vaset=lambda **kw:lambda o: each(kw.items(),lambda x: setattr(o,*x))
def vagetr(f,fw):
  class D:
    def __getattribute__(o,k):return f(k)
    def __setattr__(o,k,v):fw((k,v))
  return D()

@apart1 #Menu,Panel, Op(btn)
def bl(name,k,f, kUI="PROPERTIES WINDOW",fin=None):
  class _:
    bl_label = name;bl_idname = k
    def draw(o,c):f(o.layout,c)
    def unregister(o):fin() if fin else 0
  if -1!= (i:=k.find("_MT_")):
    class A(_,bpy.types.Menu): show=ui_.op("wm.call_menu_pie"if k.startswith("PIE",i+4) else"wm.call_menu", name=k)
  elif 1!=len( (i:=k.rsplit(".",1))):
    class A(bpy.types.Operator):
      bl_label = name;bl_idname = i[0]; ref=ui_.op(i[0],mkUI=True)
      bl_description=i[1]
      def execute(o,c):f(o,c, *[getattr(o,k)for k in __annotations__.keys()] ); return{'FINISHED'}
      poll=classmethod(fin if fin else lambda T,c:True)
      __annotations__={k:_pTy(*v) for k,v in f.__annotations__.items()}
  else:
    i=k.index("_PT_"); k0,k1=kUI.split()
    class A(_,bpy.types.Panel):
      bl_region_type = k1
      bl_space_type = k0; bl_context = k[:i].lower()
  plug.append(A) if name else 0;return A

Tys={set:"Enum",list:"Collection",str:"String",object:"Pointer", bool:0,int:0,float:0} #BL type
for k,v in Tys.items():Tys[k]=getattr(bpy.props,f"{v if v else k.__name__.capitalize()}Property")
@apart1
def _propDefOn(o,kv):
  T=getattr(bpy.types,o.bl_rna.identifier)
  k,arg=kv; setattr(T,k, _pTy(*arg) )
def _pTy(t,tt,s="",v0=None,kr={}):
  if tt=="+":tt="UNSIGNED";kr=dict(min=0)
  if tt=="%":tt="PERCENTAGE";kr=dict(min=0,max=1)
  kw={"items" if t==set else "default" if t==bool else "subtype":tt, "description":s}
  if None!=v0:kw["default"]=v0
  return Tys.get(t,t)(**kw,**kr)

# samples @prop editor: red,yellow icon
@bl("Hello World Panel","OBJECT_PT_hello")
def lay(o,c, br=ui.r()):
  it=c.object
  ui(o, br,
  ui.l(text="Hello world!", icon='WORLD_DATA'), br,
  "Active object is: " + it.name, br,
  ui.prop(it,"name"), br,
  "!mesh.primitive_cube_add")

@bl("Hello wo", "OBJECT_PT_helo")
def lay(o,c):
  S=ui_.pOf(c.scene)
  ui(o, ["fdfd","!wm.open_mainfile", "!render.render"], S.frame_current, S.frame_end)

@bl("","VIEW3D_MT_uv_map") # menus
def mUvMap():pass

@bl("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  "!wm.open_mainfile",
  "!wm.save_as_mainfile",
  "!object.shade_smooth",
  ui.l(text="Hello world!", icon='WORLD_DATA'),
  ui.menus("object.select_by_type",property="type", text="all Type"),
  mUvMap.show(text="Unwrap") )

@bl("Select Mode","VIEW3D_MT_PIE_template")
def myPie(o,c):
  ui.menu_pie(e=[ui.menuIcon("mesh.select_mode", "type")]) (o)

@bl("Show Pie", "my.menu_pie. Display float ui")
def op(o,c): myPie.show()

@bl("Layout Demo","SCENE_PT_demo")
def lay(o,c):
  S=ui_.pOf(c.scene); uis=[S.frame_start,S.frame_end];ren="!render.render"
  ui(o,
  " Simple Row:",uis,
  " Aligned Row:",ui.r(align=True,e=uis),
  ui.split(e=[
    ui.col(e=["Column One:", *uis]),
    ui.col(align=True,e=["Column Two:", *uis])
  ]),
  "Big Button:",
  [vaset(scale_y = 3.0), ren],
  "Different button sizes:",
  ui.r(align=True,e=[ren, [vaset(scale_x = 2.0),ren] ,ren]),
  [myPie.show(text="Pie"),myMenu.show(text="M"), op.ref() ] )

import bpy.utils.previews as UP
# dynamic icon(>= custom icon)
previewers={"main":also(UP.new(), vaset(my_previews_dir = "",my_previews = ()))}
def filterImg(fp,po):
  import os
  if fp == po.my_previews_dir: return po.my_previews

  print("Scanning directory: %s" %fp); res=[]
  if fp and os.path.exists(fp):
    # Scan the directory for png files
    fps = [x for x in os.listdir(fp) if x.lower().endswith(".png")]
    for i, name in enumerate(fps):
      icon = po.get(name)or po.load(name, os.path.join(fp, name), 'IMAGE') #don't use both get()&has()
      res.append((name, name, "", icon.icon_id, i))

  po.my_previews_dir = fp; po.my_previews = res #^fn:L2
  return res

wm=bpy.context.window_manager
W=ui_.pOf(wm)
@bl("Previews Example Panel","OBJECT_PT_previews", fin=lambda: each(previewers.values(), UP.remove))
def lay(o,c):
  ui(o, W.my_previews_dir, ui.template_icon_view(wm,"my_previews"), W.my_previews)

W.my_previews_dir=(str,'DIR_PATH', "Folder Path")
W.my_previews=(set,lambda o,c:filterImg(wm.my_previews_dir,previewers["main"]) if o!=None else [])


@bl("Refresh","sequencerextra.refresh_font_data.")
def fresh(o,c, n:(int,"+", "count", 1) ): [myPie.show() for i in range(n)]

@bl("Subsimport","SEQUENCER_PT_subsimport", kUI="SEQUENCE_EDITOR UI")
def lay(o,c):
  ui(o,
    [S.subtitle_edit_channel],
    ui.box(e=[
    [S.subtitle_font],
    [S.subtitle_font_size],
    [S.subtitle_font_height],
    [fresh.ref(icon="FILE_REFRESH") ]
    ]))

S=ui_.pOf(bpy.context.scene)
S.subtitle_edit_channel = (int,"+","The channel where keyboard shortcuts will act on text strips",1)
S.subtitle_font = (str,"FILE_PATH","The font of the added text strips after import")
S.subtitle_font_size = (int,"PIXEL","The font size of the added text strips after import",70)
S.subtitle_font_height = (float,"%","The height of the added text strips after import")
S.syllable_dictionary_path = (str,"FILE_PATH","Path to the text file containing words separated by syllables.\nNeeded for accurate splitting of subtitles by syllable.")
S.enhanced_subs_color = (bpy.props.FloatVectorProperty,'COLOR_GAMMA',
    "Highlight color of the subtitles in the edit channel",(1.0, 0.5, 0.0),
    dict(size=3,min=0.0, max=1.0,))
S.use_dictionary_syllabification = (bool,True,"Use (Less-Error-Prone) algorithm to syllabify words.")
S.use_algorithmic_syllabification = (bool,True,"Use (imperfect) algorithm to syllabify words.\nIf dictionary method is enabled, the algorithm is used for words not found in the dictionary.")

register()
#myMenu.show();myPie.show()

Btw: compilations about VSE-effect usage

  • Drag&drop won’t set xy-pos(time-channel)
  • Effect “Image” won’t pack png into .blend
  • Effect “Text” don’t support gradient color or fill-storke,even shadow width (quite naive! )
  • No Node Editor addons for Gmic.eu or Shadertoy filters (so U can’t turn Blender into AfterFX)
  • “Fade” only on opacity, can’t for size,color,etc

This must be one of the worst bits of python I recall seeing, duang. I understand that your proposal is about a new way to create UI elements, not the implementation itself, but the code is not in a state I’d reasonably expect other people to engage with.

1 Like

The “2x easy to write” (easier?) here seem to be a tradeoff for 4x harder to read. And no, reading someone else’s code is not a domain of just “zero-known” (knowledge?) newcomers, but also experienced programmers.

The beauty of Python is that it can, and should, be written in a way so close to English language it’s pretty much self documenting, so people engaging with someone’s code don’t have to decipher it first, but can just read it directly. Your code style pretty much craps all over this idea, to put it mildly.

This mistake has already been done before. I remember my friend, an expert programmer, telling me a joke about “uncomfortable keyboard syndrome”, joking that back in the day the C programming language was mainstream, the keyboard were so uncomfortable everyone wanted to type as few characters as possible, so variable names were often only 2-3 characters long, with exceeding 4 characters being almost a crime.

This poor habit resulted in some remarkably poorly readable code, which lasts to this day. Of course the real reason weren’t (just) the uncomfortable keyboards, but other limitations, such as low resolution displays of 4:3 aspect ratio, leaving very small amount of horizontal space to display a line of code without horizontal scrolling.

In recent years, we’re seeing this poor habit being gradually fixed in Blender’s old C codebase. A few times already, I’ve seen some random two letter variables renamed to something that actually properly describes their purpose. So it would not make a sense to start doing that mistake in the Python portion of Blender at the very same time it’s getting gradually fixed in the C/C++ portion of Blender :slight_smile:

Bottom line is that this is just selfishness in disguise. You want to reduce your own effort of writing code at the expense of increasing others’ effort of reading it. This would be absolutely fine if this was your isolated project which you either do not expect to cooperate with anyone on, or expect to cooperate with people sharing your codestyle. But Blender is a huge project so many people use and interact with that the code style has to be a lot less subjective. The more self explanatory, the better.

6 Likes

Early dos days we were limited to 80x25 characters on a screen, no mouse, no scrollbars, compromises had to be made just to make the code fit on the screen, vowels were generally the frst thng to go.

Yes, exactly. I mention that in the very next paragraph :slight_smile:

100% this. Going to leave this here for posterity:

bpy.utils.(un)register_class should not call on class list by extension module, it’s a boilerplate

“Code readability” is not for naive “keyrepeater records +if&for”. “Hello world” calls libc API but IS NOT a program at all, I don’t think this kind of program with very long lines(just for easy purpose) is for real-world apps

comb=(a,...arys)=>arys.length==0? a.map(x=>[x]) : comb(...arys).flatMap(x=> a.map(x0=> [x0,...x] ) )
/*
Is this readable? or have a better implementation?
comb([a,b])= [a or b]
comb([a,b],[c,d])= [[c], [d]].map(x=> [a,b].map(x0=>[x0,...x]) ).flat()
*/

A “high-level” readability in comments& public func names, rather than “clearer” var names:
(in fact my naming style is “single char certain usage-meaning”, NOT RANDOM craps short names like most developers , but this article is for Tree API ideas like TkGUI, not my implementation)

class blui:
  _kt=dict(l="label",r="row",op="operator",col="column", # ui.r(..)(x)=x.row(..)
  of=lambda o:vagetr(lambda k:ui.prop(o,k), _propDefOn(o) ) # blui.of(x).k=ui.prop(x,"k")

@apart1 #Menu,Panel, Op(btn)
def bl(name,k,f, kUI="PROPERTIES WINDOW",fin=None):

def _deflCaller(k,**kw): # f=blui.op("wm.menu");  f(arg=):do, f(text=,):UI
ui.r(..)(x)=x.row(..) #I wrote
blui.of(x).k=ui.prop(x,"k")
@bl can def  bpy.types. Menu,Panel, Operator(btn)
f=blui.op("wm.menu");  f(arg=):do call, f(text=,):make UI

Quite better(also shorter) than original, if it’s 4x slower to read, THEN IT IS.

I can define this UI in 5mins (already drawn in my mind) results 40 lines indented code, but bl_ developers should write them in a “flexible” “co-operable” way with ~40+70 LoC (with IDE support?)

If U write all UIs of your Blender ext <1hrs, instead of >1day + maintained:UI >1month , of course it’s unnecessary to keep “team code style” :slight_smile:

@bl("Subsimport","Tools_PT_subsimport", kUI="SEQUENCE_EDITOR UI")
def lay(self,c):
  ui(self,
  [S.subtitle_edit_channel],
  ui.box(e=[
    [S.subtitle_font],
    [S.subtitle_font_size],
    [S.subtitle_font_height],
    [fresh.op(icon="FILE_REFRESH", arg=dict(n=3)), fresh.op()],
    ui.box(e=[
      [S.use_dictionary_syllabification],
      [S.use_algorithmic_syllabification, S.syllabification_language ],
      [op.op(icon="ALIGN_FLUSH"), op.op(icon="DISK_DRIVE") ],
      [S.syllable_dictionary_path]
    ])
  ]))

S=blui.of(bpy.context.scene)
S.subtitle_edit_channel = (int,"+","The channel where keyboard shortcuts will act on text strips",1)
S.subtitle_font = (str,"FILE_PATH","The font of the added text strips after import")
S.subtitle_font_size = (int,"PIXEL","The font size of the added text strips after import",70)
S.subtitle_font_height = (float,"%","The height of the added text strips after import")
S.syllable_dictionary_path = (str,"FILE_PATH","Path to the text file containing words separated by syllables.\nNeeded for accurate splitting of subtitles by syllable.")

It’s not self explanatory, but it’s just rendered UI itself, on your screen.

another rewrite (total 140, UI 20 vs blui 70+total 30, UI 4):

renamed:

import bpy
import bpy.utils as U
import functools as F

def each(a,f):
  for x in a:f(x)
def also(x,f):f(x);return x
apart1=lambda f:lambda *a,**kw:lambda x1:f(*a,x1,**kw)

class blui: # UI Tree
  _kt=dict(l="label",r="row",op="operator",col="column", # ui.r(..)(x)=x.row(..)
    menu_sub="operator_menu_enum",menu_icon="operator_enum")
  kFmt=lambda s: " ".join(x.capitalize()for x in s.split("_")[1:])
  of=lambda o:vagetr(lambda k:ui.prop(o,k), _propDefOn(o) ) # blui.of(x).k=ui.prop(x,"k")
  def __call__(o, ui,*a):
    for x in a: (o.op(x[1:]) if x[0]=='!' else o.l(text=x))(ui) if isinstance(x,str) else (o.r(e=x)if isinstance(x,list) else x)(ui)
    return ui
  def __getattribute__(o,k):
    def iced(*a, e=[],**kw):return lambda e0: o(getattr(e0,blui._kt.get(k,k)) (*a,**kw), *e) #^11
    return iced
ui=blui(); plug=[]
def register():each(plug,U.register_class)
def unregister():each(plug,U.unregister_class)

@apart1 #Menu,Panel, Op(btn)
def bl(name,k,f, kUI="PROPERTIES WINDOW",fin=None):
  class _:
    bl_label = name;bl_idname = k
    def draw(o,c):f(o.layout,c)
    if fin:
      def unregister(o):fin()
  if -1!= (i:=k.find("_MT_")):
    class A(_,bpy.types.Menu): show=blui.op("wm.call_menu_pie"if k.startswith("PIE",i+4) else"wm.call_menu", name=k)
  elif 1!=len( (i:=k.rsplit(".",1))):
    class A(bpy.types.Operator):
      bl_label = name;bl_idname = i[0]; op=blui.op(i[0])
      bl_description=i[1]
      __annotations__={k:_pTy(k,*v) for k,v in f.__annotations__.items()}
      def execute(o,c):f(o,c, *[getattr(o,k)for k in o.__annotations__.keys()] ); return{'FINISHED'}
      if fin: poll=classmethod(fin)
  else:
    i=k.index("_PT_"); k0,k1=kUI.split()
    class A(_,bpy.types.Panel):
      bl_region_type = k1
      bl_space_type = k0; bl_context = k[:i].lower(); bl_category = k[:i]
  plug.append(A) if name else 0;return A

def _deflCaller(k,**kw): # f=blui.op("wm.menu");  f(arg=):do, f(text=,):UI
  def do(arg=None,**e):
    return F.reduce(getattr,k.split('.'), bpy.ops)(**kw,**arg or{})if( arg!=None and not e) else ui.op(k,e=[vaset(**kw,**arg or{})],**e)
  return do
blui.op=_deflCaller
@apart1 # @bl Op-prop helpers
def _propDefOn(o,kv): k,arg=kv; setattr(getattr(bpy.types,o.bl_rna.identifier), k, _pTy(k,*arg))

vaset=lambda **kw:lambda o: each(kw.items(),lambda x: setattr(o,*x))
def vagetr(f,fw):
  class D:
    def __getattribute__(o,k):return f(k)
    def __setattr__(o,k,v):fw((k,v))
  return D()

_Ty={T:getattr(bpy.props,f"{v if v else T.__name__.capitalize()}Property")
  for T,v in {set:"Enum",list:"Collection",str:"String",object:"Pointer", bool:0,int:0,float:0}.items()}
def _pTy(k, t,tt,s="",v0=None,kr={}):
  if tt=="+":tt="UNSIGNED";kr=dict(min=0)
  if tt=="%":tt="PERCENTAGE";kr=dict(min=0,max=1)
  kw={"items" if t==set else "default" if t==bool else "subtype":tt or "NONE", "description":s}
  if None!=v0:kw["default"]=v0
  return _Ty.get(t,t)(name=kr.pop("name","")or blui.kFmt(k),**kw,**kr) #ui.prop(text=)


# samples @prop editor: red^1,yellow icon
@bl("Hello World Panel","OBJECT_PT_hello")
def lay(o,c, br=ui.r()):
  it=c.object
  ui(o, br,
  ui.l(text="Hello world!", icon='WORLD_DATA'), br,
  "Active object is: " + it.name, br,
  ui.prop(it,"name"), br,
  "!mesh.primitive_cube_add")

@bl("Hello wo", "OBJECT_PT_helo")
def lay(o,c):
  S=blui.of(c.scene)
  ui(o, ["just","!wm.open_mainfile", "!render.render"], S.frame_current, S.frame_end)

@bl("","VIEW3D_MT_uv_map") # Menus
def mUvMap():pass

@bl("My Menu", "OBJECT_MT_helo")
def myMenu(o,c):
  ui(o,
  "!wm.open_mainfile",
  "!wm.save_as_mainfile",
  "!object.shade_smooth",
  ui.l(text="Hello world!", icon='WORLD_DATA'),
  ui.menu_sub("object.select_by_type",property="type", text="all Type"),
  mUvMap.show(text="Unwrap"), mUvMap.show() ) # code reuse!

@bl("Select Mode","VIEW3D_MT_PIE_template")
def myPie(o,c):
  ui.menu_pie(e=[ui.menu_icon("mesh.select_mode", "type")]) (o)


@bl("Show Pie", "my.menu_pie. Display float ui")
def op(o,c): myPie.show(arg={})

@bl("Layout Demo","SCENE_PT_demo")
def lay(o,c):
  S=blui.of(c.scene); uis=[S.frame_start,S.frame_end];ren="!render.render"
  ui(o,
  " Simple Row:",uis,
  " Aligned Row:",ui.r(align=True,e=uis),
  ui.split(e=[
    ui.col(e=["Column One:", *uis]),
    ui.col(align=True,e=["Column Two:", *uis])
  ]),
  "Big Button:",
  [vaset(scale_y = 3.0), ren],
  "Different button sizes:",
  ui.r(align=True,e=[ren, [vaset(scale_x = 2.0),ren] ,ren]),
  [myPie.show(text="Pie"),myMenu.show(text="M"), op.op() ] )

import bpy.utils.previews as UP
# dynamic icon(>= custom icon)
previewers={"main":also(UP.new(), vaset(my_previews_dir = "",my_previews = ()))}
def filterImg(fp,po):
  import os
  if fp == po.my_previews_dir: return po.my_previews

  print("Scanning directory: %s" %fp); res=[]
  if fp and os.path.exists(fp):
    # Scan the directory for png files
    fps = [x for x in os.listdir(fp) if x.lower().endswith(".png")]
    for i, name in enumerate(fps):
      icon = po.get(name)or po.load(name, os.path.join(fp, name), 'IMAGE') #don't use both get()&has()
      res.append((name, name, "", icon.icon_id, i))

  po.my_previews_dir = fp; po.my_previews = res #^fn:L2
  return res

wm=bpy.context.window_manager
W=blui.of(wm)
@bl("Previews Example Panel","OBJECT_PT_previews", fin=lambda: each(previewers.values(), UP.remove))
def lay(o,c):
  ui(o, W.my_previews_dir, ui.template_icon_view(wm,"my_previews"), W.my_previews)

W.my_previews_dir=(str,'DIR_PATH', "Folder Path")
W.my_previews=(set,lambda o,c:filterImg(wm.my_previews_dir,previewers["main"]) if o!=None else [])


@bl("Refresh","sequencerextra.refresh_font_data.")
def fresh(o,c, n:(int,"+", "count", 1) ): [myPie.show(arg={}) for i in range(n)]

@bl("Subsimport","Tools_PT_subsimport", kUI="SEQUENCE_EDITOR UI")
def lay(o,c):
  ui(o,
  [S.subtitle_edit_channel],
  ui.box(e=[
    [S.subtitle_font],
    [S.subtitle_font_size],
    [S.subtitle_font_height],
    [fresh.op(icon="FILE_REFRESH", arg=dict(n=3)), fresh.op()],
    ui.box(e=[
      [S.use_dictionary_syllabification],
      [S.use_algorithmic_syllabification, S.syllabification_language ],
      [op.op(icon="ALIGN_FLUSH"), op.op(icon="DISK_DRIVE") ],
      [S.syllable_dictionary_path]
    ])
  ]))

S=blui.of(bpy.context.scene)
S.subtitle_edit_channel = (int,"+","The channel where keyboard shortcuts will act on text strips",1)
S.subtitle_font = (str,"FILE_PATH","The font of the added text strips after import")
S.subtitle_font_size = (int,"PIXEL","The font size of the added text strips after import",70)
S.subtitle_font_height = (float,"%","The height of the added text strips after import")
S.syllable_dictionary_path = (str,"FILE_PATH","Path to the text file containing words separated by syllables.\nNeeded for accurate splitting of subtitles by syllable.")
S.enhanced_subs_color = (bpy.props.FloatVectorProperty,'COLOR_GAMMA',
    "Highlight color of the subtitles in the edit channel",(1.0, 0.5, 0.0),
    dict(size=3,min=0.0, max=1.0,))
S.use_dictionary_syllabification = (bool,True,"Use (Less-Error-Prone) algorithm to syllabify words.")
S.use_algorithmic_syllabification = (bool,True,"Use (imperfect) algorithm to syllabify words.\nIf dictionary method is enabled, the algorithm is used for words not found in the dictionary.",True, dict(name="Algorithm"))

S.syllabification_language = (set,[('en-us', 'English-U.S.', ''),('ru', 'Russian', ''),],"Set the language to use when syllabifying","en-us")
S.subtitle_combine_mode = (set,[
    ('esrt', 'ESRT', 'Combine subtitles as enhanced SRT strips'),
    ('elrc', 'ELRC', 'Combine subtitles as enhanced LRC strips')
],"How to combine the subtitles","esrt")

register()

  1. I was reading codes in Py,JS,Kotlin,Java,Ruby,C++,Bash languages, as a experienced programmer, and I use my pylib as a newcomer. “easy to write” is not about to reduce keystrokes, blui provides a really useful API for addon designers; maybe your friend truncates varname due to screen limitations, like many C devs, but it’s not why I choose my naming style
  2. Computer languages should NOT ONLY “act like human”, expression efficiency&refactoring is also a big problem, or why we call it “Computer” languages?
  3. each also _propDefOn are >4ch long. if U read Rust, Ty means type in their syntax, I hate this style. But these code is just a proposal
  4. If blui’s program(not “namining”) style is used in Blender’s gaint C codebase, it will be 50% shorter&modular&easy-to-read(so it’s easier to port new editing features) , since I use template codegen and NEVER paste boilerplates
  5. First, our code should results the same, then we can talk about certain snippet or varnames

Coding style and naming conventions aside…

It’s not a bad idea for doing simple panels through a function with a decorator. If ALL you want the panel to do is display some stuff. If you want to do custom things through the panel, like have some state in the panel object, other functions, a custom poll() function, etc. You can’t. Most addons I’ve seen make a subclass of Panel that they then use as the base class for all their other Panels and eliminate boilerplate that way.

Final ver(~120+70LoC) with Pythonic naming . CC-0.

I’m going back to make my VSE addon to restore basic features similar to Kdelive

@apart1 #Menu,Panel, Op(btn) types
def bl(name,k,f, kUI="PROPERTIES WINDOW", finer=None,invoker=None, mixin=object):

class blui: # UI Tree
  _kt=dict(fmt="label",op="operator",col="column", # ui.fmt(..)(x)=x.label(..)
  of=lambda o:vagetr(lambda k:ui.prop(o,k), _propDefOn(o) ) # blui.of(x).k=ui.prop(x,"k")

blui.op=_deflCaller # @bl Op-prop helpers: menu.show(text=), Op.op(arg=)

S=blui.of(C.scene)
S.subtitle_edit_channel = (int,"+","The channel where keyboard shortcuts will act on text strips",1)
S.subtitle_font = (str,"FILE_PATH","The font of the added text strips after import")

@bl("Previews Example Panel","OBJECT_PT_previews", finer=lambda: each(previewers.values(), UP.remove))
def lay(self):
  ui(self, W.my_previews_dir, ui.template_icon_view(wm,"my_previews"), W.my_previews)


@bl("Refresh","sequencerextra.refresh_font_data.", invoker=blui.popup())
def fresh(op, n:(int,"+", "count", 1) ): [myPie.show(arg={}) for i in range(n)]
# samples @prop editor: red^1,yellow icon
@bl("Hello World Panel","OBJECT_PT_hello")
def lay(self, br=ui.row()):
  it=C.object
  ui(self, br,
  ui.fmt(text="Hello world!", icon='WORLD_DATA'), br,
  "Active object is: " + it.name, br,
  ui.prop(it,"name"), br,
  "!mesh.primitive_cube_add")

@bl("Hello wo", "OBJECT_PT_helo")
def lay(self):
  S=blui.of(C.scene)
  ui(self, ["Just","!wm.open_mainfile", "!render.render"], S.frame_current, S.frame_end)
@bl("","VIEW3D_MT_uv_map") # Menus
def mUvMap():pass

@bl("My Menu", "OBJECT_MT_helo")
def myMenu(self):
  ui(self,
  "!wm.open_mainfile",
  "!wm.save_as_mainfile",
  "!object.shade_smooth",
  ui.fmt(text="Hello world!", icon='WORLD_DATA'),
  ui.menu_sub("object.select_by_type",property="type", text="all Type"),
  mUvMap.show(text="Unwrap"), mUvMap.show() ) # code reuse!

@bl("Select Mode","VIEW3D_MT_PIE_template")
def myPie(self):
  ui(self,ui.menu_pie(e=[ui.menu_icon("mesh.select_mode", "type")]) )


@bl("Show Pie", "my.menu_pie. Display float ui")
def pie(op): myPie.show(arg={})

@bl("Layout Demo","SCENE_PT_demo")
def lay(self):
  S=blui.of(C.scene); uis=[S.frame_start,S.frame_end];ren="!render.render"
  ui(self,
  " Simple Row:",uis,
  " Aligned Row:",ui.row(align=True,e=uis),
  ui.split(e=[
    ui.col(e=["Column One:", *uis]),
    ui.col(align=True,e=["Column Two:", *uis])
  ]),
  "Big Button:",
  [vaset(scale_y = 3.0), ren],
  "Different button sizes:",
  ui.row(align=True,e=[ren, [vaset(scale_x = 2.0),ren] ,ren]),
  [myPie.show(text="Pie"),myMenu.show(text="M"), pie.op() ] )

import bpy.utils.previews as UP
# dynamic icon(>= custom icon)
previewers={"main":also(UP.new(), vaset(my_previews_dir = "",my_previews = ()))}
def filterImg(fp,po):
  import os
  if fp == po.my_previews_dir: return po.my_previews

  print("Scanning directory: %s" %fp); res=[]
  if fp and os.path.exists(fp):
    # Scan the directory for png files
    fps = [x for x in os.listdir(fp) if x.lower().endswith(".png")]
    for i, name in enumerate(fps):
      icon = po.get(name)or po.load(name, os.path.join(fp, name), 'IMAGE') #don't use both get()&has()
      res.append((name, name, "", icon.icon_id, i))

  po.my_previews_dir = fp; po.my_previews = res #^fn:L2
  return res

wm=C.window_manager
W=blui.of(wm)
@bl("Previews Example Panel","OBJECT_PT_previews", finer=lambda: each(previewers.values(), UP.remove))
def lay(self):
  ui(self, W.my_previews_dir, ui.template_icon_view(wm,"my_previews"), W.my_previews)

W.my_previews_dir=(str,'DIR_PATH', "Folder Path")
W.my_previews=(set,lambda o,c:filterImg(wm.my_previews_dir,previewers["main"]) if o!=None else [])


@bl("Refresh","sequencerextra.refresh_font_data.", invoker=blui.popup())
def fresh(op, n:(int,"+", "count", 1) ): [myPie.show(arg={}) for i in range(n)]

@bl("Subsimport","Tool_PT_subsimport", kUI="SEQUENCE_EDITOR UI")
def lay(self):
  ui(self,
  [S.subtitle_edit_channel],
  ui.box(e=[
    [S.subtitle_font],
    [S.subtitle_font_size],
    [S.subtitle_font_height], #v operator args UI
    [fresh.op(icon="FILE_REFRESH", arg=dict(n=3)), fresh.op(), [blui.do_part("EXEC_DEFAULT"), fresh.op()] ],
    ui.box(e=[
      [S.use_dictionary_syllabification],
      [S.use_algorithmic_syllabification, S.syllabification_language ],
      [pie.op(icon="ALIGN_FLUSH"), pie.op(icon="DISK_DRIVE") ],
      [S.syllable_dictionary_path]
    ])
  ]))

S=blui.of(C.scene)
S.subtitle_edit_channel = (int,"+","The channel where keyboard shortcuts will act on text strips",1)
S.subtitle_font = (str,"FILE_PATH","The font of the added text strips after import")
S.subtitle_font_size = (int,"PIXEL","The font size of the added text strips after import",70)
S.subtitle_font_height = (float,"%","The height of the added text strips after import")
S.syllable_dictionary_path = (str,"FILE_PATH","Path to the text file containing words separated by syllables.\nNeeded for accurate splitting of subtitles by syllable.")
S.enhanced_subs_color = (bpy.props.FloatVectorProperty,'COLOR_GAMMA',
    "Highlight color of the subtitles in the edit channel",(1.0, 0.5, 0.0),
    dict(size=3,min=0.0, max=1.0,))
S.use_dictionary_syllabification = (bool,True,"Use (Less-Error-Prone) algorithm to syllabify words.")
S.use_algorithmic_syllabification = (bool,True,"Use (imperfect) algorithm to syllabify words.\nIf dictionary method is enabled, the algorithm is used for words not found in the dictionary.",True, dict(name="Algorithm"))

S.syllabification_language = (set,[('en-us', 'English-U.S.', ''),('ru', 'Russian', ''),],"Set the language to use when syllabifying","en-us")
S.subtitle_combine_mode = (set,[
    ('esrt', 'ESRT', 'Combine subtitles as enhanced SRT strips'),
    ('elrc', 'ELRC', 'Combine subtitles as enhanced LRC strips')
],"How to combine the subtitles","esrt")

register()

import bpy
import bpy.utils as U
C=bpy.context; D=bpy.data
import functools as Ft

def each(a,f):
  for x in a:f(x)
def also(x,f):f(x);return x
apart1=lambda f:lambda *a,**kw:lambda x1:f(*a,x1,**kw)

class blui: # UI Tree
  _kt=dict(fmt="label",op="operator",col="column", # ui.fmt(..)(x)=x.label(..)
    sep="separator", menu_sub="operator_menu_enum",menu_icon="operator_enum")
  kFmt=lambda k: " ".join(x.capitalize()for x in k.split("_")[1:])or k
  of=lambda o:vagetr(lambda k:ui.prop(o,k), _propDefOn(o) ) # blui.of(x).k=ui.prop(x,"k")
  def __call__(o, ui,*a):
    for x in a: (o.op(x[1:]) if x[0]=='!' else o.fmt(text=x))(ui) if isinstance(x,str) else (o.row(e=x)if isinstance(x,list) else x)(ui)
    return ui
  def __getattribute__(o,k):
    def iced(*a, e=[],**kw):return lambda e0: o(getattr(e0,blui._kt.get(k,k)) (*a,**kw), *e) #^11
    return iced
  @staticmethod
  def popup(k="props_dialog",w=300):f=getattr(bpy.types.WindowManager,"invoke_"+k);return (lambda o,c,ev:f(o,ev) )if k in{"props_popup","confirm"} else lambda o,c,ev:f(o,width=w)
  do_part=lambda k:vaset(operator_context=k)
ui=blui(); plug=[]
def register():each(plug,U.register_class)
def unregister():each(plug,U.unregister_class)

@apart1 #Menu,Panel, Op(btn)
def bl(name,k,f, kUI="PROPERTIES WINDOW", finer=None,invoker=None,add=None, mixin=object):
  class _ (mixin):
    bl_label = name;bl_idname = k
    def draw(o,ctxt_global):f(o.layout)
    if finer:
      def unregister(o):finer(o)
  if -1!= (i:=k.find("_MT_")):
    class A(_,bpy.types.Menu): show=blui.op("wm.call_menu_pie"if k.startswith("PIE",i+4) else"wm.call_menu", name=k)
  elif 1!=len( (i:=k.rsplit(".",1))):
    class A(bpy.types.Operator):
      bl_label = name;bl_idname = i[0]; op=blui.op(i[0])
      bl_description=i[1]
      __annotations__={k:_pTy(k,*v) for k,v in f.__annotations__.items()}
      def execute(o,ctxt_overriden):f(o, *[getattr(o,k)for k in o.__annotations__.keys()] ); return{'FINISHED'}
      if invoker:invoke=invoker
      if finer: poll=classmethod(finer)
      if add:
        def _insUI(T,op=ui.op(i[0])):
          f=lambda o,c:op(o.layout); T.append(f); return lambda:T.remove(f)
        unregister=_insUI(*add)
  else:
    i=k.index("_PT_"); k0,k1=kUI.split()
    class A(_,bpy.types.Panel):
      bl_region_type = k1
      bl_space_type = k0; bl_context = k[:i].lower(); bl_category = k[:i] #OBJECT_PT_x
  plug.append(A) if name else 0;return A

def _deflCaller(k,**kw): # f=blui.op("wm.menu");  f(arg=):do, f(text=,):UI
  def do(arg=None,**e):
    return Ft.reduce(getattr,k.split('.'), bpy.ops)(**kw,**arg or{})if( arg!=None and not e) else ui.op(k,e=[vaset(**kw,**arg or{})],**e)
  return do
blui.op=_deflCaller # @bl Op-prop helpers
@apart1
def _propDefOn(o,kv): k,arg=kv; setattr(getattr(bpy.types,o.bl_rna.identifier), k, _pTy(k,*arg))

vaset=lambda **kw:lambda o: each(kw.items(),lambda x: setattr(o,*x))
def vagetr(f,fw):
  class D:
    def __getattribute__(o,k):return f(k)
    def __setattr__(o,k,v):fw((k,v))
  return D()

_Ty={T:getattr(bpy.props,f"{v if v else T.__name__.capitalize()}Property")
  for T,v in {set:"Enum",list:"Collection",str:"String",object:"Pointer", bool:0,int:0,float:0}.items()}
def _pTy(k, t,tt="NONE",s="",v0=None,kr={}):
  if tt=="+":tt="UNSIGNED";kr=dict(min=0)
  if tt=="%":tt="PERCENTAGE";kr=dict(min=0,max=1)
  kw={"items" if t==set else "default" if t==bool else "subtype":tt, "description":s}
  if None!=v0:kw["default"]=v0
  return _Ty.get(t,t)(name=kr.pop("name","")or blui.kFmt(k),**kw,**kr) #ui.prop(text=)

It’s not just about how to get a usable UILayout Tree(with class(mixin) or tricks). Frequently-used bpy.props and Panel, Operator.execute are “extensible class es”, so it’s worth to take more effort on tons of boilerplates, that’s why Java bad
pass ctxt every class: def singleFunc():, use str IDs to call menu, operator(always 0-arg, in same addon), etc. for basic functionality, is quite strage.

Blender 3D is not GUI library, complicated GUI codes should be minor part of addon, UI/prop/op editing is no big deal , but in current API, you will new.py for every short Operator, Menu, even keybinding can’t done w/o “Python programming”

You clearly have strong feelings about how UI code should look, and I’m not going to argue with your opinions. But a few suggestions which others have said too:

  1. It is very hard to follow your code. Whitespace in python is your friend. If you want others to use, understand and improve what you did think about making it more “pep8” style with newlines and indentations.
  2. You do a few things like “def each(a,f):
    for x in a:f(x)”
    Why not just use map()? No need to reinvent the wheel.
  3. A couple times you mention fitting all this into 120 lines of code. Again it would be more useful it was longer and more readable. I’ve never see python code that people brag about making it more dense.
  4. Moreover, if this is what you think Blender UI system should look like, why not work with the UI module to add this directly to Blender?
  5. “even keybinding can’t done w/o “Python programming””. Yes, you do need programming to do programming.
1 Like
  1. My library won’t follow PEP8, and an auto-formatter can be used to make PEP happy

  2. map()->iterable, won’t execute directly. others use [f(x) for x in items] but it’s …

  3. not dense, it’s “less boilerpates”, less row = layout.row()...
    libAPI is not a part of my demo. plug.append(AClass); return AClass has exactly one ability: return also(plug.append,AClass) but is limited to make Python- one “computer language” happy
    If I write libAPI with lots of linebreaks, I may not be able to refactor it’s logics. Are books printed w/o long text lines?

  4. Whole Blender/3.2/scripts uses lots of “desugared” UI API, I want to replace them all(with transpiler tool) and keep performance by codecache, new UI module is just a suggestion, needs time to verify

  5. bpy.ops has an ability to record macros(e.g. Add Hotkey), so keymaps should be defined using “export”, even MS Office knows how to use “named Operators” !
    But it looks like blender won’t promise to record all op-props. That’s why opensource tools are less “friendly” than commercial ones

As much as this may bother you, any code aiming to land in mainline blender will have to follow blenders existing codestyle which mentions to follow PEP8 (with a few exceptions) and not to use single letter variable names.

PEP won’t bother me.

import ast
e=ast.parse("lonng=1; print(lonng)")
e.body[0].targets[0].id = "i";e.body[1].value.args[0].id = "i"
eval(compile(e,"","exec")) #1

Languages is just a tool for describing what U want, not how to do, like Blender uses class as much as possible, to store props and share metadata, but bl_ makes no sense for devs, a better API should be portable&modular, even the same code is ported to Maya, less changes will be made.

Texts cannot bother what U think. how to write codes, it’s for code-tools

In real world code, it is very common to have conditional UI, for example:

That sort of complex logic is very hard to do in a declarative way. Another benefit of Blender’s UI code is that it’s easy to abstract it into a function and reuse it many times:

This sort of flexibility and power is needed for complex UIs, a declarative approach is not good enough.

One of the biggest requirements to contribute something to any open source project is the ability to work together and compromise.

This is imo even more important than the code itself, because any code added needs to be maintained into the future.

While I kinda like a more declarative option for panels, your complete unwillingness to do something as simple as use ‘label’ instead of ‘l’ (for example) doesn’t really look good on the collaboration front…

7 Likes