# 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? 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) 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/' # @property # def fullname(self): # return self.designation + '-' + self.name # @property # def designation(self): # episode_code = "%2.2sE%2.2d" % (self.series_id, self.episode_id) # return episode_code + '-' + self.shortname # @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 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 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' 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' 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()