Public release

0.9.1:

- Public release
- prefs: added fps as part of project settings
  - check file use pref fps value (previously used harcoded 24fps value)
- cleanup: Remove wip GMIC-bridge tools that need to be done separately (if needed)
- update: apply changes in integrated copy-paste from the last version of standalone addon
- doc: Added fully-detailed french readme
gpv2
Pullusb 2021-01-10 19:12:09 +01:00
parent 98c6739003
commit f642d943ff
9 changed files with 195 additions and 686 deletions

View File

@ -1,34 +1,24 @@
## Can be renamed and used as standalone __init__.py file
bl_info = {
"name": "GP guided colorize",
"description": "Blender <> G'MIC bridge for auto color",
"author": "Samuel Bernou",
"version": (0, 1, 0),
"blender": (2, 82, 0),
"location": "3D view > Gpencil > Colorize",
"warning": "WIP",
"doc_url": "",#2.8 > 2.82 : "wiki_url":"",
"category": "3D View",
}
# bl_info = {
# "name": "GP guided colorize",
# "description": "Grease pencil colorisation helpers",
# "author": "Samuel Bernou",
# "version": (0, 1, 0),
# "blender": (2, 82, 0),
# "location": "3D view > Gpencil > Colorize",
# "warning": "WIP",
# "doc_url": "",
# "category": "3D View",
# }
import bpy
from . import OP_gmic
# from .OP_gmic import (GMICOLOR_OT_propagate_spots,
# GMICOLOR_OT_clear_cam_bg_images,
# GMICOLOR_OT_open_gmic_tool_folder,
# GMICOLOR_PT_auto_color_panel,)
from . import OP_line_closer
from . import OP_create_empty_frames
# from .OP_line_closer import (GPSTK_OT_extend_lines,
# GPSTK_PT_line_closer_panel,
# GPSTK_OT_comma_finder,)
## Colorize properties
class GPCOLOR_PG_settings(bpy.types.PropertyGroup) :
res_percentage: bpy.props.IntProperty(
name="Gmic out resolution", description="Overrides resolution percentage for playblast", default = 50, min=1, max=200, soft_min=10, soft_max=100, subtype='PERCENTAGE')#, precision=0
class GPCOLOR_PG_settings(bpy.types.PropertyGroup) :
# extend_layers: bpy.props.BoolProperty(name='Extend layers' default=True, description="Work on selected layers, else only active")
extend_layer_tgt : bpy.props.EnumProperty(
name="Extend layers", description="Choose which layer to target",
@ -47,14 +37,8 @@ class GPCOLOR_PG_settings(bpy.types.PropertyGroup) :
name="Deviation angle", description="Deviation angle tolerance of last point(s) to be considered accidental trace",
default=1.22, min=0.017, max=3.124, soft_min=0.017, soft_max=1.92, step=3, precision=2, unit='ROTATION')#, subtype='ANGLE')
classes = (
GPCOLOR_PG_settings,
# GMICOLOR_OT_propagate_spots,
# GMICOLOR_OT_clear_cam_bg_images,
# GMICOLOR_OT_open_gmic_tool_folder,
# GMICOLOR_PT_auto_color_panel,
)
def register():
@ -62,11 +46,9 @@ def register():
bpy.utils.register_class(cls)
OP_create_empty_frames.register()
OP_line_closer.register()
OP_gmic.register()
bpy.types.Scene.gpcolor_props = bpy.props.PointerProperty(type = GPCOLOR_PG_settings)
def unregister():
OP_gmic.unregister()
OP_line_closer.unregister()
OP_create_empty_frames.unregister()
for cls in reversed(classes):

View File

