- Implemented `cutout_shell.py` to build front normals and shell positions for balloon geometry. - Created `cutout_volume.py` to establish smooth center fields and manage balloon surface attributes. - Introduced `pisa_sr_export.py` for exporting PiSA-SR components with dependencies and asset finalization.
143 lines
6.8 KiB
Python
143 lines
6.8 KiB
Python
"""Estimate existing front volume and add continuous normal-directed inflation."""
|
|
from cutout_volume import node
|
|
|
|
|
|
def _math(group, operation, a, b, location):
|
|
result = node(group, "ShaderNodeMath", location)
|
|
result.operation = operation
|
|
for index, value in enumerate((a, b)):
|
|
if hasattr(value, "node"):
|
|
group.links.new(value, result.inputs[index])
|
|
else:
|
|
result.inputs[index].default_value = value
|
|
return result.outputs[0]
|
|
|
|
|
|
def _read(group, 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(group, 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
|
|
group.links.new(geometry, result.inputs["Geometry"])
|
|
group.links.new(value, result.inputs["Value"])
|
|
return result.outputs["Geometry"]
|
|
|
|
|
|
def build_front_fields(group, geometry):
|
|
"""Fit structural amplitude over neighbouring points before creating the shell."""
|
|
links = group.links
|
|
|
|
def math(operation, a, b, x, y):
|
|
return _math(group, operation, a, b, (4800 + x, -2500 + y))
|
|
|
|
def blur(value, x, y, weight=None):
|
|
result = node(group, "GeometryNodeBlurAttribute", (4800 + x, -2500 + y))
|
|
result.data_type = "FLOAT"
|
|
result.inputs["Iterations"].default_value = 256
|
|
links.new(value, result.inputs["Value"])
|
|
if weight is not None:
|
|
links.new(weight, result.inputs["Weight"])
|
|
return result.outputs[0]
|
|
|
|
profile_node = node(group, "GeometryNodeInputNamedAttribute", (4800, -2500))
|
|
profile_node.data_type = "FLOAT"
|
|
profile_node.inputs["Name"].default_value = "o_balloon"
|
|
profile = profile_node.outputs["Attribute"]
|
|
rim = _read(group, "rim", (4800, -3050))
|
|
interior = math("SUBTRACT", 1, rim, 240, -550)
|
|
position = node(group, "GeometryNodeInputPosition", (4800, -1900))
|
|
axes = node(group, "ShaderNodeSeparateXYZ", (5040, -1900))
|
|
links.new(position.outputs[0], axes.inputs[0])
|
|
depth = axes.outputs["Z"]
|
|
depth_slow = blur(depth, 480, 600, interior)
|
|
profile_slow = blur(profile, 480, 0, interior)
|
|
depth_bump = math("SUBTRACT", depth, depth_slow, 720, 600)
|
|
profile_bump = math("SUBTRACT", profile, profile_slow, 720, 0)
|
|
numerator = math("MULTIPLY", depth_bump, profile_bump, 960, 600)
|
|
denominator = math("MULTIPLY", profile_bump, profile_bump, 960, 0)
|
|
numerator = blur(numerator, 1200, 600)
|
|
denominator = blur(denominator, 1200, 0)
|
|
denominator = math("MAXIMUM", denominator, 1e-10, 1440, 0)
|
|
structure = math("DIVIDE", numerator, denominator, 1680, 600)
|
|
structure = math("MAXIMUM", structure, 0, 1920, 600)
|
|
carrier = _store(group, geometry, "structure", structure, (7300, 0))
|
|
|
|
normal = _read(group, "front_normal", (7100, -600), "FLOAT_VECTOR")
|
|
controls = node(group, "NodeGroupInput", (6860, -1100))
|
|
# Keep the accepted broad front-direction filter at the default of 50;
|
|
# zero disables both this filter and the initial front/back normal filters.
|
|
spread = _math(group, "MULTIPLY", controls.outputs["Normal Smooth"], 256 / 50, (7100, -1100))
|
|
smooth = node(group, "GeometryNodeBlurAttribute", (7340, -600))
|
|
smooth.data_type = "FLOAT_VECTOR"
|
|
links.new(normal, smooth.inputs["Value"])
|
|
links.new(spread, smooth.inputs["Iterations"])
|
|
normalize = node(group, "ShaderNodeVectorMath", (7580, -600))
|
|
normalize.operation = "NORMALIZE"
|
|
links.new(smooth.outputs[0], normalize.inputs[0])
|
|
carrier = _store(group, carrier, "inflation_normal", normalize.outputs[0], (7770, 0), "FLOAT_VECTOR")
|
|
return carrier
|
|
|
|
|
|
def build_inflation_position(group, position, distance, uniform):
|
|
"""Grow both modes continuously while leaving the rear cap in place."""
|
|
previous = set(group.nodes)
|
|
links = group.links
|
|
|
|
def math(operation, a, b, x, y):
|
|
return _math(group, operation, a, b, (x, y))
|
|
|
|
structure = _read(group, "structure", (14000, -4300))
|
|
control = node(group, "NodeGroupInput", (13760, -5000))
|
|
a = math("MULTIPLY", control.outputs["Thickness"], control.outputs["Front Inflation"], 14240, -5000)
|
|
# A shifted cubic blend preserves continuous growth and height order.
|
|
shifted = math("ADD", a, 0.75, 14480, -5000)
|
|
quadratic = math("MULTIPLY", a, shifted, 14720, -5000)
|
|
quadratic = math("ADD", quadratic, 0.1875, 14960, -5000)
|
|
q = math("MULTIPLY", a, quadratic, 15200, -5000)
|
|
k = math("ADD", structure, 0.25, 14240, -4300)
|
|
square = math("MULTIPLY", k, k, 14480, -4300)
|
|
cube = math("MULTIPLY", square, k, 14720, -4300)
|
|
total = math("ADD", cube, q, 14960, -4300)
|
|
root = math("POWER", total, 1 / 3, 15200, -4300)
|
|
root_square = math("MULTIPLY", root, root, 15440, -4000)
|
|
product = math("MULTIPLY", root, k, 15440, -4300)
|
|
denominator = math("ADD", root_square, product, 15680, -4300)
|
|
denominator = math("ADD", denominator, square, 15920, -4300)
|
|
response = math("DIVIDE", q, denominator, 15920, -4800)
|
|
profile = node(group, "GeometryNodeInputNamedAttribute", (15920, -3700))
|
|
profile.data_type = "FLOAT"
|
|
profile.inputs["Name"].default_value = "o_balloon"
|
|
balloon = math("MULTIPLY", profile.outputs["Attribute"], response, 16160, -4250)
|
|
uniform_control = node(group, "NodeGroupInput", (15920, -5100))
|
|
uniform_amount = math("MULTIPLY", uniform_control.outputs["Uniform Thickness"], uniform_control.outputs["Front Inflation"], 16160, -5100)
|
|
selected = node(group, "GeometryNodeSwitch", (16400, -4550))
|
|
selected.input_type = "FLOAT"
|
|
links.new(uniform, selected.inputs["Switch"])
|
|
links.new(balloon, selected.inputs["False"])
|
|
links.new(uniform_amount, selected.inputs["True"])
|
|
displacement = selected.outputs[0]
|
|
height = _read(group, "thickness", (14000, -5500))
|
|
layer = math("DIVIDE", distance, height, 14240, -5500)
|
|
layer = math("ADD", 1, layer, 14480, -5500)
|
|
layer = math("MAXIMUM", layer, 0, 14720, -5500)
|
|
layer = math("MINIMUM", layer, 1, 14960, -5500)
|
|
displacement = math("MULTIPLY", displacement, layer, 16640, -4550)
|
|
normal = _read(group, "inflation_normal", (16640, -4000), "FLOAT_VECTOR")
|
|
offset = node(group, "ShaderNodeVectorMath", (16880, -4550))
|
|
offset.operation = "SCALE"
|
|
links.new(normal, offset.inputs[0])
|
|
links.new(displacement, offset.inputs[3])
|
|
result = node(group, "ShaderNodeVectorMath", (17120, -4550))
|
|
result.operation = "ADD"
|
|
links.new(position, result.inputs[0])
|
|
links.new(offset.outputs[0], result.inputs[1])
|
|
for item in set(group.nodes) - previous:
|
|
item.location.y = -3550 + (item.location.y + 3700) * 0.7
|
|
return result.outputs[0]
|