7
$\begingroup$

How to create a new window with a certain dimensions (for example 40% of screen size) using Python API?

Functionality-wise similar to File > User Preferences — a new window shows up with User Preferences area type.

$\endgroup$
11
$\begingroup$

Currently in Blender 2.79 the only way to manage windows through Python API is by bpy.ops.screen.area_dupli() and bpy.ops.wm.window_duplicate() operators.

But they add new screens to bpy.data.screens, which are not easy to remove.

And there is no way to resize created windows.

UPDATE 1:

In my particular case I can use window created by userpref_show operator, it's size is very close to what I need and it creates 'temp' screen which is deleted after window is closed, so I do not need to cleanup bpy.data.screens afterwards:

import bpy

# Call user prefs window
bpy.ops.screen.userpref_show("INVOKE_DEFAULT")

# Change area type
area = bpy.context.window_manager.windows[-1].screen.areas[0]
area.type = "TEXT_EDITOR"

UPDATE 2:

With render.view_show operator it is possible to set the exact size of created window by modifying scene settings:

import bpy

# Modify scene settings
render = bpy.context.scene.render
render.resolution_x = 640
render.resolution_y = 480
render.resolution_percentage = 100
render.display_mode = "WINDOW"

# Call image editor window
bpy.ops.render.view_show("INVOKE_DEFAULT")

# Change area type
area = bpy.context.window_manager.windows[-1].screen.areas[0]
area.type = "TEXT_EDITOR"

# Restore scene settings
# render.resolution_x = original_value
# etc.
$\endgroup$
  • $\begingroup$ Interesting that it is just as easy to set up buttons in a panel to create editors as popups for specific uses - I made mine a small Image Editor and set it to Paint in the script, and this is actually useful. Thanks. $\endgroup$ – Craig D Jones Nov 2 '17 at 20:52
  • $\begingroup$ simple hack but works as expected, I'm looking a way to create multiple of the window, currently this code only allow only one instance of window $\endgroup$ – aditia Oct 30 '18 at 5:41
  • $\begingroup$ ah by adding as follow allow me to add a lot of windows with different type python import bpy # Call user prefs window bpy.ops.screen.userpref_show('INVOKE_DEFAULT') # Change area type area = bpy.context.window_manager.windows[-1].screen.areas[0] area.type = 'TEXT_EDITOR' bpy.ops.wm.window_duplicate() area.type = 'CONSOLE' $\endgroup$ – aditia Oct 30 '18 at 5:45
  • $\begingroup$ @aditia I do not recommend this, each time you use window_duplicate operator it will add additional screen temp.001 that will not be removed after you close the second window, which will mess up your workflow if you switch between screens as often as I do. $\endgroup$ – Mikhail Rachinskiy Oct 31 '18 at 3:32
  • $\begingroup$ @MikhailRachinskiy ah I see, you're correct, using dupli make screen messier $\endgroup$ – aditia Nov 1 '18 at 4:30
2
$\begingroup$

I have been struggeling with this as well and would like to share my solution. I wanted to be able to open several windows and change their layout in my script. bpy.ops.screen.userpref_show() opens a window with just a single area but it wouldn't allow me to open several windows and the windows would have the title "Blender User Preferences". bpy.ops.wm.window_duplicate() would allow me to open several windows and make them the same size as the current window, but it would open with my multi-area screen and produce a new screen everytime without removing it. This is my solution:

A helper to create contexts for operator calls:

# Construct context
def context(window=None, screen=None, area=None, region=None):
    if window is None: window = C.window
    if screen is None: screen = window.screen
    if area is None: area = screen.areas[0]
    if region is None: region = area.regions[0]
    return {'window': window, 'screen': screen, 'area': area, 'region': region}

Wrappers for Window, Screen and Area:

# Window class wrapper
class Window:
    def __init__(self, window):
        self.window = window

    # Get window screen
    def get_screen(self):
        return Screen(self.window.screen)

    # Create new window
    @classmethod
    def new(cls, screen_name):
        bpy.ops.wm.window_duplicate()
        window = cls(C.window_manager.windows[-1])
        window.get_screen().set_name(screen_name).join_all()
        return window

