Shading.type crash

Hey, expanding on this post: Access to workspace areas

I am using:

for area in bpy.data.workspaces['my_workspace'].screens[0].areas:
    if area.type == 'VIEW_3D':
        for spaces in area.spaces:
            if spaces.type == 'VIEW_3D':
                spaces.shading.type = 'WIREFRAME'

Which works as expected when “my_workspace” is active, however it crashes when its not active.
Is this a bug or am I missing something?

It looks like Blender can’t handle accessing shading members if the workspace isn’t open. It’s probably a bug.

Any particular reason you need to change to wireframe in an unopened workspace / editor? Could probably work around it.

I do a lot of flipping between workspaces, and I don’t like constantly having to synchronise all the settings, so in 2.7 I just made a script that keeps them all in sync. I feel like blender should have this as in built functionality but I guess I’m alone haha

That makes sense. You could get around this by applying the settings as you enter the workspace, though it requires you to use a shortcut or custom menu to swap workspace, since there are no handlers for changing workspaces.

Now, I know you didn’t ask for this, but I thought the issue was interesting enough to try and work around.

This stores shading.type and switches to ‘Animation’ with a delayed execution. An interesting thing to note: Blender restricts the context between switching, so we have to loop from ‘window’ in load_shading() to avoid getting None back from context, and to be able to apply the settings.

import bpy
from functools import partial
C, D = bpy.context, bpy.data

def store_shading():
    if C.area.type != 'VIEW_3D':
        v3d = [a.spaces[0] for a in C.screen.areas if a.type == 'VIEW_3D'][0]
        assert v3d, "No view3d"
        return v3d.shading.type
    return C.space_data.shading.type

def load_shading(shading):
        wm = C.window_manager
        v3d = [a.spaces[0] for w in wm.windows for a in w.screen.areas if a.type == 'VIEW_3D'][0]
        v3d.shading.type = shading

shading = store_shading()
C.window.workspace = D.workspaces['Animation']
bpy.app.timers.register(partial(load_shading, shading), first_interval=0.003)

This is fascinating, thats how I originally planned on doing it, I must have got stuck at the restricted context.

Just letting you know I submitted a bug report and it was confirmed and resolved:
https://developer.blender.org/T58756

I will use your method for now though, so thank you very much :slight_smile:

1 Like

Hey, sorry to bother you again. I was wondering if it would make more sense/be possible to just store the whole source VIEW_3D area.space and duplicate it to the target VIEW_3D area.space, rather than storing every .shading, .overlay etc

# store
for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':        
        for space in area.spaces:
            if space.type == 'VIEW_3D':
                stored_space = space
    
# restore
for area in bpy.data.workspaces['my_workspace'].screens[0].areas:
    if area.type == 'VIEW_3D':
        for space in area.spaces:
            if space.type == 'VIEW_3D':
                space = stored_space

# switch workspace

The above code doesn’t work, its just to show what I mean. I was wondering if you could help as i’m still a noobie coder.
Thanks

No problem. Afaik a variable can only store one type of data, eg. a Bool setting, an integer etc. However, you can use a dictionary to store different types of values tied to a string or ‘key’.

So you could first make a string tuple of all settings you want to copy, then loop through the source ‘space_data’ and grab values using getattr(), before finally looping through the target workspace’s ‘space_data’ and applying them using setattr().

Edit:
Turns out setattr works fine for setting properties in unopened workspaces. Updated the script. Also uploaded the blend file I used for testing: set_props_workspace.blend (502.0 KB)

# Using workspace 'Animation' as an example

import bpy
from bpy import context as C
D = C.blend_data

shader_settings = (
    "background_color",
    "background_type",
    "cavity_ridge_factor",
    "cavity_type",
    "cavity_valley_factor",
#    "color_type",
    "curvature_ridge_factor",
    "curvature_valley_factor",
    "light",
    "object_outline_color",
#    "selected_studio_light",
    "shadow_intensity",
    "show_cavity",
    "show_object_outline",
    "show_shadows",
    "show_specular_highlight",
    "show_xray",
    "show_xray_wireframe",
    "single_color",
#    "studio_light",
    "studiolight_background_alpha",
    "studiolight_rotate_z",
    "type",
    "use_scene_lights",
    "use_scene_world",
    "use_world_space_lighting",
    "xray_alpha",
    "xray_alpha_wireframe"    
)

