2025-07-01
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
# ##### 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)
|
||||
Reference in New Issue
Block a user