331 lines
11 KiB
Python
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)
|