@ -1,424 +0,0 @@
from .func_gmic import *
from ..utils import get_addon_prefs, open_folder
import bpy
from os.path import join, basename, exists, dirname, abspath, splitext
'''#decorator mod
def with_renderfile(filepath):
def with_renderfile_decorator(func):
def decorator(*args, **kwargs):
r = bpy.context.scene.render
old_filepath, r.filepath = r.filepath, filepath
try:
func(*args, **kwargs)
finally:
r.filepath = old_filepath
return decorator
return with_renderfile_decorator
@with_renderfile("//myfile")
'''
# self with implementation
def render_filepath(filepath):
class RenderFileRestorer:
def __enter__(self):
bpy.context.scene.render.film_transparent = True
bpy.context.scene.render.filepath = filepath
bpy.context.scene.render.resolution_percentage = bpy.context.scene.gpcolor_props.res_percentage
def __exit__(self, type, value, traceback):
bpy.context.scene.render.filepath = old_filepath
bpy.context.scene.render.film_transparent = transparent
bpy.context.scene.render.resolution_percentage = old_res
transparent = bpy.context.scene.render.film_transparent
old_filepath = bpy.context.scene.render.filepath
old_res = bpy.context.scene.render.resolution_percentage
return RenderFileRestorer()
def layer_state(gp_data):
class LayerStateRestorer:
def __enter__(self):
# mask/restore other GP object ?
self.layers_state = {l:l.hide for l in gp_data.layers}
def __exit__(self, type, value, traceback):
for k, v in self.layers_state.items():
k.hide = v
return LayerStateRestorer()
def cursor_state():
class CursorStateRestorer:
def __enter__(self):
...
def __exit__(self, type, value, traceback):
bpy.context.window_manager.progress_end()
return CursorStateRestorer()
def imtool_fp(dest='tool',name=None):
if name:
return join(bpy.path.abspath('//'), dest, name)
return join(bpy.path.abspath('//'), dest)
def generate_seq_placeholder():
with cursor_state():
wm = bpy.context.window_manager
wm.progress_begin(bpy.context.scene.frame_start, bpy.context.scene.frame_end)
for i in range(bpy.context.scene.frame_start,bpy.context.scene.frame_end+1):
fp = imtool_fp(name = f'colotmp_{str(i).zfill(4)}.png')
wm.progress_update(i)#remap from frame range to 0-1000 with transfer_value(Value, OldMin, OldMax, NewMin, NewMax)
if not exists(fp):
generate_empty_image(fp)
def set_bg_img_settings(bgimg):
bgimg.display_depth = 'FRONT' if bpy.data.version[1] < 83 else 'BACK'
bgimg.alpha = 0.6
bgimg.frame_method = 'FIT'#'STRETCH'
def load_bg_image(colo_fp):
# load new image in camera background
cam = bpy.context.scene.camera.data
cam.show_background_images = True
colo = basename(colo_fp)
colo_img = bpy.data.images.get(colo)
# load as images
if colo_img:
colo_img.reload()
else:
if not exists(colo_fp):return
colo_img = bpy.data.images.load(colo_fp)
bgimg = None
for bg in cam.background_images:
if bg.image == colo_img:
bgimg = bg
break
if not bgimg:
bgimg = cam.background_images.new()
bgimg.image = colo_img
set_bg_img_settings(bgimg)
return bgimg
def load_bg_movieclip():
# load new image in camera background
cam = bpy.context.scene.camera.data
cam.show_background_images = True
## load first image
colo = f'colotmp_{str(bpy.context.scene.frame_start).zfill(4)}.png'
colo_fp = join(imtool_fp(), colo)
if not exists(colo_fp):
try:
generate_empty_image(colo_fp)
pass
# TODO generate empty alpha image with GMIC or fast lib (numpy ?)
except Exception as identifier:
print('In load_bg_movieclip')
print(f'NOT FOUND : {colo_fp}')
return
return
# load as movie clip
colo_img = bpy.data.movieclips.get(colo)
if colo_img:
pass# colo_img.reload()# video has no reload prop
#TODO find a way to trigger refresh automagically !!
else:
if not exists(colo_fp):return
colo_img = bpy.data.movieclips.load(colo_fp)
bgimg = None
for bg in cam.background_images:
if bg.clip == colo_img:
bgimg = bg
break
if not bgimg:
bgimg = cam.background_images.new()
bgimg.source = 'MOVIE_CLIP'
bgimg.clip = colo_img
set_bg_img_settings(bgimg)
return bgimg
def guide_color(anim=False):
'''render lines and spots separately > gmic > feeback into cam background'''
scene = bpy.context.scene
#### other solution
### how about conerting polylines to stroke under the hood with opencv in camera space according to layer
### then feed generated array to gmic, might be faster (but dont take stroke thickness into account... problem or feature ?)
# Generate temporary (local for speed) folder or render in /tmp if not specified ?
if not bpy.context.object or bpy.context.object.type != 'GPENCIL': return 1
gp = bpy.context.object.data
frame = str(scene.frame_current).zfill(4)
line = f"line_{frame}.png"
spot = f"spot_{frame}.png"
colo = f"colotmp_{frame}.png"
with layer_state(gp):
# show/hide layers to render lines/spots only
for l in gp.layers:
l.hide = any(x in l.info for x in ('spot', 'colo'))#keep only lines
# better hide by material namespace ?
with render_filepath(f"//tool/{line}"):
bpy.ops.render.render(animation = anim, write_still=True)
# show/hide layers to render spots only
for l in gp.layers:
l.hide = 'spot' not in l.info#keep only spots
with render_filepath(f"//tool/{spot}"):
bpy.ops.render.render(animation = anim, write_still=True)
line_fp = join(imtool_fp(), line)
spot_fp = join(imtool_fp(), spot)
colo_fp = join(imtool_fp(), colo)
propagate_color(line_fp, spot_fp, colo_fp)
## ~4.6sec
# now try and check if openCV or gmic can smooth vectorize the stuff
## ! surely possible to avoid writting the file like giving it back as a numpy array and keep it within blender !
## >> just feed numpy array to gmic and get output ?
## Again maybe possible to avoid writing to disk but more complicated to send to gmic (use slot 8 and 9 ?)
# clear line/spot render if necessary...
class GMICOLOR_OT_propagate_spots(bpy.types.Operator):
"""
Propagate the spots with a gmic call
use shift+clic to force reload after operation.
"""
bl_idname = "bgmic.propagate_color"
bl_label = "Gmic propagate color"
@classmethod
def poll(cls, context):
return context.active_object is not None and context.active_object.type == 'GPENCIL'
anim : bpy.props.BoolProperty(
name="animation", description="render and propagate color for whole animation", default=False, subtype='NONE', options={'ANIMATABLE'})#HIDDEN
## TODO set preview mode (with low percentage)
# subtype (string) Enumerator in ['FILE_PATH', 'DIR_PATH', 'FILE_NAME', 'BYTE_STRING', 'PASSWORD', 'NONE'].
# options (set) Enumerator in ['HIDDEN', 'SKIP_SAVE', 'ANIMATABLE', 'LIBRARY_EDITABLE', 'PROPORTIONAL','TEXTEDIT_UPDATE'].
mode : bpy.props.StringProperty(
name="mode", description="Set mode for operator", default="render", maxlen=0, subtype='NONE', options={'ANIMATABLE'})
load : bpy.props.BoolProperty(default=False)
def execute(self, context):
## TODO make a warning if animation mode is on and there is a consequent number of frame with big resolutions...
frame = str(context.scene.frame_current).zfill(4)
colo = f'colotmp_{frame}.png'
colo_fp = join(imtool_fp(), colo)
bgimg = None
## render one image or full-anim
if self.mode == 'render':
## Or maybe use the technique of BG rendering... with subprocess call
bgimg = guide_color(self.anim)
## re-load
if self.mode == 'load' or self.load:
""" if not exists(colo_fp):
print(f'/!\\ {frame}: color was not generated')
return {'CANCELLED'} """
bpy.ops.bgmic.clear_cam_bg_images(real_clear=False)#clear before load
if self.anim:
bgimg = load_bg_movieclip()
generate_seq_placeholder()#generate all placeholders
else:
bgimg = load_bg_image(colo_fp)
if not bgimg:
mess = f"Not Found, Loading {'anim' if self.anim else 'image'} has failed at {dirname(colo_fp)}"
self.report({'ERROR'}, mess)#WARNING, ERROR
return {'CANCELLED'}
return {'FINISHED'}
def invoke(self, context, event):
self.load = event.shift#if shift is pressed, force inclusion of load
return self.execute(context)
class GMICOLOR_OT_clear_cam_bg_images(bpy.types.Operator):
"""
Disable and clear background images from scene camera (mask non spot imgs)
Shift+clic for a real clear (delete all refs images from camera bg images)
"""
bl_idname = "bgmic.clear_cam_bg_images"
bl_label = "Clear camera background color images"
@classmethod
def poll(cls, context):
return context.scene.camera
real_clear : bpy.props.BoolProperty(default=False)
def execute(self, context):
# TODO be selective to user cam (filter on name)
# TODO need to delete only "spot" name instead of full clear
if self.real_clear:
context.scene.camera.data.background_images.clear()
else:
to_del = []
for bgimg in context.scene.camera.data.background_images:
if bgimg.source == 'MOVIE_CLIP':
if not bgimg.clip or 'spot' in bgimg.clip.name or 'colotmp' in bgimg.clip.name:
to_del.append(bgimg)
continue
else:#just hide
bgimg.show_background_image = False
else:
if not bgimg.image or 'spot' in bgimg.image.name or 'colotmp' in bgimg.image.name:
to_del.append(bgimg)
continue
else:#just hide
bgimg.show_background_image = False
for bimg in reversed(to_del):
context.scene.camera.data.background_images.remove(bimg)
context.scene.camera.data.show_background_images = False#toggle off then on to force refresh
# context.scene.camera.data.show_background_images = True
return {'FINISHED'}
def invoke(self, context, event):
self.real_clear = event.shift#if shift is pressed, force inclusion of load
return self.execute(context)
class GMICOLOR_OT_open_gmic_tool_folder(bpy.types.Operator):
"""
Disable and clear background images from scene camera (mask non spot imgs)
Shift+clic for a real clear (delete all refs images from camera bg images)
"""
bl_idname = "bgmic.open_gmic_tool_folder"
bl_label = "Open spot-image-tool Folder"
bl_options = {'REGISTER', 'INTERNAL'}
def execute(self, context):
fp = join(bpy.path.abspath('//'), 'tool')
if not exists(fp):
mess = f'{fp} not found'
self.report({'WARNING'}, mess)
return {'CANCELLED'}
open_folder(fp)
self.report({'INFO'}, 'Gmic tool folder opened')
return {'FINISHED'}
## base panel
class GMICOLOR_PT_auto_color_panel(bpy.types.Panel):
bl_label = "GP Colorize"# title
# bl_parent_id # If set, the panel becomes a sub-panel
## bl_options = {'DEFAULT_CLOSED', 'HIDE_HEADER' }# closed by default, collapse the panel and the label
## is_popover = False # if ommited
## bl_space_type = ['EMPTY', 'VIEW_3D', 'IMAGE_EDITOR', 'NODE_EDITOR', 'SEQUENCE_EDITOR', 'CLIP_EDITOR', 'DOPESHEET_EDITOR', 'GRAPH_EDITOR', 'NLA_EDITOR', 'TEXT_EDITOR', 'CONSOLE', 'INFO', 'TOPBAR', 'STATUSBAR', 'OUTLINER', 'PROPERTIES', 'FILE_BROWSER', 'PREFERENCES'], default 'EMPTY'
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Gpencil"#name of the tab
# activating on some context only
## bl_context : object, objectmode, mesh_edit, curve_edit, surface_edit, text_edit, armature_edit, mball_edit, lattice_edit, pose_mode, imagepaint, weightpaint, vertexpaint, particlemode
#bl_context = "objectmode"#render
#need to be in object mode
@classmethod
def poll(cls, context):
return get_addon_prefs().use_color_tools#(context.object is not None and context.object.type == 'GPENCIL')
## draw stuff inside the header (place before main label)
# def draw_header(self, context):
# layout = self.layout
# layout.label(text="More text in header")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
prefs = get_addon_prefs()
if not prefs.gmic_path:
layout.label(text='Gmic path missing in addon prefs', icon='ERROR')
return
if not [l for l in context.object.data.layers if 'spot' in l.info]:
layout.label(text='Need at least one spots layer !', icon='ERROR')
layout.label(text='("spot" in name to identify)')
return
# row = layout.row()
layout.prop(context.scene.gpcolor_props, 'res_percentage')
## render and load frame
ops = layout.operator("bgmic.propagate_color", icon = 'FILE_IMAGE')
ops.mode = 'render'
ops.anim = False
## render and load anim
ops = layout.operator("bgmic.propagate_color", text='Gmic propagate animation', icon = 'FILE_MOVIE')
ops.mode = 'render'
ops.anim = True
layout.separator()
## Load frame
ops = layout.operator("bgmic.propagate_color", text='load frame', icon = 'FILE_IMAGE')
ops.mode = 'load'
ops.anim = False
## Load anim
ops = layout.operator("bgmic.propagate_color", text='Load animation', icon = 'FILE_MOVIE')
ops.mode = 'load'
ops.anim = True
layout.separator()
layout.operator("bgmic.clear_cam_bg_images")
## Open gmic tool location
layout.operator("bgmic.open_gmic_tool_folder", icon = 'FILE_FOLDER')#, text='Open img tool folder'
## TODO : Add an operator to generate empty alpha image to avoid the pink flashes...
## todo : choose to overwrite or not
## Add button to delete current "frame" (delete image on disk...)
classes = (
GMICOLOR_OT_propagate_spots,
GMICOLOR_OT_clear_cam_bg_images,
GMICOLOR_OT_open_gmic_tool_folder,
GMICOLOR_PT_auto_color_panel,
)
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,4 +1,4 @@
from .func_gmic import *
# from .func_gmic import *
from ..utils import (location_to_region,
region_to_location,
vector_length_2d,

View File

@ -1,184 +0,0 @@
import os, re
from os.path import join, basename, exists, dirname, abspath, splitext
import subprocess
import time, datetime
from ..utils import get_addon_prefs, transfer_value
def get_gmic():
prefs = get_addon_prefs()
return prefs.gmic_path
## globals
image_exts = ('.png', '.jpg', '.tiff', '.tga', '.jpeg',)
Rnum = re.compile(r'(\d+)(?!.*\d)')
def is_img(fp):
if splitext(basename(fp))[1].lower() in image_exts:
return True
def is_img_folder(d):
ct = 0
for f in os.listdir(d):
ct += 1
#if os.path.isfile(d)
if not is_img(f):
#print("not an image:", f)#Dbg
return 0
if not ct:#return false if nothing found
#print('nothing in folder')
return -1
###all files has been evaluated as images
return 1
def auto_colo(line, colo, out):
gmic = get_gmic()
opt = r'fx_colorize_lineart_smart 2,96,0,0,0,1,24,197,0,90.4,1,34.05,22.27,48.96,0.57,6.4,1,0,20,50.18,7.5,0.5,0'
if not exists(line) or not exists(colo):
print('one source directory not exists')
return
line_l = [join(line, i) for i in os.listdir(line) if i.endswith(('.png', '.jpg'))]
colo_l = [join(colo, i) for i in os.listdir(colo) if i.endswith(('.png', '.jpg'))]
line_l.sort()
colo_l.sort()
#print(line_l)
#print(colo_l)
if len(line_l) != len(colo_l):
print('lists of line and colo files have not the same lenght')
return
outfolder_name = basename(dirname(line)) + '_autocolo'
outfolder = join(out, outfolder_name)
if not exists(outfolder):
os.mkdir(outfolder)
ct = 0
for i, f in enumerate(line_l):
ct += 1
filename = outfolder_name + '_'+ str(i+101).zfill(4) +'.png'
outfile = join(outfolder, filename)
#print('-', f,outfile)
cmd = '{} {} {} {} -o[1] {}'.format(gmic, line_l[i], colo_l[i], opt, outfile)
#note on -o[1]
#this filter output 2 img, original line and the new color, here specify only color (keep your name clean)
print(cmd)
os.system(cmd)
# if ct > 3:return#limiter
print('Done')
def random_fill(line, out):
'''gmic command to convert line file to pseudo color out file'''
gmic = get_gmic()
#Todo, handle png compression, will be deleted so can be uncompressed
start_time = time.time()#timer
opt = r'fx_colorize_lineart_smart 0,100,0,0,0,80,184,0,90.5,2,0,0,0,0,4,1,2'
cmd = [gmic, line] + opt.split(' ') + ['-o[1]', out]# + ['to_rgba']
print(cmd)
subprocess.call(cmd)
print("elapsed", time.time() - start_time)#timer
def random_fill_folder(src, dest):
'''gmic command to convert a line folder to pseudo color output in another folder'''
# opt = r'fx_colorize_lineart_smart 0,97.2,0,0,0,0,210,213,0,86.5,1,49.41,28.67,37.26,0.29,12.7,0,0,0,0,0,0,0'
if not exists(src):
print('source directory not exists')
return
line_l = [join(src, i) for i in os.listdir(src) if is_img(i)]
line_l.sort()
'''
outfolder_name = basename(dirname(src)) + '_randomcolo'
outfolder = join(dest, outfolder_name)
if not exists(outfolder):
os.mkdir(outfolder)
'''
#ct = 0
for i, f in enumerate(line_l):
outfile = join(dest, 'random_fill_' + str(i+101).zfill(4) +'.png')
random_fill(f, outfile)
print('Done')
def generate_empty_image(fp):
'''
Generate an empty 1x1 pixel full transparent
width,height,depth,spectrum(channels)
doc: https://gmic.eu/tutorial/_input.shtml
generate a file of ~200byte
'''
gmic = get_gmic()
cmd = [gmic, '1,1,1,4', '-o', fp]
# cmd = [gmic, '16,9,1,4', '-o', fp] #classic ratio... influence nothing
subprocess.call(cmd)
def propagate_color(line, spot, out):
gmic = get_gmic()
start_time = time.time()#timer
## colorize
opt = ['fx_colorize_lineart', '1,3,0,0.02']
## antialias
aa = ['fx_smooth_antialias', '10,10.1,1.22,0,50,50']# smooth aa
# aa = ['gcd_anti_alias', '60,0.1,0,0']# basic aa
## BG remove
# vert pur 0,255,0,255 for BG color to delete, rose pale : [234,139,147,255] : [0.822786, 0.258183, 0.291771, 1.000000] #EA8B93
# 2 first value tolerance(1~3),smoothness(need0)
del_bg = ['to_rgba', 'replace_color', '3,0,1,255,1,255,255,255,255,0']
print('gmic: ', gmic)
# gmic = f'"{gmic}"'
cmd = [gmic, line, spot] + opt + del_bg + ['-o[1]', out]# + aa
print('cmd: ', cmd)
subprocess.call(cmd)
print("elapsed", time.time() - start_time)#timer
def propagate_color_folder(line_fp, spot_fp, outfolder):
print('Starting', datetime.datetime.now())# print full current date
start_time = time.time()# get start time
# [gmic_krita_qt]./apply/ v -99 fx_colorize_lineart 1,3,0,0.02
if not exists(line_fp) or not exists(spot_fp) or not exists(outfolder):
print(f'''some directories not exists:
{exists(line_fp)}: {line_fp}
{exists(spot_fp)}: {spot_fp}
{exists(outfolder)}: {outfolder}''')
return 1
lines = sorted([f.path for f in os.scandir(line_fp) if is_img(f.name) and Rnum.search(f.name)])
spots = sorted([f.path for f in os.scandir(spot_fp) if is_img(f.name) and Rnum.search(f.name)])
# if len(lines) != len(spots):#file number test...valid but disabled for tests
# print('lists of line and colo files have not the same lenght')
# return
for l, s in zip(lines, spots):
lframe = int(Rnum.search(l).group(1))
sframe = int(Rnum.search(s).group(1))
if lframe != sframe:
print(f'line img has not the same number as spot img {lframe} != {sframe}')
continue
out = join(outfolder, f'colo_{str(lframe).zfill(3)}.png')
print(f'frame {lframe}')
propagate_color(l,s,out)
elapsed_time = time.time() - start_time# seconds
full_time = str(datetime.timedelta(seconds=elapsed_time))# hh:mm:ss format
print("elapsed time", elapsed_time)
print(full_time)

View File

@ -11,20 +11,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
## basec on GPclipboard 1.3.1 (without addon prefs)
## based on GPclipboard 1.3.2 (just stripped addon prefs)
bl_info = {
"name": "GP clipboard",
"description": "Copy/Cut/Paste Grease Pencil strokes to/from OS clipboard across layers and blends",
"author": "Samuel Bernou",
"version": (1, 3, 1),
"version": (1, 3, 2),
"blender": (2, 83, 0),
"location": "View3D > Toolbar > Gpencil > GP clipboard",
"warning": "",
"doc_url": "https://github.com/Pullusb/GP_clipboard",
"category": "Object" }
import bpy
import os
import mathutils
@ -96,9 +95,12 @@ def dump_gp_stroke_range(s, sid, l, obj):
if s.material_index != 0:
sdic['material_index'] = s.material_index
if s.draw_cyclic:
if getattr(s, 'draw_cyclic', None):# pre-2.92
sdic['draw_cyclic'] = s.draw_cyclic
if getattr(s, 'use_cyclic', None):# from 2.92
sdic['use_cyclic'] = s.use_cyclic
if s.uv_scale != 1.0:
sdic['uv_scale'] = s.uv_scale
@ -677,10 +679,8 @@ def register_keymaps():
addon_keymaps.append((km, kmi))
def unregister_keymaps():
# wm = bpy.context.window_manager
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
# wm.keyconfigs.addon.keymaps.remove(km)
addon_keymaps.clear()

View File

@ -46,6 +46,7 @@ class GPTB_OT_file_checker(bpy.types.Operator):
## set scene res at pref res according to addon pref
rx, ry = prefs.render_res_x, prefs.render_res_y
# TODO set (rx, ry) to camera resolution if specified in camera name
if context.scene.render.resolution_x != rx or context.scene.render.resolution_y != ry:
problems.append(f'Resolution {context.scene.render.resolution_x}x{context.scene.render.resolution_y} >> {rx}x{ry}')
context.scene.render.resolution_x, context.scene.render.resolution_y = rx, ry

View File

@ -2,15 +2,16 @@
Blender addon - Various tool to help with grease pencil in animation productions.
**[Download latest](https://gitlab.com/autour-de-minuit/blender/GP_toolbox/-/archive/master/GP_toolbox-master.zi)**
**[Download latest](https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/archive/master/GP_toolbox-master.zip)**
<!-- ### [Demo Youtube]() -->
**[Readme Doc in French (Documentation en Français et plus détaillée)](https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/blob/master/README_FR.md)**
---
## Description
<!-- ![pseudo color demo](https://github.com/Pullusb/images_repo/blob/master/GPT_pseudo_tint.gif)
*In this demo F9 is pressed to call the "redo panel" and modify the hue offset with constant update* -->
@ -25,7 +26,7 @@ important point of addon preferences:
Set path to the palette folder (there is a json palette IO but you an also put a blend and use a blend importer)
Note about palette : For now thoe importer are not working with linked palette is not easy for animator (there are properies of the material you cannot access and the link grey-out fade the r eal color in UIlist preview)
Note about palette : For now the importer is not working with linked palette as it's not easy for animator (there are properties of the material you cannot access and the link grey-out fade the real color in UIlist preview)
- Mirror flip : If in cam view flip the camera X scale value (you can see and draw mnirrored to see problems)
@ -66,11 +67,25 @@ Note about palette : For now thoe importer are not working with linked palette i
Panel in sidebar : 3D view > sidebar 'N' > Gpencil
<!--
## Todo:
- viewport Face GP object -> test in 3D context tester
- move GP keyframes selection and Object keyframe selection simultaneouly (Tom viguier is test)
- -->
- Allow to render resolution from cam name
- Update multi output system:
- use render layers + file outputs instead of batch by tweaking visibility
- BG Playblast enhancement:
- Add a prefs to fallback to "simple" render.
- Settings-io for projects : Rsolution du film, palette folder, render settings...
- expose "tool setting" of canvas handling in sidebar (visible only in draw mode)
- Move automatically view to match GP Front (depending on Gpencil view settings)
- move GP keyframes selection and Object keyframe selection simultaneouly (Already Done by Tom Viguier at [Andarta](https://gitlab.com/andarta-pictures)
---
@ -82,6 +97,9 @@ Panel in sidebar : 3D view > sidebar 'N' > Gpencil
- Public release
- prefs: added fps as part of project settings
- check file use pref fps value (previously used harcoded 24fps value)
- cleanup: Remove wip GMIC-bridge tools that need to be done separately (if needed)
- update: apply changes in integrated copy-paste from the last version of standalone addon
- doc: Added fully-detailed french readme
0.8.0:

148
README_FR.md Normal file
View File

@ -0,0 +1,148 @@
# GP toolbox
Blender addon - Boîte a outils de grease pencil pour la production d'animation.
**[Télécharger la dernière version](https://gitlab.com/autour-de-minuit/blender/GP_toolbox/-/archive/master/GP_toolbox-master.zip)**
> Une fois en place un système de mise a jour facilité est accessible dans les préférence (La vérification automatique dde nouvelle mise a jour peut y être activé)
Il est recommandé de désactiver l'addon natif "Grease pencil tools" car ces outils sont déjà intégré dans la toolbox et risque de créer des conflit.
## Fonctionnalités et détails
### Exposition dans l'UI de fonction native
Exposition de certains attribut déjà existant utiles mais trop "loins" dans l'interface
Expose les options suivantes:
- Zoom 1:1 - Vue en cam prend le zoom 100% en fonction de la résolution (ops `view3d.zoom_camera_1_to_1`)
- Zoom fit - Ajuste la vue pour que la cam soit vue en totalité dans l'écran (ops `view3d.view_center_camera`)
- Onion skin - coche des overlays
- autolock layer - coche du sous-menu de l'UI list des layers
- X-ray - Option `In Front` dans les propriété de l'objet
- passepartout de caméra - active-désactive + opactité
- liste de boutons pour activer/désactiver les background de caméra (tous ou individuellement) avec icone par type.
**Edit line opacity** - Il est en réalité pratique de pouvoir cacher l'edit line pour avoir un meilleur aperçu du rendu lors du sculpt par exemple. C'est une option lié au layer. Ce slider appelle une fonction pour affecter tout les layers de tout les objets au lieu de se cantonner au layer courant.
### Tools principaux d'anim:
**Système de Main cam VS draw cam / action cam** : pour permettre le roll du viewport quand on est en vue caméra (sans affecter la caméra def du layout) la draw cam peut être activée. c'est simplement une duplication de la caméra principale (main) parenté a cette dernière et sur laquelle on peut se permettre de faire le roll via rotate canvas (Workaround tant que blender ne permet pas de roll la vue en cam).
L'action cam (aurait du s'appeler follow_cam) fonctionne sur le principe que la draw sauf qu'elle se parente à l'objet selectionné. le but est de pouvoir suivre un objet en mouvement pour le dessiner en contitnue sans nécessairement désactiver les anims. Concrètement, cette dernière solution est plus clean et sans doute moins galère, c'est ce que les animateurs ont majoritairement utilisé.
**Rotate canvas** (`ctrl + alt + clic-droit`): différence avec celui intégré a grease pencil tools : la rotation en vue cam n'est possible que si on est dans une caméra de manipulation "manip_cam" pour éviter de caasser l'anim le roll de la cam principale.
**Box deform** (`Ctrl+T`) : Déformation 4 coins (Peut-être le retirer maintenant qu'il est dans le _Grease pencil tools_ pour éviter les conlfits/redondances, mais d'un autre côté la version intégrée peut éventeullement être customisée et corriger d'éventuel souci, sans attendre les updates de blender, a voir)
**GP keyframe jump** (raccourci a ajouter manuellement, tant qu'on a pas une keymap anim un minimum standard): Essentiel, permet d'ajouter une ou plusieurs paire de raccourcis pour aller de keyframe en keyframe (filtrage du saut personnalisable). le raccourci doit appeler l'operateur `screen.gp_keyframe_jump`, (en faire un second avec le next décoché pour faire un saut arrière)
**Breakdown en mode objet** (`Shift+E`) : Breakdown en pourcentage entre les deux keyframes (pose une clé si lauto-key est actif et utilise le keying set si il est actif). Même comportement et raccourci que le breakdown de bones en pose mode (juste qu'il n'existait bizzarement pas en objet)
**Snap cursor** - Snap le curseur sur le canvas de GP (respecte les paramètres "tool setting" qui définit le comportement du canvas)
À placer dans la keymap manuellement pour le moment en utilisant l'id `view3d.cusor_snap` (original `view3d.cursor3d`)
**Playblast** - Create a playblast folder in the blend's folder.
in 0.8.0 normal playblast (file still within addon folder) was replaced by bg_playblast (adaptation of samy tichadou's playblaster)
**GP copy paste** - permet de copier coller les clés en dur.
TODO -> vérifier l'intégration. semble avoir des soucis.
Pas mal de l'avoir en addon séparé aussi vu qu'il a son propre panel a lui.
**check files** - série de check écris en dur dans le code. Pratique pour "fixer" rapidement sa scène:
- Lock main cam
- set scene res to def project res (specified in addon prefs)
- set scene percentage at 100:
- set show slider and sync range in opened dopesheet
- set fps to 24 (need generalisation with addonpref choice)
- set select cursor type (according to prefs ?)
- GP use additive drawing (else creating a frame in dopesheet makes it blank...)
- GP stroke placement/projection check (just warn if your not in 'Front')
- Warn if there are some disabled animation (and list datapath)
- Set onion skin filter to 'All type' (this became default in blender 2.91, guess who asked ;) )
### moins important
**Mirror flip X** - Applique simplement un `scale.x -1` sur la cam active (un petit indicateur s'affiche pour indiquer qu'on est en mirror, recliquer sur le bouton permet de rétablir le scale d'origine)
**Cursor Follow** - (Utilise un handler) : ubne fois activé, permet de faire suivre le curseur 3D sur un objet animé.
Souci connu: Il y a un décalage d'une frame une fois activé sur un nouvel objet, il suffit de le recaler une fois.
**Animation manager** - permet d'aciver/désactiver les anims d'objets de tout les objets ou de tout les
### Présent mais pas utilisés:
**Sticky key tool**
Les raccourci vers des tools temporaires sont très pratique, on peut facilement créer un raccourci qui appelle le tool durant la presion d'un bouton. Cet operator est un modal qui permet de faire la même chose mais en activant simplement le tool si la touche est rapide. (`OP_temp_cutter.py > GPTB_OT_sticky_cutter` idname : `wm.sticky_cutter`)
### À potentiellement mettre de côté/retirer:
**Tint layer** : Très peu utilisé, permet de mettre une teinte aléatoire par calque afin de les différencier rapidement. Souci de cet opérateurm, il ne faut pas l'utiliser si on utilise les tints de layer car il en change la couleur.
**Colorize** (gros WIP): un sous ensemble d'outils qui était censé permettre de faire du remplissage bitmap via des color spots en envoyant deux set d'images rendu automatiquement à GMIC en ligne de commande et recalé la séquence de résultat en BG de Cam. Finalement abandonné, pas eu le temps de finir la mise au point (malgré des résultats préliminaires intéressant).
Maistrop long a mettre en place, trop hackeu, et surtout c'est dommage de basculer sur du bitmap, la source de couleur doit rester au maximum GP/vecto.
## colorisation:
**Line stopper** - Extension des lignes dans un matériaux a part pour améliorer la fermeture des formes. le hack est très simple mais aide beaucoup a fermer les contour pour éviter le leak de l'outils pot de peinture.
**Create empty frame** - Permet de créer des frames vides sur un calques partout ou il y a des frames sur les calques supérieur (permet de faire un key to key sur le calque actif ensuite sur les key pré-créée pour faire sa colo). C'est pratique à utiliser.
En réalité pour quelque chose de plus pratique pour la colo, il suffit d'utiliser le `screen.gp_keyframe_jump` operator en activant le filtre (all layers) : TODO faire un "all layer _above_" ou se baser sur le nouveau filtre natif lié a cet usage depuis blender 2.91
**Render** chemin de sorties + 2 boutons
- layers individually (popup pour selecitonner des layers a render individuellement)
- layers grouped (popup pour selectionner des layers a rendre ensemble)
- TODO -> Le chemin de base est défini en dur dans le project template
`blend.parents[1] / "compo" / "base" / obname / (obname+'_')`
-> Basculer vers un chemin personnalisable dans les préférences.
TODO -> profiter du système de render layers (per layer) pour faire un meilleur batch renderer.
## tools supplémentaires
**check links** - (pop une fenêtre) permet de lister les liens de la scène, voir si il y en a des cassés et ouvrir le dossier d'un lien existant.
## raccourci supplémentaires
Sculpt mode:
point/stroke filter shortcut sur `1`, `2`, `3` en toggle (similaire a l'edit mode)
cursor_GP
Surcharge du raccourci curseur 3D pour le snapper à la surface du grease pencil.
Raccourci à remplacer manuellement dans la keymap, Pas forcément utile si il n'y a un mix de 2D/3D
Le mieux serait d'avoir un raccourci dédié, séparé de celui d'origine...
---
TODO:
- Permettre de rendre avec la résolution spécifié dans le nom de la caméra active
(utile dans les projet rendu a la résolution du BG mais ou la résolution finale peut être utilisé pour un bout-a-bout)
- Update du système de "passes" de rendu:
- utiliser des render layers + file outputs au lieux de faire des batchs par opacité
- BG Playblast enhancement:
- Tester davantage le playblast BG
- Éventuellement mettre une coche de fallback vers le playblast classique (utile en cas de pépin.
- Faire un import-export des réglage généraux en json (Déjà une bonne partie du code dans Pipe sync)
pour set : Résolution du film, dossier palette, render settings
- opt: exposer les "tool setting" de placement de canvas en permanence dans la sidebar (visible seulement en draw)
- Déplacer automatiquement la vue "Face" au GP (en fonction des Gpencil view settings)
- Déplacer les clés de dopesheet en même temps que les clés de GP (Déjà Créer par Tom Viguier sur [Andarta](https://gitlab.com/andarta-pictures)
- Meilleure table lumineuse (grosse réflexion et travail en perspective)

View File

@ -12,7 +12,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
bl_info = {
"name": "GP toolbox",
"name": "gp toolbox",
"description": "Set of tools for Grease Pencil in animation production",
"author": "Samuel Bernou",
"version": (0, 9, 1),
@ -238,18 +238,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
max=10000
)
## GMIColor
use_color_tools : BoolProperty(
name = "Use color tools",
description = "Enable guided color tools panel",
default = False)
#-# gmic tools not ready
gmic_path : StringProperty(
name="Path to gmic", description="Need to specify path to gmic binary to allow color pixel propagation features",
default="", subtype='FLIE_PATH')
## Temp cutter
# temp_cutter_use_shortcut: BoolProperty(
# name = "Use temp cutter Shortcut",
@ -279,7 +267,6 @@ class GPTB_prefs(bpy.types.AddonPreferences):
row.label(text='Render resolution')
row.prop(self, 'render_res_x', text='X')
row.prop(self, 'render_res_y', text='Y')
## Palette
box.label(text='Palette library folder:')
box.prop(self, 'palette_path')
@ -332,26 +319,7 @@ class GPTB_prefs(bpy.types.AddonPreferences):
box.prop(self, "render_obj_exclusion", icon='FILTER')#
# if self.pref_tabs == 'GMIC':
# gmic (Disabled - tool complete WIP)
box = layout.box()
box.label(text='Colorisation:')
box.prop(self, 'use_color_tools')
if self.use_color_tools:
col = box.column(align=False)
## Delete if gmic is unused
col.prop(self, 'gmic_path')
if not self.gmic_path:
box=col.box()
box.label(text='Gmic is missing. (needed for pixel color tools)', icon='INFO')
row = box.row()
row.label(text='1.Download GMIC CLI (Command-line interface) here:')
row.operator("wm.url_open", text="Get gmic").url = "https://gmic.eu/download.shtml"
box.label(text='2.unzip it somewhere and point to gmic.exe')
box.label(text='(If gmic is already in your PATH, just write "gmic")')
if self.pref_tabs == 'MAN_OPS':
# layout.separator()## notes