Page MenuHome

io_import_csv_animation.py

File Metadata

Author
Fazekas Laszlo (totoro)
Created
Aug 30 2016, 3:25 PM

io_import_csv_animation.py

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# based on the import_images_as_planes addon by Florian Meyer (tstscr), mont29, matali
# and import_gimp_image_to_scene addon by Daniel Salazar (ZanQdo)
bl_info = {
"name": "Import CSV animation",
"author": "Fazekas Laszlo (totoro)",
"version": (0, 0, 7),
"blender": (2, 76, 0),
"location": "File > Import > CSV animation",
"description": "Imports TVPaint's .CSV export format into textured planes",
"warning": "Heavy use of materials and textures (2D pixel graphics).",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.7/Py/"
"Scripts/Add_Mesh/Import_CSV_animation",
"category": "Import-Export",
}
"""
This script imports TVPaint CSV layered animation
"""
import bpy
from bpy.types import Operator
import mathutils
import os
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
IntProperty,
FloatProperty,
CollectionProperty,
)
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from bpy_extras.image_utils import load_image
# -----------------------------------------------------------------------------
# Cycles utils.
def get_input_nodes(node, nodes, links):
# Get all links going to node.
input_links = {lnk for lnk in links if lnk.to_node == node}
# Sort those links, get their input nodes (and avoid doubles!).
sorted_nodes = []
done_nodes = set()
for socket in node.inputs:
done_links = set()
for link in input_links:
nd = link.from_node
if nd in done_nodes:
# Node already treated!
done_links.add(link)
elif link.to_socket == socket:
sorted_nodes.append(nd)
done_links.add(link)
done_nodes.add(nd)
input_links -= done_links
return sorted_nodes
def auto_align_nodes(node_tree):
x_gap = 200
y_gap = 100
nodes = node_tree.nodes
links = node_tree.links
to_node = None
for node in nodes:
if node.type == 'OUTPUT_MATERIAL':
to_node = node
break
if not to_node:
return # Unlikely, but bette check anyway...
def align(to_node, nodes, links):
from_nodes = get_input_nodes(to_node, nodes, links)
for i, node in enumerate(from_nodes):
node.location.x = to_node.location.x - x_gap
node.location.y = to_node.location.y
node.location.y -= i * y_gap
node.location.y += (len(from_nodes)-1) * y_gap / (len(from_nodes))
j= len(node.outputs) - len(node.inputs)
if j > 0:
node.location.y -= (j * y_gap) / 3.0
if node.type == 'TEX_IMAGE':
node.location.y -= 1.7 * y_gap
align(node, nodes, links)
align(to_node, nodes, links)
def clean_node_tree(node_tree):
nodes = node_tree.nodes
for node in nodes:
if not node.type == 'OUTPUT_MATERIAL':
nodes.remove(node)
return node_tree.nodes[0]
# -----------------------------------------------------------------------------
# Operator
class IMPORT_OT_csv_animation(Operator, AddObjectHelper):
"""Import TVPaint CSV format into mesh plane images with animated visibility"""
bl_idname = "import.csv_animation"
bl_label = "Import .CSV file"
bl_options = {'REGISTER', 'UNDO'}
# -----------
# File props.
filepath = StringProperty(name="File Path", description=".CSV File path", maxlen= 1024,default= "")
filename_ext=".csv"
filter_glob= StringProperty(default="*.csv",options={'HIDDEN'})
# --------
# Options.
create_scene = BoolProperty(name="Create new scene", default=False,
description="Create a new scene for the animation")
add_camera = BoolProperty(name="Add orthographic camera", default=False,
description="Add camera with matching geometry")
comp_nodes = BoolProperty(name="Create Compositing nodes", default=False,
description="Create render layers and compositing nodes")
_sky_modes = (
('WHITE', "White", "Set white background (set horizon color to white)"),
('SKY', "Sky", "Set sky background"),
('TRANSPARENT', "Transparent", "Set transparent background"),
)
sky_mode = EnumProperty(name="Background Mode", default='WHITE', items=_sky_modes,
description="How the background set for Compositing nodes")
view_anim = BoolProperty(name="Animate Visibility", default=True,
description="Animate viewport visibility")
render_anim = BoolProperty(name="Animate Rendering", default=True,
description="Animate render visibility")
relative = BoolProperty(name="Relative paths", default=True,
description="Apply relative paths (if session has a .blend file)")
# -------------------
# Plane size options.
_size_modes = (
('ABSOLUTE', "Absolute", "Use absolute size"),
('DPI', "Dpi", "Use definition of the image as dots per inch"),
('DPBU', "Dots/BU", "Use definition of the image as dots per Blender Unit"),
)
size_mode = EnumProperty(name="Size Mode", default='ABSOLUTE', items=_size_modes,
description="How the size of the plane is computed")
height = FloatProperty(name="Height", description="Height of the created plane",
default=1.0, min=0.001, soft_min=0.001, subtype='DISTANCE', unit='LENGTH')
factor = FloatProperty(name="Definition", min=1.0, default=600.0,
description="Number of pixels per inch or Blender Unit")
z_step = FloatProperty(name="Z Step", description="Z offset between layers",
default=0.0, subtype='DISTANCE', unit='LENGTH')
# -------------------------
# Blender material options.
t = bpy.types.Material.bl_rna.properties["transparency_method"]
items = tuple((it.identifier, it.name, it.description) for it in t.enum_items)
transparency_method = EnumProperty(name="Transp. Method", description=t.description,
default="Z_TRANSPARENCY", items=items)
t = bpy.types.Material.bl_rna.properties["use_transparent_shadows"]
use_transparent_shadows = BoolProperty(name=t.name, default=True, description=t.description)
#-------------------------
# Cycles material options.
avoid_reflections = BoolProperty(name="Avoid reflections", default=True,
description="Add extra nodes to avoid color reflections")
# Variables
separator= ','
setlayer= 0
renlayer_last= None
arg_width= arg_height= arg_fps= arg_frame_count= arg_pixel_ar= arg_field_md= -1
def draw(self, context):
engine = context.scene.render.engine
layout = self.layout
box= layout.box()
box.label("Import options:",icon='FILTER')
row= box.row()
row.prop(self, "create_scene")
row= box.row()
row.prop(self, "add_camera")
row= box.row()
row.prop(self, "comp_nodes")
row= box.row()
row.label("Compositing nodes background:")
row.active= self.comp_nodes
row= box.row()
row.prop(self, "sky_mode", expand=True)
row.active= self.comp_nodes
row= box.row()
row.prop(self, "view_anim")
row= box.row()
row.prop(self, "render_anim")
row= box.row()
row.prop(self, "relative")
row.active = bpy.data.is_saved
box = layout.box()
if engine == 'BLENDER_RENDER':
box.label("Material Settings: (Blender)", icon='MATERIAL')
row = box.row()
row.prop(self, "transparency_method", expand=True)
box.prop(self, "use_transparent_shadows")
elif engine == 'CYCLES':
box = layout.box()
box.label("Material Settings: (Cycles)", icon='MATERIAL')
box.prop(self, 'avoid_reflections')
box = layout.box()
box.label("Plane dimensions:", icon='ARROW_LEFTRIGHT')
row = box.row()
row.prop(self, "size_mode", expand=True)
if self.size_mode == 'ABSOLUTE':
box.prop(self, "height")
else:
box.prop(self, "factor")
row= box.row()
row.prop(self, "z_step")
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
editmode= context.user_preferences.edit.use_enter_edit_mode
context.user_preferences.edit.use_enter_edit_mode= False
if context.active_object and context.active_object.mode == 'EDIT':
bpy.ops.object.mode_set(mode='OBJECT')
self.import_csv_animation(context)
context.user_preferences.edit.use_enter_edit_mode = editmode
return {'FINISHED'}
# Main...
def import_csv_animation(self, context):
engine= context.scene.render.engine
csv_file= self.filepath
try:
file= open(csv_file,"rU")
except IOError as e:
self.report({'ERROR'}, e.strerror)
return
img_folder= csv_file[:-4] + ".frames"
base_folder= os.path.dirname(csv_file)
scene= bpy.context.scene
step= 0
frame= 1
layers= []
for line in file.readlines():
if step == 0:
#the very first row, check for separator character
step= 1
if line.find(',') == -1 and line.find(';') >= 0:
self.separator= ';'
else:
keys= self.split_csv_line(line)
if (len(keys) > 0) and (len(keys[0]) > 0):
if step == 5:
if keys[0][:2] == "#0":
frame= frame + 1
bpy.context.window_manager.progress_update(frame)
lays= keys
del lays[0]
self.add_frame(context, layers, lays, frame)
elif step == 3 or step == 4:
if keys[0][0] == '#':
if step == 3:
step= 4
#add scene and extra elements
name= self.rootname(csv_file)[1]
if self.create_scene:
bpy.ops.scene.new(type='EMPTY')
scene= bpy.context.scene
scene.name= name
scene.world= bpy.data.worlds.new(name)
root= bpy.data.objects.new(name, None)
scene.objects.link(root)
self.camera_settings("Camera." + name,scene,root)
#build layer structure
for idx in range(0,len(keys) - 1):
name= "{:0>3d}".format(idx)
head= bpy.data.objects.new(name, None)
scene.objects.link(head)
head.parent= root
head.location= (0.0, 0.0,
float(idx) * -self.z_step)
layers.append( {
'parent':head, 'plane':None,
'name':"", 'density':1.0,
'visible':1, 'blending':"Color",
'lmask':None, 'lname':name,
'path':img_folder,
'ntab':[], 'ptab':[] } )
if keys[0] == "#Layers":
del keys[0]
for idx, name in enumerate(keys):
try:
newname= layers[idx]['lname']+"."+name
layers[idx]['lname']= newname
layers[idx]['parent'].name= newname
except IndexError:
pass
if keys[0] == "#Density":
del keys[0]
for idx, name in enumerate(keys):
layers[idx]['density']= float(name)
elif keys[0] == "#Visible":
del keys[0]
for idx, name in enumerate(keys):
layers[idx]['visible']= int(name)
elif keys[0] == "#Blending":
del keys[0]
for idx, name in enumerate(keys):
layers[idx]['blending']= name
elif keys[0] == "#Folder":
del keys[0]
for idx, name in enumerate(keys):
if os.path.isdir(name):
layers[idx]['path']= name
else:
prev= ""
while name != "":
name, path= self.rootname(name)
prev= os.path.join(path, prev)
if name[-7:] != ".layers":
path= os.path.join(base_folder, prev)
if os.path.isdir(path):
layers[idx]['path']= path
break
elif keys[0][:2] == "#0":
step= 5
bpy.context.window_manager.progress_begin(
1,self.arg_frame_count)
bpy.context.window_manager.progress_update(1)
lays= keys
del lays[0]
self.add_frame(context, layers, lays, 1)
elif step == 1:
step= 2
try:
self.arg_width= keys.index("Width")
except ValueError:
self.arg_width= -1
try:
self.arg_height= keys.index("Height")
except ValueError:
self.arg_height= -1
try:
self.arg_frame_count= keys.index("Frame Count")
except ValueError:
self.arg_frame_count= -1
try:
self.arg_fps= keys.index("Frame Rate")
except ValueError:
self.arg_fps= -1
try:
self.arg_pixel_ar= keys.index("Pixel Aspect Ratio")
except ValueError:
self.arg_pixel_ar= -1
try:
self.arg_field_md= keys.index("Field Mode")
except ValueError:
self.arg_field_md= -1
else:
step= 3
idx= len(keys)
if self.arg_width >= 0 and self.arg_width < idx:
self.arg_width= int(keys[self.arg_width])
else:
self.arg_width= 0
if self.arg_height >= 0 and self.arg_height < idx:
self.arg_height= int(keys[self.arg_height])
else:
self.arg_height= 0
if self.arg_frame_count >= 0 and self.arg_frame_count < idx:
self.arg_frame_count= int(keys[self.arg_frame_count])
else:
self.arg_frame_count= 1
if self.arg_fps >= 0 and self.arg_fps < idx:
self.arg_fps= float(keys[self.arg_fps])
else:
self.arg_fps= context.scene.render.fps
#pixel aspect ratio is pixel_height/pixel_width!
if self.arg_pixel_ar >= 0 and self.arg_pixel_ar < idx:
self.arg_pixel_ar= float(keys[self.arg_pixel_ar])
if self.arg_pixel_ar == 0:
self.arg_pixel_ar= 1.0
else:
self.arg_pixel_ar= 1.0
if self.arg_field_md >= 0 and self.arg_field_md < idx:
self.arg_field_md= keys[self.arg_field_md]
else:
self.arg_field_md= "Progressive"
if self.comp_nodes:
self.add_comp_nodes(scene,layers)
bpy.context.window_manager.progress_end()
def split_csv_line(self,line):
lst=[]
low= 0
l= len(line)
while low < l:
c= line[low]
if c == ' ' or c == '\t':
low+= 1
continue
if line[low] == '\"':
low += 1
name= ""
while low < l:
high= line.find('\"',low)
if high < 0:
return lst
if line[high+1] == '\"':
# double parentheses escape sequence
name+= line[low:high + 1]
low= high + 2
else:
break
if low >= l:
return lst
lst.append(name + line[low:high])
low= line.find(self.separator,high)
if low < 0:
low= l
else:
low+= 1
else:
high= line.find(self.separator,low)
if high < 0:
name= line[low:]
low= l
else:
name= line[low:high]
low= high + 1
lst.append(name.strip(" \t\r\n"))
return lst
def rootname(self, filename):
a= filename.rfind("\\")
b= filename.rfind("/")
if b > a:
a= b
b= filename.rfind(":")
if b > a:
a= b
return filename[:a], filename[(a + 1):]
def camera_settings(self,name, scene, root):
if self.create_scene:
if self.arg_width > 0 and self.arg_height > 0:
scene.render.resolution_x= self.arg_width
scene.render.resolution_y= self.arg_height
scene.render.resolution_percentage= 100
scene.render.fps= self.arg_fps
if self.arg_field_md == 'Upper First':
scene.render.use_fields= True
scene.render.field_order= 'ODD_FIRST'
elif self.arg_field_md == 'Lower First':
scene.render.use_fields= True
scene.render.field_order= 'EVEN_FIRST'
else:
scene.render.use_fields= False
if self.sky_mode == 'TRANSPARENT':
scene.render.alpha_mode = 'TRANSPARENT'
if self.arg_pixel_ar != 1.0:
scene.render.pixel_aspect_x= 1.0
scene.render.pixel_aspect_y= self.arg_pixel_ar
if self.add_camera:
bpy.ops.object.camera_add()
obj= bpy.context.active_object
camera= obj.data
obj.name= name
obj.location= scene.cursor_location
scene.camera= obj
camera.name= name
root.parent= obj
x= 2.0
if self.arg_width > 0 and self.arg_height > 0:
x,y= self.plane_dimensions(self.arg_width,self.arg_height)
if y > x:
x= y
camera.type= 'ORTHO'
camera.ortho_scale= x
obj.location.z += x
root.location = (0.0, 0.0, -x)
else:
root.location= scene.cursor_location
def add_comp_nodes(self,scene,layers):
md= len(layers)
if md < 1:
return
if self.renlayer_last != None:
if self.sky_mode == 'TRANSPARENT':
self.renlayer_last.use_sky= False
elif self.sky_mode == 'WHITE':
scene.world.horizon_color= (1.0,1.0,1.0)
bottom= -1
x= 0
#find the last layer containing images
for idx in range(0,md):
if layers[idx]['lmask'] != None:
bottom= idx
x= x + 100
if bottom == -1:
return
scene.use_nodes= True
tree= scene.node_tree
for idx in tree.nodes:
tree.nodes.remove(idx)
mnode= tree.nodes.new('CompositorNodeComposite')
mnode.name = 'Composite'
mnode.location = (x + 400,270)
chain= mnode.inputs[0]
for idx, layer in enumerate(layers):
if layer['lmask'] != None:
name= layer['parent'].name
rnode= tree.nodes.new('CompositorNodeRLayers')
rnode.location= (x, 100)
rnode.name = "R_" + name
rnode.scene= scene
rnode.layer= layer['lname']
if idx != bottom:
md= layer['blending']
if md == "Color":
mnode= tree.nodes.new('CompositorNodeAlphaOver')
else:
mnode= tree.nodes.new('CompositorNodeMixRGB')
mnode.use_alpha= True
# "Color"
# "Behind"
# "Erase"
# "Shade"
if md == "Light":
mnode.blend_type= 'LINEAR_LIGHT'
elif md == "Colorize":
mnode.blend_type= 'COLORIZE'
elif md == "Hue":
mnode.blend_type= 'HUE'
elif md == "Add":
mnode.blend_type= 'ADD'
elif md == "Sub":
mnode.blend_type= 'SUBTRACT'
elif md == "Multiply":
mnode.blend_type= 'MULTIPLY'
elif md == "Screen":
mnode.blend_type= 'SCREEN'
# "Replace"
# "Subtitute"
elif md == "Difference":
mnode.blend_type= 'DIFFERENCE'
elif md == "Divide":
mnode.blend_type= 'DIVIDE'
elif md == "Overlay":
mnode.blend_type= 'OVERLAY'
elif md == "Light2":
mnode.blend_type= 'DODGE'
elif md == "Shade2":
mnode.blend_type= 'BURN'
elif md == "HardLight":
mnode.blend_type= 'DODGE'
elif md == "SoftLight":
mnode.blend_type= 'SOFT_LIGHT'
# "GrainExtract"
# "GrainMerge"
elif md == "Sub2":
mnode.blend_type= 'SUBTRACT'
elif md == "Darken":
mnode.blend_type= 'DARKEN'
elif md == "Lighten":
mnode.blend_type= 'LIGHTEN'
elif md == "Saturation":
mnode.blend_type= 'SATURATION'
elif md == "Value":
mnode.blend_type= 'VALUE'
mnode.location = (x + 200, 350)
mnode.name = 'M_'+ name
tree.links.new(mnode.outputs[0], chain)
tree.links.new(rnode.outputs[0], mnode.inputs[2])
chain= mnode.inputs[1]
else:
#bottom layer, always overlay
tree.links.new(rnode.outputs[0], chain)
return
x= x - 200
def add_frame(self, context, layers, lays, frame):
for idx, name in enumerate(lays):
#only if visible
if layers[idx]['visible'] != 0:
if layers[idx]['name'] != name:
plane= layers[idx]['plane']
if plane != None:
if self.view_anim:
plane.hide= True
plane.keyframe_insert(data_path='hide',frame= frame)
if self.render_anim:
plane.hide_render= True
plane.keyframe_insert(data_path='hide_render',frame=frame)
if name == "":
plane= None
else:
#if the layer has the same picture
try:
was= layers[idx]['ntab'].index(name)
except ValueError:
was= -1
if was >= 0:
#reuse existing plane
plane= layers[idx]['ptab'][was]
else:
#load new plane
plane= self.import_image( context, name,
layers[idx] )
layers[idx]['ntab'].append(name)
layers[idx]['ptab'].append(plane)
if self.comp_nodes:
lmask= layers[idx]['lmask']
if lmask == None:
renlayer= self.renlayer_last
if renlayer == None:
#disable all renderlayers
for renlayer in bpy.context.scene.render.layers:
renlayer.use= False
else:
renlayer.use_sky= False
bpy.ops.scene.render_layer_add()
renlayer= bpy.context.scene.render.layers.active
renlayer.name= layers[idx]['lname']
renlayer.use_pass_vector= True
renlayer.use_edge_enhance= False
renlayer.use_strand= False
renlayer.use_halo= False
for i in range (0,20):
if i != self.setlayer:
renlayer.layers[i] = False
lmask= renlayer.layers
bpy.context.scene.layers[self.setlayer] = True
self.setlayer= self.setlayer + 1
self.renlayer_last= renlayer
layers[idx]['lname']= renlayer.name
layers[idx]['lmask']= lmask
plane.layers= lmask
if plane != None:
plane.parent= layers[idx]['parent']
if self.view_anim:
if frame > 1:
plane.hide= True
plane.keyframe_insert(data_path='hide',frame= 1)
plane.hide= False
plane.keyframe_insert(data_path='hide',frame= frame)
if self.render_anim:
if frame > 1:
plane.hide_render= True
plane.keyframe_insert(data_path='hide_render',frame= 1)
plane.hide_render= False
plane.keyframe_insert(data_path='hide_render',frame=frame)
layers[idx]['plane']= plane
layers[idx]['name']= name
#--------------------------------------------------------------------------
# Internal
def create_image_textures(self, context, image):
fn_full = os.path.normpath(bpy.path.abspath(image.filepath))
# look for texture with importsettings
for texture in bpy.data.textures:
if texture.type == 'IMAGE':
tex_img = texture.image
if (tex_img is not None) and (tex_img.library is None):
fn_tex_full = os.path.normpath(bpy.path.abspath(tex_img.filepath))
if fn_full == fn_tex_full:
self.set_texture_options(context, texture)
return texture
# if no texture is found: create one
name_compat = bpy.path.display_name_from_filepath(image.filepath)
texture = bpy.data.textures.new(name=name_compat, type='IMAGE')
texture.image = image
self.set_texture_options(context, texture)
return texture
def create_material_for_texture(self, texture, layer):
# look for material with the needed texture
for material in bpy.data.materials:
slot = material.texture_slots[0]
if slot and slot.texture == texture:
self.set_material_options(material, slot, layer)
return material
# if no material found: create one
name_compat = bpy.path.display_name_from_filepath(texture.image.filepath)
material = bpy.data.materials.new(name=name_compat)
slot = material.texture_slots.add()
slot.texture = texture
slot.texture_coords = 'UV'
self.set_material_options(material, slot, layer)
return material
def set_texture_options(self, context, texture):
texture.image_user.use_auto_refresh = True
def set_material_options(self, material, slot, layer):
material.alpha = 0.0
material.specular_alpha = 0.0
slot.use_map_alpha = True
if self.comp_nodes:
md= "Color"
if layer['blending'] == "Sub":
slot.invert= True
else:
md= layer['blending']
if md == "Add":
#it's a hdr trick
slot.alpha_factor = layer['density'] / 500.0
material.emit= 250.0
material.translucency= 1.0
elif md == "Screen":
#another hdr trick
slot.alpha_factor = layer['density'] / 500.0
material.emit= 500.0
else:
slot.alpha_factor = layer['density']
material.use_shadeless = True
if md == "Shade":
slot.use_map_color_diffuse = False
material.diffuse_color = (0, 0, 0)
material.use_transparency = True
material.transparency_method = self.transparency_method
material.use_transparent_shadows = self.use_transparent_shadows
#--------------------------------------------------------------------------
# Cycles
def create_cycles_texnode(self, context, node_tree, image):
tex_image = node_tree.nodes.new('ShaderNodeTexImage')
tex_image.image = image
tex_image.show_texture = True
self.set_texture_options(context, tex_image)
return tex_image
def create_cycles_material(self, context, image, layer):
name_compat = bpy.path.display_name_from_filepath(image.filepath)
material = bpy.data.materials.new(name=name_compat)
material.use_nodes = True
node_tree = material.node_tree
out_node = clean_node_tree(node_tree)
lightpath = None
bsdf_transparent = None
outpin= None
tex_image = self.create_cycles_texnode(context, node_tree, image)
inpin= tex_image.outputs[0]
md= layer['blending']
if self.comp_nodes:
if md == "Sub":
invert= node_tree.nodes.new('ShaderNodeInvert')
node_tree.links.new(invert.inputs[1],inpin)
inpin= invert.outputs[0]
if md != "Shade":
md= "Color"
if md == "Multiply":
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
mix_shader = node_tree.nodes.new('ShaderNodeMixShader')
outpin= mix_shader.outputs[0]
node_tree.links.new(bsdf_transparent.inputs[0], tex_image.outputs[0])
node_tree.links.new(mix_shader.inputs[0], tex_image.outputs[1])
node_tree.links.new(mix_shader.inputs[2], bsdf_transparent.outputs[0])
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
node_tree.links.new(mix_shader.inputs[1], bsdf_transparent.outputs[0])
elif md == "Add" or md == "Screen":
emission = node_tree.nodes.new('ShaderNodeEmission')
add_shader = node_tree.nodes.new('ShaderNodeAddShader')
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
outpin= add_shader.outputs[0]
node_tree.links.new(emission.inputs[0], tex_image.outputs[0])
node_tree.links.new(emission.inputs[1], tex_image.outputs[1])
node_tree.links.new(add_shader.inputs[0], bsdf_transparent.outputs[0])
node_tree.links.new(add_shader.inputs[1], emission.outputs[0])
elif md == "Shade":
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
mix_shader = node_tree.nodes.new('ShaderNodeMixShader')
outpin= mix_shader.outputs[0]
node_tree.links.new(mix_shader.inputs[0], tex_image.outputs[1])
node_tree.links.new(mix_shader.inputs[1], bsdf_transparent.outputs[0])
else:
emission = node_tree.nodes.new('ShaderNodeEmission')
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
mix_shader = node_tree.nodes.new('ShaderNodeMixShader')
outpin= mix_shader.outputs[0]
node_tree.links.new(mix_shader.inputs[0], tex_image.outputs[1])
node_tree.links.new(mix_shader.inputs[2], emission.outputs[0])
node_tree.links.new(mix_shader.inputs[1], bsdf_transparent.outputs[0])
node_tree.links.new(emission.inputs[0], inpin)
if outpin != None:
if self.avoid_reflections:
if lightpath == None:
lightpath = node_tree.nodes.new('ShaderNodeLightPath')
if bsdf_transparent == None:
bsdf_transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
mix_shader = node_tree.nodes.new('ShaderNodeMixShader')
node_tree.links.new(mix_shader.inputs[2], outpin)
outpin= mix_shader.outputs[0]
node_tree.links.new(mix_shader.inputs[0], lightpath.outputs[0])
node_tree.links.new(mix_shader.inputs[1], bsdf_transparent.outputs[0])
node_tree.links.new(out_node.inputs[0], outpin)
auto_align_nodes(node_tree)
return material
#--------------------------------------------------------------------------
def plane_dimensions(self,px,py):
if self.size_mode == 'ABSOLUTE':
y = self.height
x = px / py * y
elif self.size_mode == 'DPI':
fact = 1 / self.factor / context.scene.unit_settings.scale_length * 0.0254
x = px * fact
y = py * fact
else: # elif self.size_mode == 'DPBU'
fact = 1 / self.factor
x = px * fact
y = py * fact
y= y * self.arg_pixel_ar
return x,y
def create_image_plane(self, context, material):
engine = context.scene.render.engine
if engine in {'BLENDER_RENDER', 'BLENDER_GAME'}:
img = material.texture_slots[0].texture.image
elif engine == 'CYCLES':
nodes = material.node_tree.nodes
img = next((node.image for node in nodes if node.type == 'TEX_IMAGE'))
x,y = img.size
# can't load data
if x == 0 or y == 0:
x = y = 1
x,y = self.plane_dimensions(x,y)
bpy.ops.mesh.primitive_plane_add('INVOKE_REGION_WIN')
plane = context.scene.objects.active
# Why does mesh.primitive_plane_add leave the object in edit mode???
if plane.mode is not 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
plane.dimensions = x, y, 0.0
plane.data.name = plane.name = material.name
bpy.ops.object.transform_apply(scale=True)
plane.data.uv_textures.new()
plane.data.materials.append(material)
plane.data.uv_textures[0].data[0].image = img
plane.location= (0.0, 0.0, 0.0)
material.game_settings.use_backface_culling = False
material.game_settings.alpha_blend = 'ALPHA'
return plane
def import_image(self, context, file, layer):
engine = context.scene.render.engine
path= layer['path']
image= load_image(file,layer['path'])
if image == None:
self.report({'ERROR'}, "Bad or missing image file:"+os.path.join(layer['path'],file))
return None
image.use_alpha = True
image.alpha_mode = "STRAIGHT"
image.use_fields = False
if bpy.data.is_saved and self.relative:
try:
image.filepath= bpy.path.relpath(image.filepath)
except ValueError:
pass
if engine in {'BLENDER_RENDER', 'BLENDER_GAME'}:
texture = self.create_image_textures(context, image)
material = self.create_material_for_texture(texture, layer)
elif engine == 'CYCLES':
material = self.create_cycles_material(context, image, layer)
else:
return None
plane= self.create_image_plane(context, material)
context.scene.update()
return plane
# -----------------------------------------------------------------------------
# Register
def import_csv_button(self, context):
self.layout.operator(IMPORT_OT_csv_animation.bl_idname,
text="TVPaint CSV animation", icon='ANIM')
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(import_csv_button)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(import_csv_button)
if __name__ == "__main__":
register()

Event Timeline