Files
blender-portable-repo/extensions/user_default/blenderkit/overrides.py
T
2026-03-17 14:30:01 -06:00

331 lines
11 KiB
Python

# ##### 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)