# Screen class wrapper
class Screen:
    def __init__(self, screen):
        self.screen = screen

    # Get name
    def get_name(self, name):
        return self.screen.name

    # Set name
    def set_name(self, name):
        self.screen.name = name

        # Return self for method chaining
        return self

    # Get scene
    def get_scene(self):
        return self.screen.scene

    # Set scene
    def set_scene(self, scene):
        self.screen.scene = scene

        # Return self for method chaining
        return self

    # Get area
    def area_at(self, index):
        return Area(self.screen.areas[index])

    # Split area and return new one
    def split_area(self, area, dir, factor):
        # Set direction
        if 'vertical'.startswith(dir.lower()): dir = 'VERTICAL'
        elif 'horizontal'.startswith(dir.lower()): dir = 'HORIZONTAL'
        else: raise ValueError("Invalid direction \"%s\""%dir)

        bpy.ops.screen.area_split(
            context(screen=self.screen, area=area.area),
            direction=dir, factor=factor
        )
        return Area(self.screen.areas[-1])

    # Join areas and return success status
    def join_areas(self, area1, area2):
        # Try joining areas
        result = bpy.ops.screen.area_join(context(screen=self.screen),
            min_x=area1.x, min_y=area1.y,
            max_x=area2.x, max_y=area2.y
        )

        # Return eliminated area
        return ('FINISHED' in result)

    # Join all areas
    def join_all(self):
        # Create area index
        index = self.AreaIndex(self.screen.areas)

        # Join all areas
        for i in range(index.count):
            for key in index.x:
                for a1, a2 in index.x_pair_iterator(key):
                    if self.join_areas(a1, a2):
                        index.remove_area(a2)
                        break
            for key in index.y:
                for a1, a2 in index.y_pair_iterator(key):
                    if self.join_areas(a1, a2):
                        index.remove_area(a2)
                        break
            if index.count <= 1: break

        # Return self for method chaining
        return self

    # Area index for area joining
    class AreaIndex:
        # Create area index
        def __init__(self, areas):
            # Create index maps
            self.x = {}
            self.y = {}
            self.count = len(areas)

            # Insert areas into indices
            for area in areas:
                if not area.x in self.x: self.x[area.x] = []
                self.x[area.x].append(area)
                if not area.y in self.y: self.y[area.y] = []
                self.y[area.y].append(area)

            # Sort area lists
            for key in self.x: self.x[key].sort(key=lambda a: a.y)
            for key in self.y: self.y[key].sort(key=lambda a: a.x)

        # Remove area from index
        def remove_area(self, area):
            self.x[area.x].remove(area)
            self.y[area.y].remove(area)
            self.count -= 1

        # Iterate over area pairs for x key
        def x_pair_iterator(self, key):
            for i in range(len(self.x[key]) - 1):
                yield (self.x[key][i], self.x[key][i + 1])

        # Iterate over area pairs for x key
        def y_pair_iterator(self, key):
            for i in range(len(self.y[key]) - 1):
                yield (self.y[key][i], self.y[key][i + 1])

# Area class wrapper
class Area:
    def __init__(self, area):
        self.area = area

    # Get area type
    def get_type(self):
        return self.area.type

    # Set area type
    def set_type(self, type):
        self.area.type = type

        # Return self for method chaining
        return self

Create a window with two areas and remove unused screens:

SCREEN_NAME = "temp"

# Create new window
window = Window.new(SCREEN_NAME)
screen = window.get_screen().set_scene(D.scenes['Test'])

# Setup areas
node_area = screen.area_at(0).set_type('NODE_EDITOR')
movie_area = screen.split_area(node_area, 'v', 0.7).set_type('CLIP_EDITOR')

# Get used screens
used_screens = set(
    w.screen for w in C.window_manager.windows
    if w.screen.name.startswith(SCREEN_NAME)
)

# Delete unused screens
screen_backup = C.window.screen
for s in D.screens:
    if s.name.startswith(SCREEN_NAME) and s not in used_screens:
        bpy.ops.screen.delete(context(screen=s))
C.window.screen = screen_backup
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.