UI update and fixes
0.1.1 - ui: show number of selected obj - ui: show panels in dopesheet and node windowsmain
parent
38481de18d
commit
47baf97822
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,5 +1,19 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
<!-- TODO
|
||||
if objects has multiple user without being linked in render scene :
|
||||
duplicate the object insteat of linking
|
||||
OR always duplicate (safe but heavy scenes...)
|
||||
|
||||
if duplicate, need to "connect" with namespace ('_duprender') or something
|
||||
-->
|
||||
|
||||
0.1.1
|
||||
|
||||
- ui: show number of selected obj
|
||||
- ui: show panels in dopesheet and node windows
|
||||
|
||||
0.1.0
|
||||
|
||||
first WIP usable version
|
|
@ -69,7 +69,7 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator):
|
|||
if not scn:
|
||||
self.report({'ERROR'}, 'Could not found default scene')
|
||||
return {"CANCELLED"}
|
||||
export_gp_objects([o for o in scn.objects if o.type == 'GPENCIL'])
|
||||
export_gp_objects([o for o in scn.objects if o.type == 'GPENCIL' and not o.hide_get()])
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
|
19
OP_clear.py
19
OP_clear.py
|
@ -11,7 +11,7 @@ class GPEXP_OT_clear_render_tree(bpy.types.Operator):
|
|||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
mode : bpy.props.StringProperty(options={'SKIP_SAVE'})
|
||||
mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
|
@ -19,13 +19,25 @@ class GPEXP_OT_clear_render_tree(bpy.types.Operator):
|
|||
if not render:
|
||||
print('SKIP, no Render scene')
|
||||
|
||||
# clear all nodes
|
||||
# clear all nodes in frames
|
||||
if render.use_nodes:
|
||||
for i in range(len(render.node_tree.nodes))[::-1]:
|
||||
|
||||
# skip frames to delete later
|
||||
if render.node_tree.nodes[i].type == 'FRAME':
|
||||
continue
|
||||
|
||||
# skip unparented nodes
|
||||
if not render.node_tree.nodes[i].parent:
|
||||
continue
|
||||
render.node_tree.nodes.remove(render.node_tree.nodes[i])
|
||||
|
||||
# delete all framesWorki
|
||||
if render.use_nodes:
|
||||
for i in range(len(render.node_tree.nodes))[::-1]:
|
||||
if render.node_tree.nodes[i].type == 'FRAME':
|
||||
render.node_tree.nodes.remove(render.node_tree.nodes[i])
|
||||
|
||||
# clear all view_layers
|
||||
for vl in reversed(render.view_layers):
|
||||
if ' / ' in vl.name:
|
||||
|
@ -37,6 +49,9 @@ class GPEXP_OT_clear_render_tree(bpy.types.Operator):
|
|||
ng.use_fake_user = False
|
||||
bpy.data.node_groups.remove(ng)
|
||||
|
||||
if self.mode == 'COMPLETE':
|
||||
bpy.data.scenes.remove(render)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
classes=(
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import bpy
|
||||
|
||||
|
||||
class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator):
|
||||
bl_idname = "gp.mute_toggle_output_nodes"
|
||||
bl_label = "Mute Toggle output nodes"
|
||||
bl_description = "Mute / Unmute all output nodes"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
mute : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'})
|
||||
|
||||
def execute(self, context):
|
||||
# scene = bpy.data.scenes.get('Render')
|
||||
ct = 0
|
||||
for n in context.scene.node_tree.nodes:
|
||||
if n.type != 'OUTPUT_FILE':
|
||||
continue
|
||||
n.mute = self.mute
|
||||
ct += 1
|
||||
|
||||
state = 'muted' if self.mute else 'unmuted'
|
||||
self.report({"INFO"}, f'{ct} nodes {state}')
|
||||
return {"FINISHED"}
|
||||
|
||||
classes=(
|
||||
GPEXP_OT_mute_toggle_output_nodes,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
|
@ -1,7 +1,8 @@
|
|||
import bpy
|
||||
from . import fn
|
||||
|
||||
def merge_layers(rlayers, obname=None, active=None):
|
||||
|
||||
def merge_layers(rlayers, obname=None):
|
||||
print(f'Merging {len(rlayers)} layers')
|
||||
print('->', [r.layer for r in rlayers])
|
||||
print()
|
||||
|
@ -9,24 +10,59 @@ def merge_layers(rlayers, obname=None):
|
|||
if not rlayers:
|
||||
return ('ERROR', 'No render layer sent to merge')
|
||||
|
||||
ng = rlayers[0].outputs[0].links[0].to_node
|
||||
rlayers.sort(key=lambda x: x.location.y, reverse=True)
|
||||
# get node group
|
||||
# ng = rlayers[0].outputs[0].links[0].to_node
|
||||
|
||||
# sort RL descending
|
||||
rlayers.sort(key=lambda n: fn.real_loc(n).y, reverse=True)
|
||||
|
||||
vl_name = active.layer
|
||||
if not vl_name:
|
||||
vl_name = rlayers[0].layer
|
||||
|
||||
# change colors of those nodes
|
||||
color = random_color()
|
||||
color = fn.random_color()
|
||||
for n in rlayers:
|
||||
n.use_custom_color = True
|
||||
n.color = color
|
||||
|
||||
|
||||
# get inside socket (group input) from outside socket list (should be already ordered)
|
||||
obname, lname = vl_name.split(' / ')
|
||||
lname = bpy.path.clean_name(lname)
|
||||
|
||||
## by name
|
||||
# for i, inp in enumerate(ng.node_tree.inputs):
|
||||
# if inp.name ==
|
||||
ng_name = f'merge_NG_{obname}' # only object name
|
||||
|
||||
# by connection order
|
||||
|
||||
## clear nodes groups duplication (.00?)
|
||||
fn.clear_nodegroup(ng_name, full_clear=False)
|
||||
|
||||
# get set nodegroup from vlayer name
|
||||
if not ng:
|
||||
ng = nodes.get(ng_name)
|
||||
|
||||
if not ng:
|
||||
ngroup = bpy.data.node_groups.get(ng_name)
|
||||
# full clear True if exists but not used
|
||||
if ngroup and ngroup.users == 0:
|
||||
ngroup = None
|
||||
fn.clear_nodegroup(ng_name, full_clear=True)
|
||||
|
||||
if not ngroup:
|
||||
# delete and recreate ?
|
||||
print(f'create nodegroup {ng_name}')
|
||||
ngroup = bpy.data.node_groups.new(ng_name, 'CompositorNodeTree')
|
||||
|
||||
ng = fn.create_node('CompositorNodeGroup', tree=scene.node_tree, location=(fn.real_loc(rlayer)[0] + 600, fn.real_loc(rlayer)[1]), width=400) # (rlayer.location[0] + 600, rlayer.location[1])
|
||||
if frame:
|
||||
ng.parent= frame
|
||||
ng.node_tree = ngroup
|
||||
ng.name = ngroup.name
|
||||
|
||||
ng_in = fn.create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
|
||||
ng_out = fn.create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
|
||||
|
||||
|
||||
|
||||
"""
|
||||
socket_list = []
|
||||
grp_sockets = []
|
||||
|
||||
|
@ -48,65 +84,95 @@ def merge_layers(rlayers, obname=None):
|
|||
if inp.name != grps.name:
|
||||
print(f'\n! Problem ! : {inp.name}, {grps.name}')
|
||||
return
|
||||
"""
|
||||
|
||||
##
|
||||
# JUST CREATE ANOTHER GROUP NODE FOR THE MERGE !
|
||||
##
|
||||
|
||||
class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
|
||||
bl_idname = "gp.merge_selected_dopesheet_layers"
|
||||
bl_label = "Merge selected layers view_layers "
|
||||
bl_description = "Merge view layers of selected gp layers to a new dedicated file output"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def merge_selected_layers():
|
||||
'''Merge command from selected GP layers'''
|
||||
ob = bpy.context.object
|
||||
layer_names = [l.info for l in ob.data.layers if l.select and not l.hide]
|
||||
print("layer_names", layer_names)#Dbg
|
||||
|
||||
if len(layer_names) < 2:
|
||||
print(f'Should select multiple layers for merging')
|
||||
return
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'GPENCIL'
|
||||
|
||||
def execute(self, context):
|
||||
# merge_selected_layers() # function to merge from GP dopesheet
|
||||
ob = bpy.context.object
|
||||
layer_names = [l.info for l in ob.data.layers if l.select and not l.hide]
|
||||
print("layer_names", layer_names)#Dbg
|
||||
|
||||
if len(layer_names) < 2:
|
||||
print(f'Should select multiple layers for merging')
|
||||
return
|
||||
|
||||
render = bpy.data.scenes.get('Render')
|
||||
if render:
|
||||
nodes = render.node_tree.nodes
|
||||
|
||||
clean_ob_name = bpy.path.clean_name(ob.name)
|
||||
rlayers = []
|
||||
for l in layer_names:
|
||||
## identifier is clean_name(ob.name).layer_name
|
||||
|
||||
idname = f'{clean_ob_name}.{l}'
|
||||
|
||||
# check the render layer that have a parent frame
|
||||
rlayer = [n for n in nodes if n.type == 'R_LAYERS' and n.layer == idname and n.parent]
|
||||
if not rlayer:
|
||||
# send to function to generate the rlayer and connect
|
||||
# rlayer = creation
|
||||
continue
|
||||
|
||||
rlayers.append(rlayer)
|
||||
|
||||
merge_layers(rlayers, obname=clean_ob_name)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GPEXP_OT_merge_selected_viewlayer_nodes(bpy.types.Operator):
|
||||
bl_idname = "gp.merge_selected_viewlayer_nodes"
|
||||
bl_label = "Merge selected view_layers "
|
||||
bl_description = "Merge selected view layers to a new dedicated file output"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
render = bpy.data.scenes.get('Render')
|
||||
if not render:
|
||||
print('No render scene')
|
||||
return
|
||||
|
||||
render = bpy.data.scenes.get('Render')
|
||||
if render:
|
||||
nodes = render.node_tree.nodes
|
||||
|
||||
clean_ob_name = bpy.path.clean_name(ob.name)
|
||||
rlayers = []
|
||||
for l in layer_names:
|
||||
## identifier is clean_name(ob.name).layer_name
|
||||
|
||||
idname = f'{clean_ob_name}.{l}'
|
||||
|
||||
# check the render layer that have a parent frame
|
||||
rlayer = [n for n in nodes if n.type == 'R_LAYERS' and n.layer == idname and n.parent]
|
||||
if not rlayer:
|
||||
# send to function to generate the rlayer and connect
|
||||
# rlayer = creation
|
||||
continue
|
||||
selection = [n for n in nodes if n.select and n.type == 'R_LAYERS']
|
||||
|
||||
rlayers.append(rlayer)
|
||||
if not nodes.active in selection:
|
||||
self.report({'ERROR'}, 'The active node not within the render layer selection (used to define out name)')
|
||||
return {'CANCELLED'}
|
||||
|
||||
merge_layers(rlayers, obname=clean_ob_name)
|
||||
# should be from the same object:
|
||||
if not all(selection[0].layer.split('.')[0] == n.layer.split('.')[0] for n in selection):
|
||||
print('Merge -> Not every nodes start with the same object')
|
||||
|
||||
# obname = selection[0].layer.split('.')[0]
|
||||
merge_layers(selection, nodes.active)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def merge_selected_render_layers():
|
||||
'''Merge command from selected render layers nodes'''
|
||||
render = bpy.data.scenes.get('Render')
|
||||
if not render:
|
||||
print('No render scene')
|
||||
return
|
||||
|
||||
nodes = render.node_tree.nodes
|
||||
selection = [n for n in nodes if n.select and n.type == 'R_LAYERS']
|
||||
|
||||
# should be from the same object:
|
||||
assert all(selection[0].layer.split('.')[0] == n.layer.split('.')[0] for n in selection), 'Not every nodes start with the same object'
|
||||
classes=(
|
||||
GPEXP_OT_merge_selected_dopesheet_layers,
|
||||
GPEXP_OT_merge_selected_viewlayer_nodes,
|
||||
)
|
||||
|
||||
# obname = selection[0].layer.split('.')[0]
|
||||
merge_layers(selection)
|
||||
|
||||
|
||||
|
||||
|
||||
# merge_selected_layers() # function to merge from GP dopesheet
|
||||
|
||||
merge_selected_render_layers() # function to merge from nodegroup
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "GP exporter",
|
||||
"description": "Organise export of gp layers through compositor output",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 1, 0),
|
||||
"version": (0, 1, 1),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
@ -11,7 +11,9 @@ bl_info = {
|
|||
|
||||
|
||||
from . import OP_add_layer
|
||||
from . import OP_merge_layers
|
||||
from . import OP_clear
|
||||
from . import OP_manage_outputs
|
||||
from . import ui
|
||||
|
||||
import bpy
|
||||
|
@ -22,6 +24,8 @@ def register():
|
|||
|
||||
OP_add_layer.register()
|
||||
OP_clear.register()
|
||||
OP_merge_layers.register()
|
||||
OP_manage_outputs.register()
|
||||
ui.register()
|
||||
# bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = PROJ_PGT_settings)
|
||||
|
||||
|
@ -30,6 +34,8 @@ def unregister():
|
|||
return
|
||||
|
||||
ui.unregister()
|
||||
OP_manage_outputs.unregister()
|
||||
OP_merge_layers.unregister()
|
||||
OP_clear.unregister()
|
||||
OP_add_layer.unregister()
|
||||
|
||||
|
|
18
fn.py
18
fn.py
|
@ -35,13 +35,21 @@ def set_settings(scene=None):
|
|||
scene = bpy.context.scene
|
||||
scene.eevee.taa_render_samples = 1
|
||||
scene.grease_pencil_settings.antialias_threshold = 0
|
||||
# add transparent toggle on ?
|
||||
|
||||
def get_render_scene():
|
||||
'''Get / Create a scene named Render'''
|
||||
render = bpy.data.scenes.get('Render')
|
||||
if not render:
|
||||
render = bpy.data.scenes.new('Render')
|
||||
set_settings(render) # setup specific settings
|
||||
|
||||
## link cameras (and lights ?)
|
||||
for ob in bpy.context.scene.objects:
|
||||
if ob.type in ('CAMERA', 'LIGHT'):
|
||||
render.collection.objects.link(ob)
|
||||
|
||||
# set adapted render settings (no AA)
|
||||
set_settings(render)
|
||||
render.use_nodes = True
|
||||
return render
|
||||
|
||||
|
@ -252,4 +260,10 @@ def connect_to_group_input(n):
|
|||
val = connect_to_group_input(i.links[0].from_node)
|
||||
if val:
|
||||
return val
|
||||
return False
|
||||
return False
|
||||
|
||||
def random_color(alpha=False):
|
||||
import random
|
||||
if alpha:
|
||||
return (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1), 1)
|
||||
return (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1))
|
|
@ -229,7 +229,7 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
|
|||
|
||||
# get frame object and their contents
|
||||
# dict like : {objname : [layer_nodeA, layer_nodeB,...]}
|
||||
frame_dic = {f.label: [n for n in nodes if n.type == 'R_LAYERS' and n.parent and n.parent.name == f.name and n.layer != 'View Layer']
|
||||
frame_dic = {f.label: [n for n in nodes if n.type == 'R_LAYERS' and n.parent and n.parent.name == f.name and '/' in n.layer] # n.layer != 'View Layer'
|
||||
for f in nodes if f.type == 'FRAME'}
|
||||
|
||||
# debug print
|
||||
|
|
59
ui.py
59
ui.py
|
@ -3,38 +3,82 @@ from bpy.types import Panel
|
|||
# from .preferences import get_addon_prefs
|
||||
|
||||
|
||||
# 3D view panel
|
||||
# Node view panel
|
||||
class GPEXP_PT_gp_node_ui(Panel):
|
||||
bl_space_type = "NODE_EDITOR" # "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Item"
|
||||
bl_category = "View"
|
||||
bl_label = "Gpencil Render Manager"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'R_LAYERS' and n.select])
|
||||
txt = f'Merge {ct} Layer Nodes'
|
||||
row.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text=txt)
|
||||
row.enabled = ct > 1
|
||||
|
||||
layout.separator()
|
||||
|
||||
row=layout.row()
|
||||
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_ON', text='Mute').mute = True
|
||||
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_OFF', text='Unmute').mute = False
|
||||
|
||||
layout.separator()
|
||||
|
||||
col=layout.column()
|
||||
col.operator('gp.clear_render_tree', icon='X', text='Clear Render Tree')
|
||||
col.operator('gp.clear_render_tree', icon='X', text='Clear Delete Render Scene').mode = "COMPLETE"
|
||||
|
||||
|
||||
layout.operator('gp.clear_render_tree', icon='X', text='Full Clear Render Tree')
|
||||
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL'
|
||||
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'SELECTED'
|
||||
|
||||
|
||||
# layout.operator('gp.merge_layers', icon='X', text='Merge selected nodes')
|
||||
|
||||
class GPEXP_PT_gp_dopesheet_ui(Panel):
|
||||
bl_space_type = 'DOPESHEET_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
# bl_category = "Item"
|
||||
bl_parent_id='DOPESHEET_PT_gpencil_mode'
|
||||
bl_label = "Gpencil Render Manager"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'GPENCIL'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text=context.object.name)
|
||||
|
||||
ct = len([l for l in context.object.data.layers if l.select])
|
||||
txt = f'Merge {ct} layers'
|
||||
|
||||
# merge layers from dopesheet
|
||||
layout.operator('gp.merge_selected_dopesheet_layers', text=txt, )
|
||||
layout.enabled= ct > 1
|
||||
|
||||
def manager_ui(self, context):
|
||||
'''appended to DATA_PT_gpencil_layers'''
|
||||
|
||||
layout = self.layout
|
||||
# on layers
|
||||
|
||||
## On layers
|
||||
if context.object and context.object.type == 'GPENCIL':
|
||||
txt = f'{len([l for l in context.object.data.layers if l.select])} Layer(s) To Render'
|
||||
else:
|
||||
txt = 'Layer To Render'
|
||||
layout.operator('gp.add_layer_to_render', icon='RENDERLAYERS', text=txt)
|
||||
|
||||
# on objects
|
||||
layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Selected Object To Render').mode='SELECTED'
|
||||
## On objects
|
||||
# txt = 'Selected Object To Render'
|
||||
if context.scene.name != 'Render':
|
||||
txt = f'{len([o for o in context.selected_objects if o.type == "GPENCIL" and o.select_get()])} Selected Object(s) To Render'
|
||||
layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text=txt).mode='SELECTED'
|
||||
layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='All GP at once').mode='ALL'
|
||||
|
||||
|
||||
# ## function to append in a menu
|
||||
# def palette_manager_menu(self, context):
|
||||
# """Palette menu to append in existing menu"""
|
||||
|
@ -50,6 +94,7 @@ def manager_ui(self, context):
|
|||
|
||||
classes=(
|
||||
GPEXP_PT_gp_node_ui,
|
||||
GPEXP_PT_gp_dopesheet_ui,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
Loading…
Reference in New Issue