FBX IO: Fix animation import when the newer opt-in FBX_KTIME is used #105019

Merged
Thomas Barlow merged 3 commits from Mysteryem/blender-addons:fbx_new_ktime into main 2023-12-01 01:00:26 +01:00
10 changed files with 344 additions and 58 deletions
Showing only changes of commit dc6dd6c76e - Show all commits

View File

@ -139,7 +139,7 @@ class MAX3DS_PT_import_include(bpy.types.Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = True
layout.use_property_decorate = False
sfile = context.space_data
operator = sfile.active_operator

View File

@ -5,7 +5,7 @@
bl_info = {
"name": "FBX format",
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
"version": (5, 10, 1),
"version": (5, 10, 2),
"blender": (4, 1, 0),
"location": "File > Import-Export",
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",

View File

@ -955,7 +955,6 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
elif isinstance(item, ShapeKey):
deform_values = shape_key_deforms.setdefault(item, [])
for fbxprop, channel_to_curve in fbx_curves.items():
assert(fbxprop == b'DeformPercent')
for channel, curve in channel_to_curve.items():
@ -969,8 +968,10 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
# Store the minimum and maximum shape key values, so that the shape key's slider range can be expanded
# if necessary after reading all animations.
deform_values.append(values.min())
deform_values.append(values.max())
if values.size:
deform_values = shape_key_deforms.setdefault(item, [])
deform_values.append(values.min())
deform_values.append(values.max())
elif isinstance(item, Camera):
for fbxprop, channel_to_curve in fbx_curves.items():

View File

@ -5,7 +5,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (4, 1, 34),
"version": (4, 1, 36),
'blender': (4, 1, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@ -48,6 +48,7 @@ from bpy.props import (StringProperty,
BoolProperty,
EnumProperty,
IntProperty,
FloatProperty,
CollectionProperty)
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper, ExportHelper
@ -155,6 +156,102 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
default=''
)
# gltfpack properties
export_use_gltfpack: BoolProperty(
name='Use Gltfpack',
description='Use gltfpack to simplify the mesh and/or compress its textures',
default=False,
)
export_gltfpack_tc: BoolProperty(
name='KTX2 Compression',
description='Convert all textures to KTX2 with BasisU supercompression',
default=True,
)
export_gltfpack_tq: IntProperty(
name='Texture Encoding Quality',
description='Texture encoding quality',
default=8,
min=1,
max=10,
)
export_gltfpack_si: FloatProperty(
name='Mesh Simplification Ratio',
description='Simplify meshes targeting triangle count ratio',
default=1.0,
min=0.0,
max=1.0,
)
export_gltfpack_sa: BoolProperty(
name='Aggressive Mesh Simplification',
description='Aggressively simplify to the target ratio disregarding quality',
default=False,
)
export_gltfpack_slb: BoolProperty(
name='Lock Mesh Border Vertices',
description='Lock border vertices during simplification to avoid gaps on connected meshes',
default=False,
)
export_gltfpack_vp: IntProperty(
name='Position Quantization',
description='Use N-bit quantization for positions',
default=14,
min=1,
max=16,
)
export_gltfpack_vt: IntProperty(
name='Texture Coordinate Quantization',
description='Use N-bit quantization for texture coordinates',
default=12,
min=1,
max=16,
)
export_gltfpack_vn: IntProperty(
name='Normal/Tangent Quantization',
description='Use N-bit quantization for normals and tangents',
default=8,
min=1,
max=16,
)
export_gltfpack_vc: IntProperty(
name='Vertex Color Quantization',
description='Use N-bit quantization for colors',
default=8,
min=1,
max=16,
)
export_gltfpack_vpi: EnumProperty(
name='Vertex Position Attributes',
description='Type to use for vertex position attributes',
items=(('Integer', 'Integer', 'Use integer attributes for positions'),
('Normalized', 'Normalized', 'Use normalized attributes for positions'),
('Floating-point', 'Floating-point', 'Use floating-point attributes for positions')),
default='Integer',
)
export_gltfpack_noq: BoolProperty(
name='Disable Quantization',
description='Disable quantization; produces much larger glTF files with no extensions',
default=True,
)
# TODO: some stuff in Textures
# TODO: Animations
# TODO: Scene
# TODO: some stuff in Miscellaneous
export_format: EnumProperty(
name='Format',
items=(('GLB', 'glTF Binary (.glb)',
@ -965,6 +1062,25 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
export_settings['gltf_hierarchy_full_collections'] = self.export_hierarchy_full_collections
# gltfpack stuff
export_settings['gltf_use_gltfpack'] = self.export_use_gltfpack
if self.export_use_gltfpack:
export_settings['gltf_gltfpack_tc'] = self.export_gltfpack_tc
export_settings['gltf_gltfpack_tq'] = self.export_gltfpack_tq
export_settings['gltf_gltfpack_si'] = self.export_gltfpack_si
export_settings['gltf_gltfpack_sa'] = self.export_gltfpack_sa
export_settings['gltf_gltfpack_slb'] = self.export_gltfpack_slb
export_settings['gltf_gltfpack_vp'] = self.export_gltfpack_vp
export_settings['gltf_gltfpack_vt'] = self.export_gltfpack_vt
export_settings['gltf_gltfpack_vn'] = self.export_gltfpack_vn
export_settings['gltf_gltfpack_vc'] = self.export_gltfpack_vc
export_settings['gltf_gltfpack_vpi'] = self.export_gltfpack_vpi
export_settings['gltf_gltfpack_noq'] = self.export_gltfpack_noq
export_settings['gltf_binary'] = bytearray()
export_settings['gltf_binaryfilename'] = (
path_to_uri(os.path.splitext(os.path.basename(self.filepath))[0] + '.bin')
@ -1034,6 +1150,56 @@ class GLTF_PT_export_main(bpy.types.Panel):
layout.prop(operator, 'will_save_settings')
class GLTF_PT_export_gltfpack(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "gltfpack"
bl_parent_id = "FILE_PT_operator"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
gltfpack_path = context.preferences.addons['io_scene_gltf2'].preferences.gltfpack_path_ui.strip()
if (gltfpack_path == ''): # gltfpack not setup in plugin preferences -> dont show any gltfpack relevant options in export dialog
return False;
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
sfile = context.space_data
operator = sfile.active_operator
col = layout.column(heading = "gltfpack", align = True)
col.prop(operator, 'export_use_gltfpack')
col = layout.column(heading = "Textures", align = True)
col.prop(operator, 'export_gltfpack_tc')
col.prop(operator, 'export_gltfpack_tq')
col = layout.column(heading = "Simplification", align = True)
col.prop(operator, 'export_gltfpack_si')
col.prop(operator, 'export_gltfpack_sa')
col.prop(operator, 'export_gltfpack_slb')
col = layout.column(heading = "Vertices", align = True)
col.prop(operator, 'export_gltfpack_vp')
col.prop(operator, 'export_gltfpack_vt')
col.prop(operator, 'export_gltfpack_vn')
col.prop(operator, 'export_gltfpack_vc')
col = layout.column(heading = "Vertex positions", align = True)
col.prop(operator, 'export_gltfpack_vpi')
#col = layout.column(heading = "Animations", align = True)
#col = layout.column(heading = "Scene", align = True)
col = layout.column(heading = "Miscellaneous", align = True)
col.prop(operator, 'export_gltfpack_noq')
class GLTF_PT_export_include(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
@ -1923,15 +2089,15 @@ class GLTF_AddonPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
settings_node_ui : bpy.props.BoolProperty(
default= False,
description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
)
default= False,
description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
)
KHR_materials_variants_ui : bpy.props.BoolProperty(
default= False,
description="Displays glTF UI to manage material variants",
update=gltf_variant_ui_update
)
)
animation_ui: bpy.props.BoolProperty(
default=False,
@ -1939,12 +2105,21 @@ class GLTF_AddonPreferences(bpy.types.AddonPreferences):
update=gltf_animation_ui_update
)
gltfpack_path_ui: bpy.props.StringProperty(
default="",
name="glTFpack file path",
description="Path to gltfpack binary",
subtype='FILE_PATH'
)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
row.prop(self, "KHR_materials_variants_ui", text="Material Variants")
row.prop(self, "animation_ui", text="Animation UI")
row = layout.row()
row.prop(self, "gltfpack_path_ui", text="Path to gltfpack")
def menu_func_import(self, context):
self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
@ -1973,6 +2148,7 @@ classes = (
GLTF_PT_export_animation_shapekeys,
GLTF_PT_export_animation_sampling,
GLTF_PT_export_animation_optimize,
GLTF_PT_export_gltfpack,
GLTF_PT_export_user_extensions,
ImportGLTF2,
GLTF_PT_import_user_extensions,
@ -1983,6 +2159,7 @@ classes = (
def register():
from .blender.com import gltf2_blender_ui as blender_ui
for c in classes:
bpy.utils.register_class(c)
# bpy.utils.register_module(__name__)

View File

@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: Apache-2.0
import os
import subprocess
import time
import bpy
@ -109,6 +111,70 @@ def __create_buffer(exporter, export_settings):
return buffer
def __postprocess_with_gltfpack(export_settings):
gltfpack_binary_file_path = bpy.context.preferences.addons['io_scene_gltf2'].preferences.gltfpack_path_ui
gltf_file_path = export_settings['gltf_filepath']
gltf_file_base = os.path.splitext(os.path.basename(gltf_file_path))[0]
gltf_file_extension = os.path.splitext(os.path.basename(gltf_file_path))[1]
gltf_file_directory = os.path.dirname(gltf_file_path)
gltf_output_file_directory = os.path.join(gltf_file_directory, "gltfpacked")
if (os.path.exists(gltf_output_file_directory) is False):
os.makedirs(gltf_output_file_directory)
gltf_input_file_path = gltf_file_path
gltf_output_file_path = os.path.join(gltf_output_file_directory, gltf_file_base + gltf_file_extension)
options = []
if (export_settings['gltf_gltfpack_tc']):
options.append("-tc")
if (export_settings['gltf_gltfpack_tq']):
options.append("-tq")
options.append(f"{export_settings['gltf_gltfpack_tq']}")
if (export_settings['gltf_gltfpack_si'] != 1.0):
options.append("-si")
options.append(f"{export_settings['gltf_gltfpack_si']}")
if (export_settings['gltf_gltfpack_sa']):
options.append("-sa")
if (export_settings['gltf_gltfpack_slb']):
options.append("-slb")
if (export_settings['gltf_gltfpack_noq']):
options.append("-noq")
else:
options.append("-vp")
options.append(f"{export_settings['gltf_gltfpack_vp']}")
options.append("-vt")
options.append(f"{export_settings['gltf_gltfpack_vt']}")
options.append("-vn")
options.append(f"{export_settings['gltf_gltfpack_vn']}")
options.append("-vc")
options.append(f"{export_settings['gltf_gltfpack_vc']}")
match export_settings['gltf_gltfpack_vpi']:
case "Integer":
options.append("-vpi")
case "Normalized":
options.append("-vpn")
case "Floating-point":
options.append("-vpf")
parameters = []
parameters.append("-i")
parameters.append(gltf_input_file_path)
parameters.append("-o")
parameters.append(gltf_output_file_path)
try:
subprocess.run([gltfpack_binary_file_path] + options + parameters, check=True)
except subprocess.CalledProcessError as e:
print_console('ERROR', "Calling gltfpack was not successful")
def __write_file(json, buffer, export_settings):
try:
@ -117,6 +183,9 @@ def __write_file(json, buffer, export_settings):
export_settings,
gltf2_blender_json.BlenderJSONEncoder,
buffer)
if (export_settings['gltf_use_gltfpack'] == True):
__postprocess_with_gltfpack(export_settings)
except AssertionError as e:
_, _, tb = sys.exc_info()
traceback.print_tb(tb) # Fixed format

View File

@ -446,13 +446,15 @@ class PrimitiveCreator:
vc_color_name = material_info['vc_info']['color']
elif material_info['vc_info']['color_type'] == "active":
# Get active (render) Vertex Color
vc_color_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
if self.blender_mesh.color_attributes.render_color_index != -1:
vc_color_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
if material_info['vc_info']['alpha_type'] == "name":
vc_alpha_name = material_info['vc_info']['alpha']
elif material_info['vc_info']['alpha_type'] == "active":
# Get active (render) Vertex Color
vc_alpha_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
if self.blender_mesh.color_attributes.render_color_index != -1:
vc_alpha_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name
if vc_color_name is not None:
@ -472,7 +474,7 @@ class PrimitiveCreator:
# We need to check if we need to add alpha
add_alpha = vc_alpha_name is not None
mat = get_material_from_idx(material_idx, self.materials, self.export_settings)
add_alpha = add_alpha and not (mat.blend_method is None or mat.blend_method == 'OPAQUE')
add_alpha = mat is not None and add_alpha and not (mat.blend_method is None or mat.blend_method == 'OPAQUE')
# Manage Vertex Color (RGB and Alpha if needed)
self.__manage_color_attribute(vc_color_name, vc_alpha_name if add_alpha else None)
else:
@ -950,6 +952,33 @@ class PrimitiveCreator:
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
additional_fields.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
if self.export_settings['gltf_loose_edges']:
additional_fields_edges = []
for i in range(max_index):
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
additional_fields_edges.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
new_dt = np.dtype(self.dots_edges.dtype.descr + additional_fields_edges)
dots_edges = np.zeros(self.dots_edges.shape, dtype=new_dt)
for f in self.dots_edges.dtype.names:
dots_edges[f] = self.dots_edges[f]
self.dots_edges = dots_edges
if self.export_settings['gltf_loose_points']:
additional_fields_points = []
for i in range(max_index):
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
additional_fields_points.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))
new_dt = np.dtype(self.dots_points.dtype.descr + additional_fields_points)
dots_points = np.zeros(self.dots_points.shape, dtype=new_dt)
for f in self.dots_points.dtype.names:
dots_points[f] = self.dots_points[f]
self.dots_points = dots_points
# Keep the existing custom attribute
# Data will be exported twice, one for COLOR_O, one for the custom attribute
new_dt = np.dtype(self.dots.dtype.descr + additional_fields)
@ -964,7 +993,7 @@ class PrimitiveCreator:
self.dots['COLOR_0' +str(i)] = data_dots[:, i]
if self.export_settings['gltf_loose_edges'] and attr.domain == "POINT":
self.dots_edges['COLOR_0' + str(i)] = data_dots_edges[:, i]
if self.export_settings['gltf_loose_points'] and attr['blender_domain'] == "POINT":
if self.export_settings['gltf_loose_points'] and attr.domain == "POINT":
self.dots_points['COLOR_0' + str(i)] = data_dots_points[:, i]
# Add COLOR_0 in attribute list

View File

@ -545,6 +545,12 @@ def get_base_material(material_idx, materials, export_settings):
mat,
export_settings
)
if material is None:
# If no material, the mesh can still have vertex color
# So, retrieving it
material_info["vc_info"] = {"color_type": "active", "alpha_type": "active"}
return material, material_info
def get_all_textures(idx=0):

View File

@ -6,8 +6,8 @@
bl_info = {
"name": "Bsurfaces GPL Edition",
"author": "Eclectiel, Vladimir Spivak (cwolf3d)",
"version": (1, 8, 1),
"blender": (2, 80, 0),
"version": (1, 8, 2),
"blender": (4, 0, 0),
"location": "View3D EditMode > Sidebar > Edit Tab",
"description": "Modeling and retopology tool",
"doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/bsurfaces.html",
@ -3539,11 +3539,10 @@ class MESH_OT_SURFSK_init(Operator):
global_mesh_object = mesh_object.name
bpy.context.scene.bsurfaces.SURFSK_mesh = bpy.data.objects[global_mesh_object]
bpy.context.scene.tool_settings.snap_elements = {'FACE'}
bpy.context.scene.tool_settings.snap_elements = {'FACE_PROJECT'}
bpy.context.scene.tool_settings.use_snap = True
bpy.context.scene.tool_settings.use_snap_self = False
bpy.context.scene.tool_settings.use_snap_align_rotation = True
bpy.context.scene.tool_settings.use_snap_project = True
bpy.context.scene.tool_settings.use_snap_rotate = True
bpy.context.scene.tool_settings.use_snap_scale = True

View File

@ -16,7 +16,7 @@ bl_info = {
'author': 'Robin Hohnsbeen',
'description': 'Bake vector displacement brushes easily from a plane',
'blender': (3, 5, 0),
'version': (1, 0, 2),
'version': (1, 0, 3),
'location': 'Sculpt Mode: View3D > Sidebar > Tool Tab',
'warning': '',
'category': 'Baking',
@ -88,7 +88,6 @@ class PT_VDMBaker(bpy.types.Panel):
layout.use_property_split = True
layout.use_property_decorate = False
layout.operator(create_sculpt_plane.bl_idname, icon='ADD')
layout.separator()
@ -187,7 +186,6 @@ class create_vdm_brush(bpy.types.Operator):
return {'CANCELLED'}
vdm_plane = context.active_object
addon_data = get_addon_data()
new_brush_name = addon_data.draft_brush_name
reference_brush_name = addon_data.draft_brush_name
@ -214,6 +212,7 @@ class create_vdm_brush(bpy.types.Operator):
bpy.ops.object.mode_set(mode='OBJECT')
vdm_bake_material = bakematerial.get_vdm_bake_material()
vdm_texture_image = None
try:
# Prepare baking
scene.render.engine = 'CYCLES'

View File

@ -12,56 +12,62 @@ def get_vdm_bake_material():
material: Baking material
"""
material_name = 'VDM_baking_material'
if material_name not in bpy.data.materials:
new_material = bpy.data.materials.new(name=material_name)
if material_name in bpy.data.materials:
# Recreate material every time to ensure it is unchanged by the user which could lead to issues.
# I would like to keep it directly after bake though so people could look
# at how it is made, if they are interested.
bpy.data.materials.remove(bpy.data.materials[material_name])
new_material.use_nodes = True
nodes = new_material.node_tree.nodes
nodes.remove(nodes['Principled BSDF'])
material_output = nodes['Material Output']
new_material = bpy.data.materials.new(name=material_name)
# Create relevant nodes
combine_node = nodes.new('ShaderNodeCombineXYZ')
new_material.use_nodes = True
nodes = new_material.node_tree.nodes
principled_node = next(n for n in new_material.node_tree.nodes if n.type == "BSDF_PRINCIPLED")
nodes.remove(principled_node)
material_output = next(n for n in new_material.node_tree.nodes if n.type == "OUTPUT_MATERIAL")
separate_node1 = nodes.new('ShaderNodeSeparateXYZ')
separate_node2 = nodes.new('ShaderNodeSeparateXYZ')
# Create relevant nodes
combine_node = nodes.new('ShaderNodeCombineXYZ')
vector_subtract_node = nodes.new('ShaderNodeVectorMath')
vector_subtract_node.operation = 'SUBTRACT'
separate_node1 = nodes.new('ShaderNodeSeparateXYZ')
separate_node2 = nodes.new('ShaderNodeSeparateXYZ')
vector_multiply_node = nodes.new('ShaderNodeVectorMath')
vector_multiply_node.operation = 'MULTIPLY'
vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0]
vector_subtract_node = nodes.new('ShaderNodeVectorMath')
vector_subtract_node.operation = 'SUBTRACT'
vector_add_node = nodes.new('ShaderNodeVectorMath')
vector_add_node.operation = 'ADD'
vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5]
vector_multiply_node = nodes.new('ShaderNodeVectorMath')
vector_multiply_node.operation = 'MULTIPLY'
vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0]
tex_coord_node = nodes.new('ShaderNodeTexCoord')
vector_add_node = nodes.new('ShaderNodeVectorMath')
vector_add_node.operation = 'ADD'
vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5]
image_node = nodes.new('ShaderNodeTexImage')
image_node.name = "VDMTexture"
tex_coord_node = nodes.new('ShaderNodeTexCoord')
# Connect nodes
tree = new_material.node_tree
tree.links.new(combine_node.outputs[0], material_output.inputs[0])
image_node = nodes.new('ShaderNodeTexImage')
image_node.name = "VDMTexture"
tree.links.new(separate_node1.outputs[0], combine_node.inputs[0])
tree.links.new(separate_node1.outputs[1], combine_node.inputs[1])
# Connect nodes
tree = new_material.node_tree
tree.links.new(combine_node.outputs[0], material_output.inputs[0])
tree.links.new(
vector_subtract_node.outputs[0], separate_node1.inputs[0])
tree.links.new(separate_node1.outputs[0], combine_node.inputs[0])
tree.links.new(separate_node1.outputs[1], combine_node.inputs[1])
tree.links.new(
vector_multiply_node.outputs[0], vector_subtract_node.inputs[1])
tree.links.new(
vector_subtract_node.outputs[0], separate_node1.inputs[0])
tree.links.new(
vector_add_node.outputs[0], vector_multiply_node.inputs[0])
tree.links.new(
vector_multiply_node.outputs[0], vector_subtract_node.inputs[1])
tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0])
tree.links.new(
tex_coord_node.outputs[3], vector_subtract_node.inputs[0])
tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0])
tree.links.new(separate_node2.outputs[2], combine_node.inputs[2])
tree.links.new(
vector_add_node.outputs[0], vector_multiply_node.inputs[0])
tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0])
tree.links.new(
tex_coord_node.outputs[3], vector_subtract_node.inputs[0])
tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0])
tree.links.new(separate_node2.outputs[2], combine_node.inputs[2])
return bpy.data.materials[material_name]