Improve section cutaway shader (#2000)

* Dynamically update section shader backface color

* Refactor add_section_plane operator

* Prevent error if last section has been removed

* Refactor section plane remover
No longer removes object if it is not a section plane
Offsets nodes so that they are always nice and ordered

* Fix shader glitch when user deletes last cutaway object

* Fix nodes offset when deleting cutaway object
This commit is contained in:
Gorgious56
2022-01-23 23:47:22 +01:00
committed by GitHub
parent 0bdb4b9fcf
commit d63d62ddd6
3 changed files with 122 additions and 81 deletions
+2 -2
View File
@@ -81,8 +81,8 @@ classes = [
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.OpenUpstream,
operator.AddSectionPlane,
operator.RemoveSectionPlane,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
+103 -78
View File
@@ -106,7 +106,9 @@ class OpenUpstream(bpy.types.Operator):
return {"FINISHED"}
class AddSectionPlane(bpy.types.Operator):
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"""
bl_idname = "bim.add_section_plane"
bl_label = "Add Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
@@ -151,80 +153,100 @@ class AddSectionPlane(bpy.types.Operator):
def create_section_compare_node(self):
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
group_input = group.nodes.new(type="NodeGroupInput")
group_input.location = 0, 50
separate_xyz = group.nodes.new(type="ShaderNodeSeparateXYZ")
separate_xyz.location = 200, 0
greater = group.nodes.new(type="ShaderNodeMath")
greater.operation = "GREATER_THAN"
greater.inputs[1].default_value = 0
greater.location = 400, 0
multiply = group.nodes.new(type="ShaderNodeMath")
multiply.operation = "MULTIPLY"
multiply.inputs[0].default_value = 1
multiply.location = 600, 150
group_output = group.nodes.new(type="NodeGroupOutput")
separate_xyz_a = group.nodes.new(type="ShaderNodeSeparateXYZ")
separate_xyz_b = group.nodes.new(type="ShaderNodeSeparateXYZ")
gt_a = group.nodes.new(type="ShaderNodeMath")
gt_a.operation = "GREATER_THAN"
gt_a.inputs[1].default_value = 0
gt_b = group.nodes.new(type="ShaderNodeMath")
gt_b.operation = "GREATER_THAN"
gt_b.inputs[1].default_value = 0
add = group.nodes.new(type="ShaderNodeMath")
compare = group.nodes.new(type="ShaderNodeMath")
compare.operation = "COMPARE"
compare.inputs[1].default_value = 2
group.links.new(group_input.outputs[""], separate_xyz_a.inputs[0])
group.links.new(group_input.outputs[""], separate_xyz_b.inputs[0])
group.links.new(separate_xyz_a.outputs[2], gt_a.inputs[0])
group.links.new(separate_xyz_b.outputs[2], gt_b.inputs[0])
group.links.new(gt_a.outputs[0], add.inputs[0])
group.links.new(gt_b.outputs[0], add.inputs[1])
group.links.new(add.outputs[0], compare.inputs[0])
group.links.new(compare.outputs[0], group_output.inputs[""])
group_output.location = 800, 0
group.links.new(group_input.outputs[""], multiply.inputs[0])
group.links.new(group_input.outputs[""], separate_xyz.inputs[0])
group.links.new(separate_xyz.outputs[2], greater.inputs[0])
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):
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
links = group.links
nodes = group.nodes
group_input = group.nodes.new(type="NodeGroupInput")
group_output = group.nodes.new(type="NodeGroupOutput")
group_input = nodes.new(type="NodeGroupInput")
group_output = nodes.new(type="NodeGroupOutput")
group_output.location = 600, 250
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
emission = group.nodes.new(type="ShaderNodeEmission")
backfacing_mix = nodes.new(type="ShaderNodeMixShader")
backfacing_mix.location = group_output.location - Vector((400, 350))
backfacing = nodes.new(type="ShaderNodeNewGeometry")
backfacing.location = backfacing_mix.location + Vector((-200, 200))
group_input.location = backfacing_mix.location - Vector((200, 50))
emission = nodes.new(type="ShaderNodeEmission")
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
emission.location = backfacing_mix.location - Vector((200, 150))
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
group.links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
transparent = nodes.new(type="ShaderNodeBsdfTransparent")
transparent.location = group_output.location - Vector((400, 100))
transparent = group.nodes.new(type="ShaderNodeBsdfTransparent")
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))
group.links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
group.links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
group.links.new(section_mix.outputs["Shader"], group_output.inputs[""])
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj = nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
section_compare = group.nodes.new(type="ShaderNodeGroup")
cut_obj.location = group_output.location - Vector((800, 150))
section_compare = nodes.new(type="ShaderNodeGroup")
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
section_compare.name = "Last Section Compare"
value = group.nodes.new(type="ShaderNodeValue")
value.name = "Mock Section"
value.outputs[0].default_value = 2
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[0])
group.links.new(value.outputs[0], section_compare.inputs[1])
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
section_compare.location = group_output.location - Vector((600, 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[""])
def append_obj_to_section_override_node(self, obj):
group = bpy.data.node_groups.get("Section Override")
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
try:
last_section_node = next(
n
for n in group.nodes
if isinstance(n, bpy.types.ShaderNodeGroup)
and n.node_tree.name == "Section Compare"
and not n.inputs[0].links
)
offset = Vector((0, 0))
except StopIteration:
last_section_node = group.nodes.get("Section Mix")
offset = Vector((200, 0))
section_compare = group.nodes.new(type="ShaderNodeGroup")
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
section_compare.location = last_section_node.location - Vector((200, 0)) - offset
last_compare = group.nodes.get("Last Section Compare")
last_compare.name = "Section Compare"
mock_section = group.nodes.get("Mock Section")
section_mix = group.nodes.get("Section Mix")
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
cut_obj.object = obj
cut_obj.location = last_section_node.location - Vector((400, 150)) - offset
group.links.new(last_compare.outputs[0], section_compare.inputs[0])
group.links.new(mock_section.outputs[0], section_compare.inputs[1])
group.links.new(cut_obj.outputs["Object"], last_compare.inputs[1])
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
group.links.new(section_compare.outputs[0], last_section_node.inputs[0])
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[1])
section_compare.name = "Last Section Compare"
@@ -275,7 +297,9 @@ class AddSectionPlane(bpy.types.Operator):
return node
class RemoveSectionPlane(bpy.types.Operator):
class BIM_OT_remove_section_plane(bpy.types.Operator):
"""Remove selected section plane. No effect if executed on a regular object"""
bl_idname = "bim.remove_section_plane"
bl_label = "Remove Temporary Section Cutaway"
bl_options = {"REGISTER", "UNDO"}
@@ -287,34 +311,35 @@ class RemoveSectionPlane(bpy.types.Operator):
def execute(self, context):
name = context.active_object.name
section_override = bpy.data.node_groups.get("Section Override")
for node in section_override.nodes:
if node.type != "TEX_COORD" or node.object.name != name:
continue
section_compare = node.outputs["Object"].links[0].to_node
# If the tex coord links to section_compare.inputs[1], it is called 'Input_3'
if node.outputs["Object"].links[0].to_socket.identifier == "Input_3":
section_override.links.new(
section_compare.inputs[0].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
)
else: # If it links to section_compare.inputs[0]
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
# Then it is the very last section. Purge everything.
self.purge_all_section_data(context)
return {"FINISHED"}
section_override.links.new(
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
)
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(node)
section_override.nodes.remove(tex_coords)
bpy.data.objects.remove(context.active_object)
old_last_compare = section_override.nodes.get("Last Section Compare")
old_last_compare.name = "Section Compare"
section_mix = section_override.nodes.get("Section Mix")
new_last_compare = section_mix.inputs[0].links[0].from_node
new_last_compare.name = "Last Section Compare"
bpy.ops.object.delete({"selected_objects": [context.active_object]})
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:
+17 -1
View File
@@ -150,6 +150,17 @@ def getMaterialPsetNames(self, context):
return materialpsetnames_enum
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 StrProperty(PropertyGroup):
pass
@@ -251,7 +262,12 @@ class BIMProperties(PropertyGroup):
last_transaction: StringProperty(name="Last Transaction")
should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False)
section_plane_colour: FloatVectorProperty(
name="Temporary Section Cutaway Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0
name="Temporary Section Cutaway Colour",
subtype="COLOR",
default=(1, 0, 0),
min=0.0,
max=1.0,
update=update_section_color,
)
area_unit: EnumProperty(
default="SQUARE_METRE",