Join two areas by python (area_join) ? What arguments? (Blender 2.80+)

Hello,
I’m trying to join two areas (split horizontally) but don’t seem to understand why it’s not working…

def execute(self, context):

    area1 = bpy.context.screen.areas[3]
    area2 = bpy.context.screen.areas[-1]
        
    bpy.ops.screen.area_join(cursor=(area1.y, area2.y))

I tried all 16 combinations, some of them joined the areas but worked only once when I ran the code (not when I called the operator, strangely), but if I drag another screen to try again. It’s not working anymore.

What is the “cursor=” expecting as arguments?

Thanks.

1 Like

That should be a position (both x then y) of a point between the two areas. Here is an ideal place for this in your examples:

But note that there is quite a bit of wriggle room so you don’t have to be too exact. In your example it could be anywhere along that horizontal line and vertically just has to be close.

How do you translate that in code for only 2 arguments?

Should the first and second arguments be x and y coordinates to reach that cross you made? Something like:

bpy.ops.screen.area_join(cursor=(area1.width, int(area2.height /2)))

Probably the first thing that is going to screw you up is that I don’t think we actually have exactly what you want exposed to python, which would be a simple non-interactive join of two areas. That would be useful.

This thing that you are calling is (I think) just going to initiate an interactive join at a particular position, normally actual cursor position but here you are overriding that with that “cursor” argument. To specify that position you’d need both horizontal and vertical values. You can’t just use widths and heights because the left and top edges are probably not at the screen edges. So X is more like area1.x + (area1.width / 2) and the Y is probably just area1.y. Or you could find the (almost) identical values using the top of area2.

Something like that anyway. I have never tried it from python, although I know some people have. Programmatically starting an interactive process seems a bit goofy though, since it will still require a movement of your mouse to finish it.

I tried this, but it didn’t work:

bpy.ops.screen.area_join(cursor=(area1.x + int((area1.width / 2)), area2.y))

I was thinking that if “area_split” allows to non-interactively split the area, then so would “area_join”.
Also there are some examples on the net, but they used to require 4 arguments instead of 2…

There is also this example here, but I can’t replicate it… I’m probably messing up the formula.

I’m not really surprised as that is not what I said to try. Looking at your illustration, with area1 on top and area2 below, this would be a location half-way across horizontally, but on the bottom on area2. Certainly isn’t between them.

So I narrowed down the formulas to join 2 areas!

I printed in the console, the coordinates to have a clearer vision, with the following instructions…

print("Area1 = X: %s \t Y: %s \t W: %s \t H: %s" % (area1.x, area1.y, area1.width, area1.height))
print("Area2 = X: %s \t Y: %s \t W: %s \t H: %s" % (area2.x, area2.y, area2.width, area2.height))

And here are the formulas that work… Kind of…

# VERTICAL SPLIT FORMULA
bpy.ops.screen.area_join(cursor=(area1.x, area1.y + area1.width))

# HORIZONTAL SPLIT FORMULA
bpy.ops.screen.area_join(cursor=(area1.x, area2.y + area2.height))

Now, a very weird thing is happening…
I have attributed the shortcut ‘F1’ to launch the operator (it works from all editors on the screen).

And then I follow these exact steps in that exact order:

1) I run the script 		# From the Text Editor nearby		
2) I call the operator (F1)	# Nothing happens (here I expected the areas to be joined)
3) I re-run the script		# The 2 areas get joined without any interaction on my part... Weird!

Any idea why this is happenning?

If you want to try, here is the full script:
(Just make sure the ‘index’ is correct for your 2 areas: bpy.context.screen.areas[index])

bl_info = {
    "name": "Asset Browser Shelf",
    "blender": (2, 82, 0),
    "category": "Custom"
}

import bpy
from bpy.types import Operator

# ASSET BROWSER SHELF
class Asset_Browser_Shelf(Operator):
    bl_idname = "asset_browser.shelf"
    bl_label = "Asset Browser Shelf"
    bl_description = "Description"
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):

        # JOIN 2 AREAS
        area1 = bpy.context.screen.areas[3]
        area2 = bpy.context.screen.areas[-1]
        
        print("--------------------------")
        print("Area1 = X: %s \t Y: %s \t W: %s \t H: %s" % (area1.x, area1.y, area1.width, area1.height))
        print("Area2 = X: %s \t Y: %s \t W: %s \t H: %s" % (area2.x, area2.y, area2.width, area2.height))

        # VERTICAL SPLIT FORMULA
#        bpy.ops.screen.area_join(cursor=(area1.x, area1.y + area1.width))

        # HORIZONTAL SPLIT FORMULA
        bpy.ops.screen.area_join(cursor=(area1.x, area2.y + area2.height))

        return {'FINISHED'}

classes = (
    Asset_Browser_Shelf,
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name = "Window", space_type='EMPTY', region_type='WINDOW')
    kmi = km.keymap_items.new('asset_browser.shelf', 'F1', 'PRESS')
    kmi.active = True

def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)

    addon_keymaps = []

    if wm.keyconfigs.addon:
        for km in addon_keymaps:
            for kmi in km.keymap_items:
                km.keymap_items.remove(kmi)

            wm.keyconfigs.addon.keymaps.remove(km)

    addon_keymaps.clear()

if __name__ == "__main__":
    register()

Hey Ryxx, have you figured this out yet? I was trying your script and I believe I did everything ‘correctly’ since I have been delving pretty deep into this area_join operator, but nothing happens in my case.

I’m using Blender 3.0.0 alpha. I noticed that there is a new operator in this version called bpy.ops.screen.area_close () but it currently gives an error ‘invalid operator call’… Interesting though, maybe someone is working on another way of closing areas.

Actually, I got it to work exactly as you described on 2.93.

Fairly soon I’ll work on getting Python support for the new “Close” operator and some changes to “Join” so it can work better with a passed “Direction” so you can specify which area is remaining and which is meant to close, and should allow you to do the complex joins that are now possible.

Planning on submitting patches in a week or two or so.

5 Likes

Brilliant! I was just thinking of what an ideal add-on would look like if I were to try creating one… I thought it would be cool if the joining had similar functionality to pie menus - with the effortless press and release as you move the mouse in a direction. I made a graphic. I wonder if something like this could be possible…

Hey @Harleya,

I am stuck at the same problem at the moment. Very interested in that patch as well!

Let us know when there are some news :slight_smile:

I haven’t had much to do with Python support so started with the simpler part, doing so for my new “Close” - ⚙ D12307 Python: Allow Area Close via Scripting - then can use anything learned in the review process for “Join” afterward.

1 Like

Tried this solution but the UI goess bonkers and seems to be stuck. Only when i call the preferences does the window actually refresh or redraw. I tried adding context.area.tag_redraw() but it returns error because i think its trying to redraw the just closed screen area

Did the patch for joining areas from the api ever go anywhere?

It would be really useful for me at the moment, but it doesn’t seem like it was ever added…

Thanks!

1 Like