ABX/abx/ink_paint.py

725 lines
29 KiB
Python
Raw Normal View History

# ink_paint.py
"""
Standard Ink & Paint compositing, as used in "Lunatics!"
The purpose of the "Ink/Paint Config" feature is to simplify setting up
the render layers and compositing nodes for EXR Ink/Paint configuration,
including the most common settings in our project.
I'm not making any attempt to generalize this feature, since that would
complicate the interface and defeat the purpose. Nor can I think of any
clean way to define this functionality in the project YAML code, without
it becoming truly byzantine. So it's set up like it is, and if you like
"Lunatics!" style, you may find it useful. Otherwise, you'll have to
write your own Add-on.
It does support a few common variations:
* Optional 'Ink-Thru' feature, allowing Freestyle lines behind
'transparent' objects to be seen. 'Transparent' objects, in this
context has to do with them being on scene layer 4, not with any
material settings.
* Optional 'Billboard' feature, allowing correct masking of Freestyle
ink lines around pre-rendered "billboard" objects, using transparent
texture maps. That is, background lines are drawn up to where the
visible part of the billboard would obscure them.
* Optional 'Separate Sky' compositing. This is a work-around to allow
sky backgrounds to be composited correctly without being shadowed by
the separate shadow layer. Basically a work-around for a design flaw
in the BLENDER_INTERNAL renderer, when combined with compositing.
Currently (0.2.6), it only supports setups for 'cam' file rendering. It does
not set up post-compositing files. This feature is on my road map.
"""
import os
import bpy, bpy.props, bpy.utils
# Hard-coded default parameters:
INK_THICKNESS = 3
INK_COLOR = (0,0,0)
THRU_INK_THICKNESS = 2
THRU_INK_COLOR = (20,100,50)
# TODO: probably should have a dialog somewhere that can change these through the UI?
2022-06-28 06:02:07 +00:00
class InkPaintShot(object):
"""
General class for Lunatics Blender Scene data.
So far in 0.2.6, this duplicates a lot of function that file_context is
supposed to provide, with hard-coding naming methods. My plan is to strip
all that out and replace with calls to the appropriate NameContext object.
"""
colorcode = {
'paint': (1.00, 1.00, 1.00),
'ink': (0.75, 0.50, 0.35),
'thru': (0.35, 0.50, 0.75),
'bb': (0.35, 0.75, 0.50),
'bbthru': (0.35, 0.75, 0.75),
'sky': (0.50, 0.25, 0.75),
'compos': (0.75, 0.75, 0.75),
'output': (0.35, 0.35, 0.35)
}
def __init__(self, scene, inkthru=False, billboards=False, sepsky=False):
self.scene = scene
self.inkthru = bool(inkthru)
self.billboards = bool(billboards)
self.sepsky = bool(sepsky)
2022-06-28 06:02:07 +00:00
self.pp = scene.project_properties
# self.series_id = scene.lunaprops.series_id
# self.episode_id = scene.lunaprops.episode_id
# self.seq_id = scene.lunaprops.seq_id
# self.block_id = scene.lunaprops.block_id
# self.shot_id = scene.lunaprops.shot_id
# self.cam_id = scene.lunaprops.cam_id
# self.shot_name = scene.lunaprops.shot_name
#
# self.render_root = '//../../Renders/'
2022-06-28 06:02:07 +00:00
# @property
# def fullname(self):
# return self.designation + '-' + self.name
2022-06-28 06:02:07 +00:00
# @property
# def designation(self):
# episode_code = "%2.2sE%2.2d" % (self.series_id, self.episode_id)
# return episode_code + '-' + self.shortname
2022-06-28 06:02:07 +00:00
# @property
# def shortname(self):
# desig = str(self.seq_id) + '-' + str(self.block_id)
# if self.cam_id:
# desig = desig + '-Cam' + str(self.cam_id)
# if self.shot_id:
# desig = desig + '-' + str(self.shot_id)
# return desig
2022-06-28 06:02:07 +00:00
def cfg_scene(self, scene=None, thru=True, exr=True, multicam=False, role='shot'):
"""
Configure the Blender scene for Ink/Paint.
"""
if not scene:
scene = self.scene
2022-06-28 06:02:07 +00:00
scene.name = self.pp.scene_name
scene.render.filepath = self.pp.render_path() # TODO - NO PARAMS - this really should
# be integrated w/ render_profiles
scene.render.image_settings.file_format='PNG'
scene.render.image_settings.compression = 50
scene.render.image_settings.color_mode = 'RGB'
scene.render.use_freestyle = True
# Create Paint & Ink Render Layers
for rlayer in scene.render.layers:
rlayer.name = '~' + rlayer.name
rlayer.use = False
# Rename & turn off existing layers (but don't delete, in case they were wanted)
scene.render.layers.new('Paint')
self.cfg_paint(scene.render.layers['Paint'])
scene.render.layers.new('Ink')
self.cfg_ink(scene.render.layers['Ink'],
thickness=INK_THICKNESS, color=INK_COLOR)
if self.inkthru:
scene.render.layers.new('Ink-Thru')
self.cfg_ink(scene.render.layers['Ink-Thru'],
thickness=THRU_INK_THICKNESS, color=THRU_INK_COLOR)
if self.billboards:
scene.render.layers.new('BB-Alpha')
self.cfg_bbalpha(scene.render.layers['BB-Alpha'])
scene.render.layers.new('BB-Mat')
self.cfg_bbmat(scene.render.layers['BB-Mat'], thru=False)
if self.billboards and self.inkthru:
scene.render.layers.new('BB-Mat-Thru')
self.cfg_bbmat(scene.render.layers['BB-Mat-Thru'], thru=True)
if self.sepsky:
scene.render.layers.new('Sky')
self.cfg_sky(scene.render.layers['Sky'])
self.cfg_nodes(scene)
def _new_rlayer_in(self, name, scene, rlayer, location, color):
tree = scene.node_tree
rlayer_in = tree.nodes.new('CompositorNodeRLayers')
rlayer_in.name = '_'.join([n.lower() for n in name.split('-')])+'_in'
rlayer_in.label = name+'-In'
rlayer_in.scene = scene
rlayer_in.layer = rlayer
rlayer_in.color = color
rlayer_in.use_custom_color = True
rlayer_in.location = location
return rlayer_in
def cfg_nodes(self, scene):
"""
Configure the compositing nodes.
"""
# Create Compositing Node Tree
scene.use_nodes = True
tree = scene.node_tree
# clear default nodes
for node in tree.nodes:
tree.nodes.remove(node)
# Paint RenderLayer Nodes
paint_in = self._new_rlayer_in('Paint', scene, 'Paint',
(0,1720), self.colorcode['paint'])
if self.sepsky:
sky_in = self._new_rlayer_in('Sky', scene, 'Sky',
(0, 1200), self.colorcode['sky'])
# Configure EXR format
exr_paint = tree.nodes.new('CompositorNodeOutputFile')
exr_paint.name = 'exr_paint'
exr_paint.label = 'Paint EXR'
exr_paint.location = (300,1215)
exr_paint.color = self.colorcode['paint']
exr_paint.use_custom_color = True
exr_paint.format.file_format = 'OPEN_EXR_MULTILAYER'
exr_paint.format.color_mode = 'RGBA'
exr_paint.format.color_depth = '16'
exr_paint.format.exr_codec = 'ZIP'
2022-06-28 06:02:07 +00:00
exr_paint.base_path = os.path.join(
self.pp.render_root, self.pp.render_folder, 'EXR',
self.pp.render_stem, self.pp.render_stem + '-Paint-f#####' + '.exr')
if 'Image' in exr_paint.layer_slots:
exr_paint.layer_slots.remove(exr_paint.inputs['Image'])
# Create EXR layers and connect to render passes
rpasses = ['Image', 'Depth', 'Normal', 'Vector',
'Spec', 'Shadow','Reflect','Emit']
for rpass in rpasses:
exr_paint.layer_slots.new(rpass)
tree.links.new(paint_in.outputs[rpass], exr_paint.inputs[rpass])
if self.sepsky:
exr_paint.layer_slots.new('Sky')
tree.links.new(sky_in.outputs['Image'], exr_paint.inputs['Sky'])
# Ink RenderLayer Nodes
ink_in = self._new_rlayer_in('Ink', scene, 'Ink',
(590, 1275), self.colorcode['ink'])
if self.inkthru:
thru_in = self._new_rlayer_in('Thru', scene, 'Ink-Thru',
(590, 990), self.colorcode['thru'])
if self.billboards:
bb_in = self._new_rlayer_in('BB', scene, 'BB-Alpha',
(0, 870), self.colorcode['bb'])
bb_mat = self._new_rlayer_in('BB-Mat', scene, 'BB-Mat',
(0, 590), self.colorcode['bb'])
if self.inkthru and self.billboards:
bb_mat_thru = self._new_rlayer_in('BB-Mat-Thru', scene, 'BB-Mat-Thru',
(0, 280), self.colorcode['bbthru'])
# Ink EXR
exr_ink = tree.nodes.new('CompositorNodeOutputFile')
exr_ink.name = 'exr_ink'
exr_ink.label = 'Ink EXR'
exr_ink.location = (1150,700)
exr_ink.color = self.colorcode['ink']
exr_ink.use_custom_color = True
exr_ink.format.file_format = 'OPEN_EXR_MULTILAYER'
exr_ink.format.color_mode = 'RGBA'
exr_ink.format.color_depth = '16'
exr_ink.format.exr_codec = 'ZIP'
2022-06-28 06:02:07 +00:00
exr_ink.base_path = os.path.join(
self.pp.render_root, self.pp.render_folder, 'EXR',
self.pp.render_stem, self.pp.render_stem + '-Ink-f#####' + '.exr')
# Create EXR Ink layers and connect
if 'Image' in exr_ink.layer_slots:
exr_ink.layer_slots.remove(exr_ink.inputs['Image'])
exr_ink.layer_slots.new('Ink')
tree.links.new(ink_in.outputs['Image'], exr_ink.inputs['Ink'])
if self.inkthru:
exr_ink.layer_slots.new('Ink-Thru')
tree.links.new(thru_in.outputs['Image'], exr_ink.inputs['Ink-Thru'])
if self.billboards:
exr_ink.layer_slots.new('BB-Alpha')
tree.links.new(bb_in.outputs['Alpha'], exr_ink.inputs['BB-Alpha'])
exr_ink.layer_slots.new('BB-Mat')
tree.links.new(bb_mat.outputs['IndexMA'], exr_ink.inputs['BB-Mat'])
if self.inkthru and self.billboards:
exr_ink.layer_slots.new('BB-Mat-Thru')
tree.links.new(bb_mat_thru.outputs['IndexMA'], exr_ink.inputs['BB-Mat-Thru'])
# Preview Compositing
mix_shadow = tree.nodes.new('CompositorNodeMixRGB')
mix_shadow.name = 'mix_shadow'
mix_shadow.label = 'Mix-Shadow'
mix_shadow.location = (510,1820)
mix_shadow.color = self.colorcode['compos']
mix_shadow.use_custom_color = True
mix_shadow.blend_type = 'MULTIPLY'
mix_shadow.inputs['Fac'].default_value = 0.6
mix_shadow.use_clamp = True
tree.links.new(paint_in.outputs['Image'], mix_shadow.inputs[1])
tree.links.new(paint_in.outputs['Shadow'], mix_shadow.inputs[2])
mix_reflect = tree.nodes.new('CompositorNodeMixRGB')
mix_reflect.name = 'mix_reflect'
mix_reflect.label = 'Mix-Reflect'
mix_reflect.location = (910, 1620)
mix_reflect.color = self.colorcode['compos']
mix_reflect.use_custom_color = True
mix_reflect.blend_type = 'ADD'
mix_reflect.inputs['Fac'].default_value = 1.1
mix_reflect.use_clamp = True
tree.links.new(paint_in.outputs['Reflect'], mix_reflect.inputs[2])
mix_emit = tree.nodes.new('CompositorNodeMixRGB')
mix_emit.name = 'mix_emit'
mix_emit.label = 'Mix-Emit'
mix_emit.location = (1110, 1520)
mix_emit.blend_type = 'ADD'
mix_emit.inputs['Fac'].default_value = 1.1
mix_emit.use_clamp = True
tree.links.new(mix_reflect.outputs['Image'], mix_emit.inputs[1])
tree.links.new(paint_in.outputs['Emit'], mix_emit.inputs[2])
if self.sepsky:
sky_mix = tree.nodes.new('CompositorNodeMixRGB')
sky_mix.name = 'sky_mix'
sky_mix.label = 'Sky Mix'
sky_mix.location = (710,1720)
sky_mix.color = self.colorcode['sky']
sky_mix.use_custom_color = True
sky_mix.blend_type = 'MIX'
sky_mix.use_clamp = True
tree.links.new(sky_in.outputs['Image'], sky_mix.inputs[1])
tree.links.new(paint_in.outputs['Alpha'], sky_mix.inputs['Fac'])
tree.links.new(mix_shadow.outputs['Image'], sky_mix.inputs[2])
tree.links.new(sky_mix.outputs['Image'], mix_reflect.inputs[1])
else:
tree.links.new(mix_shadow.outputs['Image'], mix_reflect.inputs[1])
if self.billboards:
mat_idx = tree.nodes.new('CompositorNodeIDMask')
mat_idx.name = "mat_idx"
mat_idx.label = "BB-ID"
mat_idx.location = (260, 670)
mat_idx.index = 1
mat_idx.use_antialiasing = True
mat_idx.color = self.colorcode['bb']
mat_idx.use_custom_color = True
tree.links.new(bb_mat.outputs['IndexMA'], mat_idx.inputs['ID value'])
combine_bb_ma = tree.nodes.new('CompositorNodeMath')
combine_bb_ma.name = 'combine_bb_ma'
combine_bb_ma.label = 'Material x BB'
combine_bb_ma.location = (440,670)
combine_bb_ma.color = self.colorcode['bb']
combine_bb_ma.use_custom_color = True
combine_bb_ma.operation = 'MULTIPLY'
combine_bb_ma.use_clamp = True
tree.links.new(mat_idx.outputs['Alpha'], combine_bb_ma.inputs[0])
tree.links.new(bb_in.outputs['Alpha'], combine_bb_ma.inputs[1])
invert_bb_mask = tree.nodes.new('CompositorNodeInvert')
invert_bb_mask.name = 'invert_bb_mask'
invert_bb_mask.label = 'Invert Mask'
invert_bb_mask.location = (650,670)
invert_bb_mask.color = self.colorcode['bb']
invert_bb_mask.use_custom_color = True
invert_bb_mask.invert_rgb = True
tree.links.new(combine_bb_ma.outputs['Value'], invert_bb_mask.inputs['Color'])
bb_ink_mask = tree.nodes.new('CompositorNodeMath')
bb_ink_mask.name = 'bb_ink_mask'
bb_ink_mask.label = 'BB Ink Mask'
bb_ink_mask.location = (1150,1315)
bb_ink_mask.color = self.colorcode['bb']
bb_ink_mask.use_custom_color = True
bb_ink_mask.operation = 'MULTIPLY'
bb_ink_mask.use_clamp = True
tree.links.new(invert_bb_mask.outputs['Color'], bb_ink_mask.inputs[0])
blur_ink = tree.nodes.new('CompositorNodeBlur')
blur_ink.name = 'blur_ink'
blur_ink.label = 'Blur-Ink'
blur_ink.location = (1620, 1110)
blur_ink.color = self.colorcode['ink']
blur_ink.use_custom_color = True
blur_ink.filter_type = 'FAST_GAUSS'
blur_ink.size_x = 1.0
blur_ink.size_y = 1.0
blur_ink.use_extended_bounds = False
blur_ink.inputs['Size'].default_value = 1.0
if self.inkthru:
merge_ink_ao = tree.nodes.new('CompositorNodeAlphaOver')
merge_ink_ao.name = 'merge_ink'
merge_ink_ao.label = 'Merge-Ink'
merge_ink_ao.location = (1150,910)
merge_ink_ao.color = self.colorcode['thru']
merge_ink_ao.use_custom_color = True
merge_ink_ao.use_premultiply = False
merge_ink_ao.premul = 0.0
merge_ink_ao.inputs['Fac'].default_value = 1.0
tree.links.new(ink_in.outputs['Image'], merge_ink_ao.inputs[1])
tree.links.new(thru_in.outputs['Image'], merge_ink_ao.inputs[2])
tree.links.new(merge_ink_ao.outputs['Image'], blur_ink.inputs['Image'])
else:
tree.links.new(ink_in.outputs['Image'], blur_ink.inputs['Image'])
overlay_ink = tree.nodes.new('CompositorNodeAlphaOver')
overlay_ink.name = 'Overlay Ink'
overlay_ink.label = 'Overlay Ink'
overlay_ink.location = (1820,1315)
overlay_ink.color = self.colorcode['compos']
overlay_ink.use_custom_color = True
overlay_ink.use_premultiply = False
overlay_ink.premul = 0.0
overlay_ink.inputs['Fac'].default_value = 1.0
tree.links.new(mix_emit.outputs['Image'], overlay_ink.inputs[1])
tree.links.new(blur_ink.outputs['Image'], overlay_ink.inputs[2])
if self.billboards:
tree.links.new(ink_in.outputs['Alpha'], bb_ink_mask.inputs[1])
tree.links.new(bb_ink_mask.outputs['Value'], overlay_ink.inputs['Fac'])
if self.inkthru and self.billboards:
mat_idx_thru = tree.nodes.new('CompositorNodeIDMask')
mat_idx_thru.name = "mat_idx_thru"
mat_idx_thru.label = "BB-ID-Thru"
mat_idx_thru.location = (260, 425)
mat_idx_thru.index = 1
mat_idx_thru.use_antialiasing = True
mat_idx_thru.color = self.colorcode['bbthru']
mat_idx_thru.use_custom_color = True
tree.links.new(bb_mat_thru.outputs['IndexMA'], mat_idx_thru.inputs['ID value'])
combine_bbthru_ma = tree.nodes.new('CompositorNodeMath')
combine_bbthru_ma.name = 'combine_bbthru_ma'
combine_bbthru_ma.label = 'Material x BB-Thru'
combine_bbthru_ma.location = (440,425)
combine_bbthru_ma.color = self.colorcode['bbthru']
combine_bbthru_ma.use_custom_color = True
combine_bbthru_ma.operation = 'MULTIPLY'
combine_bbthru_ma.use_clamp = True
tree.links.new(mat_idx_thru.outputs['Alpha'], combine_bbthru_ma.inputs[0])
tree.links.new(bb_in.outputs['Alpha'], combine_bbthru_ma.inputs[1])
invert_bbthru_mask = tree.nodes.new('CompositorNodeInvert')
invert_bbthru_mask.name = 'invert_bbthru_mask'
invert_bbthru_mask.label = 'Invert Mask'
invert_bbthru_mask.location = (650,425)
invert_bbthru_mask.color = self.colorcode['bbthru']
invert_bbthru_mask.use_custom_color = True
invert_bbthru_mask.invert_rgb = True
tree.links.new(combine_bbthru_ma.outputs['Value'], invert_bbthru_mask.inputs['Color'])
bb_thru_mask = tree.nodes.new('CompositorNodeMath')
bb_thru_mask.name = 'bb_thru_mask'
bb_thru_mask.label = 'BB Ink Thru Mask'
bb_thru_mask.location = (1150,1115)
bb_thru_mask.color = self.colorcode['bbthru']
bb_thru_mask.use_custom_color = True
bb_thru_mask.operation = 'MULTIPLY'
bb_thru_mask.use_clamp = True
tree.links.new(thru_in.outputs['Alpha'], bb_thru_mask.inputs[0])
tree.links.new(invert_bbthru_mask.outputs['Color'], bb_thru_mask.inputs[1])
merge_bb_ink_masks = tree.nodes.new('CompositorNodeMath')
merge_bb_ink_masks.name = 'merge_bb_ink_masks'
merge_bb_ink_masks.label = 'Merge BB Ink Masks'
merge_bb_ink_masks.location = (1415, 1215)
merge_bb_ink_masks.color = self.colorcode['bbthru']
merge_bb_ink_masks.use_custom_color = True
merge_bb_ink_masks.operation = 'ADD'
merge_bb_ink_masks.use_clamp = True
tree.links.new(bb_ink_mask.outputs['Value'], merge_bb_ink_masks.inputs[0])
tree.links.new(bb_thru_mask.outputs['Value'], merge_bb_ink_masks.inputs[1])
tree.links.new(merge_bb_ink_masks.outputs['Value'], overlay_ink.inputs['Fac'])
composite = tree.nodes.new('CompositorNodeComposite')
composite.name = 'Composite'
composite.label = 'Preview Render'
composite.location = (2050,1215)
composite.color = self.colorcode['output']
composite.use_custom_color = True
composite.use_alpha = True
composite.inputs['Alpha'].default_value = 1.0
composite.inputs['Z'].default_value = 1.0
tree.links.new(overlay_ink.outputs['Image'], composite.inputs['Image'])
def _cfg_renderlayer(self, rlayer,
includes=False, passes=False, excludes=False,
layers=range(20)):
# Utility to set all the includes and passes on or off, initially
# Weird Includes (we never use these -- always have to turn these on explicitly)
rlayer.use_zmask = False
rlayer.invert_zmask = False
rlayer.use_all_z = False
# Includes
rlayer.use_solid = includes
rlayer.use_halo = includes
rlayer.use_ztransp = includes
rlayer.use_sky = includes
rlayer.use_edge_enhance = includes
rlayer.use_strand = includes
rlayer.use_freestyle = includes
# Passes
rlayer.use_pass_combined = passes
rlayer.use_pass_z = passes
rlayer.use_pass_vector = passes
rlayer.use_pass_normal = passes
rlayer.use_pass_uv = passes
rlayer.use_pass_mist = passes
rlayer.use_pass_object_index = passes
rlayer.use_pass_material_index = passes
rlayer.use_pass_color = passes
rlayer.use_pass_diffuse = passes
rlayer.use_pass_specular = passes
rlayer.use_pass_shadow = passes
rlayer.use_pass_emit = passes
rlayer.use_pass_ambient_occlusion = passes
rlayer.use_pass_environment = passes
rlayer.use_pass_indirect = passes
rlayer.use_pass_reflection = passes
rlayer.use_pass_refraction = passes
# Exclusions
rlayer.exclude_specular = excludes
rlayer.exclude_shadow = excludes
rlayer.exclude_emit = excludes
rlayer.exclude_ambient_occlusion = excludes
rlayer.exclude_environment = excludes
rlayer.exclude_indirect = excludes
rlayer.exclude_reflection = excludes
rlayer.exclude_refraction = excludes
for i in range(20):
if i in layers:
rlayer.layers[i] = True
else:
rlayer.layers[i] = False
def cfg_paint(self, paint_layer, name="Paint"):
"""
Configure the 'Paint' render layer.
"""
self._cfg_renderlayer(paint_layer,
includes=True, passes=False, excludes=False,
layers = (0,1,2,3,4, 5,6,7, 10,11,12,13,14))
# Includes
if self.sepsky:
paint_layer.use_sky = False
paint_layer.use_freestyle = False
# Passes
paint_layer.use_pass_combined = True
paint_layer.use_pass_z = True
paint_layer.use_pass_vector = True
paint_layer.use_pass_normal = True
paint_layer.use_pass_shadow = True
paint_layer.exclude_shadow = True
paint_layer.use_pass_emit = True
paint_layer.exclude_emit = True
paint_layer.use_pass_specular = True
paint_layer.exclude_specular = True
paint_layer.use_pass_reflection = True
paint_layer.exclude_reflection = True
def cfg_bbalpha(self, bb_render_layer):
"""
Configure the 'BB Alpha' render layer for billboards.
"""
self._cfg_renderlayer(bb_render_layer,
includes=False, passes=False, excludes=False,
layers=(5,6, 14))
# Includes
bb_render_layer.use_solid = True
bb_render_layer.use_ztransp = True
# Passes
bb_render_layer.use_pass_combined = True
def cfg_bbmat(self, bb_mat_layer, thru=False):
"""
Configure the 'BB Mat' material key render layer.
"""
self._cfg_renderlayer(bb_mat_layer,
includes=False, passes=False, excludes=False,
layers=(0,1,2,3, 5,6,7, 10,11,12,13,14, 15,16))
# Includes
bb_mat_layer.use_solid = True
bb_mat_layer.use_ztransp = True
# Passes
bb_mat_layer.use_pass_combined = True
bb_mat_layer.use_pass_material_index = True
if not thru:
bb_mat_layer.layers[4] = True
def cfg_sky(self, sky_render_layer):
"""
Configure the separate 'Sky' render layer.
"""
self._cfg_renderlayer(sky_render_layer,
includes=False, passes=False, excludes=False,
layers=(0,1,2,3,4, 5,6,7, 10,11,12,13,14))
# Includes
sky_render_layer.use_sky = True
# Passes
sky_render_layer.use_pass_combined = True
def cfg_ink(self, ink_layer, name="Ink", thickness=3, color=(0,0,0)):
"""
Configure a render layer for Freestyle ink ('Ink' or 'Ink-Thru').
"""
self._cfg_renderlayer(ink_layer,
includes=False, passes=False, excludes=False,
layers=(0,1,2,3, 5,6,7, 10,11,12,13, 15,16))
# Includes
ink_layer.use_freestyle = True
# Passes
ink_layer.use_pass_combined = True
# Freestyle
ink_layer.freestyle_settings.crease_angle = 2.617944
ink_layer.freestyle_settings.use_smoothness = True
ink_layer.freestyle_settings.use_culling = True
if len(ink_layer.freestyle_settings.linesets)>0:
ink_layer.freestyle_settings.linesets[0].name = name
else:
ink_layer.freestyle_settings.linesets.new(name)
lineset = ink_layer.freestyle_settings.linesets[name]
self.cfg_lineset(lineset, thickness, color)
# Turn on the transparency layer for the regular ink:
if ink_layer.name!='Ink-Thru':
ink_layer.layers[4] = True
def cfg_lineset(self, lineset, thickness=3, color=(0,0,0)):
"""
Configure the Freestyle line set (i.e. which lines are drawn).
"""
#lineset.name = 'NormalInk'
# Selection options
lineset.select_by_visibility = True
lineset.select_by_edge_types = True
lineset.select_by_image_border = True
lineset.select_by_face_marks = False
lineset.select_by_group = True
# Visibility Option
lineset.visibility = 'VISIBLE'
# Edge Type Options
lineset.edge_type_negation = 'INCLUSIVE'
lineset.edge_type_combination = 'OR'
lineset.select_silhouette = True
lineset.select_border = True
lineset.select_contour = True
lineset.select_crease = True
lineset.select_edge_mark = True
lineset.select_external_contour = True
# No Freestyle Group (If it exists)
if 'No Freestyle' in bpy.data.groups:
lineset.select_by_group = True
lineset.group = bpy.data.groups['No Freestyle']
lineset.group_negation = 'EXCLUSIVE'
else:
lineset.select_by_group = False
# Basic Ink linestyle:
if 'Ink' in bpy.data.linestyles:
lineset.linestyle = bpy.data.linestyles['Ink']
else:
lineset.linestyle.name = 'Ink'
self.cfg_linestyle(lineset.linestyle, thickness, color)
def cfg_linestyle(self, linestyle, thickness=INK_THICKNESS, color=INK_COLOR):
"""
Configure Freestyle line style (i.e. how the lines are drawn).
"""
# These are the only changeable parameters:
linestyle.color = color
linestyle.thickness = thickness
# The rest of this function just sets a common fixed style for "Lunatics!"
linestyle.alpha = 1.0
linestyle.thickness_position = 'CENTER'
linestyle.use_chaining = True
linestyle.chaining = 'PLAIN'
linestyle.use_same_object = True
linestyle.caps = 'ROUND'
# ADD THE ALONG-STROKE MODIFIER CURVE
# TODO: try using the .new(type=...) idiom to see if it works?
# This probably needs the scene context set?
# bpy.ops.scene.freestyle_thickness_modifier_add(type='ALONG_STROKE')
linestyle.thickness_modifiers.new(type='ALONG_STROKE', name='taper')
linestyle.thickness_modifiers['taper'].blend = 'MULTIPLY'
linestyle.thickness_modifiers['taper'].mapping = 'CURVE'
# These are defaults, so maybe unnecessary?
linestyle.thickness_modifiers['taper'].influence = 1.0
linestyle.thickness_modifiers['taper'].invert = False
linestyle.thickness_modifiers['taper'].value_min = 0.0
linestyle.thickness_modifiers['taper'].value_max = 1.0
# This API is awful, but what it has to do is to change the location of the first two
# points (which can't be removed), then add a third point. Then update to pick up the
# changes:
linestyle.thickness_modifiers['taper'].curve.curves[0].points[0].location = (0.0,0.0)
linestyle.thickness_modifiers['taper'].curve.curves[0].points[1].location = (0.5,1.0)
linestyle.thickness_modifiers['taper'].curve.curves[0].points.new(1.0,0.0)
linestyle.thickness_modifiers['taper'].curve.update()