UI update and fixes

0.1.1

- ui: show number of selected obj
- ui: show panels in dopesheet and node windows
main
Pullusb 2021-09-08 18:29:10 +02:00
parent 38481de18d
commit 47baf97822
9 changed files with 267 additions and 72 deletions

View File

@ -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

View File

@ -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"}

View File

@ -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=(

35
OP_manage_outputs.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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
View File

@ -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))

View File

@ -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
View File

@ -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():