Refactor Standalone section shader (#2172)

* Refactor Standalone section shader

* Remove leftover code
This commit is contained in:
Gorgious56
2022-05-03 18:21:51 +02:00
committed by GitHub
parent 9333839267
commit 6e55a04550
@@ -13,11 +13,9 @@ bl_info = {
import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty
from bpy.types import Operator, PropertyGroup, Panel
from mathutils import Vector, Matrix, Euler
from math import radians
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
@@ -30,62 +28,131 @@ from bpy.props import (
)
def update_section_color(self, context):
section_node_group = bpy.data.node_groups.get("Section Override")
if section_node_group is None:
return
try:
emission_node = next(n for n in section_node_group.nodes if isinstance(n, bpy.types.ShaderNodeEmission))
emission_node.inputs[0].default_value = list(self.section_plane_colour) + [1]
except StopIteration:
pass
class SectionCutawayManager:
@staticmethod
def get_section_tree():
return bpy.data.node_groups.get("Section Override")
@staticmethod
def get_sections_map():
section_tree = SectionCutawayManager.get_section_tree()
return {n: n.object for n in section_tree.nodes if isinstance(n, bpy.types.ShaderNodeTexCoord)}
class BIM_OT_add_section_plane(bpy.types.Operator):
"""Add a temporary empty object as a section cutaway. Cull all geometry rendering below the empty's local Z axis"""
@staticmethod
def purge_all_section_data():
section_override_mat = bpy.data.materials.get("Section Override")
if section_override_mat:
bpy.data.materials.remove(section_override_mat)
for material in bpy.data.materials:
if not material.node_tree:
continue
override = material.node_tree.nodes.get("Section Override")
if not override:
continue
material.node_tree.links.new(
override.inputs[0].links[0].from_socket, override.outputs[0].links[0].to_socket
)
material.node_tree.nodes.remove(override)
bpy.data.node_groups.remove(SectionCutawayManager.get_section_tree())
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare"))
bl_idname = "bim.add_section_plane"
bl_label = "Add Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
@staticmethod
def clean():
sections_map = SectionCutawayManager.get_sections_map()
sections_map_iterator = list(sections_map.items())
for tex_coord_node, section_obj in sections_map_iterator:
if section_obj is None or section_obj.users == 1:
SectionCutawayManager.unplug(tex_coord_node)
sections_map.pop(tex_coord_node)
if not any(o for o in sections_map.values() if o.users > 1):
SectionCutawayManager.purge_all_section_data()
def execute(self, context):
obj = self.create_section_obj(context)
if not self.has_section_override_node():
self.create_section_compare_node()
self.create_section_override_node(obj, context)
else:
self.append_obj_to_section_override_node(obj)
self.add_default_material_if_none_exists(context)
self.override_materials()
return {"FINISHED"}
@staticmethod
def unplug(tex_coords):
section_tree = SectionCutawayManager.get_section_tree()
section_compare = tex_coords.outputs["Object"].links[0].to_node
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
next_section_compare = section_compare.outputs[0].links[0].to_node
section_tree.links.new(previous_section_compare.outputs[0], next_section_compare.inputs[0])
SectionCutawayManager.offset_previous_nodes(section_compare, offset_x=200)
section_tree.nodes.remove(section_compare)
section_tree.nodes.remove(tex_coords)
def create_section_obj(self, context):
@staticmethod
def offset_previous_nodes(section_compare, offset_x=0, offset_y=0):
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
previous_section_compare.location += Vector((offset_x, offset_y))
if previous_section_compare.inputs[1].links:
previous_section_compare.inputs[1].links[0].from_node.location += Vector((offset_x, offset_y))
SectionCutawayManager.offset_previous_nodes(previous_section_compare, offset_x, offset_y)
@staticmethod
def override_materials():
override = SectionCutawayManager.get_section_tree()
for material in bpy.data.materials:
material.use_nodes = True
if material.node_tree.nodes.get(override.name):
continue
material.blend_method = "HASHED"
material.shadow_method = "HASHED"
material_output = next((n for n in material.node_tree.nodes if n.type == "OUTPUT_MATERIAL"), None)
if material_output is None:
continue
from_socket = material_output.inputs[0].links[0].from_socket
section_override = material.node_tree.nodes.new(type="ShaderNodeGroup")
section_override.name = "Section Override"
section_override.node_tree = override
material.node_tree.links.new(from_socket, section_override.inputs[0])
material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0])
@staticmethod
def create_section_obj(from_obj, scene):
section = bpy.data.objects.new("Section", None)
section.empty_display_type = "SINGLE_ARROW"
section.empty_display_size = 5
section.show_in_front = True
if (
context.active_object
and context.active_object.select_get()
and isinstance(context.active_object.data, bpy.types.Camera)
):
section.matrix_world = (
context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
)
if from_obj and from_obj.select_get() and isinstance(from_obj.data, bpy.types.Camera):
section.matrix_world = from_obj.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
else:
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), "XYZ")
section.location = context.scene.cursor.location
section.location = scene.cursor.location
collection = bpy.data.collections.get("Sections")
if not collection:
collection = bpy.data.collections.new("Sections")
context.scene.collection.children.link(collection)
scene.collection.children.link(collection)
collection.objects.link(section)
return section
def has_section_override_node(self):
return bpy.data.node_groups.get("Section Override")
@classmethod
def add_section_plane(cls, context):
obj = cls.create_section_obj(context.active_object, context.scene)
if cls.get_section_tree() is not None:
cls.append_obj_to_section_override_node(obj)
else:
cls.create_section_compare_node()
cls.create_section_override_node(obj, context)
cls.add_default_material_if_none_exists(context)
cls.override_materials()
def create_section_compare_node(self):
@classmethod
def remove_all_section_planes(cls):
objs = [o for o in cls.get_sections_map().values() if o is not None]
cls.remove_section_planes(objs)
@classmethod
def remove_section_planes(cls, objects):
sections_map = cls.get_sections_map()
for obj in objects:
tex_coords = next((n for n in sections_map if sections_map[n] == obj), None)
if tex_coords is not None:
cls.unplug(tex_coords)
sections_map.pop(tex_coords)
bpy.data.objects.remove(obj)
@staticmethod
def create_section_compare_node():
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
group_input = group.nodes.new(type="NodeGroupInput")
group_input.location = 0, 50
@@ -112,7 +179,8 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
group.links.new(greater.outputs[0], multiply.inputs[1])
group.links.new(multiply.outputs[0], group_output.inputs[""])
def create_section_override_node(self, obj, context):
@staticmethod
def create_section_override_node(obj, context):
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
links = group.links
nodes = group.nodes
@@ -121,45 +189,53 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
group_output = nodes.new(type="NodeGroupOutput")
group_output.location = 600, 250
hide_mix = group.nodes.new(type="ShaderNodeMixShader")
hide_mix.name = "Hide Mix"
hide_mix.inputs[0].default_value = 0
hide_mix.location = group_output.location - Vector((200, 0))
backfacing_mix = nodes.new(type="ShaderNodeMixShader")
backfacing_mix.location = group_output.location - Vector((400, 350))
backfacing_mix.location = group_output.location - Vector((600, 350))
backfacing = nodes.new(type="ShaderNodeNewGeometry")
backfacing.location = backfacing_mix.location + Vector((-200, 200))
group_input.location = backfacing_mix.location - Vector((200, 50))
backfacing.location = backfacing_mix.location + Vector((-400, 200))
group_input.location = backfacing_mix.location - Vector((400, 50))
emission = nodes.new(type="ShaderNodeEmission")
emission.inputs[0].default_value = list(context.scene.SectionProperties.section_plane_colour) + [1]
emission.location = backfacing_mix.location - Vector((200, 150))
emission.location = backfacing_mix.location - Vector((400, 150))
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
transparent.location = group_output.location - Vector((400, 100))
transparent.location = group_output.location - Vector((600, 100))
section_mix = group.nodes.new(type="ShaderNodeMixShader")
section_mix.name = "Section Mix"
section_mix.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway
section_mix.location = group_output.location - Vector((200, 0))
section_mix.location = group_output.location - Vector((400, 0))
cut_obj = nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
cut_obj.location = group_output.location - Vector((800, 150))
cut_obj.location = group_output.location - Vector((1000, 150))
section_compare = nodes.new(type="ShaderNodeGroup")
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
section_compare.name = "Last Section Compare"
section_compare.location = group_output.location - Vector((600, 0))
section_compare.location = group_output.location - Vector((800, 0))
links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
links.new(group_input.outputs[""], backfacing_mix.inputs[1])
links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
links.new(section_compare.outputs[0], section_mix.inputs[0])
links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
links.new(section_mix.outputs["Shader"], group_output.inputs[""])
links.new(transparent.outputs[0], section_mix.inputs[1])
links.new(backfacing_mix.outputs[0], section_mix.inputs[2])
links.new(section_mix.outputs[0], hide_mix.inputs[1])
links.new(group_input.outputs["Shader"], hide_mix.inputs[2])
links.new(hide_mix.outputs[0], group_output.inputs[""])
def append_obj_to_section_override_node(self, obj):
group = bpy.data.node_groups.get("Section Override")
@staticmethod
def append_obj_to_section_override_node(obj):
group = SectionCutawayManager.get_section_tree()
try:
last_section_node = next(
n
@@ -185,7 +261,8 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
section_compare.name = "Last Section Compare"
def add_default_material_if_none_exists(self, context):
@staticmethod
def add_default_material_if_none_exists(context):
material = bpy.data.materials.get("Section Override")
if not material:
material = bpy.data.materials.new("Section Override")
@@ -208,92 +285,74 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
else:
obj.data.materials.append(material)
def override_materials(self):
override = bpy.data.node_groups.get("Section Override")
for material in bpy.data.materials:
material.use_nodes = True
if material.node_tree.nodes.get("Section Override"):
continue
material.blend_method = "HASHED"
material.shadow_method = "HASHED"
material_output = self.get_node(material.node_tree.nodes, "OUTPUT_MATERIAL")
if not material_output:
continue
from_socket = material_output.inputs[0].links[0].from_socket
section_override = material.node_tree.nodes.new(type="ShaderNodeGroup")
section_override.name = "Section Override"
section_override.node_tree = override
material.node_tree.links.new(from_socket, section_override.inputs[0])
material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0])
def get_node(self, nodes, node_type):
for node in nodes:
if node.type == node_type:
return node
class BIM_OT_section_plane_refresh(Operator):
bl_idname = "bim.section_plane_refresh"
bl_label = "Refresh Section Cutaway Shader"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
update_visibility(None, context)
SectionCutawayManager.add_default_material_if_none_exists(context)
SectionCutawayManager.clean()
return {"FINISHED"}
class BIM_OT_remove_section_plane(bpy.types.Operator):
"""Remove selected section plane. No effect if executed on a regular object"""
class BIM_OT_section_plane_add(Operator):
"""Add a temporary empty object as a section cutaway. Cull all geometry rendering below the empty's local Z axis"""
bl_idname = "bim.remove_section_plane"
bl_idname = "bim.section_plane_add"
bl_label = "Add Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
SectionCutawayManager.add_section_plane(context)
SectionCutawayManager.clean()
return {"FINISHED"}
class BIM_OT_section_plane_remove(Operator):
bl_idname = "bim.section_plane_remove"
bl_label = "Remove Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Remove section planes. No effect if executed on a regular object"
remove_all: BoolProperty(default=False, options={"HIDDEN"})
@classmethod
def poll(cls, context):
return context.active_object and bpy.data.node_groups.get("Section Override")
return SectionCutawayManager.get_section_tree() is not None
def execute(self, context):
name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
tex_coords = next(
(
n
for n in section_override.nodes
if isinstance(n, bpy.types.ShaderNodeTexCoord) and n.object.name == name
),
None,
)
if tex_coords is not None:
section_compare = tex_coords.outputs["Object"].links[0].to_node
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
next_section_compare = section_compare.outputs[0].links[0].to_node
section_override.links.new(previous_section_compare.outputs[0], next_section_compare.inputs[0])
self.offset_previous_nodes(section_compare, offset_x=200)
section_override.nodes.remove(section_compare)
section_override.nodes.remove(tex_coords)
bpy.data.objects.remove(context.active_object)
if self.remove_all:
SectionCutawayManager.remove_all_section_planes()
else:
SectionCutawayManager.remove_section_planes(context.selected_objects)
SectionCutawayManager.clean()
return {"FINISHED"}
def offset_previous_nodes(self, section_compare, offset_x=0, offset_y=0):
if section_compare.inputs[0].links:
previous_section_compare = section_compare.inputs[0].links[0].from_node
previous_section_compare.location += Vector((offset_x, offset_y))
if previous_section_compare.inputs[1].links:
previous_section_compare.inputs[1].links[0].from_node.location += Vector((offset_x, offset_y))
self.offset_previous_nodes(previous_section_compare, offset_x, offset_y)
def purge_all_section_data(self, context):
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
for material in bpy.data.materials:
if not material.node_tree:
continue
override = material.node_tree.nodes.get("Section Override")
if not override:
continue
material.node_tree.links.new(
override.inputs[0].links[0].from_socket, override.outputs[0].links[0].to_socket
)
material.node_tree.nodes.remove(override)
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Override"))
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare"))
bpy.ops.object.delete({"selected_objects": [context.active_object]})
def update_visibility(self, context):
tree = SectionCutawayManager.get_section_tree()
if tree:
node = tree.nodes.get("Hide Mix")
if node:
node.inputs[0].default_value = int(context.scene.SectionProperties.hide)
def update_section_color(self, context):
section_node_group = SectionCutawayManager.get_section_tree()
if section_node_group is None:
return
try:
emission_node = next(n for n in section_node_group.nodes if isinstance(n, bpy.types.ShaderNodeEmission))
emission_node.inputs[0].default_value = list(self.section_plane_colour) + [1]
except StopIteration:
pass
class SectionProperties(PropertyGroup):
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
hide: BoolProperty(default=False, name="Toggle", update=update_visibility)
section_plane_colour: FloatVectorProperty(
name="Temporary Section Cutaway Colour",
subtype="COLOR",
@@ -304,7 +363,7 @@ class SectionProperties(PropertyGroup):
)
class BIM_PT_section_plane(bpy.types.Panel):
class BIM_PT_section_plane(Panel):
bl_label = "Temporary Section Cutaways"
bl_idname = "BIM_PT_section_plane"
bl_space_type = "VIEW_3D"
@@ -316,30 +375,31 @@ class BIM_PT_section_plane(bpy.types.Panel):
layout.use_property_split = True
props = context.scene.SectionProperties
row = layout.row()
row.prop(props, "should_section_selected_objects")
layout.prop(props, "should_section_selected_objects", text="Only Selected", icon="RESTRICT_SELECT_OFF")
layout.prop(props, "section_plane_colour", text="Section Color")
layout.prop(props, "hide", icon="HIDE_ON" if props.hide else "HIDE_OFF")
row = layout.row()
row.prop(props, "section_plane_colour")
row = layout.row(align=True)
row.operator("bim.add_section_plane")
row.operator("bim.remove_section_plane")
layout.operator("bim.section_plane_refresh", text="Refresh", icon="FILE_REFRESH")
layout.operator("bim.section_plane_add", text="Add Section Cutaway", icon="ADD")
layout.operator("bim.section_plane_remove", text="Remove Selected Sections", icon="REMOVE").remove_all = False
layout.operator("bim.section_plane_remove", text="Remove All Sections", icon="TRASH").remove_all = True
def register():
bpy.utils.register_class(SectionProperties)
bpy.utils.register_class(BIM_OT_add_section_plane)
bpy.utils.register_class(BIM_OT_remove_section_plane)
bpy.utils.register_class(BIM_OT_section_plane_add)
bpy.utils.register_class(BIM_OT_section_plane_remove)
bpy.utils.register_class(BIM_PT_section_plane)
bpy.types.Scene.SectionProperties = bpy.props.PointerProperty(type=SectionProperties)
bpy.utils.register_class(BIM_OT_section_plane_refresh)
bpy.types.Scene.SectionProperties = PointerProperty(type=SectionProperties)
def unregister():
bpy.utils.unregister_class(BIM_OT_add_section_plane)
bpy.utils.unregister_class(BIM_OT_remove_section_plane)
bpy.utils.unregister_class(BIM_OT_section_plane_add)
bpy.utils.unregister_class(BIM_OT_section_plane_remove)
bpy.utils.unregister_class(BIM_PT_section_plane)
bpy.utils.unregister_class(SectionProperties)
bpy.utils.unregister_class(BIM_OT_section_plane_refresh)
del bpy.types.Scene.SectionProperties