# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### import logging import bpy import mathutils from bpy.types import Operator from . import reports bk_logger = logging.getLogger(__name__) def getNodes(nt, node_type="OUTPUT_MATERIAL"): chnodes = nt.nodes[:] nodes = [] while len(chnodes) > 0: n = chnodes.pop() if n.type == node_type: nodes.append(n) if n.type == "GROUP": chnodes.extend(n.node_tree.nodes) return nodes def getShadersCrawl(nt, chnodes): shaders = [] done_nodes = chnodes[:] while len(chnodes) > 0: check_node = chnodes.pop() is_shader = False for o in check_node.outputs: if o.type == "SHADER": is_shader = True for i in check_node.inputs: if i.type == "SHADER": is_shader = False # this is for mix nodes and group inputs.. if len(i.links) > 0: for l in i.links: fn = l.from_node if fn not in done_nodes: done_nodes.append(fn) chnodes.append(fn) if fn.type == "GROUP": group_outputs = getNodes( fn.node_tree, node_type="GROUP_OUTPUT" ) shaders.extend( getShadersCrawl(fn.node_tree, group_outputs) ) if check_node.type == "GROUP": is_shader = False if is_shader: shaders.append((check_node, nt)) return shaders def addColorCorrectors(material): nt = material.node_tree output = getNodes(nt, "OUTPUT_MATERIAL")[0] shaders = getShadersCrawl(nt, [output]) correctors = [] for shader, nt in shaders: if shader.type != "BSDF_TRANSPARENT": # exclude transparent for color tweaks for i in shader.inputs: if i.type == "RGBA": if len(i.links) > 0: l = i.links[0] if not ( l.from_node.type == "GROUP" and l.from_node.node_tree.name == "bkit_asset_tweaker" ): from_socket = l.from_socket to_socket = l.to_socket g = nt.nodes.new(type="ShaderNodeGroup") g.node_tree = bpy.data.node_groups["bkit_asset_tweaker"] g.location = shader.location g.location.x -= 100 nt.links.new(from_socket, g.inputs[0]) nt.links.new(g.outputs[0], to_socket) else: g = l.from_node tweakers.append(g) else: g = nt.nodes.new(type="ShaderNodeGroup") g.node_tree = bpy.data.node_groups["bkit_asset_tweaker"] g.location = shader.location g.location.x -= 100 nt.links.new(g.outputs[0], i) correctors.append(g) # def modelProxy(): # utils.p('No proxies in Blender anymore') # return False # # s = bpy.context.scene # ao = bpy.context.active_object # if utils.is_linked_asset(ao): # utils.activate(ao) # # g = ao.instance_collection # # rigs = [] # # for ob in g.objects: # if ob.type == 'ARMATURE': # rigs.append(ob) # # if len(rigs) == 1: # # ao.instance_collection = None # bpy.ops.object.duplicate() # new_ao = bpy.context.view_layer.objects.active # new_ao.instance_collection = g # new_ao.empty_display_type = 'SPHERE' # new_ao.empty_display_size *= 0.1 # # # bpy.ops.object.proxy_make(object=rigs[0].name) # proxy = bpy.context.active_object # bpy.context.view_layer.objects.active = ao # ao.select_set(True) # new_ao.select_set(True) # new_ao.use_extra_recalc_object = True # new_ao.use_extra_recalc_data = True # bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) # return True # else: # TODO report this to ui # utils.p('not sure what to proxify') # return False eevee_transp_nodes = [ "BSDF_GLASS", "BSDF_REFRACTION", "BSDF_TRANSPARENT", "PRINCIPLED_VOLUME", "VOLUME_ABSORPTION", "VOLUME_SCATTER", ] def ensure_eevee_transparency(m): """ensures alpha for transparent materials when the user didn't set it up correctly""" # if the blend mode is opaque, it means user probably ddidn't know or forgot to # set up material properly if m.blend_method != "OPAQUE": return alpha = False for n in m.node_tree.nodes: if n.type in eevee_transp_nodes: alpha = True elif n.type == "BSDF_PRINCIPLED": if bpy.app.version < (4, 0, 0): i = n.inputs["Transmission"] else: i = n.inputs["Transmission Weight"] if i.default_value > 0 or len(i.links) > 0: alpha = True if alpha: m.blend_method = "HASHED" m.shadow_method = "HASHED" class BringToScene(Operator): """Bring linked object hierarchy to scene and make it editable""" bl_idname = "object.blenderkit_bring_to_scene" bl_label = "BlenderKit bring objects to scene" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return bpy.context.view_layer.objects.active is not None def execute(self, context): s = bpy.context.scene sobs = s.collection.all_objects aob = bpy.context.active_object dg = aob.instance_collection vlayer = bpy.context.view_layer instances_emptys = [] # first, find instances of this collection in the scene for ob in sobs: if ob.instance_collection == dg and ob not in instances_emptys: instances_emptys.append(ob) ob.instance_collection = None ob.instance_type = "NONE" # dg.make_local parent = None obs = [] for ob in dg.objects: dg.objects.unlink(ob) try: s.collection.objects.link(ob) ob.select_set(True) obs.append(ob) if ob.parent == None: parent = ob bpy.context.view_layer.objects.active = parent except Exception as e: bk_logger.exception( f"BringToScene.execute: failed to link an object to the collection" ) bpy.ops.object.make_local(type="ALL") for i, ob in enumerate(obs): if ob.name in vlayer.objects: obs[i] = vlayer.objects[ob.name] try: ob.select_set(True) except Exception as e: bk_logger.exception( f"BringToScene.execute: failed to select an object from the collection, getting a replacement." ) related = [] for i, ob in enumerate(instances_emptys): if i > 0: bpy.ops.object.duplicate(linked=True) related.append( [ ob, bpy.context.active_object, mathutils.Vector(bpy.context.active_object.scale), ] ) for relation in related: try: bpy.ops.object.select_all(action="DESELECT") except Exception as e: reports.add_report( f"BringToScene.execute: {str(e)}", 3, type="ERROR", ) raise e bpy.context.view_layer.objects.active = relation[0] relation[0].select_set(True) relation[1].select_set(True) relation[1].matrix_world = relation[0].matrix_world relation[1].scale.x = relation[2].x * relation[0].scale.x relation[1].scale.y = relation[2].y * relation[0].scale.y relation[1].scale.z = relation[2].z * relation[0].scale.z bpy.ops.object.parent_set(type="OBJECT", keep_transform=True) return {"FINISHED"} # class ModelProxy(Operator): # """Attempt to create proxy armature from the asset""" # bl_idname = "object.blenderkit_make_proxy" # bl_label = "BlenderKit Make Proxy" # # @classmethod # def poll(cls, context): # return bpy.context.view_layer.objects.active is not None # # def execute(self, context): # result = modelProxy() # if not result: # self.report({'INFO'}, 'No proxy made.There is no armature or more than one in the model.') # return {'FINISHED'} class ColorCorrector(Operator): """Add color corector to the asset.""" bl_idname = "object.blenderkit_color_corrector" bl_label = "Add color corrector" @classmethod def poll(cls, context): return bpy.context.view_layer.objects.active is not None def execute(self, context): ao = bpy.context.active_object g = ao.instance_collection ao["color correctors"] = [] mats = [] for o in g.objects: for ms in o.material_slots: if ms.material not in mats: mats.append(ms.material) for mat in mats: correctors = addColorCorrectors(mat) return "FINISHED" def register_overrides(): bpy.utils.register_class(BringToScene) # bpy.utils.register_class(ModelProxy) bpy.utils.register_class(ColorCorrector) def unregister_overrides(): bpy.utils.unregister_class(BringToScene) # bpy.utils.unregister_class(ModelProxy) bpy.utils.unregister_class(ColorCorrector)