Advertisement
Guest User

#### 3D Coat PBR to Blender Principled BSDF V 0.1.2

a guest
Feb 17th, 2018
426
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.06 KB | None | 0 0
  1. #### 3D Coat PBR to Blender Principled BSDF V 0.1.2
  2. #### Plugin to import 3D Coat's generated PBR material files to Blender's Principal BSDF shader
  3. ####
  4. #### This plugin will handle the generation of a new material for each selected object in the viewport.
  5. ####
  6. #### 3D Coat Export Settings:
  7. #### - Roughness/Metalness
  8. #### - Uncheck "Use EXPORTER CONSTRUCTOR for per /channel packing"
  9. #### - Set each extension to TGA
  10. #### - Check the following ONLY
  11. ####    - Export Color
  12. ####    - Export Roughness
  13. ####    - Export Metalness
  14. ####    - Export Tangent Normal Map
  15. ####    - ExportAO
  16. ####    - ExportCurvature
  17. #### - Set "Create Padding" to 8, should be enough.
  18. ####
  19. #### Currently supported:
  20. ####    - Albedo/Color
  21. ####    - Metallic
  22. ####    - Roughness
  23. ####    - Normal
  24. ####
  25. #### Usage:
  26. #### 1. Select object(s) to apply new material to
  27. #### 2. Select File => Import => 3D Coat PBR material
  28. #### 3. Select the .OBJ or .FBX model. The plugin will look for each texture/map file in the same directory
  29. #### 4. Profit!
  30. ####
  31. #### Thanks to:
  32. ####    digman, for infos on how to set up the Principled BSDF shader correctly
  33. ####
  34. #### Created by Haunted (Marcel Meisner)
  35. #### Official 3D Coat Forum thread: http://3dcoat.com/forum/index.php?/topic/22025-3d-coat-pbr-to-blender-principled-bsdf-add-on/
  36. ####
  37. #### Changes:
  38. ####
  39. #### V 0.1.2
  40. ####    - Script now finds .tga, .png or .bmp files
  41. ####
  42. #### V 0.1.1
  43. ####    - Added labels for the texture nodes
  44. ####    - Code clean up
  45.  
  46. bl_info = {
  47.     'name': "3D Coat PBR material",
  48.     'category': "Import",
  49.     'author': 'Marcel Meisner (Haunted)',
  50.     'version': (0, 1, 2),
  51.     'blender': (2, 79, 0),
  52.     'location': 'File > Import > 3D Coat PBR material',
  53.     'description': "Import 3D Coat's generated PBR material files to Blender's Principal BSDF shader",
  54.     'warning': '',
  55.     'support': 'COMMUNITY',
  56. }
  57.  
  58. import bpy
  59. import os
  60. from collections import namedtuple
  61. from enum import Enum
  62. import glob
  63.  
  64. # ImportHelper is a helper class, defines filename and
  65. # invoke() function which calls the file selector.
  66. from bpy_extras.io_utils import ImportHelper
  67. from bpy.props import StringProperty, BoolProperty, EnumProperty
  68. from bpy.types import Operator
  69.  
  70. class ImportFilesCollection(bpy.types.PropertyGroup):
  71.     name = StringProperty(
  72.             name="File Path",
  73.             description="Filepath used for importing the file",
  74.             maxlen=1024,
  75.             subtype='FILE_PATH',
  76.             )
  77. bpy.utils.register_class(ImportFilesCollection)
  78.  
  79. class EMapType(Enum):
  80.     diffuse     = 1
  81.     metallic    = 2
  82.     roughness   = 3
  83.     normal      = 4
  84.    
  85. AvailableMaps = namedtuple('AvailableMaps', 'diffuse metallic roughness normal')
  86.  
  87.  
  88. def CreatePBRMaterial(obj, availableMaps):
  89.     matName = obj.name + '_3d_coat_pbr'
  90.  
  91.     print("Creating material '" + matName + "'.")
  92.    
  93.     mat = bpy.data.materials.new(matName)
  94.     mat.use_nodes = True  
  95.     obj.data.materials.append(mat)
  96.    
  97.     material_output = mat.node_tree.nodes.get("Material Output")
  98.     pbrNode = mat.node_tree.nodes.new('ShaderNodeBsdfPrincipled')
  99.     mat.node_tree.links.new(pbrNode.outputs[0], material_output.inputs[0])
  100.     mat.node_tree.nodes.remove(mat.node_tree.nodes.get('Diffuse BSDF'))
  101.    
  102.     material_output.location = 200,0
  103.    
  104.     if(availableMaps.diffuse != None):
  105.         AddTexture(mat, pbrNode, EMapType.diffuse, availableMaps.diffuse)
  106.     if(availableMaps.metallic != None):
  107.         AddTexture(mat, pbrNode, EMapType.metallic, availableMaps.metallic)
  108.     if(availableMaps.roughness != None):
  109.         AddTexture(mat, pbrNode, EMapType.roughness, availableMaps.roughness)
  110.     if(availableMaps.normal != None):
  111.         AddTexture(mat, pbrNode, EMapType.normal, availableMaps.normal)
  112.  
  113.          
  114. def AddTexture(material, pbrNode, type, mapPath):
  115.     material_output = material.node_tree.nodes.get('Material Output')
  116.     tree = material.node_tree
  117.     if type == EMapType.diffuse:
  118.         print('Adding diffuse map.')
  119.         node = CreateNewTextureNode(material, -300, 200, mapPath)
  120.         node.label = 'Albedo/Diffuse'
  121.         tree.links.new(node.outputs[0], pbrNode.inputs[0])
  122.     elif type == EMapType.metallic:
  123.         print('Adding metallic map.')
  124.         node = CreateNewTextureNode(material, -500, 0, mapPath)
  125.         node.label = 'Metallic'
  126.         tree.links.new(node.outputs[0], pbrNode.inputs[5])
  127.     elif type == EMapType.roughness:
  128.         print('Adding roughness map.')
  129.         node = CreateNewTextureNode(material, -300, -200, mapPath)
  130.         node.label = 'Roughness'
  131.         tree.links.new(node.outputs[0], pbrNode.inputs[7])
  132.     elif type == EMapType.normal:
  133.         print('Adding normal map.')
  134.         nm_node = CreateNewNormalMapNode(material, -300, -500, mapPath)
  135.         node = CreateNewTextureNode(material, -500, -500, mapPath)
  136.         node.label = 'Normal'
  137.         tree.links.new(node.outputs[0], nm_node.inputs[1])
  138.         tree.links.new(nm_node.outputs[0], pbrNode.inputs["Normal"])
  139.  
  140.      
  141. def CreateNewTextureNode(material, x, y, imgPath):
  142.         node = material.node_tree.nodes.new('ShaderNodeTexImage')
  143.         node.image = bpy.data.images.load(imgPath)
  144.         node.location= x, y
  145.         return node
  146.  
  147.  
  148. def CreateNewNormalMapNode(material, x, y, imgPath):
  149.         node = material.node_tree.nodes.new('ShaderNodeNormalMap')
  150.         node.location= x, y
  151.         return node
  152.  
  153.  
  154. class Import3DCoatPBR(Operator, ImportHelper):
  155.     """Generate a Principled BSDF material for each selected object from 3D Coat's exported PBR files."""
  156.     bl_idname = "node.pbr_3dcoat"  # important since its how bpy.ops.import_test.some_data is constructed
  157.     bl_label = "Import 3DCoat PBR maps"
  158.  
  159.     # ImportHelper mixin class uses this
  160.     filename_ext    = ".obj"
  161.    
  162.     filter_glob = StringProperty(
  163.             default="*.obj;*.fbx",
  164.             options={'HIDDEN'},
  165.             )
  166.            
  167.     files = bpy.props.CollectionProperty(type=ImportFilesCollection)
  168.  
  169.     def execute(self, context):
  170.         if bpy.context.scene.render.engine != 'CYCLES':
  171.             self.report({'ERROR'},'Add-on works with Cycles Renderer only (for now).')
  172.             return {'CANCELLED'}
  173.            
  174.         for i, f in enumerate(self.files, 1):
  175.             suffix_color    = 'color'
  176.             suffix_metal    = 'metalness'
  177.             suffix_rough    = 'rough'
  178.             suffix_nmap     = 'nmap'
  179.    
  180.             # Setup base filename and base path
  181.             filename = os.path.splitext(f.name)[0]
  182.             fdir = os.path.dirname(self.properties.filepath)
  183.             filepath = os.path.join(fdir,filename)
  184.            
  185.             #Check for selected objects
  186.             layerIndex = 0  #Chosen layer
  187.             objects = bpy.context.scene.objects
  188.  
  189.             selectedObjects = [ob for ob in objects if ob.layers[layerIndex]
  190.             and ob.select
  191.             and ob.type == 'MESH']
  192.            
  193.             extension = '.tga'
  194.             pathCheckName = filepath + '_*_' + suffix_color
  195.            
  196.             if len(glob.glob(pathCheckName + '.tga')) > 0:
  197.                 extension = '.tga'
  198.             elif len(glob.glob(pathCheckName + '.png')) > 0:
  199.                 extension = '.png'
  200.             elif len(glob.glob(pathCheckName + '.bmp')) > 0:
  201.                 extension = '.bmp'
  202.                
  203.             if len(selectedObjects) > 0:
  204.                 # Flags for textures
  205.                 hasDiffuse      = False
  206.                 hasMetallic     = False
  207.                 hasRoughness    = False
  208.                 hasNormalmap    = False
  209.                
  210.                 # Search for these files on disc
  211.                 diffuse     = glob.glob(filepath + '_*_' + suffix_color + extension)
  212.                 metallic    = glob.glob(filepath + '_*_' + suffix_metal + extension)
  213.                 roughness   = glob.glob(filepath + '_*_' + suffix_rough + extension)
  214.                 normalmap   = glob.glob(filepath + '_*_' + suffix_nmap + extension)
  215.                
  216.                 diffuse = diffuse[0] if len(diffuse) > 0 else ''
  217.                 metallic = metallic[0] if len(metallic) > 0 else ''
  218.                 roughness = roughness[0] if len(roughness) > 0 else ''
  219.                 normalmap = normalmap[0] if len(normalmap) > 0 else ''
  220.                
  221.                 # Check if files are existing
  222.                 print("Searching PBR maps...")
  223.                 availableMaps = AvailableMaps(
  224.                     diffuse if os.path.isfile(diffuse) else None,
  225.                     metallic if os.path.isfile(metallic) else None,
  226.                     roughness if os.path.isfile(roughness) else None,
  227.                     normalmap if os.path.isfile(normalmap) else None,
  228.                     )
  229.                
  230.                 #Output
  231.                 print("Diffuse map: \t" + ("found=" + availableMaps.diffuse  if availableMaps.diffuse != None else "not found!"))
  232.                 print("Metallic map: \t" + ("found=" + availableMaps.metallic  if availableMaps.metallic != None  else "not found!"))
  233.                 print("Roughness map: \t" + ("found=" + availableMaps.roughness if availableMaps.roughness != None else "not found!"))
  234.                 print("Normal map: \t" + ("found=" + availableMaps.normal if availableMaps.normal != None  else "not found!"))
  235.  
  236.                 #Create material
  237.                 for obj in selectedObjects:
  238.                     CreatePBRMaterial(obj, availableMaps)
  239.             else:
  240.                 self.report({'ERROR'},'No object selected to import the PBR material to.')
  241.                 return {'CANCELLED'}
  242.            
  243.         return {'FINISHED'}
  244.  
  245.  
  246. # Only needed if you want to add into a dynamic menu
  247. def menu_func_import(self, context):
  248.     self.layout.operator(Import3DCoatPBR.bl_idname, text="3D Coat PBR material (.tga)")
  249.  
  250.  
  251. def register():
  252.     bpy.utils.register_class(Import3DCoatPBR)
  253.     bpy.types.INFO_MT_file_import.append(menu_func_import)
  254.  
  255.  
  256. def unregister():
  257.     bpy.utils.unregister_class(Import3DCoatPBR)
  258.     bpy.types.INFO_MT_file_import.remove(menu_func_import)
  259.  
  260.  
  261. if __name__ == "__main__":
  262.     register()
  263.    
  264.     #bpy.ops.node.pbr_3dcoat('INVOKE_DEFAULT')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement