Конечно логичнее спрашивать у разработчиков скрипта. Спрашивал, второй месяц нет ответа, а моя тема потихоньку принимает вид блога и собирает просмотры.
Имею очень поверхностное понимание в программировании.
Блендеровские скрипты пишутся на питоне, по этому здесь создаю эту тему.
Суть вороса…
Проект War Thunder CDK заточен под 3DsMax.
В комплекте идет куча плагинов для Максов, разных версий и разрядности.
Как я смог понять. Плагин для Макса невозможно самостоятельно переделать для блендер.
Для этого нужно знать как работает Максов плагин.
Но в War Thunder CDK рядом с папкой набитой Максовыми плагинами для разных вверсий, есть папка blender_plugins.
В папке редмишка
Copy whole this folder to blender (“…\Blender Foundation\Blender\2.71\scripts\addons\io_export_dag” )
1. В параметрах текстуры (custom properties) должен быть указан ее тип (параметр - type, значение из dagorShaders.cfg - “diffuse”, “CUBE environment”, “normalmap” и т.п.
2. В параметрах материала (custom properties) казываются необходимые параметры для его настройки, согласно dagorShaders.cfg
Сам скрипт
# http://live.warthunder.com/assistance_agreement/ bl_info = {"name": "Export to Dagor Engine", "description": "Script exports mesh to Dagor Engine", "author": "http://live.warthunder.com/assistance_agreement/", "version": (1, 0), "blender": (2, 68, 0), "location": "File > Export", "wiki_url": "", "tracker_url": "http://live.warthunder.com/assistance_agreement/", "category": "Import-Export"} # Standard library imports import sys import os # Blender imports import bpy from bpy_extras.io_utils import ImportHelper from bpy.props import IntProperty, StringProperty from bpy.types import AddonPreferences, Operator, Panel import mathutils, math, struct from mathutils import * import subprocess SUPPORTED_TYPES = ('MESH')#,'CURVE','EMPTY','TEXT','CAMERA','LAMP') class BLKWriter: file = None ident = 0 def getIdent(self): s = "" for x in range(0, self.ident): s += " " return s def writeHeader(self): self.file.write('HEAD:t="BLK(float)"\n') return def open( self, filename ): self.file = open(filename, "wt") self.writeHeader() return def close( self ): self.file.flush() self.file.close() def beginBlock(self, name): self.file.write('\n%s%s{\n' % ( self.getIdent(), name ) ) self.ident += 1 return def endBlock(self ): self.ident -= 1 if self.ident < 0 : #assert self.ident = 0 self.file.write('%s}\n' % self.getIdent() ) return def writeStr(self, name, str): self.file.write('%s%s:t="%s"\n' % ( self.getIdent(), name, str ) ) return def writeInt(self, name, val): self.file.write('%s%s:i=%d\n' % ( self.getIdent(), name, val ) ) return def writeFloat(self, name, val): self.file.write('%s%s:r=%g\n' % ( self.getIdent(), name, val ) ) return def writeBool(self, name, val): self.file.write('%s%s:b=%s\n' % ( self.getIdent(), name, "yes" if val else "no" ) ) return def writeTM(self, name, m): self.file.write('%s%s:m=[[%f, %f, %f] [%f, %f, %f] [%f, %f, %f] [%f, %f, %f]]\n' % ( self.getIdent(), name, m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], m[3][2] ) ) #self.file.write('%s%s:m=[[%f, %f, %f] [%f, %f, %f] [%f, %f, %f] [%f, %f, %f]]\n' % ( self.getIdent(), name, m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[0][3], m[1][3], m[2][3] ) ) #self.file.write('%s%s:m=[[%f, %f, %f] [%f, %f, %f] [%f, %f, %f] [%f, %f, %f]]\n' % ( self.getIdent(), name, m[0][0], m[2][0], m[1][0], m[0][1], m[2][1], m[1][1], m[0][2], m[2][2], m[1][2], m[0][3], m[2][3], m[1][3] ) ) #self.file.write('%s\n' % m ) return class MaterialExp: def __init__(self): self.index = -1 self.mat = None class MeshExp: def __init__(self): self.vertexes = list() self.faces = list() self.uv = list() self.normals = list() self.normals_per_face_ver = list() self.normals_ver = list() self.normals_faces = list() def addNormal( self, normal ): for i,n in enumerate(self.normals_ver): if n == normal: return i index = len( self.normals_ver ) self.normals_ver.append( normal ) return index class NodeExp: def __init__(self): self.name = "" self.id = -1 self.parent_id = -1 #self.parent = None self.child = list() self.mesh = None self.tm = None self.wtm = None self.renderable = True class DagExporter(Operator, ImportHelper): bl_idname = "dag.exporter" bl_label = "Dagor Engine (.dag)" bl_options = {'REGISTER', 'UNDO'} file = None nodes = None last_index = 0 writer = None materials = {} def __init__(self): self.writer = BLKWriter() self.last_index = 0 self.nodes = list() self.materials = {} # ImportHelper mixin class uses this filename_ext = ".dag" filter_glob = StringProperty(default="*.dag", options={'HIDDEN'}) def createRootNode( self ): node = NodeExp() node.id = 65535; node.parent_id = -1; node.tm = Matrix() node.tm.identity() node.wtm = Matrix() node.wtm.identity() node.renderable=False self.nodes.append( node ) return node def writeNode(self, node ): self.writer.beginBlock("node") self.writer.writeStr( "name", node.name ) self.writer.writeInt( "id", node.id ) self.writer.writeInt( "parent_id", node.parent_id ) self.writer.writeBool( "renderable", node.renderable ) self.writer.writeBool( "castshadow", False ) self.writer.writeBool( "rcvshadow", False ) self.writer.writeTM( "tm", node.tm ) if node.mesh == None: self.writer.endBlock() return self.writer.beginBlock("vert_data") for i,v in enumerate( node.mesh.vertexes): self.writer.writeStr( "vert", str( '[%d] x:%f y:%f z:%f' % (i, v.x, v.y, v.z ) ) ); self.writer.endBlock() self.writer.beginBlock("face_data") for i,f in enumerate(node.mesh.faces): self.writer.writeStr( "face", str( '[%d] i1:%d i2:%d i3:%d smgrp:%d mat: %d' % (i, f[ 0 ], f[ 1 ], f[ 2 ], 0, f[ 3 ] ) ) ); self.writer.endBlock() self.writer.beginBlock("normal_vert") for i,n in enumerate(node.mesh.normals_ver): self.writer.writeStr( "nvert", str( '[%d] x:%f y:%f z:%f' % (i, n[ 0 ], n[ 1 ], n[ 2 ] ) ) ); self.writer.endBlock() self.writer.beginBlock("normal_faces") for i,f in enumerate(node.mesh.normals_faces): self.writer.writeStr( "nface", str( '[%d] i1:%d i2:%d i3:%d' % (i, f[ 0 ], f[ 1 ], f[ 2 ] ) ) ); self.writer.endBlock() if len(node.mesh.uv) > 0: self.writer.beginBlock("uv_data") for channel in node.mesh.uv: #self.writer.writeStr( str('%s\n' % channel ) ); self.writer.beginBlock("channel") self.writer.beginBlock("uv_vert") for i,f in enumerate(channel): self.writer.writeStr( "uv", str( '[%d] x:%f y:%f' % (i, f[0],1.0-f[1] ) ) ); #self.writer.writeStr( str('%s\n' % ch ) ); self.writer.endBlock() self.writer.endBlock() self.writer.endBlock() self.writer.endBlock() return def dumpObjectData( self, obj, node, scene ): #mesh_calc_normals() if obj.type != 'MESH': try: me = obj.to_mesh(scene, True, "PREVIEW") except: me = None is_tmp_mesh = True else: me = obj.data if not me.tessfaces and me.polygons: me.calc_tessface() is_tmp_mesh = False has_uv = bool(me.tessface_uv_textures) has_vcol = bool(me.tessface_vertex_colors) if has_uv == False : self.file.write("Object hasn't UV mapping\n" ) # export data if me is not None: if len( obj.material_slots ) == 0: print( "Need assign material to object \"%s\"\n" % obj.name ) return me.calc_normals_split() materials_indexes = [ list() for p in range( len( obj.material_slots ) )] for i,slot in enumerate( obj.material_slots ): self.file.write(" Material %s\n" % slot ) #self.file.write(" Mat %s\n" % slot.material.name ) if slot.material.name not in self.materials: mat_exp = MaterialExp() mat_exp.index = len(self.materials) mat_exp.mat = slot.material self.materials[ slot.material.name ] = mat_exp val = self.materials.get( slot.material.name ) if val != None: materials_indexes[i] = val.index else: materials_indexes[i] = -1 #print("Mesh object:", me.name) self.file.write(str('mesh %s\n' % me) ) meshExp = MeshExp() for v in me.vertices: #self.file.write(str('vert %s %s %s\n' % (v.co.x, v.co.y, v.co.z) ) ) meshExp.vertexes.append( v.co ) meshExp.normals.append( -v.normal ); meshExp.uv = [ list() for p in range( len( me.tessface_uv_textures ) )] for i, f in enumerate( me.tessfaces ): #self.file.write(str('vf %s %s %s\n' % (f.vertices[0], f.vertices[1], f.vertices[2] ) ) ) useSmooth = f.use_smooth self.file.write(str('mat %d\n' % f.material_index ) ) fexp = [] fexp.append( f.vertices[0] ) fexp.append( f.vertices[2] ) fexp.append( f.vertices[1] ) fexp.append( materials_indexes[ f.material_index ] ); meshExp.faces.append( fexp ) norm = [] if useSmooth: norm.append( -meshExp.normals[ f.vertices[0] ] ) norm.append( -meshExp.normals[ f.vertices[2] ] ) norm.append( -meshExp.normals[ f.vertices[1] ] ) else: norm.append( f.normal ) norm.append( f.normal ) norm.append( f.normal ) meshExp.normals_per_face_ver.append( norm ) norm_face_ind = [] for nv in norm: norm_face_ind.append( meshExp.addNormal( nv ) ) meshExp.normals_faces.append( norm_face_ind ) for n, layer in enumerate( me.tessface_uv_textures ): uv = layer.data[i].uv #self.file.write(str('uv_face[%d] (%f,%f), (%f,%f), (%f,%f)\n' % (n, uv[0][0], uv[0][1],uv[1][0], uv[1][0],uv[2][0],uv[2][1]) ) ) meshExp.uv[ n ].append( uv[0] ) meshExp.uv[ n ].append( uv[2] ) meshExp.uv[ n ].append( uv[1] ) if len(f.vertices) == 4: # if quad meshExp.uv[ n ].append( uv[2] ) meshExp.uv[ n ].append( uv[0] ) meshExp.uv[ n ].append( uv[3] ) self.file.write(str('smth %s\n' % f.use_smooth ) ) if len(f.vertices) == 4: # if quad self.file.write(str('vf %s %s %s\n' % (f.vertices[2], f.vertices[3], f.vertices[0] ) ) ) fexp = [] fexp.append( f.vertices[2] ) fexp.append( f.vertices[0] ) fexp.append( f.vertices[3] ) fexp.append( materials_indexes[f.material_index] ); meshExp.faces.append( fexp ) #self.file.write(str('uv_face (%f,%f), (%f,%f), (%f,%f)\n' % (uv[2][0], uv[2][1],uv[3][0], uv[3][0],uv[0][0],uv[0][1]) ) ) norm = [] if useSmooth: norm.append( -meshExp.normals[ f.vertices[2] ] ) norm.append( -meshExp.normals[ f.vertices[0] ] ) norm.append( -meshExp.normals[ f.vertices[3] ] ) else: norm.append( f.normal ) norm.append( f.normal ) norm.append( f.normal ) meshExp.normals_per_face_ver.append( norm ) norm_face_ind = [] for nv in norm: norm_face_ind.append( meshExp.addNormal( nv ) ) meshExp.normals_faces.append( norm_face_ind ) node.mesh = meshExp if is_tmp_mesh: bpy.data.meshes.remove(me) return def recursObjects( self, objs, parent, scene): objects = (ob for ob in objs if ob.is_visible(scene) and ob.type in SUPPORTED_TYPES) for o in objects: self.last_index += 1; node = NodeExp() node.name = o.name node.id = self.last_index; node.parent_id = parent.id mParentInv = parent.wtm.copy() mParentInv.invert() node.wtm = o.matrix_world; node.tm = mParentInv * node.wtm; #-------------------------------------- if parent.parent_id == -1 : tmp_r = node.tm[1].copy() node.tm[1] = node.tm[2].copy() node.tm[2] = tmp_r.copy() node.tm.transpose() #self.file.write( str( '(%s) mat: %s\n' % ( node.name, node.tm ) ) ) #-------------------------------------- #if o.name == 'gear_c' : # self.file.write( str( 'mat: %s\n' % o.matrix_world ) ) parent.child.append( node ) print('Exporting %s, parent%s\n' % ( node.name, parent.name) ) self.nodes.append( node ) self.dumpObjectData( o, node, scene ) self.recursObjects( o.children, node, scene ) return def writeParam(self,params): for key in params: if key[0] == "_RNA_UI": continue self.writer.beginBlock( "param") self.writer.writeStr( "name", key[0] ) self.writer.writeStr( "value", key[1] ) self.writer.endBlock() def writeMaterail(self, mat, id): self.writer.beginBlock( "material") self.writer.writeInt( "id", id ) self.writer.writeStr( "name", mat.name ) self.writer.writeStr( "diffuse", str( 'r:%f g:%f b:%f' % (mat.diffuse_color.r, mat.diffuse_color.g, mat.diffuse_color.b)) ) self.writer.writeStr( "specular", str( 'r:%f g:%f b:%f' % (mat.specular_color.r, mat.specular_color.g, mat.specular_color.b)) ) for mtex_slot in mat.texture_slots: if mtex_slot: #self.file.write( "test: %s" % str( mtex_slot.uv_layer ) ) if hasattr(mtex_slot.texture , 'image'): self.writer.beginBlock( "texture" ) self.writer.writeStr( "filepath", mtex_slot.texture.image.filepath ) self.writeParam( mtex_slot.texture.items() ) self.writer.endBlock() self.writeParam( mat.items() ) self.writer.endBlock() def writeBlk(self, filename): self.writer.open( filename ) for n in self.nodes: self.writeNode( n ) for key, value in self.materials.items(): self.writeMaterail( value.mat, value.index ) print(key, value) self.writer.close() return def execute(self, context): filepath = self.filepath filepath = bpy.path.ensure_ext(filepath, self.filename_ext) print("Blender Version {}.{}.{}".format(bpy.app.version[0], bpy.app.version[1], bpy.app.version[2])) print("Filepath: {}".format(filepath)) scene = context.scene self.file = open(filepath+".log", "wt") objs = (ob for ob in scene.objects if ob.is_visible(scene) and ob.type in SUPPORTED_TYPES and ob.parent == None) par_node = self.createRootNode() self.recursObjects( objs, par_node, scene ) path_blk = filepath + ".blk" print( 'path_blk=%s' % path_blk ); self.writeBlk( path_blk ) prev_path = os.getcwd() new_path = os.path.dirname( os.path.realpath(__file__) ) os.chdir( new_path ) print(new_path); try: return_code = subprocess.call(["blk2dag.exe", path_blk, filepath ]) print( 'ret=%d' % return_code ); except IOError: print( 'convert failed' ); os.chdir( prev_path ) self.file.flush() self.file.close() return {'FINISHED'} def menu_func_import(self, context): self.layout.operator(DagExporter.bl_idname,text=DagExporter.bl_label) def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func_import) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func_import) if __name__ == "__main__": register()
Сам dagorShaders.cfg присутствует в папках к почти каждому Максовому плагину с небольшими вариациями.
tex1_name=“diffuse”
tex2_name=“CUBE environment”
tex3_name=“normalmap”
tex4_name=“parallax (height field)”
tex5_name=“glossmap (specular mask+shininess)”
tex6_name=“emission tex (unsupported!)”
tex7_name=“lightmap”
tex8_name=“detail tex”
lighting=enum(none lightmap vltmap)
real_two_sided=enum(yes no)
scene_emission=real optional
emission=real optional
relief = real optional
atest=text optional
reflection_multiplier=int optional
;Aces
atest=int optional
zbias=real optional
atest=text optional
zbias=real optional
atest=int optional
normal_offset=real optional
impostor=int optional
min_reflect=real optional
max_reflect=real optional
fresnel_power=real optional
opacity=real optional
atest=int optional
ablend=int optional
//тайлы детейлов:
detail1_tile_u=real optional
detail1_tile_v=real optional
detail2_tile_u=real optional
detail2_tile_v=real optional
detail1_scroll_u=text optional
detail1_scroll_v=text optional
detail2_scroll_u=text optional
detail2_scroll_v=text optional
//тайл атласа оверлея:
atlas_tile_u=real optional
atlas_tile_v=real optional
atlas_first_tile=real optional
atlas_last_tile=real optional
//тайл маски:
mask_tile_u=real optional
mask_tile_v=real optional
//скроллить или нет маску от положения (может быть 0 или 1):
mask_scroll_u=text optional
mask_scroll_v=text optional
//рандомизованная гамма (“от” .. “до”):
mask_gamma_start=real optional
mask_gamma_end=real optional
//2nd detail recolour
detail2_colored=text optional
//горизонтальное проецирование второго детейла сверху на объект:
top_projection=text optional
//углы проецирования:
top_projection_from=real optional
top_projection_to=real optional
//уровень влияния оверлая на второй детрейл при проецировании:
top_projection_detail2_modulate=real optional
//покраска 1го или 2го детейла текстурой покраски
use_painting=real optional
painting_line=real optional
paint1stdetail=real optional
//смешиваем нормали детейлов, где преобладает 2й детейл
detail2_combined_normal=real optional
min_reflect=real optional
max_reflect=real optional
atest=int optional
use_decal_tex=int optional
lit_with_gun_fire=int optional
not_use_fxaa=int optional
is_outer_cockpit=int optional
min_reflect=real optional
max_reflect=real optional
fresnel_power=real optional
sun_reflection_power=real optional
atest=int optional
use_decal_tex=int optional
texcoord_mulp=int optional
texcoord_anim=int optional
lit_with_gun_fire=int optional
//покраска модели текстурой покраски
use_painting=real optional
painting_line=real optional
min_reflect=real optional
max_reflect=real optional
fresnel_power=real optional
zbias=real optional
slope_zbias=real optional
opacity=real optional
rain_droplets=real optional
detail_scale_x=real optional
detail_scale_y=real optional
detail_distinctness=real optional
camo_gamma_mul=real optional
atest=int optional
atest=text optional
lit_with_gun_fire=int optional
atest=text optional
zbias=real optional
slope_zbias=real optional
texcoord_anim=int optional
atest=int optional
atest=int optional
water_move_period=real optional
water_dir_u=real optional
water_dir_v=real optional
water_x_scale=real optional
water_y_scale=real optional
water_min_reflect=real optional
water_max_reflect=real optional
water_height=real optional
;water_height_speed=real optional
;water_tex_rotate=real optional
big=int optional
should_use_detail=real optional
bake_landmesh_combined=int optional
render_landmesh_combined=int optional
vertical_from_geom_norm=int optional
detail1_tile_u=real optional
detail1_tile_v=real optional
detail2_tile_u=real optional
detail2_tile_v=real optional
reflection_multiplier=real optional
detail1_tile_u=real optional
detail1_tile_v=real optional
detail2_tile_u=real optional
detail2_tile_v=real optional
opacity=real optional
//тайлы детейлов:
detail1_tile_u=real optional
detail1_tile_v=real optional
detail2_tile_u=real optional
detail2_tile_v=real optional
detail1_scroll_u=text optional
detail1_scroll_v=text optional
detail2_scroll_u=text optional
detail2_scroll_v=text optional
//тайл атласа оверлея:
atlas_tile_u=real optional
atlas_tile_v=real optional
atlas_first_tile=real optional
atlas_last_tile=real optional
//тайл маски:
mask_tile_u=real optional
mask_tile_v=real optional
//скроллить или нет маску от положения (может быть 0 или 1):
mask_scroll_u=text optional
mask_scroll_v=text optional
//рандомизованная гамма (“от” .. “до”):
mask_gamma_start=real optional
mask_gamma_end=real optional
//трава
draw_grass=text optional
//2nd detail recolour
detail2_colored=text optional
//горизонтальное проецирование второго детейла сверху на объект:
top_projection=text optional
//углы проецирования:
top_projection_from=real optional
top_projection_to=real optional
//уровень влияния оверлая на второй детрейл при проецировании:
top_projection_detail2_modulate=real optional
//покраска 1го детейла текстурой покраски
use_painting=real optional
painting_line=real optional
paint1stdetail=real optional
//смешиваем нормали детейлов, где преобладает 2й детейл
detail2_combined_normal=real optional
atest=text optional
detail_scale_u=real optional
detail_scale_v=real optional
//покраска модели текстурой покраски
use_painting=real optional
painting_line=real optional
atest=text optional
zbias=real optional
slope_zbias=real optional
specularStr=real optional
specularPow=real optional
relief=real optional
detail1_tile_u=real optional
detail1_tile_v=real optional
detail2_tile_u=real optional
detail2_tile_v=real optional
В Максе плагин добавляет “менюшки” в интерфейс.
Если не соотвецтвуют требования подготовки модели к экспорту, появляется окошко с причиной ошибки.
Требования 4.
У модели должна быть UV развертка.
У модели должен быть “материал”. Выбирается нужный “матерал/шейдер” с помощью плагиного интерфейса. Иначе вообще ниего не отображается.
У модели должна быть текстура. Выбирается в настройках “материала/шейдера” выбранного с помощью плагина. Иначе модель получится черная.
У модели должны быть “группы сглаживания”, иначе будет глюковато отображаться.
По идеи, при экспорте из Блендер, ритуал должен быть похожий.
Но незнаю как запустить скрипт правильно. Модель получается всегда черная.
В интерфейс Блендер, есть пункт custom properties.
Как понимаю, используя эту менюшку, можно “привязать” шейдер и материал из Максового. В редмишке про это и говориться. Но не могу понять “что” именно нужно скрипту.
Посоветуйте хоть какой нибудь учебник по питону “внятный” для моего уровня.
Пробовал читать разные учебники, содержание вроде одинаковое, но объяснения больше запутывают.
Посоветуйте хоть что нибудь.