- Removed deprecated tests and code related to shell wall connections and coincident sheets. - Updated attribute handling in depth surface and image cutout groups, including new vector attribute functions. - Enhanced the inflation logic in the image depth cutout group to better manage shell and balloon behaviors. - Improved parameter naming and descriptions for clarity in the user interface. - Streamlined the geometry processing logic to ensure better performance and maintainability.
214 lines
7.7 KiB
Python
214 lines
7.7 KiB
Python
"""Build interfaces and basic nodes shared by asset definitions."""
|
|
from types import SimpleNamespace
|
|
|
|
|
|
def prepare_node_group(node_group):
|
|
"""Hide unused outputs and discard disconnected interface nodes."""
|
|
for node in list(node_group.nodes):
|
|
for socket in node.outputs:
|
|
socket.hide = not socket.is_linked
|
|
if node.bl_idname == "NodeGroupInput" and not any(socket.is_linked for socket in node.outputs):
|
|
node_group.nodes.remove(node)
|
|
|
|
|
|
def replace_link_source(node_group, link, source):
|
|
"""Replace a source while preserving multi-input socket order."""
|
|
multi = link.to_socket.is_multi_input
|
|
replacement = node_group.links.new(source, link.to_socket)
|
|
if multi:
|
|
replacement.swap_multi_input_sort_id(link)
|
|
node_group.links.remove(link)
|
|
|
|
|
|
def interface_socket(group, name, in_out, socket_type, parent=None):
|
|
socket = group.interface.new_socket(
|
|
name=name,
|
|
in_out=in_out,
|
|
socket_type=socket_type,
|
|
parent=parent,
|
|
)
|
|
if in_out == "INPUT" and hasattr(socket, "structure_type"):
|
|
socket.structure_type = "SINGLE"
|
|
return socket
|
|
|
|
|
|
def group_input(nodes, visible_names):
|
|
node = nodes.new("NodeGroupInput")
|
|
visible = set(visible_names)
|
|
for output in node.outputs:
|
|
output.hide = output.name not in visible
|
|
return node
|
|
|
|
|
|
def float_input(
|
|
group,
|
|
name,
|
|
default,
|
|
minimum=0.0,
|
|
maximum=None,
|
|
subtype=None,
|
|
parent=None,
|
|
):
|
|
socket = interface_socket(group, name, "INPUT", "NodeSocketFloat", parent)
|
|
socket.default_value = default
|
|
socket.min_value = minimum
|
|
if maximum is not None:
|
|
socket.max_value = maximum
|
|
if subtype is not None:
|
|
socket.subtype = subtype
|
|
return socket
|
|
|
|
|
|
def bool_input(group, name, default=False, parent=None):
|
|
socket = interface_socket(group, name, "INPUT", "NodeSocketBool", parent)
|
|
socket.default_value = default
|
|
return socket
|
|
|
|
|
|
def vector_input(group, name, default, subtype=None, parent=None):
|
|
socket = interface_socket(group, name, "INPUT", "NodeSocketVector", parent)
|
|
socket.default_value = default
|
|
if subtype is not None:
|
|
socket.subtype = subtype
|
|
return socket
|
|
|
|
|
|
def math_node(nodes, operation):
|
|
node = nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
return node
|
|
|
|
|
|
_COMPARE_INPUTS = {
|
|
"FLOAT": ("A", "B"),
|
|
"INT": ("A_INT", "B_INT"),
|
|
"VECTOR": ("A_VEC3", "B_VEC3"),
|
|
"RGBA": ("A_COL", "B_COL"),
|
|
}
|
|
|
|
|
|
def compare_node(group, operation, a, b, data_type="FLOAT"):
|
|
"""Compare two inputs on the given data type and return the boolean result."""
|
|
node = group.nodes.new("FunctionNodeCompare")
|
|
node.data_type, node.operation = data_type, operation
|
|
for value, identifier in zip((a, b), _COMPARE_INPUTS[data_type]):
|
|
socket = next(item for item in node.inputs if item.identifier == identifier)
|
|
if isinstance(value, (bool, int, float)):
|
|
socket.default_value = value
|
|
else:
|
|
group.links.new(value, socket)
|
|
return node.outputs["Result"]
|
|
|
|
|
|
def boolean_node(group, operation, a=None, b=None):
|
|
"""Combine boolean inputs and return the boolean result."""
|
|
node = group.nodes.new("FunctionNodeBooleanMath")
|
|
node.operation = operation
|
|
for value, index in ((a, 0), (b, 1)):
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, bool):
|
|
node.inputs[index].default_value = value
|
|
else:
|
|
group.links.new(value, node.inputs[index])
|
|
return node.outputs["Boolean"]
|
|
|
|
|
|
def menu_switch(group, control, names, kind):
|
|
selector = next((node for node in group.nodes if node.bl_idname == "GeometryNodeMenuSwitch" and node.inputs["Menu"].is_linked and node.inputs["Menu"].links[0].from_socket.name == control), None)
|
|
if selector is None:
|
|
selector = group.nodes.new("GeometryNodeMenuSwitch")
|
|
selector.data_type = "BOOLEAN"
|
|
for item, name in zip(selector.enum_items, names):
|
|
item.name = name
|
|
selector.inputs[names[0]].default_value = False
|
|
selector.inputs[names[1]].default_value = True
|
|
local = group_input(group.nodes, {control})
|
|
group.links.new(local.outputs[control], selector.inputs["Menu"])
|
|
node = group.nodes.new("GeometryNodeSwitch")
|
|
node.input_type = kind
|
|
group.links.new(selector.outputs[0], node.inputs["Switch"])
|
|
return SimpleNamespace(inputs={names[0]: node.inputs["False"], names[1]: node.inputs["True"]}, outputs=node.outputs)
|
|
|
|
|
|
def read_float_attribute(group, name):
|
|
node = group.nodes.new("GeometryNodeInputNamedAttribute")
|
|
node.data_type = "FLOAT"
|
|
node.inputs["Name"].default_value = name
|
|
return node.outputs["Attribute"]
|
|
|
|
|
|
def read_vector_attribute(group, name):
|
|
node = group.nodes.new("GeometryNodeInputNamedAttribute")
|
|
node.data_type = "FLOAT_VECTOR"
|
|
node.inputs["Name"].default_value = name
|
|
return node.outputs["Attribute"]
|
|
|
|
|
|
def store_float_attribute(group, geometry, name, value, domain="POINT"):
|
|
node = group.nodes.new("GeometryNodeStoreNamedAttribute")
|
|
node.domain, node.data_type = domain, "FLOAT"
|
|
node.inputs["Name"].default_value = name
|
|
group.links.new(geometry, node.inputs["Geometry"])
|
|
if isinstance(value, (int, float)):
|
|
node.inputs["Value"].default_value = value
|
|
else:
|
|
group.links.new(value, node.inputs["Value"])
|
|
return node.outputs["Geometry"]
|
|
|
|
|
|
def store_vector_attribute(group, geometry, name, value, domain="POINT"):
|
|
node = group.nodes.new("GeometryNodeStoreNamedAttribute")
|
|
node.domain, node.data_type = domain, "FLOAT_VECTOR"
|
|
node.inputs["Name"].default_value = name
|
|
group.links.new(geometry, node.inputs["Geometry"])
|
|
group.links.new(value, node.inputs["Value"])
|
|
return node.outputs["Geometry"]
|
|
|
|
|
|
def evaluate_field(group, value, data_type, domain):
|
|
"""Evaluate a field on an explicit domain before downstream adaptation."""
|
|
node = group.nodes.new("GeometryNodeFieldOnDomain")
|
|
node.data_type, node.domain = data_type, domain
|
|
group.links.new(value, node.inputs["Value"])
|
|
return node.outputs["Value"]
|
|
|
|
|
|
def sample_field(group, geometry, value, data_type, domain="POINT", index=None):
|
|
"""Evaluate a field on an explicit source geometry and index domain."""
|
|
node = group.nodes.new("GeometryNodeSampleIndex")
|
|
node.data_type, node.domain = data_type, domain
|
|
node.clamp = False
|
|
if index is None:
|
|
index = group.nodes.new("GeometryNodeInputIndex").outputs["Index"]
|
|
group.links.new(geometry, node.inputs["Geometry"])
|
|
group.links.new(value, node.inputs["Value"])
|
|
group.links.new(index, node.inputs["Index"])
|
|
return node.outputs["Value"]
|
|
|
|
|
|
def store_boolean_attribute(group, geometry, name, value, domain="POINT"):
|
|
"""Store a topology marker needed after destructive mesh operations."""
|
|
node = group.nodes.new("GeometryNodeStoreNamedAttribute")
|
|
node.domain, node.data_type = domain, "BOOLEAN"
|
|
node.inputs["Name"].default_value = name
|
|
group.links.new(geometry, node.inputs["Geometry"])
|
|
group.links.new(value, node.inputs["Value"])
|
|
read = group.nodes.new("GeometryNodeInputNamedAttribute")
|
|
read.data_type = "BOOLEAN"
|
|
read.inputs["Name"].default_value = name
|
|
return node.outputs["Geometry"], read.outputs["Attribute"]
|
|
|
|
|
|
def remove_attribute_pattern(group, geometry, pattern):
|
|
"""Remove every attribute matching an internal wildcard pattern.
|
|
|
|
Always clean internal attributes with a pattern: naming an attribute that a
|
|
branch never stored makes the Remove Named Attribute node warn.
|
|
"""
|
|
node = group.nodes.new("GeometryNodeRemoveAttribute")
|
|
node.inputs["Name"].default_value = pattern
|
|
node.pattern_mode = "WILDCARD"
|
|
group.links.new(geometry, node.inputs["Geometry"])
|
|
return node.outputs["Geometry"]
|