"""Round depth volume around the smoothed center-surface normals.""" from cutout_volume import node, shift_stage def build_volume_rounding(group, volume, offset): previous = set(group.nodes) links = group.links def math(operation, a, b, location): result = node(group, "ShaderNodeMath", location) result.operation = operation for index, value in enumerate((a, b)): if isinstance(value, (int, float)): result.inputs[index].default_value = value else: links.new(value, result.inputs[index]) return result.outputs[0] def read(name, location, kind="FLOAT"): result = node(group, "GeometryNodeInputNamedAttribute", location) result.data_type = kind result.inputs["Name"].default_value = "o_balloon_surface_" + name return result.outputs["Attribute"] def store(geometry, name, value, location, kind="FLOAT"): result = node(group, "GeometryNodeStoreNamedAttribute", location) result.data_type, result.domain = kind, "POINT" result.inputs["Name"].default_value = "o_balloon_surface_" + name links.new(geometry, result.inputs["Geometry"]) links.new(value, result.inputs["Value"]) return result.outputs["Geometry"] control = node(group, "NodeGroupInput", (0, 1250)) profile = node(group, "GeometryNodeInputNamedAttribute", (0, 850)) profile.data_type = "FLOAT" profile.inputs["Name"].default_value = "o_balloon" balloon = profile.outputs["Attribute"] maximum = node(group, "GeometryNodeAttributeStatistic", (240, 850)) maximum.data_type, maximum.domain = "FLOAT", "POINT" links.new(volume, maximum.inputs["Geometry"]) links.new(balloon, maximum.inputs["Attribute"]) limit = math("MAXIMUM", maximum.outputs["Max"], 1e-8, (480, 850)) falloff = node(group, "ShaderNodeMapRange", (720, 850)) falloff.interpolation_type = "SMOOTHERSTEP" falloff.inputs["To Min"].default_value = 1 falloff.inputs["To Max"].default_value = 0 links.new(balloon, falloff.inputs["Value"]) links.new(limit, falloff.inputs["From Max"]) weight = math("MULTIPLY", falloff.outputs[0], control.outputs["Edge Round"], (960, 850)) center = read("center", (0, 1900)) position = node(group, "GeometryNodeInputPosition", (0, 2250)) axes = node(group, "ShaderNodeSeparateXYZ", (240, 2250)) links.new(position.outputs[0], axes.inputs[0]) target = math("ADD", center, balloon, (480, 1900)) delta = math("SUBTRACT", target, axes.outputs["Z"], (720, 1900)) delta = math("MULTIPLY", delta, weight, (960, 1900)) enabled = math("MINIMUM", control.outputs["Thickness"], 1, (480, 1300)) enabled = math("MAXIMUM", enabled, 0, (720, 1300)) delta = math("MULTIPLY", delta, enabled, (1200, 1900)) carrier = store(volume, "edge_weight", weight, (1200, 0)) carrier = store(carrier, "front_delta", delta, (1440, 0)) rear = math("MULTIPLY", balloon, weight, (1200, 850)) rear = math("MULTIPLY", rear, control.outputs["Thickness"], (1440, 850)) thickness = read("thickness", (1440, 1250)) total = math("ADD", thickness, delta, (1680, 1250)) total = math("ADD", total, rear, (1920, 1250)) carrier = store(carrier, "thickness", total, (1920, 0)) # Evaluate normals on a separate center surface with identical point indices. center_position = node(group, "ShaderNodeCombineXYZ", (480, -550)) for axis in ("X", "Y"): links.new(axes.outputs[axis], center_position.inputs[axis]) links.new(center, center_position.inputs["Z"]) surface = node(group, "GeometryNodeSetPosition", (720, -550)) links.new(volume, surface.inputs["Geometry"]) links.new(center_position.outputs[0], surface.inputs["Position"]) normal = node(group, "GeometryNodeInputNormal", (720, -1000)) blur = node(group, "GeometryNodeBlurAttribute", (960, -1000)) blur.data_type = "FLOAT_VECTOR" links.new(normal.outputs[0], blur.inputs["Value"]) local = node(group, "NodeGroupInput", (720, -1300)) links.new(local.outputs["Normal Smooth"], blur.inputs["Iterations"]) normalize = node(group, "ShaderNodeVectorMath", (1200, -1000)) normalize.operation = "NORMALIZE" links.new(blur.outputs[0], normalize.inputs[0]) index = node(group, "GeometryNodeInputIndex", (1200, -1350)) sample = node(group, "GeometryNodeSampleIndex", (1440, -550)) sample.data_type, sample.domain = "FLOAT_VECTOR", "POINT" links.new(surface.outputs[0], sample.inputs["Geometry"]) links.new(normalize.outputs[0], sample.inputs["Value"]) links.new(index.outputs[0], sample.inputs["Index"]) carrier = store(carrier, "normal", sample.outputs[0], (2160, 0), "FLOAT_VECTOR") captured = read("front_delta", (2160, 850)) displacement = node(group, "ShaderNodeCombineXYZ", (2400, 850)) links.new(captured, displacement.inputs["Z"]) front = node(group, "GeometryNodeSetPosition", (2640, 0)) links.new(carrier, front.inputs["Geometry"]) links.new(displacement.outputs[0], front.inputs["Offset"]) shift_stage(group, previous, offset) return front.outputs[0]