From d63d62ddd6352573bf6cd617299ca429df60fa12 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Sun, 23 Jan 2022 23:47:22 +0100 Subject: [PATCH] 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 --- src/blenderbim/blenderbim/bim/__init__.py | 4 +- src/blenderbim/blenderbim/bim/operator.py | 181 ++++++++++++---------- src/blenderbim/blenderbim/bim/prop.py | 18 ++- 3 files changed, 122 insertions(+), 81 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index d075705735..5ffc744353 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 99473710aa..087a6dcede 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -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: diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 397e991e7d..06af23d2e4 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -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",