├── Bake
├── __init__.py
├── bake_manager.py
└── bake_utilities.py
├── Panels
├── __init__.py
├── panel_ui_list.py
└── panel.py
├── Functions
├── __init__.py
├── basic_functions.py
├── constants.py
├── object_functions.py
├── material_functions.py
├── gui_functions.py
├── image_functions.py
├── visibility_functions.py
└── node_functions.py
├── Keycodes
├── __init__.py
└── key_codes.py
├── Operators
├── __init__.py
├── modal_wrap.py
├── bake_operator.py
└── operators.py
├── Properties
├── __init__.py
└── properties.py
├── CNAME
├── _config.yml
├── .gitignore
├── .vscode
├── .ropeproject
│ ├── objectdb
│ └── config.py
└── settings.json
├── debug.log
├── README.md
├── todo.txt
├── Utilities
└── clean_dupli_textures.py
├── LICENSE
├── __init__.py
├── .github
└── workflows
│ └── auto_deploy.yml
└── auto_load.py
/Bake/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Panels/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Functions/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Keycodes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Operators/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Properties/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | lorenz.wieseke.glbtexturetools
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | .idea/
3 | __pycache__/
4 | glbtexturetools_updater
--------------------------------------------------------------------------------
/.vscode/.ropeproject/objectdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LorenzWieseke/GLBTextureTools/HEAD/.vscode/.ropeproject/objectdb
--------------------------------------------------------------------------------
/debug.log:
--------------------------------------------------------------------------------
1 | [1016/121205.994:ERROR:directory_reader_win.cc(43)] FindFirstFile: Das System kann den angegebenen Pfad nicht finden. (0x3)
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Documentation can be found here and a help
2 |
3 | https://docs.google.com/document/d/1w1h3ySyZG4taG01RbbDsPODsUP06cRCggxQKCt32QTk/edit
4 |
--------------------------------------------------------------------------------
/todo.txt:
--------------------------------------------------------------------------------
1 | use uv-packmaster if installed
2 | change camera size back to what is was
3 | reconstruct node tree in compositor
4 | pbr texture bake -> add same uv to bake plane to be able to bake procedural textures or ! add uv automaticly
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Functions/basic_functions.py:
--------------------------------------------------------------------------------
1 | def Diff(li1, li2):
2 | return (list(set(li1) - set(li2)))
3 |
4 | def Intersection(li1, li2):
5 | set1 = set(li1)
6 | set2 = set(li2)
7 | return list(set.intersection(set1,set2))
8 |
9 | def flatten(t):
10 | return [item for sublist in t for item in sublist]
11 |
12 | def remove_duplicate(l):
13 | return list(dict.fromkeys(l))
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.autoComplete.addBrackets": true,
3 | "blender.addon.reloadOnSave": true,
4 | "python.linting.pylintEnabled": true,
5 | "python.linting.enabled": true,
6 | "python.autoComplete.extraPaths": [
7 | "c:/3D Animation/blender_autocomplete/2.82"
8 | ],
9 | "python.analysis.extraPaths": [
10 | "c:/3D Animation/blender_autocomplete/2.82"
11 | ],
12 | "python.analysis.completeFunctionParens": true
13 |
14 |
15 | }
--------------------------------------------------------------------------------
/Keycodes/key_codes.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | addon_keymaps = []
4 |
5 |
6 | # keymap
7 | def register():
8 |
9 | kcfg = bpy.context.window_manager.keyconfigs.addon
10 | if kcfg:
11 | km = kcfg.keymaps.new(name='3D View', space_type='VIEW_3D')
12 |
13 | kmi = km.keymap_items.new("scene.node_to_texture_operator", 'B', 'PRESS', shift=True, ctrl=True)
14 |
15 | addon_keymaps.append((km, kmi))
16 | def unregister():
17 |
18 | for km, kmi in addon_keymaps:
19 | km.keymap_items.remove(kmi)
20 | addon_keymaps.clear()
--------------------------------------------------------------------------------
/Operators/modal_wrap.py:
--------------------------------------------------------------------------------
1 |
2 | from bpy.ops import OBJECT_OT_bake as op
3 |
4 | def callback(ret):
5 | print('Callback triggered: {} !!'.format(ret))
6 |
7 |
8 | def modal_wrap(modal_func, callback):
9 | def wrap(self, context, event):
10 | ret, = retset = modal_func(self, context, event)
11 | if ret in {'CANCELLED'}: # my plugin emits the CANCELED event on finish - yours might do FINISH or FINISHED, you might have to look it up in the source code, __init__.py , there look at the modal() function for things like return {'FINISHED'} or function calls that return things alike.
12 | print(f"{self.bl_idname} returned {ret}")
13 | callback(ret)
14 | return retset
15 | return wrap
16 |
17 | # op._modal_org = op.modal
18 | op.modal = modal_wrap(op.modal, callback)
19 |
20 |
--------------------------------------------------------------------------------
/Utilities/clean_dupli_textures.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from . import functions
3 | from .. Functions import material_functions
4 |
5 |
6 | def Diff(li1, li2):
7 | return (list(set(li1) - set(li2)))
8 |
9 |
10 | def compareImages(images):
11 | firstImg = images[0]
12 | firstImgPixels = firstImg.pixels
13 |
14 | for img in images[1:]:
15 | currentImgPixels = img.pixels
16 | diff = Diff(firstImgPixels, currentImgPixels)
17 | if len(diff) == 0:
18 | print(firstImg.name + " and " + img.name + " map !")
19 |
20 | return compareImages(images[1:])
21 |
22 |
23 | class CleanDupliTexturesOperator(bpy.types.Operator):
24 | """By checking the incoming links in the PBR Shader, new Textures are generated that will include all the node transformations."""
25 | bl_idname = "object.clean_dupli_textures"
26 | bl_label = "Delete Texture Duplicates"
27 |
28 | def execute(self, context):
29 |
30 | # images = bpy.data.images
31 | # compareImages(images)
32 | material_functions.clean_empty_materials(self)
33 |
34 | return {'FINISHED'}
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Lorenz Wieseke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Functions/constants.py:
--------------------------------------------------------------------------------
1 | import os
2 | import bpy
3 | class Node_Types:
4 | image_texture = 'TEX_IMAGE'
5 | pbr_node = 'BSDF_PRINCIPLED'
6 | mapping = 'MAPPING'
7 | normal_map = 'NORMAL_MAP'
8 | bump_map = 'BUMP'
9 | material_output = 'OUTPUT_MATERIAL'
10 |
11 | class Shader_Node_Types:
12 | emission = "ShaderNodeEmission"
13 | image_texture = "ShaderNodeTexImage"
14 | mapping = "ShaderNodeMapping"
15 | normal = "ShaderNodeNormalMap"
16 | ao = "ShaderNodeAmbientOcclusion"
17 | uv = "ShaderNodeUVMap"
18 | comp_image_node = 'CompositorNodeImage'
19 | mix ="ShaderNodeMixRGB"
20 |
21 |
22 | class Bake_Passes:
23 | pbr = ["EMISSION"]
24 | lightmap = ["NOISY", "NRM", "COLOR"]
25 | ao = ["AO","COLOR"]
26 |
27 | class Material_Suffix:
28 | bake_type_mat_suffix = {
29 | "pbr" : "_Bake",
30 | "ao" : "_AO",
31 | "lightmap" : "_AO"
32 | }
33 | class Path_List:
34 | def get_project_dir():
35 | return os.path.dirname(bpy.data.filepath)
36 |
37 | def get_textures_dir():
38 | return os.path.join(os.path.dirname(bpy.data.filepath),'textures','GLBTexTool')
39 |
40 |
--------------------------------------------------------------------------------
/Functions/object_functions.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .. Functions import node_functions
3 |
4 | def apply_transform_on_linked():
5 | bpy.ops.object.select_linked(type='OBDATA')
6 | bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, animation=False)
7 | bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
8 | bpy.ops.object.make_links_data(type='OBDATA')
9 |
10 | def select_object(self, obj):
11 | C = bpy.context
12 | O = bpy.ops
13 | try:
14 | O.object.select_all(action='DESELECT')
15 | C.view_layer.objects.active = obj
16 | obj.select_set(True)
17 | except:
18 | self.report({'INFO'}, "Object not in View Layer")
19 |
20 | def select_obj_by_mat(mat,self=None):
21 | D = bpy.data
22 | result = []
23 | for obj in D.objects:
24 | if obj.type == "MESH":
25 | object_materials = [slot.material for slot in obj.material_slots]
26 | if mat in object_materials:
27 | result.append(obj)
28 | if (self):
29 | select_object(self, obj)
30 |
31 | return result
32 |
33 | # TODO - save objects in array, unlink objects, apply scale and link them back
34 | def apply_scale_on_multiuser():
35 | O = bpy.ops
36 | O.object.transform_apply(location=False, rotation=False, scale=True)
--------------------------------------------------------------------------------
/Functions/material_functions.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | def get_all_visible_materials():
3 | objects=[ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
4 | slot_array = [object.material_slots for object in objects]
5 | vis_mat = set()
6 | for slots in slot_array:
7 | for slot in slots:
8 | vis_mat.add(slot.material)
9 |
10 | # to remove None values in list
11 | vis_mat = list(filter(None, vis_mat))
12 | return vis_mat
13 |
14 | def get_selected_materials(selected_objects):
15 | selected_materials = set()
16 | slots_array = [obj.material_slots for obj in selected_objects]
17 | for slots in slots_array:
18 | for slot in slots:
19 | selected_materials.add(slot.material)
20 | return selected_materials
21 |
22 | def clean_empty_materials():
23 | for obj in bpy.data.objects:
24 | for slot in obj.material_slots:
25 | mat = slot.material
26 | if mat is None:
27 | print("Removed Empty Materials from " + obj.name)
28 | bpy.ops.object.select_all(action='DESELECT')
29 | obj.select_set(True)
30 | bpy.ops.object.material_slot_remove()
31 |
32 | def clean_no_user_materials():
33 | for material in bpy.data.materials:
34 | if not material.users:
35 | bpy.data.materials.remove(material)
36 |
37 | def use_nodes():
38 | for material in bpy.data.materials:
39 | if not material.use_nodes:
40 | material.use_nodes = True
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # This program is free software; you can redistribute it and/or modify
2 | # it under the terms of the GNU General Public License as published by
3 | # the Free Software Foundation; either version 3 of the License, or
4 | # (at your option) any later version.
5 | #
6 | # This program is distributed in the hope that it will be useful, but
7 | # WITHOUT ANY WARRANTY; without even the implied warranty of
8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 | # General Public License for more details.
10 | #
11 | # You should have received a copy of the GNU General Public License
12 | # along with this program. If not, see .
13 |
14 | bl_info = {
15 | "name": "GLBTextureTools",
16 | "author": "Lorenz Wieseke",
17 | "description": "",
18 | "blender": (4, 0, 0),
19 | "version": (0,1,5),
20 | "location": "3DView > Properties (N -KEY) > GLB Texture Tools",
21 | "warning": "",
22 | "wiki_url": "https://govie-editor.de/help/glb-texture-tools/",
23 | "tracker_url": "https://github.com/LorenzWieseke/GLBTextureTools/issues",
24 | "category": "Generic"
25 | }
26 |
27 | import bpy
28 | from .Functions import gui_functions
29 | from .Functions import image_functions
30 |
31 |
32 | from . import auto_load
33 |
34 | auto_load.init()
35 | classes = auto_load.init()
36 |
37 | def register():
38 | auto_load.register()
39 | bpy.app.handlers.depsgraph_update_post.clear()
40 | bpy.app.handlers.depsgraph_update_post.append(gui_functions.update_on_selection)
41 | bpy.app.handlers.load_post.append(gui_functions.init_values)
42 | bpy.app.handlers.save_pre.append(image_functions.save_images)
43 |
44 | def unregister():
45 | auto_load.unregister()
46 | bpy.app.handlers.depsgraph_update_post.clear()
47 | bpy.app.handlers.load_post.clear()
48 | bpy.app.handlers.save_pre.clear()
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.github/workflows/auto_deploy.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | tags:
4 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
5 |
6 | jobs:
7 | build:
8 | name: Upload Release Asset
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v2
13 |
14 | - name: Create Release Folder
15 | run: rsync -arv --exclude='.git/' --exclude='.vscode/' --exclude='.github/' --exclude='.gitignore' . ./${{ github.event.repository.name }}
16 |
17 | - name: Switch to Release Folder
18 | run: |
19 | cd ${{ github.event.repository.name }}
20 | ls -la
21 |
22 | - name: Zip Release Folder
23 | run: |
24 | zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }}
25 |
26 |
27 | - name: Create Release
28 | id: create_release
29 | uses: actions/create-release@v1
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.TOKEN }}
32 | with:
33 | tag_name: ${{ github.ref }}
34 | release_name: Release ${{ github.ref }}
35 | draft: false
36 | prerelease: false
37 |
38 | - name: Upload Release Asset
39 | id: upload-release-asset
40 | uses: actions/upload-release-asset@v1
41 | env:
42 | GITHUB_TOKEN: ${{ secrets.TOKEN }}
43 |
44 | with:
45 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
46 | asset_path: ./${{ github.event.repository.name }}.zip
47 | asset_name: ${{ github.event.repository.name }}.zip
48 | asset_content_type: application/zip
49 |
--------------------------------------------------------------------------------
/Bake/bake_manager.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from . import bake_utilities
3 |
4 | from .. Functions import constants
5 | from .. Functions import visibility_functions
6 |
7 |
8 | def bake_texture(self, selected_objects, bake_settings):
9 | parent_operator = self
10 | # ----------------------- CREATE INSTANCE --------------------#
11 | lightmap_utilities = bake_utilities.BakeUtilities(parent_operator, selected_objects, bake_settings)
12 |
13 | if not lightmap_utilities.checkPBR():
14 | return
15 |
16 | # -----------------------SET LIGHTMAP UV--------------------#
17 | lightmap_utilities.set_active_uv_to_lightmap()
18 |
19 | # -----------------------SETUP UV'S--------------------#
20 | lightmap_utilities.unwrap_selected()
21 |
22 | # -----------------------SETUP ENGINE--------------------#
23 | lightmap_utilities.setup_engine()
24 |
25 | # -----------------------SWITCH BACK TO SHOW ORG MATERIAL --------------------#
26 | visibility_functions.preview_bake_texture(self,context=bpy.context)
27 |
28 | # ----------------------- CREATE NEW MATERIAL FOR BAKING --------------------#
29 | lightmap_utilities.create_bake_material("_AO")
30 |
31 | # -----------------------SETUP NODES--------------------#
32 | lightmap_utilities.add_node_setup()
33 |
34 | # ----------------------- BAKING --------------------#
35 | if bake_settings.lightmap_bake:
36 | lightmap_utilities.save_metal_value()
37 | lightmap_utilities.bake(constants.Bake_Passes.lightmap)
38 | lightmap_utilities.load_metal_value()
39 | lightmap_utilities.add_lightmap_flag()
40 |
41 | if bake_settings.ao_bake:
42 | lightmap_utilities.bake(constants.Bake_Passes.ao)
43 |
44 | lightmap_utilities.cleanup()
45 | del lightmap_utilities
46 | return
47 |
48 |
49 | def bake_on_plane(self,selected_objects,bake_settings):
50 |
51 | parent_operator = self
52 |
53 | # ----------------------- CREATE INSTANCE --------------------#
54 | pbr_utilities = bake_utilities.PbrBakeUtilities(parent_operator,selected_objects,bake_settings)
55 |
56 | # -----------------------SETUP ENGINE--------------------#
57 |
58 | pbr_utilities.setup_engine()
59 |
60 | # ----------------------- BAKE --------------------#
61 |
62 | pbr_utilities.bake_materials_on_object()
63 | del pbr_utilities
64 |
65 | return
66 |
--------------------------------------------------------------------------------
/Operators/bake_operator.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from ..Bake import bake_manager
3 | from .. Functions import gui_functions
4 |
5 |
6 | class GTT_BakeOperator(bpy.types.Operator):
7 | """Bake all attached Textures"""
8 | bl_idname = "object.gtt_bake_operator"
9 | bl_label = "Simple Object Operator"
10 |
11 | @classmethod
12 | def poll(cls, context):
13 | if len(context.selected_objects) < 1:
14 | return False
15 | else:
16 | return True
17 |
18 | def deselect_everything_but_mesh(self,selected_objects):
19 | for obj in selected_objects:
20 | if obj.type != 'MESH':
21 | obj.select_set(False)
22 |
23 |
24 | def execute(self,context):
25 |
26 | # ----------------------- VAR --------------------#
27 | selected_objects = context.selected_objects
28 | bake_settings = context.scene.bake_settings
29 | texture_settings = context.scene.texture_settings
30 | self.deselect_everything_but_mesh(selected_objects)
31 |
32 | # ----------------------- CHECK SELECTION --------------------#
33 | for obj in selected_objects:
34 | if obj.type != 'MESH':
35 | obj.select_set(False)
36 |
37 | if len(obj.material_slots) == 0:
38 | self.report({'INFO'}, 'No Material on ' + obj.name)
39 |
40 | context.view_layer.objects.active = context.selected_objects[0]
41 | # ----------------------- SET VISIBLITY TO MATERIAL --------------------#
42 | texture_settings.preview_bake_texture = False
43 |
44 | # ----------------------- LIGHTMAP / AO --------------------#
45 | if bake_settings.lightmap_bake or bake_settings.ao_bake:
46 | bake_manager.bake_texture(self,selected_objects,bake_settings)
47 |
48 | if bake_settings.show_texture_after_bake and bake_settings.denoise:
49 | texture_settings.preview_bake_texture = True
50 |
51 | for obj in selected_objects:
52 | if bake_settings.ao_bake:
53 | obj.ao_map_name = bake_settings.bake_image_name
54 | if bake_settings.lightmap_bake:
55 | obj.lightmap_name = bake_settings.bake_image_name
56 |
57 | gui_functions.update_active_element_in_bake_list()
58 |
59 | # ----------------------- PBR Texture --------------------#
60 | if bake_settings.pbr_bake:
61 | bake_manager.bake_on_plane(self,selected_objects,bake_settings)
62 |
63 |
64 | return {'FINISHED'}
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/Functions/gui_functions.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from bpy.app.handlers import persistent
3 |
4 |
5 | def update_pbr_button(self,context):
6 | self["lightmap_bake"] = False
7 | self["ao_bake"] = False
8 |
9 | def update_lightmap_button(self,context):
10 | self["pbr_bake"] = False
11 | self["ao_bake"] = False
12 | update_active_element_in_bake_list()
13 |
14 | def update_ao_button(self,context):
15 | self["lightmap_bake"] = False
16 | self["pbr_bake"] = False
17 | update_active_element_in_bake_list()
18 |
19 |
20 | # ----------------------- UPDATE BAKE IMAGE NAME / ENUM--------------------#
21 |
22 | last_selection = []
23 |
24 | @persistent
25 | def update_on_selection(scene):
26 | C = bpy.context
27 | global last_selection
28 | object = getattr(C,"object",None)
29 | if object is None:
30 | return
31 |
32 | if C.selected_objects != last_selection:
33 | last_selection = C.selected_objects
34 | update_active_element_in_bake_list()
35 |
36 | def update_bake_list(bake_settings,context):
37 |
38 | bake_textures_set = set()
39 |
40 | for obj in bpy.data.objects:
41 | if bake_settings.lightmap_bake:
42 | if obj.get("lightmap_name"):
43 | bake_textures_set.add((obj.lightmap_name, obj.lightmap_name, "Baked Texture Name"))
44 | if bake_settings.ao_bake:
45 | if obj.get("ao_map_name"):
46 | bake_textures_set.add((obj.ao_map_name, obj.ao_map_name, "Baked Texture Name"))
47 |
48 | if len(bake_textures_set) == 0:
49 | bake_textures_set.add(("-- Baking Groups --","-- Baking Groups --","No Lightmap baked yet"))
50 |
51 | return list(bake_textures_set)
52 |
53 |
54 | def update_active_element_in_bake_list():
55 | C = bpy.context
56 | active_object = C.active_object
57 | bake_settings = C.scene.bake_settings
58 | new_bake_image_name = ""
59 |
60 | if bake_settings.lightmap_bake:
61 | new_bake_image_name = active_object.get("lightmap_name")
62 | if new_bake_image_name is None:
63 | new_bake_image_name = "Lightmap " + active_object.name
64 | if bake_settings.ao_bake:
65 | new_bake_image_name = active_object.get("ao_map_name")
66 | if new_bake_image_name is None:
67 | new_bake_image_name = "AO " + active_object.name
68 |
69 | enum_items = bake_settings.get_baked_lightmaps()
70 | keys = [key[0] for key in enum_items]
71 | if new_bake_image_name in keys:
72 | bake_settings.bake_image_name = new_bake_image_name
73 | bake_settings.baking_groups = new_bake_image_name
74 | else:
75 | if active_object.type == "MESH":
76 | bake_settings.bake_image_name = new_bake_image_name
77 |
78 |
79 | def headline(layout,*valueList):
80 | box = layout.box()
81 | row = box.row()
82 |
83 | split = row.split()
84 | for pair in valueList:
85 | split = split.split(factor=pair[0])
86 | split.label(text=pair[1])
87 |
88 | @persistent
89 | def init_values(self,context):
90 | bpy.context.scene.world.light_settings.distance = 1
91 |
92 |
--------------------------------------------------------------------------------
/Functions/image_functions.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import os
3 | from . import node_functions
4 | from . import constants
5 | from bpy.app.handlers import persistent
6 |
7 | @persistent
8 | def save_images(self,context):
9 | images = bpy.data.images
10 | for img in images:
11 | if img.is_dirty:
12 | print(img.name)
13 | save_image(img) # img.save()
14 |
15 |
16 | def get_all_images_in_ui_list():
17 |
18 | images_in_scene = bpy.data.images
19 | image_name_list = bpy.types.GTT_TEX_UL_List.image_name_list
20 | images_found = []
21 |
22 | if len(image_name_list) > 0:
23 | images_found = [img for img in images_in_scene for name_list_entry in image_name_list if img.name == name_list_entry]
24 |
25 | return images_found
26 |
27 |
28 | def save_image(image,save_internally=False):
29 |
30 | if save_internally:
31 | image.pack()
32 | else:
33 | filePath = bpy.data.filepath
34 | path = os.path.dirname(filePath)
35 |
36 | if not os.path.exists(path + "/textures"):
37 | os.mkdir(path + "/textures")
38 |
39 | if not os.path.exists(path + "/textures/GLBTexTool"):
40 | os.mkdir(path + "/textures/GLBTexTool")
41 |
42 | if not os.path.exists(path + "/textures/GLBTexTool/" + str(image.size[0])):
43 | os.mkdir(path + "/textures/GLBTexTool/" + str(image.size[0]))
44 |
45 | # file format
46 | image.file_format = bpy.context.scene.img_file_format
47 |
48 | # change path
49 | savepath = path + "\\textures\\GLBTexTool\\" + str(image.size[0]) + "\\" + image.name + "." + image.file_format
50 | # image.use_fake_user = True
51 | image.filepath_raw = savepath
52 | image.save()
53 |
54 |
55 |
56 | def create_image(image_name, image_size):
57 | D = bpy.data
58 | # find image
59 | image = D.images.get(image_name)
60 |
61 | if image:
62 | old_size = list(image.size)
63 | new_size = list(image_size)
64 |
65 | if old_size != new_size:
66 | D.images.remove(image)
67 | image = None
68 |
69 | # image = D.images.get(image_name)
70 |
71 | if image is None:
72 | image = D.images.new(
73 | image_name, width=image_size[0], height=image_size[1])
74 | image.name = image_name
75 |
76 | return image
77 |
78 | def get_file_size(filepath):
79 | size = "Unpack Files"
80 | try:
81 | path = bpy.path.abspath(filepath)
82 | size = os.path.getsize(path)
83 | size /= 1024
84 | except:
85 | return ("Unpack")
86 | # print("error getting file path for " + filepath)
87 |
88 | return (size)
89 |
90 | def scale_image(image, new_size):
91 | if (image.org_filepath != ''):
92 | image.filepath = image.org_filepath
93 |
94 | image.org_filepath = image.filepath
95 |
96 | if new_size[0] > image.size[0] or new_size[1] > image.size[1]:
97 | new_size[0] = image.size[0]
98 | new_size[1] = image.size[1]
99 |
100 | # set image back to original if size is 0, else scale it
101 | if new_size[0] == 0:
102 | image.filepath_raw = image.org_filepath
103 | else:
104 | image.scale(new_size[0], new_size[1])
105 | save_image(image)
106 |
107 |
108 |
--------------------------------------------------------------------------------
/Panels/panel_ui_list.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .. Functions import node_functions
3 | from .. Functions import image_functions
4 | from .. Functions import constants
5 |
6 | class GTT_TEX_UL_List(bpy.types.UIList):
7 | image_name_list = set()
8 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
9 | if self.layout_type in {'DEFAULT', 'COMPACT'}:
10 | row = layout.row()
11 |
12 | split = row.split(factor=0.6)
13 | split.label(text=str(item.users) + " " +item.name)
14 |
15 | split = split.split(factor=0.5)
16 | split.label(text=str(item.size[0]))
17 |
18 | split = split.split(factor=1)
19 |
20 | filepath = item.filepath
21 | if filepath != '':
22 | filesize = image_functions.get_file_size(filepath)
23 | split.label(text=str(filesize).split('.')[0])
24 | else:
25 | split.label(text="file not saved")
26 |
27 | def filter_items(self, context, data, propname):
28 | texture_settings = context.scene.texture_settings
29 | selected_objects = context.selected_objects
30 |
31 | all_materials = set()
32 | all_image_names = [image.name for image in data.images]
33 |
34 | slots_array = [obj.material_slots for obj in selected_objects]
35 | for slots in slots_array:
36 | for slot in slots:
37 | all_materials.add(slot.material)
38 |
39 | # Default return values.
40 | flt_flags = []
41 | flt_neworder = []
42 | images = []
43 |
44 | if texture_settings.show_all_textures:
45 | images = [image.name for image in data.images if image.name not in ('Viewer Node','Render Result')]
46 | if texture_settings.show_per_material:
47 | mat = context.active_object.active_material
48 | nodes = mat.node_tree.nodes
49 | tex_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
50 | [images.append(node.image.name) for node in tex_nodes]
51 | else:
52 | for mat in all_materials:
53 | if mat is None:
54 | continue
55 | nodes = mat.node_tree.nodes
56 | tex_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
57 | [images.append(node.image.name) for node in tex_nodes]
58 |
59 | # save filtered list to texture settings / ui_list_itmes
60 | self.image_name_list.clear()
61 | for img in images:
62 | self.image_name_list.add(img)
63 | flt_flags = [self.bitflag_filter_item if name in images else 0 for name in all_image_names]
64 |
65 | return flt_flags, flt_neworder
66 |
67 |
68 |
69 | class GTT_BAKE_IMAGE_UL_List(bpy.types.UIList):
70 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
71 |
72 | if self.layout_type in {'DEFAULT', 'COMPACT'}:
73 | row = layout.row()
74 | row.label(text=item.name)
75 |
76 | def filter_items(self, context, data, propname):
77 | bake_settings = context.scene.bake_settings
78 | objects = data.objects
79 |
80 | # Default return values.
81 | flt_flags = []
82 | flt_neworder = []
83 |
84 | flt_flags = [self.bitflag_filter_item if obj.lightmap_name == bake_settings.baking_groups and obj.hasLightmap else 0 for obj in objects]
85 |
86 | return flt_flags, flt_neworder
87 |
88 |
89 |
--------------------------------------------------------------------------------
/.vscode/.ropeproject/config.py:
--------------------------------------------------------------------------------
1 | # The default ``config.py``
2 | # flake8: noqa
3 |
4 |
5 | def set_prefs(prefs):
6 | """This function is called before opening the project"""
7 |
8 | # Specify which files and folders to ignore in the project.
9 | # Changes to ignored resources are not added to the history and
10 | # VCSs. Also they are not returned in `Project.get_files()`.
11 | # Note that ``?`` and ``*`` match all characters but slashes.
12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
14 | # '.svn': matches 'pkg/.svn' and all of its children
15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
18 | '.hg', '.svn', '_svn', '.git', '.tox']
19 |
20 | # Specifies which files should be considered python files. It is
21 | # useful when you have scripts inside your project. Only files
22 | # ending with ``.py`` are considered to be python files by
23 | # default.
24 | # prefs['python_files'] = ['*.py']
25 |
26 | # Custom source folders: By default rope searches the project
27 | # for finding source folders (folders that should be searched
28 | # for finding modules). You can add paths to that list. Note
29 | # that rope guesses project source folders correctly most of the
30 | # time; use this if you have any problems.
31 | # The folders should be relative to project root and use '/' for
32 | # separating folders regardless of the platform rope is running on.
33 | # 'src/my_source_folder' for instance.
34 | # prefs.add('source_folders', 'src')
35 |
36 | # You can extend python path for looking up modules
37 | # prefs.add('python_path', '~/python/')
38 |
39 | # Should rope save object information or not.
40 | prefs['save_objectdb'] = True
41 | prefs['compress_objectdb'] = False
42 |
43 | # If `True`, rope analyzes each module when it is being saved.
44 | prefs['automatic_soa'] = True
45 | # The depth of calls to follow in static object analysis
46 | prefs['soa_followed_calls'] = 0
47 |
48 | # If `False` when running modules or unit tests "dynamic object
49 | # analysis" is turned off. This makes them much faster.
50 | prefs['perform_doa'] = True
51 |
52 | # Rope can check the validity of its object DB when running.
53 | prefs['validate_objectdb'] = True
54 |
55 | # How many undos to hold?
56 | prefs['max_history_items'] = 32
57 |
58 | # Shows whether to save history across sessions.
59 | prefs['save_history'] = True
60 | prefs['compress_history'] = False
61 |
62 | # Set the number spaces used for indenting. According to
63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's
64 | # unit-tests use 4 spaces it is more reliable, too.
65 | prefs['indent_size'] = 4
66 |
67 | # Builtin and c-extension modules that are allowed to be imported
68 | # and inspected by rope.
69 | prefs['extension_modules'] = []
70 |
71 | # Add all standard c-extensions to extension_modules list.
72 | prefs['import_dynload_stdmods'] = True
73 |
74 | # If `True` modules with syntax errors are considered to be empty.
75 | # The default value is `False`; When `False` syntax errors raise
76 | # `rope.base.exceptions.ModuleSyntaxError` exception.
77 | prefs['ignore_syntax_errors'] = False
78 |
79 | # If `True`, rope ignores unresolvable imports. Otherwise, they
80 | # appear in the importing namespace.
81 | prefs['ignore_bad_imports'] = False
82 |
83 | # If `True`, rope will insert new module imports as
84 | # `from import ` by default.
85 | prefs['prefer_module_from_imports'] = False
86 |
87 | # If `True`, rope will transform a comma list of imports into
88 | # multiple separate import statements when organizing
89 | # imports.
90 | prefs['split_imports'] = False
91 |
92 | # If `True`, rope will remove all top-level import statements and
93 | # reinsert them at the top of the module when making changes.
94 | prefs['pull_imports_to_top'] = True
95 |
96 | # If `True`, rope will sort imports alphabetically by module name instead
97 | # of alphabetically by import statement, with from imports after normal
98 | # imports.
99 | prefs['sort_imports_alphabetically'] = False
100 |
101 | # Location of implementation of
102 | # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
103 | # case, you don't have to change this value, unless you're an rope expert.
104 | # Change this value to inject you own implementations of interfaces
105 | # listed in module rope.base.oi.type_hinting.providers.interfaces
106 | # For example, you can add you own providers for Django Models, or disable
107 | # the search type-hinting in a class hierarchy, etc.
108 | prefs['type_hinting_factory'] = (
109 | 'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
110 |
111 |
112 | def project_opened(project):
113 | """This function is called after opening the project"""
114 | # Do whatever you like here!
115 |
--------------------------------------------------------------------------------
/auto_load.py:
--------------------------------------------------------------------------------
1 | import os
2 | import bpy
3 | import sys
4 | import typing
5 | import inspect
6 | import pkgutil
7 | import importlib
8 | from pathlib import Path
9 |
10 | __all__ = (
11 | "init",
12 | "register",
13 | "unregister",
14 | )
15 |
16 | blender_version = bpy.app.version
17 |
18 | modules = None
19 | ordered_classes = None
20 |
21 | def init():
22 | global modules
23 | global ordered_classes
24 |
25 | modules = get_all_submodules(Path(__file__).parent)
26 | ordered_classes = get_ordered_classes_to_register(modules)
27 |
28 | def register():
29 | for cls in ordered_classes:
30 | bpy.utils.register_class(cls)
31 |
32 | for module in modules:
33 | if module.__name__ == __name__:
34 | continue
35 | if hasattr(module, "register"):
36 | module.register()
37 |
38 | def unregister():
39 | for cls in reversed(ordered_classes):
40 | bpy.utils.unregister_class(cls)
41 |
42 | for module in modules:
43 | if module.__name__ == __name__:
44 | continue
45 | if hasattr(module, "unregister"):
46 | module.unregister()
47 |
48 |
49 | # Import modules
50 | #################################################
51 |
52 | def get_all_submodules(directory):
53 | return list(iter_submodules(directory, directory.name))
54 |
55 | def iter_submodules(path, package_name):
56 | for name in sorted(iter_submodule_names(path)):
57 | yield importlib.import_module("." + name, package_name)
58 |
59 | def iter_submodule_names(path, root=""):
60 | for _, module_name, is_package in pkgutil.iter_modules([str(path)]):
61 | if is_package:
62 | sub_path = path / module_name
63 | sub_root = root + module_name + "."
64 | yield from iter_submodule_names(sub_path, sub_root)
65 | else:
66 | yield root + module_name
67 |
68 |
69 | # Find classes to register
70 | #################################################
71 |
72 | def get_ordered_classes_to_register(modules):
73 | return toposort(get_register_deps_dict(modules))
74 |
75 | def get_register_deps_dict(modules):
76 | my_classes = set(iter_my_classes(modules))
77 | my_classes_by_idname = {cls.bl_idname : cls for cls in my_classes if hasattr(cls, "bl_idname")}
78 |
79 | deps_dict = {}
80 | for cls in my_classes:
81 | deps_dict[cls] = set(iter_my_register_deps(cls, my_classes, my_classes_by_idname))
82 | return deps_dict
83 |
84 | def iter_my_register_deps(cls, my_classes, my_classes_by_idname):
85 | yield from iter_my_deps_from_annotations(cls, my_classes)
86 | yield from iter_my_deps_from_parent_id(cls, my_classes_by_idname)
87 |
88 | def iter_my_deps_from_annotations(cls, my_classes):
89 | for value in typing.get_type_hints(cls, {}, {}).values():
90 | dependency = get_dependency_from_annotation(value)
91 | if dependency is not None:
92 | if dependency in my_classes:
93 | yield dependency
94 |
95 | def get_dependency_from_annotation(value):
96 | if blender_version >= (2, 93):
97 | if isinstance(value, bpy.props._PropertyDeferred):
98 | return value.keywords.get("type")
99 | else:
100 | if isinstance(value, tuple) and len(value) == 2:
101 | if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
102 | return value[1]["type"]
103 | return None
104 |
105 | def iter_my_deps_from_parent_id(cls, my_classes_by_idname):
106 | if bpy.types.Panel in cls.__bases__:
107 | parent_idname = getattr(cls, "bl_parent_id", None)
108 | if parent_idname is not None:
109 | parent_cls = my_classes_by_idname.get(parent_idname)
110 | if parent_cls is not None:
111 | yield parent_cls
112 |
113 | def iter_my_classes(modules):
114 | base_types = get_register_base_types()
115 | for cls in get_classes_in_modules(modules):
116 | if any(base in base_types for base in cls.__bases__):
117 | if not getattr(cls, "is_registered", False):
118 | yield cls
119 |
120 | def get_classes_in_modules(modules):
121 | classes = set()
122 | for module in modules:
123 | for cls in iter_classes_in_module(module):
124 | classes.add(cls)
125 | return classes
126 |
127 | def iter_classes_in_module(module):
128 | for value in module.__dict__.values():
129 | if inspect.isclass(value):
130 | yield value
131 |
132 | def get_register_base_types():
133 | return set(getattr(bpy.types, name) for name in [
134 | "Panel", "Operator", "PropertyGroup",
135 | "AddonPreferences", "Header", "Menu",
136 | "Node", "NodeSocket", "NodeTree",
137 | "UIList", "RenderEngine",
138 | "Gizmo", "GizmoGroup",
139 | ])
140 |
141 |
142 | # Find order to register to solve dependencies
143 | #################################################
144 |
145 | def toposort(deps_dict):
146 | sorted_list = []
147 | sorted_values = set()
148 | while len(deps_dict) > 0:
149 | unsorted = []
150 | for value, deps in deps_dict.items():
151 | if len(deps) == 0:
152 | sorted_list.append(value)
153 | sorted_values.add(value)
154 | else:
155 | unsorted.append(value)
156 | deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
157 | return sorted_list
--------------------------------------------------------------------------------
/Properties/properties.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from bpy import context
3 | from bpy.props import *
4 | from .. Functions import gui_functions
5 | from .. Functions import visibility_functions
6 |
7 |
8 | bpy.types.Scene.img_bake_size = EnumProperty(
9 | name='Image Size',
10 | description='Set resolution for baking and scaling images. This effects PBR, AO and Lightmap baking as well as scaling imges. As of scaling images, choose ORIGINAL to switch back to your image before scaling.',
11 | default='1024',
12 | items=[
13 | ('128', '128', 'Set image size to 128'),
14 | ('256', '256', 'Set image size to 256'),
15 | ('512', '512', 'Set image size to 512'),
16 | ('1024', '1024', 'Set image size to 1024'),
17 | ('2048', '2048', 'Set image size to 2048'),
18 | ('4096', '4096', 'Set image size to 4096'),
19 | ('8184', '8184', 'Set image size to 8184'),
20 | ('0', 'Original', 'Set image back to original file'),
21 | ])
22 |
23 | bpy.types.Scene.img_file_format = EnumProperty(
24 | name='File Format',
25 | description='Set file format for output image',
26 | default='JPEG',
27 | items=[
28 | ('JPEG', 'JPEG', 'JPG is a lossy format with no additional alpha channel, use for color maps'),
29 | ('PNG', 'PNG', 'PNG is lossless and has option for alpha channel, use for normal maps'),
30 | ('HDR', 'HDR', 'HDR is a 32 bit format, use if you need more details or see color banding'),
31 | ])
32 |
33 | bpy.types.Scene.affect = EnumProperty(
34 | name='Affect',
35 | description='Define if operator should run on active material, all materials on selected object, all materials on visible objects or all materials in scene.',
36 | default='linked',
37 | items=[
38 | ('active', 'ACTIVE', 'Change only active materials'),
39 | ('selected', 'SELECTED', 'Change all selected materials'),
40 | ('linked', 'LINKED MATERIALS', 'Change all materials on selected objects and all objects that share these materials'),
41 | ('visible', 'VISIBLE', 'Change all visible materials'),
42 | ('scene', 'SCENE', 'Change all materials in scene'),
43 | ])
44 |
45 |
46 |
47 | class GTT_UV_Settings(bpy.types.PropertyGroup):
48 | uv_slot: IntProperty(default=1)
49 | uv_name: StringProperty(default="AO")
50 |
51 | bpy.utils.register_class(GTT_UV_Settings)
52 | bpy.types.Scene.uv_settings = PointerProperty(type=GTT_UV_Settings)
53 |
54 | class GTT_Cleanup_Settings(bpy.types.PropertyGroup):
55 | clean_texture: BoolProperty(default=True)
56 | clean_material: BoolProperty(default=True)
57 | clean_bake: BoolProperty(default=False)
58 | clean_node_tree: BoolProperty(default=False)
59 |
60 | bpy.utils.register_class(GTT_Cleanup_Settings)
61 | bpy.types.Scene.cleanup_settings = PointerProperty(type=GTT_Cleanup_Settings)
62 |
63 |
64 | class GTT_Bake_Settings(bpy.types.PropertyGroup):
65 | open_bake_settings_menu: BoolProperty(default = False)
66 | open_object_bake_list_menu: BoolProperty(default = False)
67 |
68 | # Type of bake
69 | pbr_bake: BoolProperty(default = True,update=gui_functions.update_pbr_button)
70 | pbr_samples: IntProperty(name = "Samples for PBR bake", default = 1)
71 |
72 | ao_bake: BoolProperty(default = False,update=gui_functions.update_ao_button)
73 | ao_samples: IntProperty(name = "Samples for AO bake", default = 2)
74 |
75 | lightmap_bake: BoolProperty(default = False,update=gui_functions.update_lightmap_button)
76 | lightmap_samples: IntProperty(name = "Samples for Lightmap bake", default = 10)
77 |
78 | baking_groups: EnumProperty(
79 | name='Baked Textures',
80 | description='Groups of objects that share the same baking maps. Click on cursor on the right to select all objectes in that group.',
81 | items=gui_functions.update_bake_list
82 | )
83 |
84 | def get_current_bake_type(self):
85 | if self.pbr_bake:
86 | current_bake_type = "pbr"
87 | if self.ao_bake:
88 | current_bake_type = "ao"
89 | if self.lightmap_bake:
90 | current_bake_type = "lightmap"
91 |
92 | return current_bake_type
93 |
94 | def get_baked_lightmaps(context):
95 | return gui_functions.update_bake_list(context,context)
96 |
97 | # render_pass : EnumProperty(name='Render Pass',description='Define Render Pass',items=[("Combined","Combined","Bake all passes in this singel Combined Pass"),("Lightmap","Lightmap","Lightmap")])
98 |
99 | # Checkbox settings
100 | bake_image_name: StringProperty(default="Lightmap")
101 | bake_image_clear: BoolProperty(default= True)
102 | mute_texture_nodes: BoolProperty(default = True)
103 | bake_margin:IntProperty(default=2)
104 | unwrap_margin:FloatProperty(default=0.002)
105 | unwrap: BoolProperty(default= True)
106 | denoise: BoolProperty(default=False)
107 | show_texture_after_bake: BoolProperty(default=True)
108 | bake_object_index:IntProperty(name = "Index for baked Objects", default = 0)
109 |
110 | uv_name="Lightmap"
111 | texture_node_lightmap="Lightmap"
112 | texture_node_ao="AO"
113 | cleanup_textures=False
114 |
115 | bpy.utils.register_class(GTT_Bake_Settings)
116 | bpy.types.Scene.bake_settings = PointerProperty(type=GTT_Bake_Settings)
117 | class GTT_Texture_Settings(bpy.types.PropertyGroup):
118 | open_texture_settings_menu:BoolProperty(default=False)
119 | open_sel_mat_menu:BoolProperty(default=False)
120 | show_all_textures:BoolProperty(default=False)
121 | show_per_material:BoolProperty(default=False)
122 | operate_on_all_textures:BoolProperty(default=False)
123 |
124 | preview_bake_texture:BoolProperty(default=False,update=visibility_functions.preview_bake_texture)
125 | preview_lightmap:BoolProperty(default=False,update=visibility_functions.preview_lightmap)
126 | texture_index:IntProperty(name = "Index for Texture List", default=0, update=visibility_functions.update_selected_image)
127 |
128 | bpy.utils.register_class(GTT_Texture_Settings)
129 | bpy.types.Scene.texture_settings = PointerProperty(type=GTT_Texture_Settings)
130 |
131 | # HELP PANEL PROPERTIES
132 | def run_help_operator(self,context):
133 | bpy.ops.scene.help('INVOKE_DEFAULT')
134 |
135 | bpy.types.Scene.help_tex_tools = BoolProperty(default=False,update=run_help_operator)
136 |
137 | # MATERIAL PROPERTIES
138 | bpy.types.Material.bake_material_name = StringProperty()
139 |
140 |
141 | # IMAGE PROPERTIES
142 | bpy.types.Image.org_filepath = StringProperty()
143 | bpy.types.Image.org_image_name = StringProperty()
144 |
145 | # OBJECT PROPERTIES
146 | bpy.types.Object.hasLightmap = BoolProperty()
147 | bpy.types.Object.lightmap_name = StringProperty()
148 | bpy.types.Object.ao_map_name = StringProperty()
149 | bpy.types.Object.bake_version = StringProperty()
150 |
151 |
--------------------------------------------------------------------------------
/Functions/visibility_functions.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from bpy import context
3 | from . import node_functions
4 | from . import material_functions
5 | from . import object_functions
6 | from . import constants
7 | from . import basic_functions
8 | import mathutils
9 |
10 |
11 | def update_selected_image(self, context):
12 | sel_texture = bpy.data.images[self.texture_index]
13 | show_image_in_image_editor(sel_texture)
14 |
15 |
16 | def show_image_in_image_editor(image):
17 | for area in bpy.context.screen.areas:
18 | if area.type == 'IMAGE_EDITOR':
19 | area.spaces.active.image = image
20 |
21 |
22 | def switch_baked_material(show_bake_material,affect):
23 |
24 | current_bake_type = bpy.context.scene.bake_settings.get_current_bake_type()
25 | material_name_suffix = constants.Material_Suffix.bake_type_mat_suffix[current_bake_type]
26 |
27 | # on what object to work
28 | if affect == 'active':
29 | objects = [bpy.context.active_object]
30 | elif affect == 'selected':
31 | objects = bpy.context.selected_editable_objects
32 | elif affect == 'visible':
33 | objects = [ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
34 | elif affect == 'scene':
35 | objects = bpy.context.scene.objects
36 | elif affect == 'linked':
37 | objects = []
38 | selected_objects = bpy.context.selected_editable_objects
39 | selected_materials = material_functions.get_selected_materials(selected_objects)
40 |
41 | for material in selected_materials:
42 | objs = object_functions.select_obj_by_mat(material)
43 | objects.append(objs)
44 | objects = basic_functions.flatten(objects)
45 | objects = basic_functions.remove_duplicate(objects)
46 |
47 |
48 | all_mats = bpy.data.materials
49 | baked_mats = [mat for mat in all_mats if material_name_suffix in mat.name]
50 |
51 |
52 | for obj in objects:
53 | # if current_bake_type != "pbr":
54 | # baked_ao_flag = getattr(obj,"ao_map_name") != '' or getattr(obj,"lightmap_name") != ''
55 | # if not baked_ao_flag:
56 | # continue
57 |
58 | for slot in obj.material_slots:
59 | if show_bake_material:
60 | for baked_mat in baked_mats:
61 | try:
62 | if baked_mat.name == slot.material.name + material_name_suffix + obj.bake_version:
63 | slot.material = baked_mat
64 | except:
65 | pass
66 |
67 | else:
68 | if (material_name_suffix in slot.material.name):
69 | bake_material = slot.material
70 | index = bake_material.name.find(material_name_suffix)
71 | org_mat = all_mats.get(bake_material.name[0:index])
72 | if org_mat is not None:
73 | slot.material = org_mat
74 |
75 | def preview_bake_texture(self,context):
76 | context = bpy.context
77 | bake_settings = context.scene.bake_settings
78 | preview_bake_texture = context.scene.texture_settings.preview_bake_texture
79 | vis_mats = material_functions.get_all_visible_materials()
80 | for mat in vis_mats:
81 | if not mat.node_tree:
82 | continue
83 |
84 | nodes = mat.node_tree.nodes
85 | bake_texture_node = None
86 | if bake_settings.lightmap_bake:
87 | bake_texture_node = nodes.get(bake_settings.texture_node_lightmap)
88 |
89 | elif bake_settings.ao_bake:
90 | bake_texture_node = nodes.get(bake_settings.texture_node_ao)
91 |
92 |
93 | if bake_texture_node is not None:
94 | if preview_bake_texture:
95 | node_functions.emission_setup(mat, bake_texture_node.outputs["Color"])
96 | else:
97 | pbr_node = node_functions.get_nodes_by_type(nodes, constants.Node_Types.pbr_node)
98 | if len(pbr_node) == 0:
99 | return
100 |
101 | pbr_node = pbr_node[0]
102 | node_functions.remove_node(mat, "Emission Bake")
103 | node_functions.reconnect_PBR(mat, pbr_node)
104 |
105 |
106 | def preview_lightmap(self, context):
107 | preview_lightmap = context.scene.texture_settings.preview_lightmap
108 | vis_mats = material_functions.get_all_visible_materials()
109 | for material in vis_mats:
110 | if not material.node_tree:
111 | continue
112 |
113 | nodes = material.node_tree.nodes
114 |
115 | lightmap_node = nodes.get("Lightmap")
116 | if lightmap_node is None:
117 | continue
118 |
119 | pbr_node = node_functions.get_pbr_node(material)
120 | if pbr_node is None:
121 | print("\n " + material.name + " has no PBR Node \n")
122 | continue
123 | base_color_input = node_functions.get_pbr_inputs(pbr_node)["base_color_input"]
124 | emission_input = node_functions.get_pbr_inputs(pbr_node)["emission_input"]
125 |
126 | lightmap_output = lightmap_node.outputs["Color"]
127 |
128 | if preview_lightmap:
129 |
130 | # add mix node
131 | mix_node_name = "Mulitply Lightmap"
132 | mix_node = node_functions.add_node(material,constants.Shader_Node_Types.mix, mix_node_name)
133 | mix_node.blend_type = 'MULTIPLY'
134 | mix_node.inputs[0].default_value = 1 # set factor to 1
135 | pos_offset = mathutils.Vector((-200, 200))
136 | mix_node.location = pbr_node.location + pos_offset
137 |
138 | mix_node_input1 = mix_node.inputs["Color1"]
139 | mix_node_input2 = mix_node.inputs["Color2"]
140 | mix_node_output = mix_node.outputs["Color"]
141 |
142 | # image texture in base color
143 | if base_color_input.is_linked:
144 | node_before_base_color = base_color_input.links[0].from_node
145 | if not node_before_base_color.name == mix_node_name:
146 | node_functions.make_link(material, node_before_base_color.outputs["Color"], mix_node_input1)
147 | node_functions.make_link(material, lightmap_output, mix_node_input2)
148 | node_functions.make_link(material, mix_node_output, base_color_input)
149 | else :
150 | mix_node_input1.default_value = base_color_input.default_value
151 | node_functions.make_link(material, lightmap_output, mix_node_input2)
152 | node_functions.make_link(material, mix_node_output, base_color_input)
153 |
154 | node_functions.remove_link(material,lightmap_output,emission_input)
155 |
156 | if not preview_lightmap:
157 |
158 | # remove mix and reconnect base color
159 |
160 | mix_node = nodes.get("Mulitply Lightmap")
161 |
162 | if mix_node is not None:
163 | color_input_connections = len(mix_node.inputs["Color1"].links)
164 |
165 | if (color_input_connections == 0):
166 | node_functions.remove_node(material,mix_node.name)
167 | else:
168 | node_functions.remove_reconnect_node(material,mix_node.name)
169 |
170 | node_functions.link_pbr_to_output(material,pbr_node)
171 |
172 |
173 |
174 |
175 |
176 | def lightmap_to_emission(self, context, connect):
177 |
178 | vis_mats = material_functions.get_all_visible_materials()
179 | for material in vis_mats:
180 | if not material.node_tree:
181 | continue
182 |
183 | nodes = material.node_tree.nodes
184 |
185 | pbr_node = node_functions.get_pbr_node(material)
186 | lightmap_node = nodes.get("Lightmap")
187 |
188 | if lightmap_node is None:
189 | continue
190 |
191 | emission_input = node_functions.get_pbr_inputs(pbr_node)["emission_input"]
192 | lightmap_output = lightmap_node.outputs["Color"]
193 |
194 | if connect:
195 | node_functions.make_link(material, lightmap_output, emission_input)
196 | else:
197 | node_functions.remove_link(material,lightmap_output,emission_input)
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/Panels/panel.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .. Functions import gui_functions
3 | # from .. Update import addon_updater_ops
4 |
5 |
6 | class GTT_ResolutionPanel(bpy.types.Panel):
7 | bl_idname = "GLBTEXTOOLS_PT_resolution_panel"
8 | bl_label = "Global Settings"
9 | bl_space_type = "VIEW_3D"
10 | bl_region_type = "UI"
11 | bl_category = 'GLB Texture Tools'
12 | # bl_parent_id = "TEXTURETOOLS_PT_parent_panel"
13 | bl_order = 0
14 |
15 | def draw(self, context):
16 | layout = self.layout
17 | scene = context.scene
18 |
19 | box = layout.box()
20 | column = box.column()
21 | column.prop(scene, "img_bake_size")
22 | column.prop(scene,"img_file_format")
23 | column.prop(scene,"affect")
24 | column.prop(scene.cycles,"device")
25 |
26 | class GTT_BakeTexturePanel(bpy.types.Panel):
27 | bl_idname = "GLBTEXTOOLS_PT_bake_panel"
28 | bl_label = "Baking"
29 | bl_space_type = "VIEW_3D"
30 | bl_region_type = "UI"
31 | bl_category = 'GLB Texture Tools'
32 | bl_order = 1
33 |
34 | def draw(self, context):
35 | layout = self.layout
36 | scene = context.scene
37 | data = bpy.data
38 | bake_settings = bpy.context.scene.bake_settings
39 |
40 | row = layout.row()
41 | row.prop(bake_settings, 'open_bake_settings_menu', text="Bake Settings", icon = 'TRIA_DOWN' if bake_settings.open_bake_settings_menu else 'TRIA_RIGHT' )
42 |
43 | if bake_settings.open_bake_settings_menu:
44 |
45 | box = layout.box()
46 |
47 | col = box.column(align = True)
48 | row = col.row(align = True)
49 | row.prop(scene.bake_settings, 'pbr_bake', text="PBR",toggle = True)
50 | row.prop(scene.bake_settings, 'pbr_samples', text="Samples",toggle = True)
51 |
52 | row = col.row(align = True)
53 | row.prop(scene.bake_settings, 'ao_bake', text="AO",toggle = True)
54 | row.prop(scene.bake_settings, 'ao_samples', text="Samples")
55 |
56 | row = col.row(align = True)
57 | row.prop(scene.bake_settings, 'lightmap_bake', text="Lightmap",toggle = True)
58 | row.prop(scene.bake_settings, 'lightmap_samples', text="Samples")
59 |
60 |
61 | if bake_settings.pbr_bake:
62 | row = box.row()
63 | # col = row.collumn()
64 | row.prop(scene.bake_settings, 'mute_texture_nodes', text="Mute Texture Mapping")
65 | # row.prop(scene.bake_settings, 'bake_image_clear', text="Clear Bake Image")
66 |
67 |
68 | if bake_settings.lightmap_bake or bake_settings.ao_bake:
69 |
70 | row = box.row()
71 | row.prop(scene.bake_settings, 'bake_image_name', text="")
72 | row = box.row()
73 | row.prop(scene.bake_settings, 'baking_groups',text="")
74 | row.operator("object.select_lightmap_objects",text="",icon="RESTRICT_SELECT_OFF")
75 |
76 | if bake_settings.lightmap_bake:
77 | try:
78 | box.prop(scene.world.node_tree.nodes["Background"].inputs[1],'default_value',text="World Influence")
79 | except:
80 | pass
81 |
82 | if bake_settings.ao_bake:
83 | box.prop(scene.world.light_settings,"distance",text="AO Distance")
84 |
85 | box.prop(scene.bake_settings, 'unwrap_margin', text="UV Margin")
86 | box.prop(scene.bake_settings, 'bake_margin', text="Bake Margin")
87 |
88 | split = box.split()
89 | col = split.column(align=True)
90 | col.prop(scene.bake_settings, 'unwrap', text="Unwrap")
91 | col.prop(scene.bake_settings, 'bake_image_clear', text="Clear Bake Image")
92 |
93 | col = split.column(align=True)
94 | col.prop(scene.bake_settings, 'denoise', text="Denoise")
95 | if bake_settings.denoise:
96 | col.prop(scene.bake_settings, 'show_texture_after_bake', text="Show Texture after Bake")
97 |
98 | # LIGHTMAPPED OBJECT LIST
99 | # row = layout.row()
100 | # row.prop(scene.bake_settings, 'open_object_bake_list_menu', text="Lightmapped Objects", icon = 'TRIA_DOWN' if bake_settings.open_object_bake_list_menu else 'TRIA_RIGHT' )
101 |
102 | # BAKE LIST
103 | if bake_settings.open_object_bake_list_menu:
104 | layout.template_list("GTT_BAKE_IMAGE_UL_List", "", data, "objects", scene.bake_settings, "bake_object_index")
105 |
106 |
107 | row = layout.row(align=True)
108 | row.scale_y = 2.0
109 | row.operator("object.gtt_bake_operator",text="Bake Textures")
110 | row.operator("scene.open_textures_folder",icon='FILEBROWSER')
111 |
112 | class GTT_VisibilityPanel(bpy.types.Panel):
113 | bl_idname = "GLBTEXTOOLS_PT_visibility_panel"
114 | bl_label = "Visibility"
115 | bl_space_type = "VIEW_3D"
116 | bl_region_type = "UI"
117 | bl_category = 'GLB Texture Tools'
118 | bl_order = 2
119 |
120 | def draw(self, context):
121 | layout = self.layout
122 | scene = context.scene
123 |
124 | col = layout.column()
125 | row = col.row()
126 | row.operator("object.switch_org_mat_operator",icon = 'NODE_MATERIAL', text="Show Original Material")
127 | row.operator("object.switch_bake_mat_operator",icon = 'MATERIAL', text="Show Baked Material")
128 |
129 | # row = col.row(align=True)
130 | layout.prop(scene.texture_settings,"preview_lightmap",text="Show without Lightmap" if scene.texture_settings.preview_lightmap else "Preview Lightmap on Material", icon="SHADING_RENDERED" if scene.texture_settings.preview_bake_texture else "NODE_MATERIAL")
131 | layout.prop(scene.texture_settings,"preview_bake_texture", text="Show Material" if scene.texture_settings.preview_bake_texture else "Preview Baked Texture", icon="SHADING_RENDERED" if scene.texture_settings.preview_bake_texture else "NODE_MATERIAL")
132 |
133 |
134 | class GTT_CleanupPanel(bpy.types.Panel):
135 | bl_idname = "GLBTEXTOOLS_PT_cleanup_panel"
136 | bl_label = "Cleanup"
137 | bl_space_type = "VIEW_3D"
138 | bl_region_type = "UI"
139 | bl_category = 'GLB Texture Tools'
140 | bl_order = 3
141 |
142 | def draw(self, context):
143 | layout = self.layout
144 |
145 | row = layout.row()
146 | row.operator("image.clean_textures",text="Clean Textures",icon = 'OUTLINER_OB_IMAGE')
147 | row.operator("material.clean_materials",text="Clean Materials",icon = 'NODE_MATERIAL')
148 |
149 | row = layout.row()
150 | row.operator("material.clean_lightmap",text="Clean Lightmap",icon = 'MOD_UVPROJECT')
151 | row.operator("material.clean_ao_map",text="Clean AO Map",icon = 'TRASH')
152 |
153 | row = layout.row()
154 | # row.operator("scene.clean_unused_images",text="Clean Unused Images",icon = 'TRASH')
155 |
156 |
157 | class GTT_TextureSelectionPanel(bpy.types.Panel):
158 | bl_idname = "GLBTEXTOOLS_PT_tex_selection_panel"
159 | bl_label = "Texture"
160 | bl_space_type = "VIEW_3D"
161 | bl_region_type = "UI"
162 | bl_category = 'GLB Texture Tools'
163 | # bl_parent_id = "TEXTURETOOLS_PT_parent_panel"
164 | bl_order = 4
165 |
166 | def draw(self, context):
167 | layout = self.layout
168 | scene = context.scene
169 | data = bpy.data
170 |
171 | texture_settings = bpy.context.scene.texture_settings
172 |
173 | # UI LIST
174 | gui_functions.headline(layout,(0.6,"IMAGE NAME"),(0.5,"SIZE"),(1,"KB"))
175 | layout.template_list("GTT_TEX_UL_List", "", data, "images", scene.texture_settings, "texture_index")
176 |
177 | row = layout.row()
178 | row.prop(texture_settings, 'open_texture_settings_menu', text="Texture Settings", icon = 'TRIA_DOWN' if texture_settings.open_texture_settings_menu else 'TRIA_RIGHT' )
179 |
180 | if texture_settings.open_texture_settings_menu:
181 |
182 | box = layout.box()
183 |
184 | box.prop(scene.texture_settings, 'show_all_textures', text="Show all Textures")
185 | box.prop(scene.texture_settings, 'show_per_material', text="Show Textures per Material")
186 | box.prop(scene.texture_settings, 'operate_on_all_textures', text="Operate on all Textures in List")
187 | box.label(text="If scaled images don't get saved/exported, try unpacking before scaling !")
188 | row = box.row()
189 | row.operator("file.unpack_all",text="Unpack")
190 | row.operator("file.pack_all",text="Pack")
191 |
192 | # Select Material by Texture
193 | row = layout.row()
194 | row.prop(scene.texture_settings,"open_sel_mat_menu",text="Select Material by Texture", icon = 'TRIA_DOWN' if scene.texture_settings.open_sel_mat_menu else 'TRIA_RIGHT' )
195 |
196 | if scene.texture_settings.open_sel_mat_menu:
197 | box = layout.box()
198 | col = box.column(align = True)
199 | col.operator("scene.select_mat_by_tex",text="Select Material",icon='RESTRICT_SELECT_OFF')
200 | col = box.column(align = True)
201 | if len(scene.materials_found) > 0:
202 | col.label(text="Texture found in Material :")
203 | for mat in scene.materials_found:
204 | col.label(text=mat)
205 | else:
206 | col.label(text="Texture not used")
207 |
208 | # Scale and Clean
209 | row = layout.row()
210 | row.scale_y = 2.0
211 | row.operator("image.scale_image",text="Scale Image",icon= 'FULLSCREEN_EXIT')
212 |
213 | class GTT_UVPanel(bpy.types.Panel):
214 | bl_idname = "GLBTEXTOOLS_PT_UV_panel"
215 | bl_label = "UV"
216 | bl_space_type = "VIEW_3D"
217 | bl_region_type = "UI"
218 | bl_category = "GLB Texture Tools"
219 | bl_options = {'DEFAULT_CLOSED'}
220 | bl_order = 5
221 |
222 | def draw(self, context):
223 | layout = self.layout
224 |
225 | uv_settings = context.scene.uv_settings
226 | box = layout.box()
227 |
228 | row = box.row()
229 | row.prop(uv_settings,"uv_name", text="UV Name")
230 | row.prop(uv_settings,"uv_slot", text="UV Slot")
231 | row = box.row()
232 | row.operator("object.add_uv",text="Add UV",icon = 'ADD').uv_name = uv_settings.uv_name
233 | row.operator("object.remove_uv",text="Remove UV",icon = 'REMOVE').uv_slot = uv_settings.uv_slot
234 | row.operator("object.set_active_uv",text="Set Active",icon = 'RESTRICT_SELECT_OFF').uv_slot = uv_settings.uv_slot
235 |
236 |
237 | class GTT_HelpPanel(bpy.types.Panel):
238 | bl_idname = "GLBTEXTOOLS_PT_help_panel"
239 | bl_label = "Help"
240 | bl_space_type = "VIEW_3D"
241 | bl_region_type = "UI"
242 | bl_category = "GLB Texture Tools"
243 | bl_order = 6
244 |
245 | def draw(self,context):
246 |
247 | layout = self.layout
248 | lang = bpy.app.translations.locale
249 | if lang == 'en_US':
250 | layout.label(text="Find out how to use this addon")
251 | layout.operator("scene.open_link",text="Add-on Documentation",icon='HELP').url = "https://govie.de/en/tutorials-blender/?utm_source=blender-add-on&utm_medium=button#glb_texture_tools"
252 | if lang == 'de_DE':
253 | layout.label(text="Hilfe zur Bedienung des Add-ons")
254 | layout.operator("scene.open_link",text="Add-on Documentation",icon='HELP').url = "https://govie.de/tutorials-blender/?utm_source=blender-add-on&utm_medium=button#glb_texture_tools"
255 |
256 |
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/Functions/node_functions.py:
--------------------------------------------------------------------------------
1 | import bpy.ops as O
2 | import bpy
3 | import os
4 | from .. Functions import constants
5 | import mathutils
6 |
7 |
8 | # -----------------------COMPOSITING--------------------#
9 | def blur_bake_image(noisy_image,color_image):
10 |
11 | # switch on nodes and get reference
12 | if not bpy.context.scene.use_nodes:
13 | bpy.context.scene.use_nodes = True
14 |
15 | tree = bpy.context.scene.node_tree
16 |
17 | # add cam if not in scene
18 | cam = bpy.context.scene.camera
19 | if not cam:
20 | bpy.ops.object.camera_add()
21 |
22 | # bake image
23 | image_node = tree.nodes.new(type='CompositorNodeImage')
24 | image_node.image = noisy_image
25 | image_node.location = 0, 0
26 |
27 | # color image
28 | color_image_node = tree.nodes.new(type='CompositorNodeImage')
29 | color_image_node.image = color_image
30 | color_image_node.location = 0, 300
31 |
32 | # create blur node
33 | blur_node = tree.nodes.new(type='CompositorNodeBilateralblur')
34 | blur_node.location = 300, 0
35 |
36 | # create output node
37 | comp_node = tree.nodes.new('CompositorNodeComposite')
38 | comp_node.location = 600, 0
39 |
40 | # link nodes
41 | links = tree.links
42 | links.new(image_node.outputs[0], blur_node.inputs[0])
43 | links.new(color_image_node.outputs[0], blur_node.inputs[1])
44 | links.new(blur_node.outputs[0], comp_node.inputs[0])
45 |
46 | # set output resolution to image res
47 | bpy.context.scene.render.resolution_x = noisy_image.size[0]
48 | bpy.context.scene.render.resolution_y = noisy_image.size[1]
49 |
50 |
51 | # set output path
52 | scene = bpy.context.scene
53 | outputImagePath = constants.Path_List.get_textures_dir()
54 |
55 | # set image format and quality
56 | scene.render.image_settings.file_format = bpy.context.scene.img_file_format
57 | scene.render.image_settings.quality = 100
58 |
59 | scene.render.filepath = os.path.join(outputImagePath,noisy_image.name + "_Denoise_AO")
60 | bpy.ops.render.render(write_still=True)
61 |
62 | if bpy.context.scene.img_file_format == 'JPEG':
63 | file_extention = '.jpg'
64 | elif bpy.context.scene.img_file_format == 'PNG':
65 | file_extention = '.png'
66 | elif bpy.context.scene.img_file_format == 'HDR':
67 | file_extention = '.hdr'
68 |
69 | # cleanup
70 | comp_nodes = [image_node,color_image_node,blur_node,comp_node]
71 | for node in comp_nodes:
72 | tree.nodes.remove(node)
73 |
74 | return scene.render.filepath + file_extention
75 |
76 | def comp_ai_denoise(noisy_image, nrm_image, color_image):
77 |
78 | # switch on nodes and get reference
79 | if not bpy.context.scene.use_nodes:
80 | bpy.context.scene.use_nodes = True
81 |
82 | tree = bpy.context.scene.node_tree
83 |
84 | # add cam if not in scene
85 | cam = bpy.context.scene.camera
86 | if not cam:
87 | bpy.ops.object.camera_add()
88 |
89 | # bake image
90 | image_node = tree.nodes.new(type='CompositorNodeImage')
91 | image_node.image = noisy_image
92 | image_node.location = 0, 0
93 |
94 | # nrm image
95 | nrm_image_node = tree.nodes.new(type='CompositorNodeImage')
96 | nrm_image_node.image = nrm_image
97 | nrm_image_node.location = 0, 300
98 |
99 | # color image
100 | color_image_node = tree.nodes.new(type='CompositorNodeImage')
101 | color_image_node.image = color_image
102 | color_image_node.location = 0, 600
103 |
104 | # create denoise node
105 | denoise_node = tree.nodes.new(type='CompositorNodeDenoise')
106 | denoise_node.location = 300, 0
107 |
108 | # create output node
109 | comp_node = tree.nodes.new('CompositorNodeComposite')
110 | comp_node.location = 600, 0
111 |
112 | # link nodes
113 | links = tree.links
114 | links.new(image_node.outputs[0], denoise_node.inputs[0])
115 | links.new(nrm_image_node.outputs[0], denoise_node.inputs[1])
116 | links.new(color_image_node.outputs[0], denoise_node.inputs[2])
117 | links.new(denoise_node.outputs[0], comp_node.inputs[0])
118 |
119 | # set output resolution to image res
120 | bpy.context.scene.render.resolution_x = noisy_image.size[0]
121 | bpy.context.scene.render.resolution_y = noisy_image.size[1]
122 |
123 | # set output path
124 | scene = bpy.context.scene
125 | outputImagePath = constants.Path_List.get_textures_dir()
126 |
127 | # set image format and quality
128 | scene.render.image_settings.file_format = bpy.context.scene.img_file_format
129 | scene.render.image_settings.quality = 100
130 |
131 | scene.render.filepath = os.path.join(outputImagePath,noisy_image.name + "_Denoise_LM")
132 | print("Starting Denoise")
133 | bpy.ops.render.render(write_still=True)
134 |
135 | if bpy.context.scene.img_file_format == 'JPEG':
136 | file_extention = '.jpg'
137 | elif bpy.context.scene.img_file_format == 'PNG':
138 | file_extention = '.png'
139 | elif bpy.context.scene.img_file_format == 'HDR':
140 | file_extention = '.hdr'
141 |
142 | # cleanup
143 | comp_nodes = [image_node, nrm_image_node,color_image_node, denoise_node, comp_node]
144 | for node in comp_nodes:
145 | tree.nodes.remove(node)
146 |
147 | return scene.render.filepath + file_extention
148 |
149 |
150 | # -----------------------CHECKING --------------------#
151 |
152 |
153 | def check_pbr(self, material):
154 | check_ok = True
155 |
156 | if material is None:
157 | return False
158 |
159 | if material.node_tree is None:
160 | return False
161 |
162 | if material.node_tree.nodes is None:
163 | return False
164 |
165 | # get pbr shader
166 | nodes = material.node_tree.nodes
167 | pbr_node_type = constants.Node_Types.pbr_node
168 | pbr_nodes = get_nodes_by_type(nodes, pbr_node_type)
169 |
170 | # check only one pbr node
171 | if len(pbr_nodes) == 0:
172 | self.report({'INFO'}, 'No PBR Shader Found')
173 | check_ok = False
174 |
175 | if len(pbr_nodes) > 1:
176 | self.report(
177 | {'INFO'}, 'More than one PBR Node found ! Clean before Baking.')
178 | check_ok = False
179 |
180 | return check_ok
181 |
182 |
183 | def check_is_org_material(self, material):
184 | check_ok = True
185 | if "_Bake" in material.name:
186 | self.report({'INFO'}, 'Change back to org. Material')
187 | check_ok = False
188 |
189 | return check_ok
190 |
191 |
192 | # -----------------------NODES --------------------#
193 |
194 |
195 | def get_pbr_inputs(pbr_node):
196 |
197 | base_color_input = pbr_node.inputs["Base Color"]
198 | metallic_input = pbr_node.inputs["Metallic"]
199 | specular_input = pbr_node.inputs["Specular Tint"]
200 | roughness_input = pbr_node.inputs["Roughness"]
201 | normal_input = pbr_node.inputs["Normal"]
202 | emission_input = pbr_node.inputs["Emission Color"]
203 | alpha_input = pbr_node.inputs["Alpha"]
204 |
205 | pbr_inputs = {"base_color_input": base_color_input, "metallic_input": metallic_input,
206 | "specular_input": specular_input, "roughness_input": roughness_input,
207 | "normal_input": normal_input, "emission_input": emission_input,"alpha_input":alpha_input}
208 | return pbr_inputs
209 |
210 |
211 | def get_nodes_by_type(nodes, node_type):
212 | nodes_found = [n for n in nodes if n.type == node_type]
213 | return nodes_found
214 |
215 |
216 | def get_node_by_type_recusivly(material, note_to_start, node_type, del_nodes_inbetween=False):
217 | nodes = material.node_tree.nodes
218 | if note_to_start.type == node_type:
219 | return note_to_start
220 |
221 | for input in note_to_start.inputs:
222 | for link in input.links:
223 | current_node = link.from_node
224 | if (del_nodes_inbetween and note_to_start.type != constants.Node_Types.normal_map and note_to_start.type != constants.Node_Types.bump_map):
225 | nodes.remove(note_to_start)
226 | return get_node_by_type_recusivly(material, current_node, node_type, del_nodes_inbetween)
227 |
228 |
229 | def get_node_by_name_recusivly(node, idname):
230 | if node.bl_idname == idname:
231 | return node
232 |
233 | for input in node.inputs:
234 | for link in input.links:
235 | current_node = link.from_node
236 | return get_node_by_name_recusivly(current_node, idname)
237 |
238 |
239 | def get_pbr_node(material):
240 | nodes = material.node_tree.nodes
241 | pbr_node = get_nodes_by_type(nodes, constants.Node_Types.pbr_node)
242 | if len(pbr_node) > 0:
243 | return pbr_node[0]
244 |
245 |
246 | def make_link(material, socket1, socket2):
247 | links = material.node_tree.links
248 | links.new(socket1, socket2)
249 |
250 |
251 | def remove_link(material, socket1, socket2):
252 |
253 | node_tree = material.node_tree
254 | links = node_tree.links
255 |
256 | for l in socket1.links:
257 | if l.to_socket == socket2:
258 | links.remove(l)
259 |
260 |
261 | def add_in_gamme_node(material, pbrInput):
262 | nodeToPrincipledOutput = pbrInput.links[0].from_socket
263 |
264 | gammaNode = material.node_tree.nodes.new("ShaderNodeGamma")
265 | gammaNode.inputs[1].default_value = 2.2
266 | gammaNode.name = "Gamma Bake"
267 |
268 | # link in gamma
269 | make_link(material, nodeToPrincipledOutput, gammaNode.inputs["Color"])
270 | make_link(material, gammaNode.outputs["Color"], pbrInput)
271 |
272 |
273 | def remove_gamma_node(material, pbrInput):
274 | nodes = material.node_tree.nodes
275 | gammaNode = nodes.get("Gamma Bake")
276 | nodeToPrincipledOutput = gammaNode.inputs[0].links[0].from_socket
277 |
278 | make_link(material, nodeToPrincipledOutput, pbrInput)
279 | material.node_tree.nodes.remove(gammaNode)
280 |
281 |
282 | def emission_setup(material, node_output):
283 | nodes = material.node_tree.nodes
284 | emission_node = add_node(
285 | material, constants.Shader_Node_Types.emission, "Emission Bake")
286 |
287 | # link emission to whatever goes into current pbrInput
288 | emission_input = emission_node.inputs[0]
289 | make_link(material, node_output, emission_input)
290 |
291 | # link emission to materialOutput
292 | surface_input = get_nodes_by_type(nodes,constants.Node_Types.material_output)[0].inputs[0]
293 | emission_output = emission_node.outputs[0]
294 | make_link(material, emission_output, surface_input)
295 |
296 |
297 | def link_pbr_to_output(material, pbr_node):
298 | nodes = material.node_tree.nodes
299 | surface_input = get_nodes_by_type(nodes,constants.Node_Types.material_output)[0].inputs[0]
300 | make_link(material, pbr_node.outputs[0], surface_input)
301 |
302 | def reconnect_PBR(material, pbrNode):
303 | nodes = material.node_tree.nodes
304 | pbr_output = pbrNode.outputs[0]
305 | surface_input = get_nodes_by_type(
306 | nodes, constants.Node_Types.material_output)[0].inputs[0]
307 | make_link(material, pbr_output, surface_input)
308 |
309 |
310 | def mute_all_texture_mappings(material, do_mute):
311 | nodes = material.node_tree.nodes
312 | for node in nodes:
313 | if node.bl_idname == "ShaderNodeMapping":
314 | node.mute = do_mute
315 |
316 |
317 | def add_node(material, shader_node_type, node_name):
318 | nodes = material.node_tree.nodes
319 | new_node = nodes.get(node_name)
320 | if new_node is None:
321 | new_node = nodes.new(shader_node_type)
322 | new_node.name = node_name
323 | new_node.label = node_name
324 | return new_node
325 |
326 |
327 | def remove_node(material, node_name):
328 | nodes = material.node_tree.nodes
329 | node = nodes.get(node_name)
330 | if node is not None:
331 | nodes.remove(node)
332 |
333 | def remove_reconnect_node(material, node_name):
334 | nodes = material.node_tree.nodes
335 | node = nodes.get(node_name)
336 | input_node = node.inputs["Color1"].links[0].from_node
337 | output_node = node.outputs["Color"].links[0].to_node
338 |
339 | if node is not None:
340 | make_link(material,input_node.outputs["Color"],output_node.inputs["Base Color"])
341 | nodes.remove(node)
342 |
343 | def remove_unused_nodes(material):
344 | nodes = material.node_tree.nodes
345 | all_nodes = set(nodes)
346 | connected_nodes = set()
347 | material_output = nodes.get("Material Output")
348 |
349 | get_all_connected_nodes(material_output, connected_nodes)
350 |
351 | unconnected_nodes = all_nodes - connected_nodes
352 |
353 | for node in unconnected_nodes:
354 | nodes.remove(node)
355 |
356 |
357 | def remove_double_linking(material,texture_node):
358 | color_output = texture_node.outputs["Color"]
359 | links_count = len(color_output.links)
360 | org_vector_input = texture_node.inputs["Vector"]
361 | position_y = texture_node.location.y
362 |
363 | if links_count > 1:
364 | for link in color_output.links:
365 |
366 | new_texture_node = add_node(material,constants.Shader_Node_Types.image_texture,texture_node.name + "_Copy" + str(link))
367 | new_texture_node.image = texture_node.image
368 | new_texture_node.location = texture_node.location
369 | position_y -= 250
370 | new_texture_node.location.y = position_y
371 |
372 | # relink tex node output
373 | make_link(material,new_texture_node.outputs["Color"],link.to_socket)
374 |
375 | # remap texture mapping
376 | if len(org_vector_input.links) != 0:
377 | new_vector_input = new_texture_node.inputs["Vector"]
378 | tex_transform_socket = org_vector_input.links[0].from_socket
379 | make_link(material,tex_transform_socket,new_vector_input)
380 |
381 |
382 |
383 | def get_all_connected_nodes(node, connected_nodes):
384 |
385 | connected_nodes.add(node)
386 |
387 | for input in node.inputs:
388 | for link in input.links:
389 | current_node = link.from_node
390 | get_all_connected_nodes(current_node, connected_nodes)
391 |
--------------------------------------------------------------------------------
/Operators/operators.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | import bpy
4 |
5 |
6 | from ..Functions import (basic_functions, constants,
7 | image_functions, material_functions, node_functions,
8 | visibility_functions,object_functions)
9 |
10 |
11 | class GTT_VerifyMaterialsOperator(bpy.types.Operator):
12 | """Check for each visbile material if it has a PBR Shader so the GLTF Export works fine"""
13 | bl_idname = "object.verify_materials"
14 | bl_label = "Verify Materials"
15 |
16 | def execute(self, context):
17 |
18 | vis_mats = material_functions.get_all_visible_materials()
19 |
20 | for mat in vis_mats:
21 | check_ok = node_functions.check_pbr(self,mat)
22 | if not check_ok:
23 | self.report({'INFO'}, "No PBR Shader in " + mat.name)
24 |
25 | objects_in_scene = bpy.data.objects
26 | for obj in objects_in_scene:
27 | for slot in obj.material_slots:
28 | if slot.material is None:
29 | self.report({'INFO'}, "Empty Material Slot on " + obj.name)
30 | return {'FINISHED'}
31 |
32 |
33 |
34 | # ----------------------- LIGHTAP OPERATORS--------------------#
35 | class GTT_SelectLightmapObjectsOperator(bpy.types.Operator):
36 | """Select all Objects in the list that have the according lightmap attached to them. Makes it easy to rebake multiple Objects"""
37 | bl_idname = "object.select_lightmap_objects"
38 | bl_label = "Select Lightmap Objects"
39 |
40 | @classmethod
41 | def poll(cls, context):
42 | if context.scene.bake_settings.baking_groups == '-- Baking Groups --':
43 | return False
44 | return True
45 |
46 | def execute(self, context):
47 |
48 | C = context
49 | O = bpy.ops
50 |
51 | bake_settings = C.scene.bake_settings
52 | active_lightmap = bake_settings.baking_groups
53 | objects = [ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
54 |
55 | O.object.select_all(action='DESELECT')
56 | for obj in objects:
57 | if obj.lightmap_name == active_lightmap or obj.ao_map_name == active_lightmap:
58 | C.view_layer.objects.active = obj
59 | obj.select_set(True)
60 |
61 | return {'FINISHED'}
62 |
63 | # ----------------------- TEXTURE OPERATORS--------------------#
64 |
65 | class GTT_GetMaterialByTextureOperator(bpy.types.Operator):
66 | bl_idname = "scene.select_mat_by_tex"
67 | bl_label = "Select Material By Texture"
68 | bl_description = "Selecting all materials in scene that use the selected texture"
69 | bl_options = {"REGISTER"}
70 |
71 | bpy.types.Scene.materials_found = []
72 |
73 | @classmethod
74 | def poll(cls, context):
75 | D = bpy.data
76 | images = D.images
77 |
78 | display = True
79 |
80 | # image to index not found
81 | try:
82 | sel_image_texture = images[context.scene.texture_settings.texture_index]
83 | if sel_image_texture.name in ('Viewer Node', 'Render Result'):
84 | display = False
85 | except:
86 | display = False
87 | return display
88 |
89 |
90 | def execute(self, context):
91 | D = bpy.data
92 |
93 | images = D.images
94 | sel_image_texture = images[context.scene.texture_settings.texture_index]
95 | materials = D.materials
96 |
97 | # to print materials with current image texture
98 | materials_found = context.scene.materials_found
99 | materials_found.clear()
100 |
101 | for mat in materials:
102 | if hasattr(mat,"node_tree"):
103 | if hasattr(mat.node_tree,"nodes"):
104 | nodes = mat.node_tree.nodes
105 | tex_node_type = constants.Node_Types.image_texture
106 | tex_nodes = node_functions.get_nodes_by_type(nodes,tex_node_type)
107 |
108 | # if texture node in current node tree
109 | if len(tex_nodes) > 0:
110 | images = [node.image for node in tex_nodes]
111 | if sel_image_texture in images:
112 | materials_found.append(mat.name)
113 | object_functions.select_obj_by_mat(mat,self)
114 |
115 |
116 | return {"FINISHED"}
117 |
118 | class GTT_ScaleImageOperator(bpy.types.Operator):
119 | """Scale all Images on selected Material to specific resolution"""
120 | bl_idname = "image.scale_image"
121 | bl_label = "Scale Images"
122 |
123 | @classmethod
124 | def poll(cls, context):
125 | D = bpy.data
126 | images = D.images
127 |
128 | display = True
129 |
130 | # if operate on all textures is checked we don't need to get the index
131 | if context.scene.texture_settings.operate_on_all_textures:
132 | return True
133 |
134 | # image to index not found
135 | try:
136 | sel_image_texture = images[context.scene.texture_settings.texture_index]
137 | if sel_image_texture.name in ('Viewer Node','Render Result'):
138 | display = False
139 | except:
140 | display = False
141 | return display
142 |
143 | def invoke(self,context,event):
144 | D = bpy.data
145 |
146 | texture_settings = context.scene.texture_settings
147 | image_size = [int(context.scene.img_bake_size),int(context.scene.img_bake_size)]
148 |
149 | images_in_scene = D.images
150 | all_images = image_functions.get_all_images_in_ui_list()
151 |
152 | if texture_settings.operate_on_all_textures:
153 | for img in all_images:
154 | image_functions.scale_image(img,image_size)
155 | else:
156 | sel_image_texture = images_in_scene[texture_settings.texture_index]
157 | image_functions.scale_image(sel_image_texture,image_size)
158 |
159 | return {'FINISHED'}
160 |
161 | def modal(self, context, event):
162 | if event.type in {'RIGHTMOUSE', 'ESC'}:
163 | return {'CANCELLED'}
164 |
165 | return {'PASS_THROUGH'}
166 |
167 | # ----------------------- VIEW OPERATORS--------------------#
168 |
169 | class GTT_SwitchBakeMaterialOperator(bpy.types.Operator):
170 | """Switch to baked material"""
171 | bl_idname = "object.switch_bake_mat_operator"
172 | bl_label = "Baked Material"
173 |
174 | def execute(self, context):
175 |
176 | show_bake_material = True
177 | visibility_functions.switch_baked_material(show_bake_material,context.scene.affect)
178 |
179 | return {'FINISHED'}
180 |
181 | class GTT_SwitchOrgMaterialOperator(bpy.types.Operator):
182 | """Switch from baked to original material"""
183 | bl_idname = "object.switch_org_mat_operator"
184 | bl_label = "Org. Material"
185 |
186 | def execute(self, context):
187 |
188 | show_bake_material = False
189 | visibility_functions.switch_baked_material(show_bake_material,context.scene.affect)
190 |
191 | return {'FINISHED'}
192 |
193 | class GTT_PreviewBakeTextureOperator(bpy.types.Operator):
194 | """Connect baked texture to emission to see result"""
195 | bl_idname = "object.preview_bake_texture"
196 | bl_label = "Preview Bake Texture"
197 |
198 | connect : bpy.props.BoolProperty()
199 | def execute(self, context):
200 | context.scene.texture_settings.preview_bake_texture = self.connect
201 | visibility_functions.preview_bake_texture(self,context)
202 |
203 | return {'FINISHED'}
204 |
205 |
206 |
207 | class GTT_LightmapEmissionOperator(bpy.types.Operator):
208 | """Connect baked Lightmap to Emission input of Principled Shader"""
209 | bl_idname = "object.lightmap_to_emission"
210 | bl_label = "Lightmap to Emission"
211 |
212 | connect : bpy.props.BoolProperty()
213 | def execute(self, context):
214 |
215 | visibility_functions.lightmap_to_emission(self,context,self.connect)
216 |
217 |
218 | return {'FINISHED'}
219 |
220 | class GTT_PreviewLightmap(bpy.types.Operator):
221 | """Connect baked Lightmap to Base Color input of Principled Shader"""
222 | bl_idname = "object.preview_lightmap"
223 | bl_label = "Lightmap to Base Color"
224 |
225 | connect : bpy.props.BoolProperty()
226 | def execute(self, context):
227 | context.scene.texture_settings.preview_lightmap = self.connect
228 | visibility_functions.preview_lightmap(self,context)
229 |
230 | return {'FINISHED'}
231 |
232 |
233 | # ----------------------- UV OPERATORS--------------------#
234 |
235 | class GTT_AddUVOperator(bpy.types.Operator):
236 | """Add uv layer with layer name entered above"""
237 | bl_idname = "object.add_uv"
238 | bl_label = "Add UV to all selected objects"
239 |
240 | uv_name:bpy.props.StringProperty()
241 |
242 | def execute(self, context):
243 | sel_objects = context.selected_objects
244 |
245 | for obj in sel_objects:
246 | if obj.type != "MESH":
247 | continue
248 | uv_layers = obj.data.uv_layers
249 | if self.uv_name in uv_layers:
250 | print("UV Name already take, choose another one")
251 | continue
252 | uv_layers.new(name=self.uv_name)
253 | uv_layers.get(self.uv_name).active = True
254 |
255 | return {'FINISHED'}
256 |
257 | class GTT_RemoveUVOperator(bpy.types.Operator):
258 | """Delete all uv layers found in uv_slot entered above"""
259 | bl_idname = "object.remove_uv"
260 | bl_label = "Remove UV"
261 |
262 | uv_slot: bpy.props.IntProperty()
263 |
264 |
265 | def execute(self, context):
266 | sel_objects = context.selected_objects
267 | self.uv_slot -= 1
268 |
269 | for obj in sel_objects:
270 | if obj.type != "MESH":
271 | continue
272 | uv_layers = obj.data.uv_layers
273 |
274 | if len(uv_layers) > self.uv_slot:
275 | uv_layers.remove(uv_layers[self.uv_slot])
276 |
277 | return {'FINISHED'}
278 |
279 | class GTT_SetActiveUVOperator(bpy.types.Operator):
280 | """Set the acive uv to the slot entered above"""
281 | bl_idname = "object.set_active_uv"
282 | bl_label = "Set Active UV"
283 |
284 | uv_slot:bpy.props.IntProperty()
285 |
286 | def execute(self, context):
287 | sel_objects = context.selected_objects
288 | self.uv_slot -= 1
289 |
290 | for obj in sel_objects:
291 | if obj.type != "MESH":
292 | continue
293 | uv_layers = obj.data.uv_layers
294 |
295 | if len(uv_layers) > self.uv_slot:
296 | uv_layers.active_index = self.uv_slot
297 |
298 | return {'FINISHED'}
299 | # ----------------------- CLEAN OPERATORS--------------------#
300 |
301 | # class CleanBakesOperator(bpy.types.Operator):
302 | class GTT_RemoveLightmapOperator(bpy.types.Operator):
303 | """Remove Lightmap and UV Node"""
304 | bl_idname = "material.clean_lightmap"
305 | bl_label = "Clean Lightmap"
306 |
307 | def execute(self, context):
308 |
309 | selected_objects = context.selected_objects
310 | bake_settings = context.scene.bake_settings
311 | all_materials = set()
312 | slots_array = [obj.material_slots for obj in selected_objects]
313 | for slots in slots_array:
314 | for slot in slots:
315 | all_materials.add(slot.material)
316 |
317 | for mat in all_materials:
318 | if mat is None:
319 | continue
320 | node_functions.remove_node(mat,bake_settings.texture_node_lightmap)
321 | node_functions.remove_node(mat,"Mulitply Lightmap")
322 | node_functions.remove_node(mat,"Second_UV")
323 |
324 | #remove lightmap flag
325 | for obj in selected_objects:
326 | obj.hasLightmap = False
327 | if obj.get('lightmap_name') is not None :
328 | del obj["lightmap_name"]
329 |
330 | return {'FINISHED'}
331 |
332 | class GTT_RemoveAOOperator(bpy.types.Operator):
333 | """Remove AO Node and clear baking flags on object"""
334 | bl_idname = "material.clean_ao_map"
335 | bl_label = "Clean AO map"
336 |
337 | def execute(self, context):
338 | # visibility_functions.switch_baked_material(False,"scene")
339 | bpy.ops.material.clean_materials()
340 |
341 | bake_settings = context.scene.bake_settings
342 | selected_objects = context.selected_objects
343 | all_materials = set()
344 | slots_array = [obj.material_slots for obj in selected_objects]
345 | for slots in slots_array:
346 | for slot in slots:
347 | all_materials.add(slot.material)
348 |
349 | for mat in all_materials:
350 | if mat is None:
351 | continue
352 |
353 | node_functions.remove_node(mat,bake_settings.texture_node_ao)
354 | node_functions.remove_node(mat,"glTF Settings")
355 |
356 | #remove flag
357 | for obj in selected_objects:
358 | if obj.get('ao_map_name') is not None :
359 | del obj["ao_map_name"]
360 | if obj.get('bake_version') is not None :
361 | del obj["bake_version"]
362 |
363 | return {'FINISHED'}
364 |
365 | class GTT_CleanTexturesOperator(bpy.types.Operator):
366 | """Remove unreferenced images"""
367 | bl_idname = "image.clean_textures"
368 | bl_label = "Clean Textures"
369 |
370 | def execute(self, context):
371 | for image in bpy.data.images:
372 | if not image.users or list(image.size) == [0,0]:
373 | bpy.data.images.remove(image)
374 | return {'FINISHED'}
375 |
376 | class GTT_CleanMaterialsOperator(bpy.types.Operator):
377 | """Clean materials with no users and remove empty material slots"""
378 | bl_idname = "material.clean_materials"
379 | bl_label = "Clean Materials"
380 |
381 | def execute(self, context):
382 | material_functions.clean_empty_materials()
383 | material_functions.clean_no_user_materials()
384 | material_functions.use_nodes()
385 |
386 | return {'FINISHED'}
387 |
388 | class GTT_CleanUnusedImagesOperator(bpy.types.Operator):
389 | """Clean all images from Hard Disk that are not used in scene"""
390 | bl_idname = "scene.clean_unused_images"
391 | bl_label = "Clean Images"
392 | bl_options = {"REGISTER"}
393 |
394 | @classmethod
395 | def poll(cls, context):
396 | return True
397 |
398 | def execute(self, context):
399 | images_in_folder = []
400 | images_in_blender = bpy.data.images
401 | image_paths_in_blender = []
402 |
403 | filePath = bpy.data.filepath
404 | path = os.path.dirname(filePath) + "\\textures"
405 |
406 | # find images on hard drive
407 | if os.path.exists(path):
408 | for path, subdirs, files in os.walk(path):
409 | for name in files:
410 | images_in_folder.append(path+"\\"+name)
411 |
412 | for img in images_in_blender:
413 | image_paths_in_blender.append(img.filepath)
414 |
415 | images_intersection = basic_functions.Intersection(image_paths_in_blender,images_in_folder)
416 | images_to_clean = basic_functions.Diff(images_in_folder,images_intersection)
417 |
418 | print("Deleting files :")
419 | for img in images_to_clean:
420 | os.remove(img)
421 | print(img)
422 | return {'FINISHED'}
423 |
424 |
425 | # ----------------------- FILE OPERATORS--------------------#
426 |
427 | class GTT_OpenTexturesFolderOperator(bpy.types.Operator):
428 | """Open Texture folder if it exists, bake or scale texture to create texture folder"""
429 | bl_idname = "scene.open_textures_folder"
430 | bl_label = "Open Folder"
431 |
432 | texture_path="\\textures\\"
433 |
434 | @classmethod
435 | def poll(self, context):
436 | filePath = bpy.data.filepath
437 | path = os.path.dirname(filePath)
438 | if os.path.exists(path + self.texture_path):
439 | return True
440 | return False
441 |
442 | def execute(self, context):
443 | filepath = bpy.data.filepath
444 | directory = os.path.dirname(filepath) + self.texture_path
445 |
446 | if filepath != "":
447 | subprocess.call("explorer " + directory, shell=True)
448 | else:
449 | self.report({'INFO'}, 'You need to save Blend file first !')
450 |
451 | return {"FINISHED"}
452 |
453 | class GOVIE_Open_Link_Operator(bpy.types.Operator):
454 | bl_idname = "scene.open_link"
455 | bl_label = "Open Website"
456 | bl_description = "Go to GOVIE Website"
457 |
458 | url : bpy.props.StringProperty(name="url")
459 |
460 | @classmethod
461 | def poll(cls, context):
462 | return True
463 | def execute(self, context):
464 | bpy.ops.wm.url_open(url=self.url)
465 | return {"FINISHED"}
466 |
--------------------------------------------------------------------------------
/Bake/bake_utilities.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from bpy.types import ObjectShaderFx
3 | import mathutils
4 | from .. Functions import node_functions
5 | from .. Functions import image_functions
6 | from .. Functions import constants
7 | from .. Functions import visibility_functions
8 | from .. Functions import material_functions
9 |
10 | blender_version = bpy.app.version
11 |
12 | class BakeUtilities():
13 | C = bpy.context
14 | D = bpy.data
15 | O = bpy.ops
16 |
17 | all_materials = None
18 | image_texture_nodes = None
19 | bake_settings = None
20 | bake_image = None
21 | render_engine = None
22 | selected_objects = None
23 | image_size = None
24 | parent_operator = None
25 | tex_node_name = None
26 |
27 | def __init__(self,parent_operator,selected_objects, bake_settings):
28 | self.C = bpy.context
29 | self.D = bpy.data
30 | self.parent_operator = parent_operator
31 | self.render_engine = self.C.scene.render.engine
32 | self.selected_objects = selected_objects
33 | self.all_materials = self.D.materials
34 | self.selected_materials = material_functions.get_selected_materials(self.selected_objects)
35 | self.bake_settings = bake_settings
36 | self.baked_images = []
37 | self.image_texture_nodes = set()
38 | self.image_size = [int(self.C.scene.img_bake_size),
39 | int(self.C.scene.img_bake_size)]
40 | image_name = bake_settings.bake_image_name
41 |
42 | self.bake_image = image_functions.create_image(image_name, self.image_size)
43 |
44 | def setup_engine(self):
45 | # setup engine
46 | if self.render_engine == 'BLENDER_EEVEE':
47 | self.C.scene.render.engine = 'CYCLES'
48 |
49 | # setup device type
50 | self.cycles_device_type = self.C.preferences.addons['cycles'].preferences.compute_device_type
51 | if self.cycles_device_type == 'OPTIX':
52 | self.C.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA'
53 |
54 | # setup samples
55 | if self.bake_settings.pbr_bake:
56 | self.C.scene.cycles.samples = self.bake_settings.pbr_samples
57 | if self.bake_settings.lightmap_bake:
58 | self.C.scene.cycles.samples = self.bake_settings.lightmap_samples
59 | if self.bake_settings.ao_bake:
60 | self.C.scene.cycles.samples = self.bake_settings.ao_samples
61 |
62 | self.C.scene.render.resolution_percentage = 100
63 |
64 | def set_active_uv_to_lightmap(self):
65 | bpy.ops.object.set_active_uv(uv_slot=2)
66 |
67 | def checkPBR(self):
68 | for material in self.selected_materials:
69 | self.active_material = material
70 | # check if pbr node exists
71 | check_ok = node_functions.check_pbr(self.parent_operator,material)
72 | if not check_ok :
73 | self.parent_operator.report({'INFO'}, "Material " + material.name + " has no PBR Node !")
74 | return check_ok
75 |
76 | def unwrap_selected(self):
77 | if self.bake_settings.unwrap:
78 | self.O.object.add_uv(uv_name=self.bake_settings.uv_name)
79 |
80 | # apply scale on linked
81 | sel_objects = self.C.selected_objects
82 | scene_objects = self.D.objects
83 | linked_objects = set()
84 |
85 | for sel_obj in sel_objects:
86 | for scene_obj in scene_objects:
87 | if sel_obj.data.original is scene_obj.data and sel_obj is not scene_obj:
88 | linked_objects.add(sel_obj)
89 |
90 | # do not apply transform if linked objects in selection
91 | if not len(linked_objects)>0:
92 | bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
93 |
94 |
95 | self.O.object.mode_set(mode='EDIT')
96 | self.O.mesh.reveal()
97 | self.O.mesh.select_all(action='SELECT')
98 | self.O.uv.smart_project(island_margin=self.bake_settings.unwrap_margin)
99 | self.O.object.mode_set(mode='OBJECT')
100 |
101 | def create_bake_material(self,material_name_suffix):
102 |
103 | bake_materials = []
104 | selected_materials = []
105 | for obj in self.selected_objects:
106 | for slot in obj.material_slots:
107 | selected_materials.append(slot.material)
108 |
109 | # switch to ao material if we are on org and ao was already baked
110 | visibility_functions.switch_baked_material(True,"scene")
111 |
112 | for obj in self.selected_objects:
113 | for slot in obj.material_slots:
114 | material = slot.material
115 | bake_material_name = material.name + material_name_suffix
116 |
117 | # check if material was already baked and continue
118 | if material_name_suffix in material.name:
119 | bake_materials.append(material)
120 | continue
121 |
122 | # if not, copy material or take one out of the previewsly filled bake list
123 | else :
124 | bake_material = list(filter(lambda material: material.name == bake_material_name, bake_materials))
125 |
126 | if len(bake_material) == 0:
127 | bake_material = material.copy()
128 | bake_material.name = bake_material_name
129 | bake_materials.append(bake_material)
130 | slot.material = bake_material
131 |
132 | else:
133 | bake_material = bake_material[0]
134 | slot.material = bake_material
135 |
136 | index = bake_material.name.find(".")
137 | if index == -1:
138 | obj.bake_version = ""
139 | else:
140 | obj.bake_version = bake_material.name[index:]
141 |
142 | material.use_fake_user = True
143 |
144 |
145 | # remove duplicate entries
146 | self.selected_materials = list(set(bake_materials))
147 |
148 | def add_gltf_material_output_node(self, material):
149 | nodes = material.node_tree.nodes
150 |
151 | name = "glTF Material Output"
152 | gltf_node_group = bpy.data.node_groups.new(name, 'ShaderNodeTree')
153 | gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
154 | thicknessFactor = gltf_node_group.inputs.new("NodeSocketFloat", "Thickness")
155 | thicknessFactor.default_value = 0.0
156 | gltf_node_group.nodes.new('NodeGroupOutput')
157 | gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
158 | specular = gltf_node_group.inputs.new("NodeSocketFloat", "Specular")
159 | specular.default_value = 1.0
160 | specularColor = gltf_node_group.inputs.new("NodeSocketColor", "Specular Color")
161 | specularColor.default_value = [1.0,1.0,1.0,1.0]
162 | gltf_node_group_input.location = -200, 0
163 |
164 |
165 | gltf_settings_node = nodes.get(name)
166 | if gltf_settings_node is None:
167 | gltf_settings_node = nodes.new('ShaderNodeGroup')
168 | gltf_settings_node.name = name
169 | gltf_settings_node.node_tree = bpy.data.node_groups[name]
170 |
171 | return gltf_settings_node
172 |
173 |
174 |
175 |
176 | def add_gltf_settings_node(self, material):
177 | nodes = material.node_tree.nodes
178 | # create group data
179 | gltf_settings = bpy.data.node_groups.get('glTF Settings')
180 | if gltf_settings is None:
181 | bpy.data.node_groups.new('glTF Settings', 'ShaderNodeTree')
182 |
183 | # add group to node tree
184 | gltf_settings_node = nodes.get('glTF Settings')
185 | if gltf_settings_node is None:
186 | gltf_settings_node = nodes.new('ShaderNodeGroup')
187 | gltf_settings_node.name = 'glTF Settings'
188 | gltf_settings_node.node_tree = bpy.data.node_groups['glTF Settings']
189 |
190 | # create group inputs
191 | if gltf_settings_node.inputs.get('Occlusion') is None:
192 | gltf_settings_node.inputs.new('NodeSocketFloat','Occlusion')
193 |
194 | return gltf_settings_node
195 |
196 | def add_image_texture_node(self, material):
197 | nodes = material.node_tree.nodes
198 |
199 | # add image texture
200 | if self.bake_settings.lightmap_bake:
201 | self.tex_node_name = self.bake_settings.texture_node_lightmap
202 |
203 | if self.bake_settings.ao_bake:
204 | self.tex_node_name = self.bake_settings.texture_node_ao
205 |
206 | image_texture_node = node_functions.add_node(material, constants.Shader_Node_Types.image_texture, self.tex_node_name)
207 | image_texture_node.image = self.bake_image
208 | self.bake_image.colorspace_settings.name = "Linear FilmLight E-Gamut"
209 | nodes.active = image_texture_node
210 |
211 | # save texture nodes and pbr nodes for later
212 | self.image_texture_nodes.add(image_texture_node)
213 |
214 | return image_texture_node
215 |
216 | def save_metal_value(self):
217 | for material in self.selected_materials:
218 | pbr_node = node_functions.get_pbr_node(material)
219 |
220 | # save metal value
221 | metallic_value = pbr_node.inputs["Metallic"].default_value
222 | pbr_node["original_metallic"] = metallic_value
223 | pbr_node.inputs["Metallic"].default_value = 0
224 |
225 | # save metal image
226 | if pbr_node.inputs["Metallic"].is_linked:
227 |
228 | # get metal image node, save it in pbr node and remove connection
229 | metal_image_node_socket = pbr_node.inputs["Metallic"].links[0].from_socket
230 | self.metal_image_node_output = metal_image_node_socket
231 | node_functions.remove_link(material,metal_image_node_socket,pbr_node.inputs["Metallic"])
232 |
233 | def load_metal_value(self):
234 | for material in self.selected_materials:
235 | pbr_node = node_functions.get_pbr_node(material)
236 | pbr_node.inputs["Metallic"].default_value = pbr_node["original_metallic"]
237 |
238 | # reconnect metal image
239 | if hasattr(self,"metal_image_node_output"):
240 | node_functions.make_link(material,self.metal_image_node_output,pbr_node.inputs["Metallic"])
241 |
242 | def add_uv_node(self,material):
243 |
244 | uv_node = node_functions.add_node(material, constants.Shader_Node_Types.uv, "Second_UV")
245 | uv_node.uv_map = self.bake_settings.uv_name
246 | return uv_node
247 |
248 | def position_gltf_setup_nodes(self,material,uv_node,image_texture_node,gltf_settings_node):
249 | nodes = material.node_tree.nodes
250 | # uv node
251 | pbr_node = node_functions.get_pbr_node(material)
252 | pos_offset = mathutils.Vector((-900, 400))
253 | loc = pbr_node.location + pos_offset
254 | uv_node.location = loc
255 |
256 | # image texture
257 | loc = loc + mathutils.Vector((300, 0))
258 | image_texture_node.location = loc
259 |
260 | # ao node
261 | loc = loc + mathutils.Vector((300, 0))
262 | gltf_settings_node.location = loc
263 |
264 | nodes.active = image_texture_node
265 |
266 | def add_node_setup(self):
267 | for material in self.selected_materials:
268 | # AO
269 | if self.bake_settings.ao_bake:
270 | uv_node = self.add_uv_node(material)
271 | image_texture_node = self.add_image_texture_node(material)
272 |
273 | if blender_version <= (3, 3):
274 | gltf_settings_node = self.add_gltf_settings_node(material)
275 | else:
276 | gltf_settings_node = self.add_gltf_material_output_node(material)
277 |
278 |
279 | # position
280 | self.position_gltf_setup_nodes(material,uv_node,image_texture_node,gltf_settings_node)
281 |
282 | # linking
283 | node_functions.make_link(material, uv_node.outputs["UV"],image_texture_node.inputs['Vector'])
284 | node_functions.make_link(material, image_texture_node.outputs['Color'], gltf_settings_node.inputs['Occlusion'])
285 |
286 | # LIGHTMAP
287 | if self.bake_settings.lightmap_bake:
288 | image_texture_node = self.add_image_texture_node(material)
289 | uv_node = self.add_uv_node(material)
290 | # position
291 | image_texture_node.location = mathutils.Vector((-500, 200))
292 | uv_node.location = mathutils.Vector((-700, 200))
293 |
294 | # linking
295 | node_functions.make_link(material, uv_node.outputs["UV"],image_texture_node.inputs['Vector'])
296 |
297 | def bake(self,bake_type):
298 | channels_to_bake = bake_type
299 | self.baked_images = []
300 | denoise = self.bake_settings.denoise
301 |
302 | # no denoise
303 | if not denoise:
304 | channel = bake_type[0]
305 | image = self.bake_images(self.bake_image,channel,denoise)
306 | image_functions.save_image(image,False)
307 | return
308 |
309 | # bake channels for denoise
310 | for channel in channels_to_bake:
311 | image_name = self.bake_image.name + "_" + channel
312 | image = image_functions.create_image(image_name,self.bake_image.size)
313 | self.change_image_in_nodes(image)
314 | baked_channel_image = self.bake_images(image,channel,denoise)
315 | image_functions.save_image(image,True)
316 | self.baked_images.append(baked_channel_image)
317 |
318 | self.denoise()
319 |
320 | def change_image_in_nodes(self,image):
321 | for image_texture_node in self.image_texture_nodes:
322 | if image_texture_node.name == self.tex_node_name:
323 | image_texture_node.image = image
324 |
325 | def bake_images(self, image, channel,denoise):
326 | if channel == "NRM":
327 | print("Baking Normal Pass")
328 | self.C.scene.cycles.samples = 1
329 | self.O.object.bake(type="NORMAL", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
330 |
331 | if channel == "COLOR":
332 | print("Baking Color Pass")
333 | self.C.scene.cycles.samples = 1
334 | self.O.object.bake(type="DIFFUSE", pass_filter={'COLOR'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
335 |
336 | if channel == "AO":
337 | if not denoise:
338 | self.O.object.bake('INVOKE_DEFAULT',type="AO", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
339 | else:
340 | self.O.object.bake(type="AO", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
341 |
342 | if channel == "NOISY":
343 | print("Baking Diffuse Pass")
344 | if not denoise:
345 | self.O.object.bake('INVOKE_DEFAULT',type="DIFFUSE", pass_filter={'DIRECT', 'INDIRECT'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
346 | else:
347 | self.O.object.bake(type="DIFFUSE", pass_filter={'DIRECT', 'INDIRECT'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
348 |
349 | return image
350 |
351 | def denoise(self):
352 | # denoise
353 | if self.bake_settings.lightmap_bake:
354 | denoised_image_path = node_functions.comp_ai_denoise(self.baked_images[0],self.baked_images[1],self.baked_images[2])
355 |
356 | self.bake_image.filepath = denoised_image_path
357 | self.bake_image.source = "FILE"
358 |
359 | self.change_image_in_nodes(self.bake_image)
360 |
361 | # blur
362 | if self.bake_settings.ao_bake and self.bake_settings.denoise:
363 | blur_image_path = node_functions.blur_bake_image(self.baked_images[0],self.baked_images[1])
364 | self.bake_image.filepath = blur_image_path
365 | self.bake_image.source = "FILE"
366 | self.change_image_in_nodes(self.bake_image)
367 |
368 | def add_lightmap_flag(self):
369 | for obj in self.selected_objects:
370 | obj.hasLightmap = True
371 |
372 | def cleanup(self):
373 | # set back engine
374 | # self.C.scene.render.engine = self.render_engine
375 | self.C.preferences.addons['cycles'].preferences.compute_device_type = self.cycles_device_type
376 |
377 | # cleanup images
378 | if self.bake_settings.cleanup_textures:
379 | for img in self.D.images:
380 | if self.bake_image.name in img.name and ("_COLOR" in img.name or "_NRM" in img.name or "_NOISY" in img.name) :
381 | self.D.images.remove(img)
382 |
383 | # show image
384 | visibility_functions.show_image_in_image_editor(self.bake_image)
385 |
386 |
387 | class PbrBakeUtilities(BakeUtilities):
388 | active_material = None
389 | parent_operator = None
390 |
391 | def __init__(self,parent_operator,selected_objects, bake_settings):
392 | super().__init__(parent_operator,selected_objects,bake_settings)
393 | self.selected_materials = material_functions.get_selected_materials(selected_objects)
394 | self.parent_operator = parent_operator
395 |
396 | def ready_for_bake(self,material):
397 |
398 | # check if not baked material
399 | if "_Bake" in material.name:
400 | print("Skipping cause already baked : " + material.name)
401 | return False
402 |
403 | print("\n Checking " + material.name + "\n")
404 |
405 | # check if renderer not set to optix
406 | self.setup_engine()
407 |
408 | # check if selected to active is on
409 | bpy.context.scene.render.bake.use_selected_to_active = False
410 |
411 | # check if pbr node exists
412 | check_ok = node_functions.check_pbr(self.parent_operator,material) and node_functions.check_is_org_material(self.parent_operator,material)
413 | if not check_ok :
414 | self.parent_operator.report({'INFO'}, "Material " + material.name + " has no PBR Node !")
415 | return False
416 |
417 | # copy texture nodes if they are linked multiple times
418 | nodes = material.node_tree.nodes
419 | image_textrure_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
420 | for image_texture_node in image_textrure_nodes:
421 | node_functions.remove_double_linking(material,image_texture_node)
422 | return True
423 |
424 |
425 |
426 | def bake_materials_on_object(self):
427 | for material in self.selected_materials:
428 | self.active_material = material
429 | if not (self.ready_for_bake(material)):
430 | continue
431 | self.add_bake_plane()
432 | self.bake_pbr()
433 | self.create_pbr_bake_material("_Bake")
434 | self.create_nodes_after_pbr_bake()
435 | self.cleanup_nodes()
436 |
437 | visibility_functions.switch_baked_material(True,"visible")
438 |
439 | def add_bake_plane(self):
440 | material = self.active_material
441 | bake_plane = self.D.objects.get(material.name + "_Bake")
442 |
443 | if bake_plane is not None:
444 | self.parent_operator.report({'INFO'}, 'Delete Bake Plane')
445 | return
446 |
447 | self.O.mesh.primitive_plane_add(size=2, location=(2, 0, 0))
448 | bake_plane = self.C.object
449 | bake_plane.name = material.name + "_Bake"
450 | bake_plane.data.materials.append(material)
451 |
452 |
453 | def bake_pbr(self):
454 | material = self.active_material
455 | material.use_fake_user = True
456 |
457 | nodes = material.node_tree.nodes
458 | pbr_node = node_functions.get_pbr_node(material)
459 | pbr_inputs = node_functions.get_pbr_inputs(pbr_node)
460 | image_texture_node = None
461 |
462 | # mute texture mapping
463 | if self.bake_settings.mute_texture_nodes:
464 | node_functions.mute_all_texture_mappings(material, True)
465 |
466 | for pbr_input in pbr_inputs.values():
467 |
468 | # -----------------------TESTING--------------------#
469 | # skip if input has no connection
470 | if not pbr_input.is_linked:
471 | continue
472 |
473 | # -----------------------IMAGE --------------------#
474 |
475 | image_name = material.name + "_" + pbr_input.name
476 |
477 | # find image
478 | bake_image = self.D.images.get(image_name)
479 |
480 | # remove image
481 | if bake_image is not None:
482 | self.D.images.remove(bake_image)
483 |
484 | bake_image = self.D.images.new(image_name, width=self.image_size[0], height=self.image_size[1])
485 | bake_image.name = image_name
486 |
487 | image_texture_node = node_functions.add_node(material,constants.Shader_Node_Types.image_texture,"PBR Bake")
488 |
489 | image_texture_node.image = bake_image
490 | nodes.active = image_texture_node
491 |
492 | # -----------------------SET COLOR SPACE--------------------#
493 | if pbr_input is not pbr_inputs["base_color_input"]:
494 | bake_image.colorspace_settings.name = "Non-Color"
495 |
496 | # -----------------------BAKING--------------------#
497 | if pbr_input is pbr_inputs["normal_input"]:
498 | node_functions.link_pbr_to_output(material, pbr_node)
499 | self.O.object.bake(type="NORMAL", use_clear=True)
500 | else:
501 | node_functions.emission_setup(material, pbr_input.links[0].from_socket)
502 | self.O.object.bake(type="EMIT", use_clear=True)
503 |
504 | # unmute texture mappings
505 | node_functions.mute_all_texture_mappings(material, False)
506 |
507 | # delete plane
508 | self.O.object.delete()
509 |
510 | # cleanup nodes
511 | node_functions.remove_node(material,"Emission Bake")
512 | node_functions.remove_node(material,"PBR Bake")
513 | node_functions.reconnect_PBR(material, pbr_node)
514 |
515 | def create_pbr_bake_material(self,material_name_suffix):
516 |
517 | # -----------------------CREATE MATERIAL--------------------#
518 | org_material = self.active_material
519 | bake_material_name = org_material.name + material_name_suffix
520 | bake_material = bpy.data.materials.get(bake_material_name)
521 |
522 | if bake_material is not None:
523 | bpy.data.materials.remove(bake_material)
524 |
525 | # and create new from org. material
526 | bake_material = org_material.copy()
527 | bake_material.name = bake_material_name
528 | self.bake_material = bake_material
529 |
530 | def create_nodes_after_pbr_bake(self):
531 | # -----------------------SETUP VARS--------------------#
532 | org_material = self.active_material
533 | bake_material = self.bake_material
534 | nodes = bake_material.node_tree.nodes
535 | pbr_node = node_functions.get_pbr_node(bake_material)
536 | pbr_inputs = node_functions.get_pbr_inputs(pbr_node)
537 |
538 | for pbr_input in pbr_inputs.values():
539 |
540 | if not pbr_input.is_linked:
541 | continue
542 |
543 | # -----------------------REPLACE IMAGE TEXTURES--------------------#
544 | first_node_after_input = pbr_input.links[0].from_node
545 | tex_node = node_functions.get_node_by_type_recusivly(bake_material,first_node_after_input,constants.Node_Types.image_texture,True)
546 |
547 | bake_image_name = org_material.name + "_" + pbr_input.name
548 | bake_image = self.D.images.get(bake_image_name)
549 |
550 | # if no texture node found (baking procedural textures) add new one
551 | if tex_node is None:
552 | tex_node = node_functions.add_node(bake_material,constants.Shader_Node_Types.image_texture,bake_image_name)
553 |
554 | # keep org image if nothing changed
555 | if bake_image is None:
556 | org_image = self.D.images.get(tex_node.image.org_image_name)
557 | tex_node.image = org_image
558 | else:
559 | image_functions.save_image(bake_image)
560 | tex_node.image = bake_image
561 |
562 | # -----------------------LINKING--------------------#
563 | if pbr_input is pbr_inputs["normal_input"]:
564 | normal_node = first_node_after_input
565 | normal_node.inputs["Strength"].default_value = 1
566 |
567 |
568 | if normal_node.type == constants.Node_Types.bump_map:
569 | bump_node = normal_node
570 | normal_node = node_functions.add_node(bake_material,constants.Shader_Node_Types.normal,"Normal from Bump")
571 | normal_node.location = bump_node.location
572 | nodes.remove(bump_node)
573 |
574 | node_functions.make_link(bake_material, tex_node.outputs[0], normal_node.inputs["Color"])
575 | node_functions.make_link(bake_material, normal_node.outputs["Normal"], pbr_input)
576 | else:
577 | node_functions.make_link(bake_material,tex_node.outputs[0], pbr_input)
578 |
579 | # -----------------------SET COLOR SPACE--------------------#
580 | if pbr_input is not pbr_inputs["base_color_input"]:
581 | tex_node.image.colorspace_settings.name = "Non-Color"
582 |
583 | self.active_material = bake_material
584 | return bake_material
585 |
586 | def cleanup_nodes(self):
587 | bake_material = self.active_material
588 | node_functions.remove_unused_nodes(bake_material)
--------------------------------------------------------------------------------