1"""Material export classes. 2 3 @author Michael Reimpell 4""" 5# Copyright (C) 2005 Michael Reimpell 6# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2.1 of the License, or (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 21# epydoc doc format 22__docformat__ = "javadoc en" 23 24import base 25from base import * 26 27import os 28import shutil 29import Blender 30 31def clamp(value): 32 if value < 0.0: 33 value = 0.0 34 elif value > 1.0: 35 value = 1.0 36 return value 37 38class MaterialInterface: 39 def getName(self): 40 """Returns the material name. 41 42 @return Material name. 43 """ 44 return 45 def write(self, f): 46 """Write material script entry. 47 48 All used texture files are registered at the MaterialManager. 49 50 @param f Material script file object to write into. 51 """ 52 return 53 54class DefaultMaterial(MaterialInterface): 55 def __init__(self, manager, name): 56 self.manager = manager 57 self.name = name 58 return 59 def getName(self): 60 return self.name 61 def write(self, f): 62 f.write("material %s\n" % self.getName()) 63 f.write("{\n") 64 self.writeTechniques(f) 65 f.write("}\n") 66 return 67 def writeTechniques(self, f): 68 f.write(indent(1) + "technique\n" + indent(1) + "{\n") 69 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 70 # empty pass 71 f.write(indent(2) + "}\n") # pass 72 f.write(indent(1) + "}\n") # technique 73 return 74 75class GameEngineMaterial(DefaultMaterial): 76 def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient): 77 self.mesh = blenderMesh 78 self.face = blenderFace 79 self.colouredAmbient = colouredAmbient 80 # check if a Blender material is assigned 81 try: 82 blenderMaterial = self.mesh.materials[self.face.mat] 83 except: 84 blenderMaterial = None 85 self.material = blenderMaterial 86 DefaultMaterial.__init__(self, manager, self._createName()) 87 return 88 def writeTechniques(self, f): 89 mat = self.material 90 if (not(mat) 91 and not(self.mesh.vertexColors) 92 and not(self.mesh.vertexUV or self.mesh.faceUV)): 93 # default material 94 DefaultMaterial.writeTechniques(self, f) 95 else: 96 # default material 97 # SOLID, white, no specular 98 f.write(indent(1)+"technique\n") 99 f.write(indent(1)+"{\n") 100 f.write(indent(2)+"pass\n") 101 f.write(indent(2)+"{\n") 102 # ambient 103 # (not used in Blender's game engine) 104 if mat: 105 if (not(mat.mode & Blender.Material.Modes["TEXFACE"]) 106 and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"]) 107 and (self.colouredAmbient)): 108 ambientRGBList = mat.rgbCol 109 else: 110 ambientRGBList = [1.0, 1.0, 1.0] 111 # ambient <- amb * ambient RGB 112 ambR = clamp(mat.amb * ambientRGBList[0]) 113 ambG = clamp(mat.amb * ambientRGBList[1]) 114 ambB = clamp(mat.amb * ambientRGBList[2]) 115 ##f.write(indent(3)+"ambient %f %f %f\n" % (ambR, ambG, ambB)) 116 # diffuse 117 # (Blender's game engine uses vertex colours 118 # instead of diffuse colour. 119 # 120 # diffuse is defined as 121 # (mat->r, mat->g, mat->b)*(mat->emit + mat->ref) 122 # but it's not used.) 123 if self.mesh.vertexColors: 124 #TODO: Broken in Blender 2.36. 125 # Blender does not handle "texface" mesh with vertex colours 126 f.write(indent(3)+"diffuse vertexcolour\n") 127 elif mat: 128 if (not(mat.mode & Blender.Material.Modes["TEXFACE"]) 129 and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"])): 130 # diffuse <- rgbCol 131 diffR = clamp(mat.rgbCol[0]) 132 diffG = clamp(mat.rgbCol[1]) 133 diffB = clamp(mat.rgbCol[2]) 134 f.write(indent(3)+"diffuse %f %f %f\n" % (diffR, diffG, diffB)) 135 elif (mat.mode & Blender.Material.Modes["VCOL_PAINT"]): 136 f.write(indent(3)+"diffuse vertexcolour\n") 137 if mat: 138 # specular <- spec * specCol, hard/4.0 139 specR = clamp(mat.spec * mat.specCol[0]) 140 specG = clamp(mat.spec * mat.specCol[1]) 141 specB = clamp(mat.spec * mat.specCol[2]) 142 specShine = mat.hard/4.0 143 f.write(indent(3)+"specular %f %f %f %f\n" % (specR, specG, specB, specShine)) 144 # emissive 145 # (not used in Blender's game engine) 146 if(not(mat.mode & Blender.Material.Modes["TEXFACE"]) 147 and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"])): 148 # emissive <-emit * rgbCol 149 emR = clamp(mat.emit * mat.rgbCol[0]) 150 emG = clamp(mat.emit * mat.rgbCol[1]) 151 emB = clamp(mat.emit * mat.rgbCol[2]) 152 ##f.write(indent(3)+"emissive %f %f %f\n" % (emR, emG, emB)) 153 if self.mesh.faceUV: 154 # mesh has texture values, resp. tface data 155 # scene_blend <- transp 156 if (self.face.transp == Blender.Mesh.FaceTranspModes["ALPHA"]): 157 f.write(indent(3)+"scene_blend alpha_blend \n") 158 elif (self.face.transp == Blender.NMesh.FaceTranspModes["ADD"]): 159 f.write(indent(3)+"scene_blend add\n") 160 # cull_hardware/cull_software 161 if (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']): 162 f.write(indent(3) + "cull_hardware none\n") 163 f.write(indent(3) + "cull_software none\n") 164 # shading 165 # (Blender's game engine is initialized with glShadeModel(GL_FLAT)) 166 ##f.write(indent(3) + "shading flat\n") 167 # texture 168 if (self.face.mode & Blender.Mesh.FaceModes['TEX']) and (self.face.image): 169 f.write(indent(3)+"texture_unit\n") 170 f.write(indent(3)+"{\n") 171 f.write(indent(4)+"texture %s\n" % self.manager.registerTextureFile(self.face.image.filename)) 172 f.write(indent(3)+"}\n") # texture_unit 173 f.write(indent(2)+"}\n") # pass 174 f.write(indent(1)+"}\n") # technique 175 return 176 # private 177 def _createName(self): 178 """Create unique material name. 179 180 The name consists of several parts: 181 <OL> 182 <LI>rendering material name/</LI> 183 <LI>blend mode (ALPHA, ADD, SOLID)</LI> 184 <LI>/TEX</LI> 185 <LI>/texture file name</LI> 186 <LI>/VertCol</LI> 187 <LI>/TWOSIDE></LI> 188 </OL> 189 """ 190 materialName = '' 191 # nonempty rendering material? 192 if self.material: 193 materialName += self.material.getName() + '/' 194 # blend mode 195 if self.mesh.faceUV and (self.face.transp == Blender.Mesh.FaceTranspModes['ALPHA']): 196 materialName += 'ALPHA' 197 elif self.mesh.faceUV and (self.face.transp == Blender.Mesh.FaceTranspModes['ADD']): 198 materialName += 'ADD' 199 else: 200 materialName += 'SOLID' 201 # TEX face mode and texture? 202 if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TEX']): 203 materialName += '/TEX' 204 if self.face.image: 205 materialName += '/' + PathName(self.face.image.filename).basename() 206 # vertex colours? 207 if self.mesh.vertexColors: 208 materialName += '/VertCol' 209 # two sided? 210 if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']): 211 materialName += '/TWOSIDE' 212 return materialName 213 214class RenderingMaterial(DefaultMaterial): 215 def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient): 216 self.mesh = blenderMesh 217 self.face = blenderFace 218 self.colouredAmbient = colouredAmbient 219 self.key = 0 220 self.mTexUVCol = None 221 self.mTexUVNor = None 222 self.mTexUVCsp = None 223 self.material = None 224 try: 225 self.material = self.mesh.materials[self.face.mat] 226 except IndexError: 227 Log.getSingleton().logWarning("Can't get material for mesh \"%s\"! Are any materials linked to object instead of linked to mesh?" % self.mesh.name) 228 if self.material: 229 self._generateKey() 230 DefaultMaterial.__init__(self, manager, self._createName()) 231 else: 232 DefaultMaterial.__init__(self, manager, 'None') 233 return 234 def writeTechniques(self, f): 235 # parse material 236 if self.key: 237 if self.TECHNIQUES.has_key(self.key): 238 techniques = self.TECHNIQUES[self.key] 239 techniques(self, f) 240 else: 241 # default 242 self.writeColours(f) 243 else: 244 # Halo or empty material 245 DefaultMaterial('').writeTechniques(f) 246 return 247 def writeColours(self, f): 248 # receive_shadows 249 self.writeReceiveShadows(f, 1) 250 f.write(indent(1) + "technique\n" + indent(1) + "{\n") 251 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 252 # ambient 253 if (self.colouredAmbient): 254 col = self.material.getRGBCol() 255 else: 256 col = [1.0, 1.0, 1.0] 257 self.writeAmbient(f, col, 3) 258 # diffuse 259 self.writeDiffuse(f, self.material.rgbCol, 3) 260 # specular 261 self.writeSpecular(f, 3) 262 # emissive 263 self.writeEmissive(f, self.material.rgbCol, 3) 264 # blend mode 265 self.writeSceneBlend(f,3) 266 # options 267 self.writeCommonOptions(f, 3) 268 # texture units 269 self.writeDiffuseTexture(f, 3) 270 f.write(indent(2) + "}\n") # pass 271 f.write(indent(1) + "}\n") # technique 272 return 273 def writeTexFace(self, f): 274 # preconditions: TEXFACE set 275 # 276 # Note that an additional Col texture replaces the 277 # TEXFACE texture instead of blend over according to alpha. 278 # 279 # (amb+emit)textureCol + diffuseLight*ref*textureCol + specular 280 # 281 imageFileName = None 282 if self.mTexUVCol: 283 # COL MTex replaces UV/Image Editor texture 284 imageFileName = self.manager.registerTextureFile(self.mTexUVCol.tex.getImage().getFilename()) 285 elif (self.mesh.faceUV and self.face.image): 286 # UV/Image Editor texture 287 imageFileName = self.manager.registerTextureFile(self.face.image.filename) 288 289 self.writeReceiveShadows(f, 1) 290 f.write(indent(1) + "technique\n" + indent(1) + "{\n") 291 col = [1.0, 1.0, 1.0] 292 # texture pass 293 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 294 self.writeAmbient(f, col, 3) 295 self.writeDiffuse(f, col, 3) 296 if not(imageFileName): 297 self.writeSpecular(f, 3) 298 self.writeEmissive(f, col, 3) 299 self.writeSceneBlend(f,3) 300 self.writeCommonOptions(f, 3) 301 if imageFileName: 302 f.write(indent(3) + "texture_unit\n") 303 f.write(indent(3) + "{\n") 304 f.write(indent(4) + "texture %s\n" % imageFileName) 305 if self.mTexUVCol: 306 self.writeTextureAddressMode(f, self.mTexUVCol, 4) 307 self.writeTextureFiltering(f, self.mTexUVCol, 4) 308 # multiply with factors 309 f.write(indent(4) + "colour_op modulate\n") 310 f.write(indent(3) + "}\n") # texture_unit 311 f.write(indent(2) + "}\n") # texture pass 312 # specular pass 313 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 314 f.write(indent(3) + "ambient 0.0 0.0 0.0\n") 315 f.write(indent(3) + "diffuse 0.0 0.0 0.0\n") 316 self.writeSpecular(f, 3) 317 f.write(indent(3) + "scene_blend add\n") 318 hasAlpha = 0 319 if (self.material.getAlpha() < 1.0): 320 hasAlpha = 1 321 else: 322 for mtex in self.material.getTextures(): 323 if mtex: 324 if ((mtex.tex.type == Blender.Texture.Types['IMAGE']) 325 and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])): 326 hasAlpha = 1 327 if (hasAlpha): 328 f.write(indent(3) + "depth_write off\n") 329 self.writeCommonOptions(f, 3) 330 f.write(indent(2) + "}\n") # pass 331 f.write(indent(1) + "}\n") # technique 332 return 333 def writeVertexColours(self, f): 334 # preconditions: VCOL_PAINT set 335 # 336 # ambient = Amb*White resp. Amb*VCol if "Coloured Ambient" 337 # diffuse = Ref*VCol 338 # specular = Spec*SpeRGB, Hard/4.0 339 # emissive = Emit*VCol 340 # alpha = A 341 # 342 # Best match without vertex shader: 343 # ambient = Amb*white 344 # diffuse = Ref*VCol 345 # specular = Spec*SpeRGB, Hard/4.0 346 # emissive = black 347 # alpha = 1 348 # 349 self.writeReceiveShadows(f, 1) 350 f.write(indent(1) + "technique\n" + indent(1) + "{\n") 351 if (self.material.mode & Blender.Material.Modes['SHADELESS']): 352 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 353 self.writeCommonOptions(f,3) 354 f.write(indent(2) + "}\n") 355 else: 356 # vertex colour pass 357 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 358 f.write(indent(3) + "ambient 0.0 0.0 0.0\n") 359 f.write(indent(3) + "diffuse vertexcolour\n") 360 self.writeCommonOptions(f, 3) 361 f.write(indent(2) + "}\n") # vertex colour pass 362 363 # factor pass 364 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 365 f.write(indent(3) + "ambient 0.0 0.0 0.0\n") 366 ref = self.material.getRef() 367 f.write(indent(3) + "diffuse %f %f %f\n" % (ref, ref, ref)) 368 f.write(indent(3) + "scene_blend modulate\n") 369 self.writeCommonOptions(f, 3) 370 f.write(indent(2) + "}\n") # factor pass 371 372 # ambient and specular pass 373 f.write(indent(2) + "pass\n" + indent(2) + "{\n") 374 self.writeAmbient(f, [1.0, 1.0, 1.0], 3) 375 f.write(indent(3) + "diffuse 0.0 0.0 0.0\n") 376 self.writeSpecular(f, 3) 377 f.write(indent(3) + "scene_blend add\n") 378 self.writeCommonOptions(f, 3) 379 f.write(indent(2) + "}\n") # specular pass 380 381 f.write(indent(1) + "}\n") # technique 382 return 383 def writeNormalMap(self, f): 384 # preconditions COL and NOR textures 385 colImage = self.manager.registerTextureFile(self.mTexUVCol.tex.image.filename) 386 norImage = self.manager.registerTextureFile(self.mTexUVNor.tex.image.filename) 387 f.write(""" technique 388 { 389 pass 390 { 391 ambient 1 1 1 392 diffuse 0 0 0 393 specular 0 0 0 0 394 vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture 395 { 396 param_named_auto worldViewProj worldviewproj_matrix 397 param_named_auto ambient ambient_light_colour 398 } 399 } 400 pass 401 { 402 ambient 0 0 0 403 iteration once_per_light 404 scene_blend add 405 vertex_program_ref Examples/BumpMapVPSpecular 406 { 407 param_named_auto lightPosition light_position_object_space 0 408 param_named_auto eyePosition camera_position_object_space 409 param_named_auto worldViewProj worldviewproj_matrix 410 } 411 fragment_program_ref Examples/BumpMapFPSpecular 412 { 413 param_named_auto lightDiffuse light_diffuse_colour 0 414 param_named_auto lightSpecular light_specular_colour 0 415 } 416 texture_unit 417 { 418 texture %s 419 colour_op replace 420 } 421 texture_unit 422 { 423 cubic_texture nm.png combinedUVW 424 tex_coord_set 1 425 tex_address_mode clamp 426 } 427 texture_unit 428 { 429 cubic_texture nm.png combinedUVW 430 tex_coord_set 2 431 tex_address_mode clamp 432 } 433 } 434 pass 435 { 436 lighting off 437 vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture 438 { 439 param_named_auto worldViewProj worldviewproj_matrix 440 param_named ambient float4 1 1 1 1 441 } 442 scene_blend dest_colour zero 443 texture_unit 444 { 445 texture %s 446 } 447 } 448 } 449 technique 450 { 451 pass 452 { 453 ambient 1 1 1 454 diffuse 0 0 0 455 specular 0 0 0 0 456 vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture 457 { 458 param_named_auto worldViewProj worldviewproj_matrix 459 param_named_auto ambient ambient_light_colour 460 } 461 } 462 pass 463 { 464 ambient 0 0 0 465 iteration once_per_light 466 scene_blend add 467 vertex_program_ref Examples/BumpMapVP 468 { 469 param_named_auto lightPosition light_position_object_space 0 470 param_named_auto eyePosition camera_position_object_space 471 param_named_auto worldViewProj worldviewproj_matrix 472 } 473 texture_unit 474 { 475 texture %s 476 colour_op replace 477 } 478 texture_unit 479 { 480 cubic_texture nm.png combinedUVW 481 tex_coord_set 1 482 tex_address_mode clamp 483 colour_op_ex dotproduct src_texture src_current 484 colour_op_multipass_fallback dest_colour zero 485 } 486 } 487 pass 488 { 489 lighting off 490 vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture 491 { 492 param_named_auto worldViewProj worldviewproj_matrix 493 param_named ambient float4 1 1 1 1 494 } 495 scene_blend dest_colour zero 496 texture_unit 497 { 498 texture %s 499 } 500 } 501 } 502""" % (norImage, colImage, norImage, colImage)) 503 return 504 def writeReceiveShadows(self, f, indentation=0): 505 if (self.material.mode & Blender.Material.Modes["SHADOW"]): 506 f.write(indent(indentation)+"receive_shadows on\n") 507 else: 508 f.write(indent(indentation)+"receive_shadows off\n") 509 return 510 def writeAmbient(self, f, col, indentation=0): 511 # ambient <- amb * ambient RGB 512 ambR = clamp(self.material.getAmb() * col[0]) 513 ambG = clamp(self.material.getAmb() * col[1]) 514 ambB = clamp(self.material.getAmb() * col[2]) 515 if len(col) < 4: 516 alpha = self.material.getAlpha() 517 else: 518 alpha = col[3] 519 f.write(indent(indentation)+"ambient %f %f %f %f\n" % (ambR, ambG, ambB, alpha)) 520 return 521 def writeDiffuse(self, f, col, indentation=0): 522 # diffuse = reflectivity*colour 523 diffR = clamp(col[0] * self.material.getRef()) 524 diffG = clamp(col[1] * self.material.getRef()) 525 diffB = clamp(col[2] * self.material.getRef()) 526 if len(col) < 4: 527 alpha = self.material.getAlpha() 528 else: 529 alpha = col[3] 530 f.write(indent(indentation)+"diffuse %f %f %f %f\n" % (diffR, diffG, diffB, alpha)) 531 return 532 def writeSpecular(self, f, indentation=0): 533 # specular <- spec * specCol, hard/4.0 534 specR = clamp(self.material.getSpec() * self.material.getSpecCol()[0]) 535 specG = clamp(self.material.getSpec() * self.material.getSpecCol()[1]) 536 specB = clamp(self.material.getSpec() * self.material.getSpecCol()[2]) 537 specShine = self.material.getHardness()/4.0 538 alpha = self.material.getAlpha() 539 f.write(indent(indentation)+"specular %f %f %f %f %f\n" % (specR, specG, specB, alpha, specShine)) 540 return 541 def writeEmissive(self, f, col, indentation=0): 542 # emissive <-emit * rgbCol 543 emR = clamp(self.material.getEmit() * col[0]) 544 emG = clamp(self.material.getEmit() * col[1]) 545 emB = clamp(self.material.getEmit() * col[2]) 546 if len(col) < 4: 547 alpha = self.material.getAlpha() 548 else: 549 alpha = col[3] 550 f.write(indent(indentation)+"emissive %f %f %f %f\n" % (emR, emG, emB, alpha)) 551 return 552 def writeSceneBlend(self, f, indentation=0): 553 hasAlpha = 0 554 if (self.material.getAlpha() < 1.0): 555 hasAlpha = 1 556 else: 557 for mtex in self.material.getTextures(): 558 if mtex: 559 if ((mtex.tex.type == Blender.Texture.Types['IMAGE']) 560 and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])): 561 hasAlpha = 1 562 if (hasAlpha): 563 f.write(indent(indentation) + "scene_blend alpha_blend\n") 564 f.write(indent(indentation) + "depth_write off\n") 565 return 566 def writeCommonOptions(self, f, indentation=0): 567 # Shadeless, ZInvert, NoMist, Env 568 # depth_func <- ZINVERT; ENV 569 if (self.material.mode & Blender.Material.Modes['ENV']): 570 f.write(indent(indentation)+"depth_func always_fail\n") 571 elif (self.material.mode & Blender.Material.Modes['ZINVERT']): 572 f.write(indent(indentation)+"depth_func greater_equal\n") 573 # twoside 574 if self.mesh.faceUV and (self.face.mode & Blender.NMesh.FaceModes['TWOSIDE']): 575 f.write(indent(3) + "cull_hardware none\n") 576 f.write(indent(3) + "cull_software none\n") 577 # lighting <- SHADELESS 578 if (self.material.mode & Blender.Material.Modes['SHADELESS']): 579 f.write(indent(indentation)+"lighting off\n") 580 # fog_override <- NOMIST 581 if (self.material.mode & Blender.Material.Modes['NOMIST']): 582 f.write(indent(indentation)+"fog_override true\n") 583 return 584 def writeDiffuseTexture(self, f, indentation = 0): 585 if self.mTexUVCol: 586 f.write(indent(indentation)+"texture_unit\n") 587 f.write(indent(indentation)+"{\n") 588 f.write(indent(indentation + 1) + "texture %s\n" % self.manager.registerTextureFile(self.mTexUVCol.tex.getImage().getFilename())) 589 self.writeTextureAddressMode(f, self.mTexUVCol, indentation + 1) 590 self.writeTextureFiltering(f, self.mTexUVCol, indentation + 1) 591 self.writeTextureColourOp(f, self.mTexUVCol, indentation + 1) 592 f.write(indent(indentation)+"}\n") # texture_unit 593 return 594 def writeTextureAddressMode(self, f, blenderMTex, indentation = 0): 595 # tex_address_mode inside texture_unit 596 # 597 # EXTEND | clamp 598 # CLIP | 599 # CLIPCUBE | 600 # REPEAT | wrap 601 # 602 if (blenderMTex.tex.extend & Blender.Texture.ExtendModes['REPEAT']): 603 f.write(indent(indentation) + "tex_address_mode wrap\n") 604 elif (blenderMTex.tex.extend & Blender.Texture.ExtendModes['EXTEND']): 605 f.write(indent(indentation) + "tex_address_mode clamp\n") 606 return 607 def writeTextureFiltering(self, f, blenderMTex, indentation = 0): 608 # filtering inside texture_unit 609 # 610 # InterPol | MidMap | filtering 611 # ---------+--------+---------- 612 # yes | yes | trilinear 613 # yes | no | linear linear none 614 # no | yes | bilinear 615 # no | no | none 616 # 617 if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['INTERPOL']): 618 if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']): 619 f.write(indent(indentation) + "filtering trilinear\n") 620 else: 621 f.write(indent(indentation) + "filtering linear linear none\n") 622 else: 623 if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']): 624 f.write(indent(indentation) + "filtering bilinear\n") 625 else: 626 f.write(indent(indentation) + "filtering none\n") 627 return 628 def writeTextureColourOp(self, f, blenderMTex, indentation = 0): 629 # colour_op inside texture_unit 630 if ((blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['USEALPHA']) 631 and not(blenderMTex.mapto & Blender.Texture.MapTo['ALPHA'])): 632 f.write(indent(indentation) + "colour_op alpha_blend\n") 633 return 634 # private 635 def _createName(self): 636 # must be called after _generateKey() 637 materialName = self.material.getName() 638 # two sided? 639 if self.mesh.faceUV and (self.face.mode & Blender.NMesh.FaceModes['TWOSIDE']): 640 materialName += '/TWOSIDE' 641 # use UV/Image Editor texture? 642 if ((self.key & self.TEXFACE) and not(self.key & self.IMAGEUVCOL)): 643 materialName += '/TEXFACE' 644 if self.face.image: 645 materialName += '/' + PathName(self.face.image.filename).basename() 646 return materialName 647 def _generateKey(self): 648 # generates key and populates mTex fields 649 if self.material: 650 if not(self.material.mode & Blender.Material.Modes['HALO']): 651 self.key |= self.NONHALO 652 if (self.material.mode & Blender.Material.Modes['VCOL_LIGHT']): 653 self.key |= self.VCOLLIGHT 654 if (self.material.mode & Blender.Material.Modes['VCOL_PAINT']): 655 self.key |= self.VCOLPAINT 656 if (self.material.mode & Blender.Material.Modes['TEXFACE']): 657 self.key |= self.TEXFACE 658 # textures 659 for mtex in self.material.getTextures(): 660 if mtex: 661 if (mtex.tex.type == Blender.Texture.Types['IMAGE']): 662 if (mtex.texco & Blender.Texture.TexCo['UV']): 663 if (mtex.mapto & Blender.Texture.MapTo['COL']): 664 self.key |= self.IMAGEUVCOL 665 self.mTexUVCol = mtex 666 if (mtex.mapto & Blender.Texture.MapTo['NOR']): 667 # Check "Normal Map" image option 668 if (mtex.tex.imageFlags & 2048): 669 self.key |= self.IMAGEUVNOR 670 self.mTexUVNor = mtex 671 # else bumpmap 672 if (mtex.mapto & Blender.Texture.MapTo['CSP']): 673 self.key |= self.IMAGEUVCSP 674 self.mTexUVCsp = mtex 675 return 676 NONHALO = 1 677 VCOLLIGHT = 2 678 VCOLPAINT = 4 679 TEXFACE = 8 680 IMAGEUVCOL = 16 681 IMAGEUVNOR = 32 682 IMAGEUVCSP = 64 683 # material techniques export methods 684 TECHNIQUES = { 685 NONHALO|IMAGEUVCOL : writeColours, 686 NONHALO|IMAGEUVCOL|IMAGEUVCSP : writeColours, 687 NONHALO|TEXFACE : writeTexFace, 688 NONHALO|TEXFACE|VCOLLIGHT : writeTexFace, 689 NONHALO|TEXFACE|IMAGEUVCOL : writeTexFace, 690 NONHALO|TEXFACE|IMAGEUVNOR : writeTexFace, 691 NONHALO|TEXFACE|IMAGEUVCSP : writeTexFace, 692 NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCOL : writeTexFace, 693 NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVNOR : writeTexFace, 694 NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCSP : writeTexFace, 695 NONHALO|TEXFACE|IMAGEUVCOL|IMAGEUVCSP : writeTexFace, 696 NONHALO|TEXFACE|IMAGEUVNOR|IMAGEUVCSP : writeTexFace, 697 NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCOL|IMAGEUVCSP : writeTexFace, 698 NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVNOR|IMAGEUVCSP : writeTexFace, 699 NONHALO|VCOLPAINT : writeVertexColours, 700 NONHALO|VCOLPAINT|VCOLLIGHT : writeVertexColours, 701 NONHALO|IMAGEUVCOL|IMAGEUVNOR : writeNormalMap, 702 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT : writeNormalMap, 703 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT : writeNormalMap, 704 NONHALO|IMAGEUVCOL|IMAGEUVNOR|TEXFACE : writeNormalMap, 705 NONHALO|IMAGEUVCOL|IMAGEUVNOR|IMAGEUVCSP : writeNormalMap, 706 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT : writeNormalMap, 707 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|TEXFACE : writeNormalMap, 708 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|IMAGEUVCSP : writeNormalMap, 709 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|TEXFACE : writeNormalMap, 710 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|IMAGEUVCSP : writeNormalMap, 711 NONHALO|IMAGEUVCOL|IMAGEUVNOR|TEXFACE|IMAGEUVCSP : writeNormalMap, 712 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|TEXFACE|IMAGEUVCSP : writeNormalMap, 713 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|TEXFACE|IMAGEUVCSP : writeNormalMap, 714 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|IMAGEUVCSP : writeNormalMap, 715 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|TEXFACE : writeNormalMap, 716 NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|TEXFACE|IMAGEUVCSP : writeNormalMap 717 } 718 719class CustomMaterial(RenderingMaterial): 720 def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient, customMaterialTemplate): 721 self.mesh = blenderMesh 722 self.face = blenderFace 723 self.colouredAmbient = colouredAmbient 724 self.customMaterialTemplate = customMaterialTemplate 725 self.key = 0 726 self.mTexUVCol = None 727 self.mTexUVNor = None 728 self.mTexUVCsp = None 729 self.material = None 730 self.template = None 731 try: 732 self.material = self.mesh.materials[self.face.mat] 733 except IndexError: 734 Log.getSingleton().logWarning("Can't get material for mesh \"%s\"! Are any materials linked to object instead of linked to mesh?" % self.mesh.name) 735 if self.material: 736 self._generateKey() 737 DefaultMaterial.__init__(self, manager, self._createName()) 738 else: 739 DefaultMaterial.__init__(self, manager, 'None') 740 self.properties = self.material.properties 741 return 742 class Template(string.Template): 743 delimiter = '%' 744 idpattern = '[_a-z][_a-z0-9.]*' 745 def setupTemplate(self, templateString): 746 self.template = self.Template(templateString) 747 return 748 def write(self, f): 749 # fallback if we don't have a valid template set. 750 if not(self.template): 751 RenderingMaterial.write(self, f) 752 return 753 754 # do alpha check for scene blend and depth write flag. 755 self.hasAlpha = 0 756 if (self.material.alpha < 1.0): 757 self.hasAlpha = 1 758 else: 759 for mtex in self.material.getTextures(): 760 if mtex: 761 if ((mtex.tex.type == Blender.Texture.Types['IMAGE']) 762 and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])): 763 self.hasAlpha = 1 764 765 # setup dictionary. 766 templateDict = { \ 767 '_materialName' : self.name, \ 768 '_ambient' : self._getAmbient(), \ 769 '_diffuse' : self._getDiffuse(), \ 770 '_specular' : self._getSpecular(), \ 771 '_emisive' : self._getEmisive(), \ 772 '_scene_blend' : self._getSceneBlend(), \ 773 '_depth_write' : self._getDepthWrite(), \ 774 '_depth_func' : self._getDepthFunc(), \ 775 '_receive_shadows' : self._getReceiveShadows(), \ 776 '_culling' : self._getCulling(), \ 777 '_lighting' : self._getLighting(), \ 778 '_fog_override' : self._getFogOverride()} 779 780 textureIndex = 0 781 for mtex in self.material.getTextures(): 782 if mtex: 783 if (mtex.tex.type == Blender.Texture.Types['IMAGE']): 784 textureName = mtex.tex.getName() 785 name, ext = Blender.sys.splitext(textureName) 786 if ext.lstrip('.').isdigit() : textureName = name 787 788 # alternate texture index access 789 textureIndexName = '_tex[%d]' % textureIndex 790 ++textureIndex 791 792 if mtex.tex.getImage(): 793 textureFilename = self.manager.registerTextureFile(mtex.tex.getImage().getFilename()) 794 templateDict[textureName + '._texture'] = textureFilename 795 templateDict[textureIndexName + '._texture'] = textureFilename 796 if mtex.tex.extend & Blender.Texture.ExtendModes['REPEAT']: 797 templateDict[textureName + '._tex_address_mode'] = 'wrap' 798 templateDict[textureIndexName + '._tex_address_mode'] = 'wrap' 799 else: 800 templateDict[textureName + '._tex_address_mode'] = 'clamp' 801 templateDict[textureIndexName + '._tex_address_mode'] = 'clamp' 802 if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['INTERPOL']): 803 if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']): 804 templateDict[textureName + '._filtering'] = 'trilinear' 805 templateDict[textureIndexName + '._filtering'] = 'trilinear' 806 else: 807 templateDict[textureName + '._filtering'] = 'linear linear none' 808 templateDict[textureIndexName + '._filtering'] = 'linear linear none' 809 else: 810 if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']): 811 templateDict[textureName + '._filtering'] = 'bilinear' 812 templateDict[textureIndexName + '._filtering'] = 'bilinear' 813 else: 814 templateDict[textureName + '._filtering'] = 'none' 815 templateDict[textureIndexName + '._filtering'] = 'none' 816 if ((mtex.tex.imageFlags & Blender.Texture.ImageFlags['USEALPHA']) 817 and not(mtex.mapto & Blender.Texture.MapTo['ALPHA'])): 818 templateDict[textureName + '._colour_op'] = 'alpha_blend' 819 templateDict[textureIndexName + '._colour_op'] = 'alpha_blend' 820 else: 821 templateDict[textureName + '._colour_op'] = 'modulate' 822 templateDict[textureIndexName + '._colour_op'] = 'modulate' 823 templateDict[textureName + '._sizeX'] = "%.6g" % mtex.size[0] 824 templateDict[textureIndexName + '._sizeX'] = "%.6g" % mtex.size[0] 825 templateDict[textureName + '._sizeY'] = "%.6g" % mtex.size[1] 826 templateDict[textureIndexName + '._sizeY'] = "%.6g" % mtex.size[1] 827 templateDict[textureName + '._sizeZ'] = "%.6g" % mtex.size[2] 828 templateDict[textureIndexName + '._sizeZ'] = "%.6g" % mtex.size[2] 829 templateDict[textureName + '._offsetX'] = "%.6g" % mtex.ofs[0] 830 templateDict[textureIndexName + '._offsetX'] = "%.6g" % mtex.ofs[0] 831 templateDict[textureName + '._offsetY'] = "%.6g" % mtex.ofs[1] 832 templateDict[textureIndexName + '._offsetY'] = "%.6g" % mtex.ofs[1] 833 templateDict[textureName + '._offsetZ'] = "%.6g" % mtex.ofs[2] 834 templateDict[textureIndexName + '._offsetZ'] = "%.6g" % mtex.ofs[2] 835 try: 836 propertyGroup = self.material.properties['properties'] 837 self._parseProperties(templateDict, propertyGroup) 838 except KeyError: 839 pass 840 841 f.write(self.template.safe_substitute(templateDict)) 842 843 return 844 def _getAmbient(self): 845 if self.colouredAmbient: 846 amb = self.material.amb 847 ambR = clamp(self.material.R * amb) 848 ambG = clamp(self.material.G * amb) 849 ambB = clamp(self.material.B * amb) 850 return "%.6g %.6g %.6g" % (ambR, ambG, ambB) 851 amb = self.material.amb 852 return "%.6g %.6g %.6g" % (amb, amb, amb) 853 def _getDiffuse(self): 854 return "%.6g %.6g %.6g %.6g" % (self.material.R, self.material.G, self.material.B, self.material.alpha) 855 def _getSpecular(self): 856 spec = self.material.spec 857 specR = clamp(self.material.specR * spec) 858 specG = clamp(self.material.specG * spec) 859 specB = clamp(self.material.specB * spec) 860 shininess = self.material.getHardness() / 4.0 861 return "%.6g %.6g %.6g %.6g" % (specR, specG, specB, shininess) 862 def _getEmisive(self): 863 emit = self.material.emit 864 emitR = clamp(self.material.R * emit) 865 emitG = clamp(self.material.G * emit) 866 emitB = clamp(self.material.B * emit) 867 return "%.6g %.6g %.6g" % (emitR, emitG, emitB) 868 def _getSceneBlend(self): 869 if (self.hasAlpha): 870 return 'alpha_blend' 871 return 'one zero' 872 def _getDepthWrite(self): 873 if (self.hasAlpha): 874 return 'off' 875 return 'on' 876 def _getDepthFunc(self): 877 if (self.material.mode & Blender.Material.Modes['ENV']): 878 return 'always_fail' 879 elif (self.material.mode & Blender.Material.Modes['ZINVERT']): 880 return 'greater_equal' 881 return 'less_equal' 882 def _getReceiveShadows(self): 883 if (self.material.mode & Blender.Material.Modes["SHADOW"]): 884 return 'on' 885 return 'off' 886 def _getCulling(self): 887 if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']): 888 return 'none' 889 return 'clockwise' 890 def _getLighting(self): 891 if (self.material.mode & Blender.Material.Modes['SHADELESS']): 892 return 'off' 893 return 'on' 894 def _getFogOverride(self): 895 if (self.material.mode & Blender.Material.Modes['NOMIST']): 896 return 'true' 897 return 'false' 898 def _parseProperties(self, templateDict, propertyGroup, parent=None): 899 if type(propertyGroup) is Blender.Types.IDGroupType: 900 for propName, propValue in propertyGroup.iteritems(): 901 if parent : propName = parent + '.' + propName 902 propType = type(propValue) 903 if propType is Blender.Types.IDArrayType: 904 arraySize = propValue.__len__() 905 propValueString = "%.6g" % propValue.__getitem__(0) 906 i = 1 907 while i < arraySize: 908 propValueString += " %.6g" % propValue.__getitem__(i) 909 i += 1 910 templateDict[propName] = propValueString 911 elif propType is Blender.Types.IDGroupType: 912 self._parseProperties(templateDict, propValue, propName) 913 elif propType is float: 914 templateDict[propName] = "%.6g" % propValue 915 else: 916 templateDict[propName] = propValue 917 return 918 919class MaterialManager: 920 """Manages database of material definitions. 921 """ 922 #TODO append to existing material script 923 def __init__(self, dir=None, file=None, gameEngineMaterial=False, customMaterial=False, customMaterialTplPath=None, requireFaceMats=True): 924 """Constructor. 925 926 @param path Directory to the material file. Default is directory of the last file read or written with Blender. 927 @param file Material script file. Default is current scene name with ".material" prefix. 928 """ 929 self.dir = dir or Blender.sys.dirname(Blender.Get('filename')) 930 self.file = file or (Blender.Scene.GetCurrent().getName() + ".material") 931 self.gameEngineMaterial = gameEngineMaterial 932 self.customMaterial = customMaterial 933 self.customMaterialTplPath = customMaterialTplPath 934 # key=name, value=material 935 self.materialsDict = {} 936 # key=basename, value=path 937 self.textureFilesDict = {} 938 if requireFaceMats: 939 self.noMatWarned = set() 940 # We only warn once for each blender Obj X mesh 941 # This set keeps track of what we have issued warnings for already 942 else: 943 self.noMatWarned = None 944 return 945 def getMaterial(self, bMesh, bMFace, colouredAmbient, objName): 946 """Returns material of a given face or <code>None</code>. 947 """ 948 ## get name of export material for Blender's material settings of that face. 949 # The objName param provides for precise and optimally concise 950 # error messages 951 faceMaterial = None 952 if self.gameEngineMaterial: 953 if bMesh.faceUV and not(bMFace.mode & Blender.Mesh.FaceModes['INVISIBLE']): 954 if (bMFace.image and (bMFace.mode & Blender.Mesh.FaceModes['TEX'])): 955 # image texture 956 faceMaterial = GameEngineMaterial(self, bMesh, bMFace, colouredAmbient) 957 else: 958 # material only 959 if (bMesh.materials == None 960 or bMFace.mat >= len(bMesh.materials) 961 or not bMesh.materials[bMFace.mat]): 962 if self.noMatWarned != None and ( 963 objName + '|' + bMesh.name) not in self.noMatWarned: 964 Log.getSingleton().logError(('Face(s) without eng. ' 965 + 'material assignment in obj "%s", mesh "%s"!') 966 % (objName, bMesh.name)) 967 self.noMatWarned.add(objName + '|' + bMesh.name) 968 faceMaterial = DefaultMaterial(self, 'BaseWhite') 969 else: 970 faceMaterial = GameEngineMaterial(self, bMesh, bMFace, colouredAmbient) 971 elif self.customMaterial: 972 if (bMesh.materials == None or bMFace.mat >= len(bMesh.materials) 973 or not bMesh.materials[bMFace.mat]): 974 if self.noMatWarned != None and ( 975 objName + '|' + bMesh.name) not in self.noMatWarned: 976 Log.getSingleton().logError(('Face(s) without custom ' 977 + 'material assignment in obj "%s", mesh "%s"!') 978 % (objName, bMesh.name)) 979 self.noMatWarned.add(objName + '|' + bMesh.name) 980 faceMaterial = DefaultMaterial(self, 'BaseWhite') 981 else: 982 faceMaterial = CustomMaterial(self, bMesh, bMFace, colouredAmbient, self.customMaterialTplPath) 983 else: 984 # rendering material 985 if (bMesh.materials == None or bMFace.mat >= len(bMesh.materials) 986 or not bMesh.materials[bMFace.mat]): 987 if self.noMatWarned != None and ( 988 objName + '|' + bMesh.name) not in self.noMatWarned: 989 Log.getSingleton().logError(('Face(s) without ' 990 + 'material assignment in obj "%s", mesh "%s"!') 991 % (objName, bMesh.name)) 992 self.noMatWarned.add(objName + '|' + bMesh.name) 993 faceMaterial = DefaultMaterial(self, 'BaseWhite') 994 else: 995 faceMaterial = RenderingMaterial(self, bMesh, bMFace, colouredAmbient) 996 ## return material or None 997 material = None 998 if faceMaterial: 999 if not(self.materialsDict.has_key(faceMaterial.getName())): 1000 self.materialsDict[faceMaterial.getName()] = faceMaterial 1001 material = self.materialsDict[faceMaterial.getName()] 1002 return material 1003 def registerTextureFile(self, path): 1004 """Register texture for export. 1005 1006 @param path Texture file path, i.e. dirname and basename. 1007 @return Basename of texture file. 1008 """ 1009 texturePath = PathName(path) 1010 key = texturePath.basename() 1011 if self.textureFilesDict.has_key(key): 1012 if (path != self.textureFilesDict[key]): 1013 Log.getSingleton().logWarning('Texture filename conflict: \"%s\"' % key) 1014 Log.getSingleton().logWarning(' Location: \"%s\"' % path) 1015 Log.getSingleton().logWarning(' Conflicting location: \"%s\"' % self.textureFilesDict[key]) 1016 self.textureFilesDict[key] = path 1017 return key 1018 def export(self, dir=None, file=None, copyTextures=False): 1019 exportDir = dir or self.dir 1020 exportFile = file or self.file 1021 Log.getSingleton().logInfo("Exporting materials \"%s\"" % exportFile) 1022 f = open(Blender.sys.join(exportDir, exportFile), "w") 1023 1024 if self.customMaterial : 1025 # set of material import lines. 1026 templateImportSet = set() 1027 # map of template strings. 1028 templateStringDict = {} 1029 for material in self.materialsDict.values(): 1030 if isinstance(material, CustomMaterial): 1031 try: 1032 template = material.properties['template'] 1033 if type(template) is not str: 1034 Log.getSingleton().logWarning("==========================================================") 1035 Log.getSingleton().logWarning("Material \"%s\" not assigned to a valid template!" % material.getName()) 1036 Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.") 1037 else: 1038 if not(templateStringDict.has_key(template)) : 1039 Log.getSingleton().logInfo("Loading template \"%s\"" % template) 1040 templateFile = Blender.sys.join(self.customMaterialTplPath, template + '.tpl') 1041 if not(Blender.sys.exists(templateFile) == 1): 1042 Log.getSingleton().logWarning("==========================================================") 1043 Log.getSingleton().logWarning("Material \"%s\" assigned to unknown template \"%s\"!" % (material.getName(), template)) 1044 Log.getSingleton().logWarning("The template file \"%s\" does not exist." % templateFile) 1045 Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.") 1046 templateStringDict[template] = None 1047 else: 1048 t = open(templateFile, 'r') 1049 templateImportSet.add(t.readline()) 1050 templateStringDict[template] = t.read() 1051 t.close() 1052 if (templateStringDict[template]): 1053 material.setupTemplate(templateStringDict[template]) 1054 except KeyError: 1055 Log.getSingleton().logWarning("==========================================================") 1056 Log.getSingleton().logWarning("Material \"%s\" is not assigned to a template!" % (material.getName())) 1057 Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.") 1058 # write import lines. 1059 for importString in templateImportSet: 1060 f.write(importString) 1061 1062 Log.getSingleton().logInfo("Begin writing materials:") 1063 for material in self.materialsDict.values(): 1064 Log.getSingleton().logInfo(" %s" % (material.getName())) 1065 material.write(f) 1066 f.close() 1067 if copyTextures and os.path.exists(dir): 1068 baseDirname = os.path.dirname(Blender.Get("filename")) 1069 for path in self.textureFilesDict.values(): 1070 # convert Blender's relative paths "//" to absolute path 1071 if (path[0:2] == "//"): 1072 Log.getSingleton().logInfo("Converting relative image name \"%s\"" % path) 1073 path = os.path.join(baseDirname, path[2:]) 1074 if os.path.exists(path): 1075 # copy texture to dir 1076 Log.getSingleton().logInfo("Copying texture \"%s\"" % path) 1077 try: 1078 shutil.copy(path, dir) 1079 except (IOError, OSError), detail: 1080 Log.getSingleton().logError("Error copying \"%s\": %s" % (path, str(detail))) 1081 else: 1082 Log.getSingleton().logWarning("Can't copy texture \"%s\" because file does not exists!" % path) 1083 return 1084