material.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """
  2. Exports materials. For now I'm targetting the blender internal, however this
  3. will be deprecated in Blender 2.8 in favor of EEVEE. EEVEE has PBR and
  4. should be able to match Godot better, but unfortunately parseing a node
  5. tree into a flat bunch of parameters is not trivial. So for someone else:"""
  6. import logging
  7. import os
  8. import bpy
  9. from .material_node_tree.exporters import export_node_tree
  10. from ..structures import (
  11. InternalResource, ExternalResource, gamma_correct, ValidationError)
  12. def export_image(escn_file, export_settings, image):
  13. """
  14. Saves an image as an external reference relative to the blend location
  15. """
  16. image_id = escn_file.get_external_resource(image)
  17. if image_id is not None:
  18. return image_id
  19. imgpath = image.filepath
  20. if imgpath.startswith("//"):
  21. imgpath = bpy.path.abspath(imgpath)
  22. imgpath = os.path.relpath(
  23. imgpath,
  24. os.path.dirname(export_settings['path'])
  25. ).replace("\\", "/")
  26. # Add the image to the file
  27. image_resource = ExternalResource(imgpath, "Image")
  28. image_id = escn_file.add_external_resource(image_resource, image)
  29. return image_id
  30. def export_material(escn_file, export_settings, material):
  31. """Exports blender internal/cycles material as best it can"""
  32. external_material = find_material(export_settings, material)
  33. if external_material is not None:
  34. resource_id = escn_file.get_external_resource(material)
  35. if resource_id is None:
  36. ext_mat = ExternalResource(
  37. external_material[0], # Path
  38. external_material[1] # Material Type
  39. )
  40. resource_id = escn_file.add_external_resource(ext_mat, material)
  41. return "ExtResource({})".format(resource_id)
  42. resource_id = generate_material_resource(
  43. escn_file, export_settings, material
  44. )
  45. return "SubResource({})".format(resource_id)
  46. def generate_material_resource(escn_file, export_settings, material):
  47. """Export blender material as an internal resource"""
  48. resource_id = escn_file.get_internal_resource(material)
  49. if resource_id is not None:
  50. return resource_id
  51. engine = bpy.context.scene.render.engine
  52. mat = None
  53. if export_settings['generate_external_material']:
  54. material_rsc_name = material.name
  55. else:
  56. # leave material_name as empty, prevent godot
  57. # to convert material to external file
  58. material_rsc_name = ''
  59. if engine == 'CYCLES' and material.node_tree is not None:
  60. mat = InternalResource("ShaderMaterial", material_rsc_name)
  61. try:
  62. export_node_tree(
  63. escn_file, export_settings, material, mat
  64. )
  65. except ValidationError as exception:
  66. mat = None # revert to SpatialMaterial
  67. logging.error(
  68. "%s, in material '%s'", str(exception), material.name
  69. )
  70. if mat is None:
  71. mat = InternalResource("SpatialMaterial", material_rsc_name)
  72. mat['albedo_color'] = gamma_correct(material.diffuse_color)
  73. return escn_file.add_internal_resource(mat, material)
  74. # ------------------- Tools for finding existing materials -------------------
  75. def _find_material_in_subtree(folder, material):
  76. """Searches for godot materials that match a blender material. If found,
  77. it returns (path, type) otherwise it returns None"""
  78. candidates = []
  79. material_file_name = material.name + '.tres'
  80. for dir_path, _subdirs, files in os.walk(folder):
  81. if material_file_name in files:
  82. candidates.append(os.path.join(dir_path, material_file_name))
  83. # Checks it is a material and finds out what type
  84. valid_candidates = []
  85. for candidate in candidates:
  86. with open(candidate) as mat_file:
  87. first_line = mat_file.readline()
  88. if "SpatialMaterial" in first_line:
  89. valid_candidates.append((candidate, "SpatialMaterial"))
  90. if "ShaderMaterial" in first_line:
  91. valid_candidates.append((candidate, "ShaderMaterial"))
  92. if not valid_candidates:
  93. return None
  94. if len(valid_candidates) > 1:
  95. logging.warning("Multiple materials found for %s", material.name)
  96. return valid_candidates[0]
  97. def find_material(export_settings, material):
  98. """Searches for an existing Godot material"""
  99. search_type = export_settings["material_search_paths"]
  100. if search_type == "PROJECT_DIR":
  101. search_dir = export_settings["project_path_func"]()
  102. elif search_type == "EXPORT_DIR":
  103. search_dir = os.path.dirname(export_settings["path"])
  104. else:
  105. search_dir = None
  106. if search_dir is None:
  107. return None
  108. return _find_material_in_subtree(search_dir, material)