Proposal: Python logic brick with custom GUI

While thinking about a enhancing the design of DynamicTexture, I stumbled across the fact that I need controller individual parameters.

Let me explain on that exact example.

Situation

Just now Dynamic Texture allows to setup a texture source:


The Python controller tells what code to run. This code needs the path to the image file as input. The advantage is that this code can be used on any object with different filenames. The parameter (as property) is object individual. This means several objects can have different parameter content.

So far so nice.

Today I was looking at ImageMix (Due to a question regarding transition between textures). Implementing ImageMix is not a big deal - it has no initial parameters.

ImageMix is fed with parameters after creation (the texture sources to mix in). I would like to use the existing methods to create the sources. Unfortunately I need a way map the input parameters to code. Here is an example to do that by name by adding a suffix (.1, .2):


This is possible, but really no good design. It is not clear what parameter belongs to what controller. It also requires a huge impact on the implementation of the already existing sources. The design feels not good.

What if we had an option to setup

Logic brick individual parameters

This means it is possible to setup parameters at the logic brick. The above example could be configured like that:


This has several advantages:

  • easy to see relationship between logic brick and parameter
  • no need to remember the parameter name
  • visible default values
  • correct value type
  • more options to enter values (e.g. by file dialog, property name, selector …)
  • fully backward compatible
  • works in module and script mode

There are drawbacks too:

  • does not exist
  • currently no build in method to configure GUI for Logic bricks
  • describing the GUI is Blender API, the executed code is BGE -> different scripts

Implementation

At the moment I have just a vague idea how that could be implemented.

The BGE player needs to be enhanced to read and apply the custom parameters when loading the scene.

The BGE API needs to be enhanced to allow access to the custom parameters. Examples:

call with additional arguments (module mode only):


def setup(controller, image):
    print( "image file name:", image)

Parameter in context (script and module mode):
via controller attribute


import bge

controller = bge.logic.getCurrentController()

imageFileName = controller.image
print( "image file name:", imageFileName )

via controller item


import bge

controller = bge.logic.getCurrentController()

imageFileName = controller["image"]
print( "image file name:", imageFileName )

Blender already provides options to describe GUI elements. It needs to be enhanced to use it at Logic bricks (python controller).

The Brick Setup and the BGE code needs to be kept separately. You can see BGE code as “just another parameter” - which it already is.

Configuring gui elements could look like that:


   imageProperty = bpy.props.StringProperty(name="Image", subtype = 'FILE_PATH')
...
   def draw_buttons(self, context, layout):
        layout.prop(self, "purposeProperty")

This snippet is taken from setting up elements of the node editor. So why not follow the same road?

What do you think?

Can such a design work?

One simple approach could be to use the Python type-annotations. Given the currently restricted support for UI_configured Game Properties, it would be trivial to support the basic str, int, float bool types. This way, the feature would be provided for free for those functions which annotate their arguments, whilst nothing would change for existing code:


def some_controller(cont, x:int=1, y:bool=False):
    pass

However, I think this gets a little confusing … Controllers are already a little cumbersome. In nearly every case, controllers aren’t stateless, and in addition require their own private state that is typically facilitated by using object properties.

Such a proposal might be a time to consider upgrading the support for Python controllers to a class object rather than a pure function. A less invasive change would be to pass a state dict to the controller function, but at that point you’re trying to mimic a class without providing one. Using a class would also benefit defining the controller parameters, as a dictionary object could be used.

I really think that bge should be moved into the node editor

then a python script node could get multiple input channels defined by the programmers easily. same with output from script.

Hi, I personaly think logic bricks are very intuitive as they are. My point of view is maybe a bit “old-fashionned” as everyone is talking about logic redesign but I think there are other ways to improve logic management. Overload logic bricks could break current simplicity. Moguri’s python components are very interesting to write kind of custom logic with custom properties, but this system needs that users begins to work on it and share their components as we share GLSL 2D filters on BA ressources forum hehe.

Both (logic bricks + python components) are my preference.

(For nodes, I’m not against, but first you need to find someone that knows Blender UI C code and is motivated to write new design, then motivated devs to implement logic in C++, and if it replaces logic bricks, there won’t be backward compatibility… Too huge redesign and too much potential problems in my opinion.)

All of this to say: If you are interested Monster, you can have a look at python components system (we already implemented it in upbge but I think almost no one use it except pqftgs (And I’m not sure that he uses it often)). The title of your post made me think about python components, and this is why I answered (a bit off topic sorry) edit: we could eventually try to add an askOpenFilename button beside string property edit: no bad idea in fact hehe

I posted the proposal as I read the Blender 2.8 we stay with the logic bricks. Therefore improving them (at least a tiny bit) can help us.

@agoose77, providing the parameters as dictionary is a nice idea, too. It could be like that:


def setup(controller, **kwargs):
   kwargs.get("image"
imageFileName = kwargs.get("image")
print( "image file name:", imageFileName)

At the moment I do not get what you mean with “Controllers are […] a little cumbersome”.

I see the controllers (wihin BGE API) are classes already. Placing the arguments into the controller as attribute would fit very well (controller.<argumentname>). Isn’t that what you mean?

Describing the GUI-Description (the Blender code) as annotations to the BGE code requires to write a complete new way of Blender scriping. I’m not sure if a third API provides many benefits.

An argument description should contain more than just a name and a type. As a GUI developer I should provide anything that helps the user to use it. The bpy.props already do that (even when my above example is very puristic). It allows to describe:

  • name
  • type
  • doc string (description)
  • default value
  • additional gui elements (such as select file dialog)
  • listeners (e.g. to validate user input in Blender)
  • where it is placed within the GUI

As said this is already present in Blender (not as annotation). It is currently not available at the logic editor.

I sugget to invert the dependency from BGE code transfers GUI description to a Blender script that either contains or refers to BGE code. (Keep in mind, neither the Blender nor the BGE code should be forced to be in a single file).

As a side effect you can install that Blender code (which can provide the BGE code) as Add-on. This way you have a custom script that can be provided as third-party product to anyone who wants to use it.

I’m unsure if Blender code and BGE code should ever share a file and how.

Development process
How would you write the custom controller code? BGE code first Blender code first? I guess you do not need Blener code when there are no GUI arguments. Therfore I would start with the BGE code.

When I discover I need an argument I would start a new Blender script that allows me to describe this single parameter at th GUI. So I get two scripts (one with BGE code, one with Blender code).

Then I would continue on the BG code until I need to modify the arguments again. This way I toggle between BGE code and Blender code.

I need to decribe the argument names in both files. But I describe other metadata ( type, description …) in the Blender file only. This means the impact on the BGE code would be very small.

@BluePrintRandom: I’m not talking about nodes, or nodes logic. This is a differnet topic. The reference to the node editor is that it already supports the description custom UI (see above). That is something the the logic editor would benefit too. Indeed it should be possible to mimic the current logic editor with the node editor. This way it would be possibleto get a feeling, what is necessary to describe the GUI elements.

Proposal was already submitted for UPBGE logic brick interface for pythonComponent API:

At the moment, unless a Python controller is used only to deal with external state (e.g GameObject properties defined in the UI, activating controllers etc) then it usually requires several parts to “get started”.

For example, if you need to initialise some state in a controller, you either must do so lazily (with a special getter) e.g


def get_initial_value(cont):
    own = cont.owner
    try:
        return own['_value']
    except KeyError:
        value = own['_value'] = list(range(100))
        return value

def some_func(cont):
   initial_value = get_initial_value(cont)

or with a separate Python controller (a mess).

In addition, when using Python scripts you really don’t know whether they are safe to use with other scripts. Very few programmers actually obfuscate their script properties by name, and to do so would be incredibly tedious:


def get_namespace(cont):
    key = cont.name
    
    try:
        return cont.owner[key]
    except KeyError:
        namespace = cont.owner[key] = {} 
        return namespace




def with_namespace(func):
    def wrapper(cont):
        namespace = get_namespace(cont)
        func(cont, namespace)
    return wrapper




@with_namespace
def some_controller(cont, data):
    data['x'] = data.get('x', 0) + 1

In fact, this is all rather ugly. I generally dislike writing data to and from dictionaries using the slice syntax when I’m dealing with a namespace. So I wrote a small module to make controllers more “class-like”, in that it provides some basic features that ease the pain of writing controllers: lazy init support (and other methods), controller-specific namespaces and configuration properties.

Using this tool, script writers won’t accidentally shadow other instances of their controller, or other scripts entirely, as long as they use the built-in namespace. This still doesn’t solve the issue of parameters, but we can go one step further and search for prefixed properties defined in the property panel.

https://blenderartists.org/forum/showthread.php?410769-Python-Controller-Helper&p=3121829#post3121829 - See Functional API


from utils import magic




####################### Format timer class ######################
@magic # Here's the magic
def format_timer(self):
    self.owner['Text'] = self.message




@format_timer.method
def __repr__(self):
    return "&lt;Magic formatter!&gt;"




@format_timer.method
def __init__(self):
    '''Custom init method'''
    print(self)
    print(dir(self))




@format_timer.property
def message(self):
    '''Custom property'''
    return self.format.format(self.timer)
    


@format_timer.property
def timer(self):
    return self.owner[self.prop_name]




##########################################################
# To support setter properties, a custom property descriptor is used, which is mutable
# This means that the name of the setter function is ignored.
@timer.setter
def timer(self, value):
    raise ValueError("Can't set current time!")



In the example, a class with an init and repr method, and two property descriptors was defined in a functional syntax.

Now, once you start adding methods here and there, it might be cleaner to write your class using the conventional Python API. In this case, the Class API approach is likely a better approach.


from utils import CustomController




####################### Format timer class ######################
class FormatTimerController(CustomController):


    def __init__(self):
        '''Custom init method'''
        print(self)
        print(dir(self))


    @property
    def message(self):
        '''Custom property'''
        return self.format.format(self.timer)


    @property
    def timer(self):
        return self.owner[self.prop_name]


    @timer.setter
    def timer(self, value):
        raise ValueError("Can't set current time!")
    
    def on_triggered(self):
        self.owner['Text'] = self.message


    def __repr__(self):
        return "&lt;Magic formatter!&gt;"


format_timer = FormatTimerController.entry_point

why not use

if property (imagename) changed—and----set Run = true

if run=true--------transition(when complete set run to false)

so then just changing the property fires the control logic?

This proposal seams to deal with components. From my perspective components act as constantly running actuators (without the option to deactivate them from an higher level).

I’m not talking about manipulating the logic at runtime. It is the opposite. This proposal improves design time as it enhances the SCA-GUI - which is not available at runtime.

Currently I do not see it is a similar thing.

While this is correct it is not always an issue the object is part of the narrow context of the Python controller. I do not see a bit deal in using it. The issue I’m looking at is when we have two or more python controller at the same object requiring different parameters. As long as the parameter names are different it is no big deal but when they share the same name there is no way to map them to the according controller/code. [I can remember after introduction of the module mode there was a demand to add parameters to the call].

Initialization and processing are two separate (but dependent) operations. They do different things and they are triggered by different events. Therefore they deserve different logical units (different python controllers). When you mix them you mix all conditions (context and events) with all the benefits and drawbacks of a god object.

Due to this different operations additional parameters are not required. You need different code already. In this situationmy propoals has not much use.

That is where the proposal can really help. You provide the parameter by python controller rather than by object. This way you can ensure the arguments can’t be incorrect for the controller (unless the user configures the incorrect script/module). The option to use properties is untouched and would mix very well with GUI arguments.

That is why I ask for opinions. With enough different options it should be easier to validate what is good and what not. At the moment I prefer the “attribute at controller”-option.

That is already possible. The proposal is to provide an option with better usage. The user does not even need to type the property name, as the parameter is set by the Blender script.

I do not understand that. This has nothing to do with controller individual parameters.

Just to make it clear … “image” is already set within the GUI. It will not change over time.

The goal is to have two controllers that use the same property name but with different content. This can be the same script as well as different scripts (but using the same parameter name).

This situation does not occur very often. If it occurs you need some workarounds that doe not feel right and easily can lead to mis-configuration.

How about that:

  • select Python controller.
  • select the GUI script - which installs the parameters at that logic brick
  • setup the parameters via GUI.

This can include the BGE code

or the GUI script already provides the reference to the BGE code:

The last one enables you to create an “all-in-one” script that. I’m not sure if it is necessary to show the BGE script/module name to the user.

Controllers should support the dict API as GameObjects do, to provide better encapsulation support.

I really don’t think the UI should be separate to the controller code.
The code used in Python controllers is written explicitly for use in Python controllers. It rarely has utility outside of those controllers. For script writers who wish to redistribute their scripts, they would then be required to distribute two files-per brick, unless we allow a module-like function to be used, in the same way we do for the executable code for the controller. In that case, it gets slightly better, but why stop there? The definition of a Python controller is already explicit as aforementioned, why not include a UI specification?

I understand that because these parameters are being drawn in the Blender editor it’s tempting to declare draw code separately using bpy. But, the thing is that it’s such a constrained option set, that it seems better to just parse a schema/spec for the UI. I do see that being able to define a custom UI for the parameters may have some use when providing custom UIs (e.g an FSM designer), but we still need to enforce some safety on data types and supported values (we can’t go passing BPY data into the BGE), so this is probably a null point.


my_controller_params = {'some_value': (logic.KX_IntProp, 1), 'some_path': (logic.KX_FilePath, '')} 

@bge.logic.parameters(my_controller_params)
def my_controller(cont):
    pass


However, I feel that it’s nicer to use the native annotations for this.


def my_cont(cont, filepath:logic.KX_FilePathProp='', count:logic.KX_IntProp=0):
    pass

Still ugly though.

This would be a good time to leverage the updates in Python 3.6 - variable annotations (see this task too https://developer.blender.org/T47811).

At this point, it seems better to move towards subclassing than anonymous functions. Here we want to have distinct controllers with their own parameters - that shouts “type” to me, not “behaviour”. This way, we could declare a 3rd Python mode - “Custom” which would perform the native subclass. Of course, if we can now store our own data on a subclass, it might be useful to drop controller-specific dictionaries. But it would still be a useful feature when using traditional controllers.


from bge import logic, types




class MyBrick(types.SCA_PythonController)
    
    filepath: logic.KX_FilePathPop = ''
    count: logic.KX_IntProp = 0
    
    def on_triggered(self):
        pass

I dont understand why

own[‘Image1’] and own[‘Image2’]
are a bad design?

and if we can get the whole logic brick system coded up in the node editor with all the data hooks exposed, you could design it however you want right?

(python nodes could have custom in/out?)

I do see that “logic brick developers” want to distribute as less assets as possible (e.g. a single file).

I see that the description of a custom GUI has nothing to do with the BGE code, beside the named access to the actual values (which is finally the interface between the two aspects).

By trying to provide a single file solution I would invert the ownership. I would write BPY code to describe the GUI which can include the BGE code as part of the configuration.

This way it would be possible to define the BGE code in more ways:

  • user configured Module/Script (Means the BPY code can be bound to any BGE code by the user)
  • hard-coded Module/Script by referring to the separate BGE code files
  • hard-coded Python code provided within the BPY code