Update for Blender 5.2
- use new api compositor access - Fix file output nodes connexion/renumber/rename/slots handlibng - consider the new output types - fixes node position with new API - fix errors with handler LLM assisted update
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Blender addon for rendering setup and checks
|
Blender addon for rendering setup and checks
|
||||||
|
|
||||||
|
Requires Blender 5.2+ (for Blender 4.x, use version 1.0 of the addon)
|
||||||
|
|
||||||
## Environment variable available:
|
## Environment variable available:
|
||||||
|
|
||||||
File Output path templates :
|
File Output path templates :
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@ bl_info = {
|
|||||||
"name": "Render Toolbox",
|
"name": "Render Toolbox",
|
||||||
"description": "Setup outputs and perform cheks for rendering",
|
"description": "Setup outputs and perform cheks for rendering",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (1, 0, 0),
|
"version": (2, 0, 0),
|
||||||
"blender": (4, 0, 0),
|
"blender": (5, 2, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox",
|
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox",
|
||||||
|
|||||||
@@ -11,31 +11,84 @@ def get_addon_prefs():
|
|||||||
|
|
||||||
# region Manage nodes
|
# region Manage nodes
|
||||||
|
|
||||||
|
def get_compositor_tree(scene=None):
|
||||||
|
'''Return the compositing node group of passed scene (active scene if not passed)
|
||||||
|
None if the scene has no compositing node group
|
||||||
|
'''
|
||||||
|
scene = scene or bpy.context.scene
|
||||||
|
return scene.compositing_node_group
|
||||||
|
|
||||||
|
def get_group_output_node(tree, create=False):
|
||||||
|
'''Return the active group output node of the tree (compositor output since Blender 5.0)
|
||||||
|
create: create the node, and an "Image" output socket in tree interface, if missing
|
||||||
|
'''
|
||||||
|
out = next((n for n in tree.nodes if n.type == 'GROUP_OUTPUT' and n.is_active_output), None)
|
||||||
|
if out is None:
|
||||||
|
out = next((n for n in tree.nodes if n.type == 'GROUP_OUTPUT'), None)
|
||||||
|
if not create:
|
||||||
|
return out
|
||||||
|
|
||||||
|
if not any(i.item_type == 'SOCKET' and i.in_out == 'OUTPUT' for i in tree.interface.items_tree):
|
||||||
|
tree.interface.new_socket(name='Image', in_out='OUTPUT', socket_type='NodeSocketColor')
|
||||||
|
if out is None:
|
||||||
|
out = tree.nodes.new('NodeGroupOutput')
|
||||||
|
return out
|
||||||
|
|
||||||
def real_loc(n):
|
def real_loc(n):
|
||||||
if not n.parent:
|
return n.location_absolute
|
||||||
return n.location
|
|
||||||
return n.location + real_loc(n.parent)
|
def get_media_type(file_format):
|
||||||
|
'''Return the media type corresponding to passed file format'''
|
||||||
|
if file_format == 'OPEN_EXR_MULTILAYER':
|
||||||
|
return 'MULTI_LAYER_IMAGE'
|
||||||
|
if file_format == 'FFMPEG':
|
||||||
|
return 'VIDEO'
|
||||||
|
return 'IMAGE'
|
||||||
|
|
||||||
|
def set_format_settings(fmt, settings):
|
||||||
|
'''Apply a dict of attributes to an image format settings (e.g: file output node format)
|
||||||
|
media_type and file_format are set first, available values of other attributes depend on them
|
||||||
|
'''
|
||||||
|
settings = dict(settings)
|
||||||
|
file_format = settings.pop('file_format', None)
|
||||||
|
media_type = settings.pop('media_type', None)
|
||||||
|
if file_format and not media_type:
|
||||||
|
media_type = get_media_type(file_format)
|
||||||
|
|
||||||
|
if media_type:
|
||||||
|
fmt.media_type = media_type
|
||||||
|
if file_format:
|
||||||
|
fmt.file_format = file_format
|
||||||
|
for k, v in settings.items():
|
||||||
|
setattr(fmt, k, v)
|
||||||
|
|
||||||
def set_scene_output_from_active_fileout_item():
|
def set_scene_output_from_active_fileout_item():
|
||||||
scn = bpy.context.scene
|
scn = bpy.context.scene
|
||||||
rd = scn.render
|
rd = scn.render
|
||||||
ntree = scn.node_tree
|
ntree = get_compositor_tree(scn)
|
||||||
fo = ntree.nodes.active
|
fo = ntree.nodes.active
|
||||||
|
|
||||||
if fo.type != 'OUTPUT_FILE':
|
if fo.type != 'OUTPUT_FILE':
|
||||||
return
|
return
|
||||||
sl = fo.file_slots[fo.active_input_index]
|
item = fo.file_output_items[fo.active_item_index]
|
||||||
full_path = os.path.join(fo.base_path, sl.path)
|
|
||||||
|
if fo.format.media_type == 'MULTI_LAYER_IMAGE':
|
||||||
|
## All items are layers of the same file
|
||||||
|
full_path = os.path.join(fo.directory, fo.file_name)
|
||||||
|
fmt = fo.format
|
||||||
|
else:
|
||||||
|
full_path = os.path.join(fo.directory, fo.file_name + item.name)
|
||||||
|
fmt = item.format if item.override_node_format else fo.format
|
||||||
|
|
||||||
rd.filepath = full_path
|
rd.filepath = full_path
|
||||||
|
|
||||||
fmt = fo.format if sl.use_node_format else sl.format
|
|
||||||
## set those attr first to avoid error settings other attributes in next loop
|
## set those attr first to avoid error settings other attributes in next loop
|
||||||
|
rd.image_settings.media_type = fmt.media_type
|
||||||
rd.image_settings.file_format = fmt.file_format
|
rd.image_settings.file_format = fmt.file_format
|
||||||
rd.image_settings.color_mode = fmt.color_mode
|
rd.image_settings.color_mode = fmt.color_mode
|
||||||
rd.image_settings.color_depth = fmt.color_depth if fmt.color_depth else 8 # Force set since Sometimes it's weirdly set to "" (not in enum choice)
|
rd.image_settings.color_depth = fmt.color_depth if fmt.color_depth else '8' # Force set since Sometimes it's weirdly set to "" (not in enum choice)
|
||||||
|
|
||||||
excluded = ['file_format', 'color_mode', 'color_depth',
|
excluded = ['media_type', 'file_format', 'color_mode', 'color_depth',
|
||||||
'view_settings', 'views_format']
|
'view_settings', 'views_format']
|
||||||
|
|
||||||
''' ## all attrs
|
''' ## all attrs
|
||||||
@@ -68,21 +121,61 @@ def set_file_output_format(fo):
|
|||||||
'color_mode': 'RGBA'
|
'color_mode': 'RGBA'
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v in env_file_format.items():
|
set_format_settings(fo.format, env_file_format)
|
||||||
setattr(fo.format, k, v)
|
|
||||||
|
def set_file_output_path(fo, path, suffix=''):
|
||||||
|
'''Set directory and file name of a file output node from a single path
|
||||||
|
Multilayer: last element is the file name, ex: "//render/name/name_"
|
||||||
|
Other formats: whole path is the directory, ex: "//render/name/"
|
||||||
|
suffix: optional subfolder added at the end of the directory, ex: "tech/"
|
||||||
|
'''
|
||||||
|
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
|
||||||
|
directory, file_name = re.match(r'(.*[\\/])?(.*)', path).groups()
|
||||||
|
directory = directory or ''
|
||||||
|
else:
|
||||||
|
directory, file_name = path, ''
|
||||||
|
|
||||||
|
if suffix:
|
||||||
|
directory = directory.rstrip('\\/') + '/' + suffix if directory else suffix
|
||||||
|
|
||||||
|
fo.directory = directory
|
||||||
|
fo.file_name = file_name
|
||||||
|
|
||||||
|
## Source socket type to file output item socket type
|
||||||
|
FILE_OUTPUT_ITEM_TYPES = {
|
||||||
|
'VALUE': 'FLOAT',
|
||||||
|
'INT': 'INT',
|
||||||
|
'BOOLEAN': 'BOOLEAN',
|
||||||
|
'VECTOR': 'VECTOR',
|
||||||
|
'RGBA': 'RGBA',
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_file_output_item(fo, name, socket=None):
|
||||||
|
'''Add an item to file output node
|
||||||
|
socket: source socket, if passed item type (and vector dimensions) match the socket
|
||||||
|
return created item and its input socket
|
||||||
|
'''
|
||||||
|
socket_type = FILE_OUTPUT_ITEM_TYPES.get(socket.type, 'RGBA') if socket is not None else 'RGBA'
|
||||||
|
item = fo.file_output_items.new(socket_type, name)
|
||||||
|
if socket_type == 'VECTOR' and hasattr(socket, 'default_value'):
|
||||||
|
## ex: render layer "Vector" pass is 4D
|
||||||
|
item.vector_socket_dimensions = len(socket.default_value)
|
||||||
|
## items inputs are in the same order (followed by a virtual socket)
|
||||||
|
return item, fo.inputs[len(fo.file_output_items) - 1]
|
||||||
|
|
||||||
def clear_disconnected(fo):
|
def clear_disconnected(fo):
|
||||||
'''Remove unlinked inputs from file output node'''
|
'''Remove unlinked items from file output node'''
|
||||||
for inp in reversed(fo.inputs):
|
items = fo.file_output_items
|
||||||
if not inp.is_linked:
|
for i in reversed(range(len(items))):
|
||||||
print(f'Deleting unlinked fileout slot: {inp.name}')
|
if not fo.inputs[i].is_linked:
|
||||||
fo.inputs.remove(inp)
|
print(f'Deleting unlinked fileout item: {items[i].name}')
|
||||||
|
items.remove(items[i])
|
||||||
|
|
||||||
def create_node(type, tree=None, **kargs):
|
def create_node(type, tree=None, **kargs):
|
||||||
'''Get a type, a tree to add in, and optionnaly multiple attribute to set
|
'''Get a type, a tree to add in, and optionnaly multiple attribute to set
|
||||||
return created node
|
return created node
|
||||||
'''
|
'''
|
||||||
tree = tree or bpy.context.scene.node_tree
|
tree = tree or get_compositor_tree()
|
||||||
|
|
||||||
node = tree.nodes.new(type)
|
node = tree.nodes.new(type)
|
||||||
for k,v in kargs.items():
|
for k,v in kargs.items():
|
||||||
@@ -129,79 +222,68 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
|
|||||||
"""
|
"""
|
||||||
if not outputs:
|
if not outputs:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
nodes = scene.node_tree.nodes
|
tree = get_compositor_tree(scene)
|
||||||
links = scene.node_tree.links
|
nodes = tree.nodes
|
||||||
|
links = tree.links
|
||||||
|
|
||||||
fo = file_out
|
fo = file_out
|
||||||
if not fo:
|
if not fo:
|
||||||
fo = nodes.get(out_name)
|
fo = nodes.get(out_name)
|
||||||
if not fo:
|
if not fo:
|
||||||
fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree,
|
fo = create_node('CompositorNodeOutputFile', tree=tree, width=600)
|
||||||
location=(real_loc(node)[0] + location_offset[0],
|
fo.file_output_items.clear() # Ensure there is no default item
|
||||||
real_loc(node)[1] + location_offset[1]), width=600)
|
|
||||||
fo.inputs.remove(fo.inputs[0]) # Remove default image input
|
|
||||||
|
|
||||||
if file_format:
|
if file_format:
|
||||||
for k, v in file_format.items():
|
set_format_settings(fo.format, file_format)
|
||||||
setattr(fo.format, k, v)
|
|
||||||
else:
|
else:
|
||||||
set_file_output_format(fo)
|
set_file_output_format(fo)
|
||||||
|
|
||||||
if color_depth:
|
if color_depth:
|
||||||
fo.format.color_depth = color_depth
|
fo.format.color_depth = color_depth
|
||||||
|
|
||||||
fo.name = out_name
|
fo.name = out_name
|
||||||
if node.parent:
|
if node.parent:
|
||||||
fo.parent = node.parent
|
fo.parent = node.parent
|
||||||
|
fo.location_absolute = (real_loc(node)[0] + location_offset[0],
|
||||||
|
real_loc(node)[1] + location_offset[1])
|
||||||
|
|
||||||
if base_path_template is not None:
|
if base_path_template is not None:
|
||||||
fo.base_path = build_path_from_template(base_path_template,
|
path = build_path_from_template(base_path_template,
|
||||||
node_name=node.name.strip(),
|
node_name=node.name.strip(),
|
||||||
node_label=node.label.strip(),
|
node_label=node.label.strip(),
|
||||||
scene_name=node.scene.name if node.type == 'R_LAYERS' else None,
|
scene_name=node.scene.name if node.type == 'R_LAYERS' else None,
|
||||||
viewlayer_name=node.layer if node.type == 'R_LAYERS' else None,
|
viewlayer_name=node.layer if node.type == 'R_LAYERS' else None,
|
||||||
)
|
)
|
||||||
if suffix:
|
elif base_path:
|
||||||
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
|
path = base_path
|
||||||
## insert suffix right after last slash as folder
|
elif fo.format.file_format == 'OPEN_EXR_MULTILAYER':
|
||||||
path_list = re.split('(\\/)', fo.base_path)
|
|
||||||
path_list.insert(-1, suffix)
|
|
||||||
fo.base_path = ''.join(path_list)
|
|
||||||
else:
|
|
||||||
## Ensure slash separator then add suffix
|
|
||||||
fo.base_path = fo.base_path.rstrip('\\/') + '/' + suffix
|
|
||||||
else:
|
|
||||||
# default behavior without template
|
# default behavior without template
|
||||||
if base_path:
|
path = f'//render/{out_base}/{out_base}_'
|
||||||
fo.base_path = base_path
|
else:
|
||||||
else:
|
path = f'//render/{out_base}/'
|
||||||
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
|
|
||||||
fo.base_path = f'//render/{out_base}/{suffix}{out_base}_'
|
## suffix is added as a subfolder of the directory
|
||||||
else:
|
set_file_output_path(fo, path, suffix=suffix)
|
||||||
fo.base_path = f'//render/{out_base}/{suffix}'
|
|
||||||
|
|
||||||
for o in outputs:
|
for o in outputs:
|
||||||
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None):
|
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)):
|
if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)):
|
||||||
slot_name = bpy.path.clean_name(custom_name)
|
slot_name = bpy.path.clean_name(custom_name)
|
||||||
else:
|
else:
|
||||||
slot_name = bpy.path.clean_name(o.name)
|
slot_name = bpy.path.clean_name(o.name)
|
||||||
|
|
||||||
fs = fo.file_slots.new('tmp')
|
## Multilayer: item name is the layer name in the file
|
||||||
ls = fo.layer_slots.new('tmp')
|
## Other formats: item name is the subpath added to directory + file name
|
||||||
|
if fo.format.file_format == 'OPEN_EXR_MULTILAYER' or use_slot_template:
|
||||||
|
## use_slot_template always False (currently never use slot template)
|
||||||
|
item_name = slot_name
|
||||||
|
else:
|
||||||
|
item_name = f'{slot_name}/{slot_name}_'
|
||||||
|
|
||||||
ls = fo.layer_slots[-1]
|
_item, out_input = add_file_output_item(fo, item_name, socket=o)
|
||||||
ls.name = slot_name
|
|
||||||
fs = fo.file_slots[-1]
|
|
||||||
fs.path = f'{slot_name}/{slot_name}_'
|
|
||||||
if use_slot_template:
|
|
||||||
## Always False (currently never use slot template)
|
|
||||||
fs.path = slot_name
|
|
||||||
|
|
||||||
out_input = fo.inputs[-1]
|
|
||||||
links.new(o, out_input)
|
links.new(o, out_input)
|
||||||
|
|
||||||
clear_disconnected(fo)
|
clear_disconnected(fo)
|
||||||
@@ -451,6 +533,12 @@ def set_properties_editor_tab(tab, skip_if_exists=True):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_icon(obj):
|
||||||
|
'''Return outliner icon name of passed object'''
|
||||||
|
if obj.type == 'LIGHT_PROBE':
|
||||||
|
return 'OUTLINER_OB_LIGHTPROBE'
|
||||||
|
return f'OUTLINER_OB_{obj.type}'
|
||||||
|
|
||||||
def show_and_active_object(obj, make_active=True, select=True, unhide=True):
|
def show_and_active_object(obj, make_active=True, select=True, unhide=True):
|
||||||
'''
|
'''
|
||||||
Show the object
|
Show the object
|
||||||
@@ -685,7 +773,7 @@ def store_visibility_states(collection=None):
|
|||||||
|
|
||||||
|
|
||||||
def store_collection_states(collection, context=None):
|
def store_collection_states(collection, context=None):
|
||||||
'''Pass a colelction and store visibility state'''
|
'''Pass a collection and store visibility state'''
|
||||||
context = context or bpy.context
|
context = context or bpy.context
|
||||||
|
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ class RT_OT_conform_collection_hierarchy(Operator):
|
|||||||
|
|
||||||
for k, v in type_dict.items():
|
for k, v in type_dict.items():
|
||||||
if item_type == "objects":
|
if item_type == "objects":
|
||||||
icon = f'OUTLINER_OB_{k.type}'
|
icon = fn.get_object_icon(k)
|
||||||
name = k.name
|
name = k.name
|
||||||
else:
|
else:
|
||||||
icon = 'OUTLINER_COLLECTION'
|
icon = 'OUTLINER_COLLECTION'
|
||||||
|
|||||||
@@ -11,24 +11,16 @@ from .. import fn
|
|||||||
# region renumber outputs
|
# region renumber outputs
|
||||||
|
|
||||||
def delete_numbering(fo): # padding=3
|
def delete_numbering(fo): # padding=3
|
||||||
'''Delete prefix numbering on all slots on passed file output'''
|
'''Delete prefix numbering on all items on passed file output'''
|
||||||
|
|
||||||
if fo.type != 'OUTPUT_FILE': return
|
if fo.type != 'OUTPUT_FILE': return
|
||||||
|
|
||||||
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
|
for fs in fo.file_output_items:
|
||||||
slots = fo.layer_slots
|
elems = fs.name.split('/')
|
||||||
field_attr = 'name'
|
|
||||||
else:
|
|
||||||
slots = fo.file_slots
|
|
||||||
field_attr = 'path'
|
|
||||||
|
|
||||||
for fs in slots:
|
|
||||||
elems = getattr(fs, field_attr).split('/')
|
|
||||||
for i, e in enumerate(elems):
|
for i, e in enumerate(elems):
|
||||||
elems[i] = re.sub(r'^\d{3}_', '', e)
|
elems[i] = re.sub(r'^\d{3}_', '', e)
|
||||||
|
|
||||||
new = '/'.join(elems)
|
fs.name = '/'.join(elems)
|
||||||
setattr(fs, field_attr, new)
|
|
||||||
|
|
||||||
## Add - Insert prefix incrementation in fileoutput
|
## Add - Insert prefix incrementation in fileoutput
|
||||||
|
|
||||||
@@ -43,9 +35,7 @@ def get_numbered_output(out, slot_name):
|
|||||||
|
|
||||||
|
|
||||||
def add_fileslot_number(fs, number):
|
def add_fileslot_number(fs, number):
|
||||||
field_attr = 'name' if hasattr(fs, 'name') else 'path'
|
elems = fs.name.split('/')
|
||||||
|
|
||||||
elems = getattr(fs, field_attr).split('/')
|
|
||||||
for i, e in enumerate(elems):
|
for i, e in enumerate(elems):
|
||||||
if re.match(r'^\d{3}_', e):
|
if re.match(r'^\d{3}_', e):
|
||||||
elems[i] = re.sub(r'^(\d{3})', lambda x: str(number).zfill(3), e)
|
elems[i] = re.sub(r'^(\d{3})', lambda x: str(number).zfill(3), e)
|
||||||
@@ -53,37 +43,34 @@ def add_fileslot_number(fs, number):
|
|||||||
elems[i] = f'{str(number).zfill(3)}_{e}'
|
elems[i] = f'{str(number).zfill(3)}_{e}'
|
||||||
new = '/'.join(elems)
|
new = '/'.join(elems)
|
||||||
|
|
||||||
setattr(fs, field_attr, new)
|
fs.name = new
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def renumber(fo, offset=10):
|
def renumber(fo, offset=10):
|
||||||
'''Force renumber all the slots with a 3'''
|
'''Force renumber all the items with a 3'''
|
||||||
|
|
||||||
if fo.type != 'OUTPUT_FILE': return
|
if fo.type != 'OUTPUT_FILE': return
|
||||||
ct = 10 # start at 10
|
ct = 10 # start at 10
|
||||||
slots = fo.layer_slots if fo.format.file_format == 'OPEN_EXR_MULTILAYER' else fo.file_slots
|
for fs in fo.file_output_items:
|
||||||
for fs in slots:
|
|
||||||
add_fileslot_number(fs, ct)
|
add_fileslot_number(fs, ct)
|
||||||
ct += offset
|
ct += offset
|
||||||
|
|
||||||
def get_num(string) -> int:
|
def get_num(string) -> int:
|
||||||
'''get a tring or a file_slot object
|
'''get a string or a file output item object
|
||||||
return leading number or None
|
return leading number or None
|
||||||
'''
|
'''
|
||||||
if not isinstance(string, str):
|
if not isinstance(string, str):
|
||||||
if hasattr(string, 'path'):
|
string = string.name
|
||||||
string = string.path
|
|
||||||
else:
|
|
||||||
string = string.name
|
|
||||||
|
|
||||||
num = re.search(r'^(\d{3})_', string)
|
num = re.search(r'^(\d{3})_', string)
|
||||||
if num:
|
if num:
|
||||||
return int(num.group(1))
|
return int(num.group(1))
|
||||||
|
|
||||||
def reverse_fileout_inputs(fo):
|
def reverse_fileout_inputs(fo):
|
||||||
count = len(fo.inputs)
|
items = fo.file_output_items
|
||||||
|
count = len(items)
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
fo.inputs.move(count-1, i)
|
items.move(count-1, i)
|
||||||
|
|
||||||
def renumber_keep_existing(fo, offset=10, invert=True):
|
def renumber_keep_existing(fo, offset=10, invert=True):
|
||||||
'''Renumber by keeping existing numbers and inserting new one whenever possible
|
'''Renumber by keeping existing numbers and inserting new one whenever possible
|
||||||
@@ -96,7 +83,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
|
|||||||
if invert:
|
if invert:
|
||||||
reverse_fileout_inputs(fo)
|
reverse_fileout_inputs(fo)
|
||||||
|
|
||||||
fsl = fo.layer_slots if fo.format.file_format == 'OPEN_EXR_MULTILAYER' else fo.file_slots
|
fsl = fo.file_output_items
|
||||||
|
|
||||||
last_idx = len(fsl) - 1
|
last_idx = len(fsl) - 1
|
||||||
prev = None
|
prev = None
|
||||||
@@ -140,7 +127,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
|
|||||||
if next_num is None:
|
if next_num is None:
|
||||||
add_fileslot_number(fs, 0)
|
add_fileslot_number(fs, 0)
|
||||||
elif next_num == 0:
|
elif next_num == 0:
|
||||||
print(f'Cannot insert value before 0 to {fsl.path}')
|
print(f'Cannot insert value before 0 to {fs.name}')
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
add_fileslot_number(fs, int(next_num / 2))
|
add_fileslot_number(fs, int(next_num / 2))
|
||||||
@@ -161,7 +148,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
|
|||||||
# iterate rename
|
# iterate rename
|
||||||
gap_inc = int((next_num - prev_num) / divider)
|
gap_inc = int((next_num - prev_num) / divider)
|
||||||
if gap_inc < 1: # same values !
|
if gap_inc < 1: # same values !
|
||||||
print(f'cannot insert a median value at {fs.path} between {prev_num} and {next_num}')
|
print(f'cannot insert a median value at {fs.name} between {prev_num} and {next_num}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ct = prev_num
|
ct = prev_num
|
||||||
@@ -187,7 +174,7 @@ class RT_OT_number_outputs(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return True
|
return fn.get_compositor_tree(context.scene) is not None
|
||||||
|
|
||||||
mode : StringProperty(default='SELECTED', options={'SKIP_SAVE'})
|
mode : StringProperty(default='SELECTED', options={'SKIP_SAVE'})
|
||||||
clear : BoolProperty(default=False, options={'SKIP_SAVE'})
|
clear : BoolProperty(default=False, options={'SKIP_SAVE'})
|
||||||
@@ -199,10 +186,8 @@ class RT_OT_number_outputs(Operator):
|
|||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
scn = context.scene
|
|
||||||
|
|
||||||
ct = 0
|
ct = 0
|
||||||
nodes = scn.node_tree.nodes
|
nodes = fn.get_compositor_tree(context.scene).nodes
|
||||||
for fo in nodes:
|
for fo in nodes:
|
||||||
if fo.type != 'OUTPUT_FILE':
|
if fo.type != 'OUTPUT_FILE':
|
||||||
continue
|
continue
|
||||||
@@ -234,9 +219,13 @@ class RT_OT_mute_toggle_output_nodes(Operator):
|
|||||||
|
|
||||||
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
|
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return fn.get_compositor_tree(context.scene) is not None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
ct = 0
|
ct = 0
|
||||||
for n in context.scene.node_tree.nodes:
|
for n in fn.get_compositor_tree(context.scene).nodes:
|
||||||
if n.type != 'OUTPUT_FILE':
|
if n.type != 'OUTPUT_FILE':
|
||||||
continue
|
continue
|
||||||
n.mute = self.mute
|
n.mute = self.mute
|
||||||
@@ -255,8 +244,12 @@ class RT_OT_set_output_node_format(Operator):
|
|||||||
|
|
||||||
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
|
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return fn.get_compositor_tree(context.scene) is not None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
nodes = context.scene.node_tree.nodes
|
nodes = fn.get_compositor_tree(context.scene).nodes
|
||||||
if not nodes.active or nodes.active.type != 'OUTPUT_FILE':
|
if not nodes.active or nodes.active.type != 'OUTPUT_FILE':
|
||||||
self.report({"ERROR"}, f'Active node should be an output file to use as reference for output format')
|
self.report({"ERROR"}, f'Active node should be an output file to use as reference for output format')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -273,8 +266,14 @@ class RT_OT_set_output_node_format(Operator):
|
|||||||
if n.type != 'OUTPUT_FILE' or n == ref or not n.select:
|
if n.type != 'OUTPUT_FILE' or n == ref or not n.select:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
## media type and file format first, available values of other attributes depend on them
|
||||||
|
n.format.media_type = ref.format.media_type
|
||||||
|
n.format.file_format = ref.format.file_format
|
||||||
|
|
||||||
for attr in dir(ref.format):
|
for attr in dir(ref.format):
|
||||||
if attr.startswith('__') or attr in {'rna_type','bl_rna', 'view_settings', 'display_settings','stereo_3d_format'}: # views_format
|
if attr.startswith('__') or attr in {'rna_type','bl_rna', 'media_type', 'file_format',
|
||||||
|
'view_settings', 'display_settings', 'linear_colorspace_settings',
|
||||||
|
'has_linear_colorspace', 'stereo_3d_format'}: # views_format
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
setattr(n.format, attr, getattr(ref.format, attr))
|
setattr(n.format, attr, getattr(ref.format, attr))
|
||||||
@@ -295,36 +294,38 @@ class RT_OT_set_output_node_format(Operator):
|
|||||||
|
|
||||||
class RT_OT_set_active_file_output_slot_to_composite(bpy.types.Operator):
|
class RT_OT_set_active_file_output_slot_to_composite(bpy.types.Operator):
|
||||||
bl_idname = "rt.set_active_file_output_slot_to_composite"
|
bl_idname = "rt.set_active_file_output_slot_to_composite"
|
||||||
bl_label = "Set Active File Output Slot To Composite"
|
bl_label = "Set Active File Output Item To Composite"
|
||||||
bl_description = "Use active slot of active file output node to set scene output settings (swap connection)"
|
bl_description = "Use active item of active file output node to set scene output settings\
|
||||||
|
\nand swap connection to compositor Group Output node"
|
||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.scene.use_nodes\
|
tree = fn.get_compositor_tree(context.scene)
|
||||||
and context.scene.node_tree\
|
return bool(tree\
|
||||||
and context.scene.node_tree.nodes.active\
|
and tree.nodes.active\
|
||||||
and context.scene.node_tree.nodes.active.type == 'OUTPUT_FILE'
|
and tree.nodes.active.type == 'OUTPUT_FILE')
|
||||||
|
|
||||||
relink_composite : bpy.props.BoolProperty(
|
relink_composite : bpy.props.BoolProperty(
|
||||||
name='Relink Composite',
|
name='Relink Composite',
|
||||||
default=True,
|
default=True,
|
||||||
description='In case file slot is linked, swap link to Composite file',
|
description='In case file output item is linked, swap link to compositor Group Output',
|
||||||
options={'SKIP_SAVE'},
|
options={'SKIP_SAVE'},
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.fo = context.scene.node_tree.nodes.active
|
tree = fn.get_compositor_tree(context.scene)
|
||||||
if not len(self.fo.file_slots):
|
self.fo = tree.nodes.active
|
||||||
self.report({'ERROR'}, 'no slots in active file output')
|
if not len(self.fo.file_output_items):
|
||||||
|
self.report({'ERROR'}, 'no items in active file output')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# check if active slot has a source
|
# check if active item has a source
|
||||||
if not self.fo.inputs[self.fo.active_input_index].is_linked:
|
if not self.fo.inputs[self.fo.active_item_index].is_linked:
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
# check if composite linked
|
# check if composite linked
|
||||||
out = context.scene.node_tree.nodes.get('Composite')
|
out = fn.get_group_output_node(tree)
|
||||||
if not out or not out.inputs[0].is_linked:
|
if not out or not out.inputs[0].is_linked:
|
||||||
self.compo_out_from_link = ''
|
self.compo_out_from_link = ''
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
@@ -337,34 +338,36 @@ class RT_OT_set_active_file_output_slot_to_composite(bpy.types.Operator):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.label(text=f'Composite node connected to: {self.compo_out_from_link}')
|
col.label(text=f'Group Output node connected to: {self.compo_out_from_link}')
|
||||||
col.label(text=f'Would you like to replace by file output slot source ?')
|
col.label(text=f'Would you like to replace by file output item source ?')
|
||||||
layout.prop(self, 'relink_composite')
|
layout.prop(self, 'relink_composite')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
self.fo = fn.get_compositor_tree(context.scene).nodes.active
|
||||||
|
if not len(self.fo.file_output_items):
|
||||||
|
self.report({'ERROR'}, 'no items in active file output')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# if comp
|
# if comp
|
||||||
fn.set_scene_output_from_active_fileout_item()
|
fn.set_scene_output_from_active_fileout_item()
|
||||||
idx = self.fo.active_input_index
|
idx = self.fo.active_item_index
|
||||||
sl = self.fo.file_slots[idx]
|
item = self.fo.file_output_items[idx]
|
||||||
sk = self.fo.inputs[idx]
|
sk = self.fo.inputs[idx]
|
||||||
|
|
||||||
if not sk.is_linked:
|
if not sk.is_linked:
|
||||||
self.report({'INFO'}, f'Outut changed to match {sl.path} (slot was not linked)')
|
self.report({'INFO'}, f'Outut changed to match {item.name} (item was not linked)')
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
## If linked replace links to Composite node
|
## If linked replace links to Group Output node
|
||||||
if not self.relink_composite:
|
if not self.relink_composite:
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
ntree = context.scene.node_tree
|
ntree = fn.get_compositor_tree(context.scene)
|
||||||
links = context.scene.node_tree.links
|
links = ntree.links
|
||||||
nodes = context.scene.node_tree.nodes
|
|
||||||
|
|
||||||
out = nodes.get('Composite')
|
out = fn.get_group_output_node(ntree, create=True)
|
||||||
if not out:
|
|
||||||
out = fn.create_node('COMPOSITE', tree=ntree)
|
|
||||||
fo_loc = fn.real_loc(self.fo)
|
fo_loc = fn.real_loc(self.fo)
|
||||||
out.location = (fo_loc.x, fo_loc.y + 160)
|
out.location_absolute = (fo_loc.x, fo_loc.y + 160)
|
||||||
|
|
||||||
# if out.inputs[0].is_linked:
|
# if out.inputs[0].is_linked:
|
||||||
# self.report({'WARNING'}, f'Outut changed to match {sl.path} (Composite node already linked)')
|
# self.report({'WARNING'}, f'Outut changed to match {sl.path} (Composite node already linked)')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from bpy.props import (StringProperty,
|
|||||||
class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
||||||
bl_idname = "rt.outputs_search_and_replace"
|
bl_idname = "rt.outputs_search_and_replace"
|
||||||
bl_label = "Search And Replace Outputs Paths"
|
bl_label = "Search And Replace Outputs Paths"
|
||||||
bl_description = "Search/Replace texts in output path and slots"
|
bl_description = "Search/Replace texts in output paths (directory and file name) and items"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
## Search and replace options
|
## Search and replace options
|
||||||
@@ -22,9 +22,9 @@ class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
|||||||
name="Target Fields",
|
name="Target Fields",
|
||||||
description="Fields to search and replace in outputs",
|
description="Fields to search and replace in outputs",
|
||||||
items=(
|
items=(
|
||||||
('path', "Base Paths", "search and replace in output node paths"),
|
('path', "Paths", "search and replace in output node paths (directory and file name, separately)"),
|
||||||
('slots', "File Slots", "search and replace in output node file slots (also Layer slots for multilayers outputs)"),
|
('slots', "Items", "search and replace in output node items (subpaths, or layer names for multilayers outputs)"),
|
||||||
('all', "All", "search and replace in both paths and slots"),
|
('all', "All", "search and replace in both paths and items"),
|
||||||
),
|
),
|
||||||
default='all'
|
default='all'
|
||||||
)
|
)
|
||||||
@@ -46,7 +46,8 @@ class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.scene.node_tree and context.scene.node_tree.nodes
|
tree = fn.get_compositor_tree(context.scene)
|
||||||
|
return bool(tree and tree.nodes)
|
||||||
|
|
||||||
def rename(self, source):
|
def rename(self, source):
|
||||||
if not self.find:
|
if not self.find:
|
||||||
@@ -133,12 +134,12 @@ class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
|||||||
# return
|
# return
|
||||||
|
|
||||||
## Checked in poll
|
## Checked in poll
|
||||||
node_tree = context.scene.node_tree
|
node_tree = fn.get_compositor_tree(context.scene)
|
||||||
|
|
||||||
file_outputs = [f for f in node_tree.nodes if f.type == 'OUTPUT_FILE']
|
file_outputs = [f for f in node_tree.nodes if f.type == 'OUTPUT_FILE']
|
||||||
if self.selected_node_only:
|
if self.selected_node_only:
|
||||||
file_outputs = [f for f in file_outputs if f.select]
|
file_outputs = [f for f in file_outputs if f.select]
|
||||||
|
|
||||||
if not file_outputs:
|
if not file_outputs:
|
||||||
self.report({'ERROR'}, 'No file output nodes found')
|
self.report({'ERROR'}, 'No file output nodes found')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
@@ -148,29 +149,21 @@ class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
|||||||
for file_out in file_outputs:
|
for file_out in file_outputs:
|
||||||
## Get the target prop
|
## Get the target prop
|
||||||
if self.target in ('path', 'all'):
|
if self.target in ('path', 'all'):
|
||||||
current_base_path = file_out.base_path
|
for attr in ('directory', 'file_name'):
|
||||||
new_base_path = self.rename(current_base_path)
|
current_path = getattr(file_out, attr)
|
||||||
if new_base_path is not None and current_base_path != new_base_path:
|
new_path = self.rename(current_path)
|
||||||
print(f"\n{file_out.name}: base path: {current_base_path} to {new_base_path}")
|
if new_path is not None and current_path != new_path:
|
||||||
file_out.base_path = new_base_path
|
print(f"\n{file_out.name}: {attr}: {current_path} to {new_path}")
|
||||||
count += 1
|
setattr(file_out, attr, new_path)
|
||||||
|
count += 1
|
||||||
|
|
||||||
if self.target in ('slots', 'all'):
|
if self.target in ('slots', 'all'):
|
||||||
for slot in file_out.file_slots:
|
for item in file_out.file_output_items:
|
||||||
current_slot_path = slot.path
|
current_item_name = item.name
|
||||||
new_slot_path = self.rename(current_slot_path)
|
new_item_name = self.rename(current_item_name)
|
||||||
if new_slot_path is not None and current_slot_path != new_slot_path:
|
if new_item_name is not None and current_item_name != new_item_name:
|
||||||
print(f"{file_out.name}: file slot: {current_slot_path} to {new_slot_path}")
|
print(f"{file_out.name}: item: {current_item_name} to {new_item_name}")
|
||||||
slot.path = new_slot_path
|
item.name = new_item_name
|
||||||
count += 1
|
|
||||||
|
|
||||||
## Layers slots
|
|
||||||
for layer in file_out.layer_slots:
|
|
||||||
current_layer_path = layer.name
|
|
||||||
new_layer_path = self.rename(current_layer_path)
|
|
||||||
if new_layer_path is not None and current_layer_path != new_layer_path:
|
|
||||||
print(f"{file_out.name}: layer slot: {current_layer_path} to {new_layer_path}")
|
|
||||||
layer.name = new_layer_path
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if count:
|
if count:
|
||||||
|
|||||||
@@ -324,9 +324,16 @@ class RT_OT_create_output_layers(bpy.types.Operator):
|
|||||||
self.socket_collection = context.window_manager.rt_socket_collection
|
self.socket_collection = context.window_manager.rt_socket_collection
|
||||||
|
|
||||||
self.socket_collection.clear()
|
self.socket_collection.clear()
|
||||||
|
|
||||||
|
tree = fn.get_compositor_tree(context.scene)
|
||||||
|
if tree is None:
|
||||||
|
self.report({'ERROR'}, 'Scene has no compositing node group')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
if event.ctrl:
|
if event.ctrl:
|
||||||
# Direct connect, do not use any options
|
# Direct connect, do not use any options
|
||||||
self.base_path = ''
|
self.base_path = ''
|
||||||
|
self.final_base_path_template = None
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
## Store variable usable in templates
|
## Store variable usable in templates
|
||||||
@@ -344,7 +351,7 @@ class RT_OT_create_output_layers(bpy.types.Operator):
|
|||||||
|
|
||||||
tech_passes_names = [tp.strip().lower() for tp in tech_passes_names.split(',') if tp.strip()]
|
tech_passes_names = [tp.strip().lower() for tp in tech_passes_names.split(',') if tp.strip()]
|
||||||
|
|
||||||
selected = [n for n in context.scene.node_tree.nodes if n.select and n.type != 'OUTPUT_FILE']
|
selected = [n for n in tree.nodes if n.select and n.type != 'OUTPUT_FILE']
|
||||||
if not selected:
|
if not selected:
|
||||||
self.report({'ERROR'}, 'No render layer nodes selected')
|
self.report({'ERROR'}, 'No render layer nodes selected')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
@@ -411,9 +418,21 @@ class RT_OT_create_output_layers(bpy.types.Operator):
|
|||||||
if ref.type == 'R_LAYERS':
|
if ref.type == 'R_LAYERS':
|
||||||
self.scene_name = ref.scene.name
|
self.scene_name = ref.scene.name
|
||||||
self.view_layer_name = ref.layer
|
self.view_layer_name = ref.layer
|
||||||
self.final_base_path_template = ''
|
## Set here too, since draw only update it when settings are expanded
|
||||||
|
self.final_base_path_template = self.get_base_path_template(context)
|
||||||
return context.window_manager.invoke_props_dialog(self, width=530)
|
return context.window_manager.invoke_props_dialog(self, width=530)
|
||||||
|
|
||||||
|
def get_base_path_template(self, context):
|
||||||
|
'''Return base path template from scene settings, None if templates are disabled'''
|
||||||
|
if not self.use_base_path_templates:
|
||||||
|
return None
|
||||||
|
final_format = self.file_format
|
||||||
|
if final_format == 'NONE':
|
||||||
|
final_format = os.environ.get('FILE_FORMAT', 'OPEN_EXR_MULTILAYER')
|
||||||
|
if final_format == 'OPEN_EXR_MULTILAYER':
|
||||||
|
return context.scene.render_toolbox.default_multilayer_base_path
|
||||||
|
return context.scene.render_toolbox.default_base_path
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
@@ -530,7 +549,7 @@ Possible variables:
|
|||||||
display_node_name = item.node_name
|
display_node_name = item.node_name
|
||||||
## A bit dirty: For render layer node, show render layer-node name.
|
## A bit dirty: For render layer node, show render layer-node name.
|
||||||
if display_node_name.startswith('Render Layer'):
|
if display_node_name.startswith('Render Layer'):
|
||||||
rln = context.scene.node_tree.nodes.get(item.node_name)
|
rln = fn.get_compositor_tree(context.scene).nodes.get(item.node_name)
|
||||||
display_node_name = f'{rln.scene.name}/{rln.layer} ({display_node_name})'
|
display_node_name = f'{rln.scene.name}/{rln.layer} ({display_node_name})'
|
||||||
col.label(text=display_node_name, icon='NODE_SEL')
|
col.label(text=display_node_name, icon='NODE_SEL')
|
||||||
|
|
||||||
@@ -623,8 +642,7 @@ Possible variables:
|
|||||||
'color_depth' : '32',
|
'color_depth' : '32',
|
||||||
}
|
}
|
||||||
|
|
||||||
scn = context.scene
|
nodes = fn.get_compositor_tree(context.scene).nodes
|
||||||
nodes = scn.node_tree.nodes
|
|
||||||
selected = [n for n in nodes if n.select]
|
selected = [n for n in nodes if n.select]
|
||||||
outfile = next((n for n in selected if n.type == 'OUTPUT_FILE'), None)
|
outfile = next((n for n in selected if n.type == 'OUTPUT_FILE'), None)
|
||||||
# Exclude output file from
|
# Exclude output file from
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from bpy.props import (BoolProperty,
|
|||||||
CollectionProperty,
|
CollectionProperty,
|
||||||
StringProperty)
|
StringProperty)
|
||||||
|
|
||||||
|
from .. import fn
|
||||||
|
|
||||||
def list_simplify_affected_objects(context):
|
def list_simplify_affected_objects(context):
|
||||||
"""Return a list of object affected by simplify
|
"""Return a list of object affected by simplify
|
||||||
return : [[obj, [{type: "SUBSURF", ..}, {..}], icon], ..]
|
return : [[obj, [{type: "SUBSURF", ..}, {..}], icon], ..]
|
||||||
@@ -103,7 +105,7 @@ def list_simplify_affected_objects(context):
|
|||||||
|
|
||||||
# If object has affected data, add it to the list
|
# If object has affected data, add it to the list
|
||||||
if affected_data:
|
if affected_data:
|
||||||
ob_list.append([obj, affected_data, "OUTLINER_OB_" + obj.type])
|
ob_list.append([obj, affected_data, fn.get_object_icon(obj)])
|
||||||
|
|
||||||
return ob_list
|
return ob_list
|
||||||
|
|
||||||
@@ -288,7 +290,7 @@ class RT_OT_list_object_affected_by_simplify(Operator):
|
|||||||
|
|
||||||
# Create subrow for hair info
|
# Create subrow for hair info
|
||||||
subrow = row.row()
|
subrow = row.row()
|
||||||
subrow.label(text=f"Hair: {mod.name}", icon='HAIR')
|
subrow.label(text=f"Hair: {mod.name}", icon='CURVES_DATA')
|
||||||
subrow.label(text=f"Node Group: {data['node_group']}")
|
subrow.label(text=f"Node Group: {data['node_group']}")
|
||||||
|
|
||||||
elif data['type'] == 'SUBSURFACE':
|
elif data['type'] == 'SUBSURFACE':
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from bpy.props import (StringProperty,
|
|||||||
BoolProperty,
|
BoolProperty,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .. import fn
|
||||||
|
|
||||||
class RT_OT_enable_all_viewlayers(Operator):
|
class RT_OT_enable_all_viewlayers(Operator):
|
||||||
bl_idname = "rt.enable_all_viewlayers"
|
bl_idname = "rt.enable_all_viewlayers"
|
||||||
bl_label = "Enable All Viewlayers"
|
bl_label = "Enable All Viewlayers"
|
||||||
@@ -27,10 +29,14 @@ class RT_OT_activate_only_selected_layers(Operator):
|
|||||||
bl_description = "Activate only selected node view layer , excluding all others"
|
bl_description = "Activate only selected node view layer , excluding all others"
|
||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return fn.get_compositor_tree(context.scene) is not None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
scn = context.scene
|
scn = context.scene
|
||||||
|
|
||||||
nodes = scn.node_tree.nodes
|
nodes = fn.get_compositor_tree(scn).nodes
|
||||||
|
|
||||||
rlayers_nodes = [n for n in nodes if n.select and n.type == 'R_LAYERS']
|
rlayers_nodes = [n for n in nodes if n.select and n.type == 'R_LAYERS']
|
||||||
vls = [scn.view_layers.get(n.layer) for n in rlayers_nodes if scn.view_layers.get(n.layer)]
|
vls = [scn.view_layers.get(n.layer) for n in rlayers_nodes if scn.view_layers.get(n.layer)]
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ class RT_OT_list_modifier_visibility(Operator):
|
|||||||
for m in o.modifiers:
|
for m in o.modifiers:
|
||||||
if m.show_viewport != m.show_render:
|
if m.show_viewport != m.show_render:
|
||||||
if not mods:
|
if not mods:
|
||||||
self.ob_list.append([o, mods, "OUTLINER_OB_" + o.type])
|
self.ob_list.append([o, mods, fn.get_object_icon(o)])
|
||||||
mods.append(m)
|
mods.append(m)
|
||||||
self.ob_list.sort(key=lambda x: x[2]) # regroup by objects type (this or x[0] for object name)
|
self.ob_list.sort(key=lambda x: x[2]) # regroup by objects type (this or x[0] for object name)
|
||||||
return context.window_manager.invoke_props_dialog(self, width=350)
|
return context.window_manager.invoke_props_dialog(self, width=350)
|
||||||
|
|||||||
+1
-1
@@ -133,7 +133,7 @@ def register():
|
|||||||
bpy.app.handlers.load_post.append(set_render_toolbox_settings)
|
bpy.app.handlers.load_post.append(set_render_toolbox_settings)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
if not 'set_render_toolbox_settings' in [hand.__name__ for hand in bpy.app.handlers.load_post]:
|
if 'set_render_toolbox_settings' in [hand.__name__ for hand in bpy.app.handlers.load_post]:
|
||||||
bpy.app.handlers.load_post.remove(set_render_toolbox_settings)
|
bpy.app.handlers.load_post.remove(set_render_toolbox_settings)
|
||||||
|
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
|
|||||||
@@ -68,14 +68,15 @@ class RT_PT_render_toolbox_ui(Panel):
|
|||||||
col.prop(scn.render, 'resolution_percentage', text="Resolution Percentage")
|
col.prop(scn.render, 'resolution_percentage', text="Resolution Percentage")
|
||||||
percent = scn.render.resolution_percentage
|
percent = scn.render.resolution_percentage
|
||||||
col.label(text=f"{int(scn.render.resolution_x * percent / 100)}x{int(scn.render.resolution_y * percent / 100)}", icon='INFO')
|
col.label(text=f"{int(scn.render.resolution_x * percent / 100)}x{int(scn.render.resolution_y * percent / 100)}", icon='INFO')
|
||||||
if cam and cam.data.shift_x != 0 or cam.data.shift_y != 0:
|
if cam and (cam.data.shift_x != 0 or cam.data.shift_y != 0):
|
||||||
col.label(text='Camera has Shift', icon='INFO')
|
col.label(text='Camera has Shift', icon='INFO')
|
||||||
# col.prop(cam.data, 'shift_x', text="Shift X")
|
# col.prop(cam.data, 'shift_x', text="Shift X")
|
||||||
# col.prop(cam.data, 'shift_y', text="Shift Y")
|
# col.prop(cam.data, 'shift_y', text="Shift Y")
|
||||||
|
|
||||||
## viewlayer section
|
## viewlayer section
|
||||||
layout.label(text='View layers:')
|
layout.label(text='View layers:')
|
||||||
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'R_LAYERS' and n.select])
|
tree = fn.get_compositor_tree(scn)
|
||||||
|
ct = len([n for n in tree.nodes if n.type == 'R_LAYERS' and n.select]) if tree else 0
|
||||||
|
|
||||||
# col = layout.column(align=True)
|
# col = layout.column(align=True)
|
||||||
# row=col.row(align=True)
|
# row=col.row(align=True)
|
||||||
@@ -109,13 +110,18 @@ class RT_PT_file_output_ui(bpy.types.Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
tree = fn.get_compositor_tree(context.scene)
|
||||||
|
if tree is None:
|
||||||
|
layout.label(text='Scene has no compositing node group', icon='INFO')
|
||||||
|
return
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.operator("rt.create_output_layers", text='Create File Output', icon="NODE")
|
col.operator("rt.create_output_layers", text='Create File Output', icon="NODE")
|
||||||
col.operator("rt.outputs_search_and_replace", text='Search And Replace Outputs', icon="BORDERMOVE")
|
col.operator("rt.outputs_search_and_replace", text='Search And Replace Outputs', icon="BORDERMOVE")
|
||||||
|
|
||||||
col.separator()
|
col.separator()
|
||||||
col.operator('rt.set_output_node_format', icon='OUTPUT', text='Copy Output Format To Selected')
|
col.operator('rt.set_output_node_format', icon='OUTPUT', text='Copy Output Format To Selected')
|
||||||
col.operator('rt.set_active_file_output_slot_to_composite', icon='OUTPUT', text='Active Slot To Composite')
|
col.operator('rt.set_active_file_output_slot_to_composite', icon='OUTPUT', text='Active Item To Composite')
|
||||||
|
|
||||||
layout.label(text='All Outputs:')
|
layout.label(text='All Outputs:')
|
||||||
|
|
||||||
@@ -123,17 +129,15 @@ class RT_PT_file_output_ui(bpy.types.Panel):
|
|||||||
row.operator('rt.mute_toggle_output_nodes', icon='NODE_INSERT_ON', text='Mute').mute = True
|
row.operator('rt.mute_toggle_output_nodes', icon='NODE_INSERT_ON', text='Mute').mute = True
|
||||||
row.operator('rt.mute_toggle_output_nodes', icon='NODE_INSERT_OFF', text='Unmute').mute = False
|
row.operator('rt.mute_toggle_output_nodes', icon='NODE_INSERT_OFF', text='Unmute').mute = False
|
||||||
|
|
||||||
scn = context.scene
|
disabled_output = [n for n in tree.nodes if n.type == 'OUTPUT_FILE' and n.mute]
|
||||||
|
|
||||||
disabled_output = [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.mute]
|
|
||||||
if disabled_output:
|
if disabled_output:
|
||||||
output_ct = len([n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE'])
|
output_ct = len([n for n in tree.nodes if n.type == 'OUTPUT_FILE'])
|
||||||
layout.label(text=f'{len(disabled_output)}/{output_ct} Output Muted', icon='INFO')
|
layout.label(text=f'{len(disabled_output)}/{output_ct} Output Muted', icon='INFO')
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
|
|
||||||
## (re)number exports
|
## (re)number exports
|
||||||
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.select])
|
ct = len([n for n in tree.nodes if n.type == 'OUTPUT_FILE' and n.select])
|
||||||
txt = f'Renumber {ct} Selected Outputs'
|
txt = f'Renumber {ct} Selected Outputs'
|
||||||
subcol = col.column()
|
subcol = col.column()
|
||||||
subcol.enabled = bool(ct)
|
subcol.enabled = bool(ct)
|
||||||
|
|||||||
Reference in New Issue
Block a user