# Returns a dict with shader props from C.space_data
def get_shader_props():
    areas = C.screen.areas
    v3d = [a.spaces[0] for a in areas if a.type == 'VIEW_3D'][0]
    dict = {}
    for setting in shader_settings:
        dict[setting] = getattr(v3d.shading, setting)
    return dict


# Applies props from supplied dict 
def set_shader_props(dict, workspace):
    areas = workspace.screens[0].areas
    v3d = [a.spaces[0] for a in areas if a.type == 'VIEW_3D'][0]
    for setting in dict:
        setattr(v3d.shading, setting, dict[setting])

# Target workspace
workspace = D.workspaces['Animation']

props = get_shader_props()
set_shader_props(props, workspace)

# Switch to workspace
C.window.workspace = D.workspaces['Animation']
1 Like

Btw, I just noticed something:

setattr doesn’t seem to crash Blender when changing shading values for a workspace you’re not in. So technically you could apply the settings before entering and my previous workaround can possibly be disregarded.

Edit: Updated my previous post.

1 Like

Wow this is amazing thank you so, so much. It works perfectly.
I added overlay and spaces settings. Not sure I did it the best way possible (I made it a tuple) but it works, so I’m just going to leave it here in case other people need it (feel free to correct it if the tuple is bad code):

import bpy
from bpy import context as C
D = C.blend_data

shader_settings = (
    "background_color",
    "background_type",
    "cavity_ridge_factor",
    "cavity_type",
    "cavity_valley_factor",
#    "color_type",
    "curvature_ridge_factor",
    "curvature_valley_factor",
    "light",
    "object_outline_color",
#    "selected_studio_light",
    "shadow_intensity",
    "show_cavity",
    "show_object_outline",
    "show_shadows",
    "show_specular_highlight",
    "show_xray",
    "show_xray_wireframe",
    "single_color",
#    "studio_light",
    "studiolight_background_alpha",
    "studiolight_rotate_z",
    "type",
    "use_scene_lights",
    "use_scene_world",
    "use_world_space_lighting",
    "xray_alpha",
    "xray_alpha_wireframe"    
)

overlay_settings = (
    "show_overlays",
    "show_wireframes",
    "show_floor"
)

spaces_settings = (
    "clip_start",
    "clip_end",
    "show_object_viewport_mesh"
)

# Returns a dict with shader props from C.space_data
def get_shader_props():
    areas = C.screen.areas
    v3d = [a.spaces[0] for a in areas if a.type == 'VIEW_3D'][0]
    dict_shading = {}
    dict_overlay = {}
    dict_spaces = {}    
    for setting in shader_settings:
        dict_shading[setting] = getattr(v3d.shading, setting)
    for setting in overlay_settings:
        dict_overlay[setting] = getattr(v3d.overlay, setting)
    for setting in spaces_settings:
        dict_spaces[setting] = getattr(v3d, setting)
    dict = (dict_shading, dict_overlay, dict_spaces)

    return dict


# Applies props from supplied dict 
def set_shader_props(dict, workspace):
    areas = workspace.screens[0].areas
    v3d = [a.spaces[0] for a in areas if a.type == 'VIEW_3D'][0]
    for setting in dict[0]:
        setattr(v3d.shading, setting, dict[0][setting])
    for setting in dict[1]:
        setattr(v3d.overlay, setting, dict[1][setting])
    for setting in dict[2]:
        setattr(v3d, setting, dict[2][setting])

# Target workspace
workspace = D.workspaces['Workspace_name']

props = get_shader_props()
set_shader_props(props, workspace)

# Switch to workspace
C.window.workspace = D.workspaces['Workspace_name']

Again thank you so much :slight_smile:

1 Like