# ##### 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 ##### if "bpy" in locals(): from importlib import reload utils = reload(utils) else: from blenderkit import utils import bpy, mathutils from bpy.types import ( Operator) 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(): 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 print('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': alpha = False for n in m.node_tree.nodes: if n.type in eevee_transp_nodes: alpha = True elif n.type == 'BSDF_PRINCIPLED': i = n.inputs['Transmission'] 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: print(e) 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: print('failed to select an object from the collection, getting a replacement.') print(e) 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: bpy.ops.object.select_all(action='DESELECT') 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)