- Implemented the "O Image Relief Plane" Geometry Nodes group to create a fixed-base image relief using depth images and various parameters. - Created the "O Shadeless" shader node group to display color without scene lighting while preserving shadows and reflections. - Introduced a preview feature for node groups, allowing for interactive visualization of node arrangements in an HTML format. - Added a template HTML file for rendering node previews with customizable views and controls.
163 lines
6.5 KiB
Python
163 lines
6.5 KiB
Python
"""Build geometry calculations shared by Cutout assets."""
|
|
from .nodes import math_node
|
|
from anyimage.operators.cutout_tool.shape import BALLOON_ATTRIBUTE_NAME
|
|
|
|
|
|
def _island_cleanup(group, geometry, cleanup):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
mesh_island = nodes.new("GeometryNodeInputMeshIsland")
|
|
face_area = nodes.new("GeometryNodeInputMeshFaceArea")
|
|
island_area = nodes.new("GeometryNodeAccumulateField")
|
|
island_area.data_type = "FLOAT"
|
|
island_area.domain = "FACE"
|
|
links.new(face_area.outputs["Area"], island_area.inputs["Value"])
|
|
links.new(mesh_island.outputs["Island Index"], island_area.inputs["Group ID"])
|
|
|
|
tiny = math_node(nodes, "LESS_THAN")
|
|
links.new(island_area.outputs["Total"], tiny.inputs[0])
|
|
links.new(cleanup, tiny.inputs[1])
|
|
delete = nodes.new("GeometryNodeDeleteGeometry")
|
|
delete.domain = "FACE"
|
|
links.new(geometry, delete.inputs["Geometry"])
|
|
links.new(tiny.outputs[0], delete.inputs["Selection"])
|
|
return delete.outputs["Geometry"]
|
|
|
|
|
|
def _profile_field(group, geometry):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
profile = nodes.new("GeometryNodeInputNamedAttribute")
|
|
profile.data_type = "FLOAT"
|
|
profile.inputs["Name"].default_value = BALLOON_ATTRIBUTE_NAME
|
|
profile_max = nodes.new("GeometryNodeAttributeStatistic")
|
|
profile_max.data_type = "FLOAT"
|
|
profile_max.domain = "POINT"
|
|
links.new(geometry, profile_max.inputs["Geometry"])
|
|
links.new(profile.outputs["Attribute"], profile_max.inputs["Attribute"])
|
|
safe_max = math_node(nodes, "MAXIMUM")
|
|
safe_max.inputs[1].default_value = 1e-6
|
|
links.new(profile_max.outputs["Max"], safe_max.inputs[0])
|
|
normalized = math_node(nodes, "DIVIDE")
|
|
links.new(profile.outputs["Attribute"], normalized.inputs[0])
|
|
links.new(safe_max.outputs[0], normalized.inputs[1])
|
|
smooth = nodes.new("ShaderNodeMapRange")
|
|
smooth.data_type = "FLOAT"
|
|
smooth.interpolation_type = "SMOOTHERSTEP"
|
|
smooth.clamp = True
|
|
smooth.inputs["From Min"].default_value = 0.0
|
|
smooth.inputs["From Max"].default_value = 1.0
|
|
smooth.inputs["To Min"].default_value = 0.0
|
|
smooth.inputs["To Max"].default_value = 1.0
|
|
links.new(normalized.outputs[0], smooth.inputs["Value"])
|
|
return profile.outputs["Attribute"], smooth.outputs["Result"]
|
|
|
|
|
|
def _extrude_layers(group, geometry):
|
|
node = group.nodes.new("GeometryNodeExtrudeMesh")
|
|
node.mode = "FACES"
|
|
node.inputs["Individual"].default_value = False
|
|
node.inputs["Offset"].default_value = (0.0, 0.0, 1.0)
|
|
# Keep distinct front and back vertices before overriding their Z positions.
|
|
node.inputs["Offset Scale"].default_value = 1.0
|
|
group.links.new(geometry, node.inputs["Mesh"])
|
|
return node
|
|
|
|
|
|
def _offset_geometry_z(group, geometry, z_value):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
offset = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(z_value, offset.inputs["Z"])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
links.new(geometry, set_position.inputs["Geometry"])
|
|
links.new(offset.outputs["Vector"], set_position.inputs["Offset"])
|
|
return set_position.outputs["Geometry"]
|
|
|
|
|
|
def _set_layer_z(group, geometry, extrude, front_z, back_z):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
choose_z = nodes.new("GeometryNodeSwitch")
|
|
choose_z.input_type = "FLOAT"
|
|
links.new(extrude.outputs["Top"], choose_z.inputs["Switch"])
|
|
links.new(front_z, choose_z.inputs["False"])
|
|
links.new(back_z, choose_z.inputs["True"])
|
|
position = nodes.new("GeometryNodeInputPosition")
|
|
separate = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(position.outputs["Position"], separate.inputs["Vector"])
|
|
combine = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(separate.outputs["X"], combine.inputs["X"])
|
|
links.new(separate.outputs["Y"], combine.inputs["Y"])
|
|
links.new(choose_z.outputs["Output"], combine.inputs["Z"])
|
|
set_position = nodes.new("GeometryNodeSetPosition")
|
|
links.new(extrude.outputs["Mesh"], set_position.inputs["Geometry"])
|
|
links.new(combine.outputs["Vector"], set_position.inputs["Position"])
|
|
|
|
side = nodes.new("GeometryNodeSeparateGeometry")
|
|
side.domain = "FACE"
|
|
links.new(set_position.outputs["Geometry"], side.inputs["Geometry"])
|
|
links.new(extrude.outputs["Side"], side.inputs["Selection"])
|
|
flip_side = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(side.outputs["Selection"], flip_side.inputs["Mesh"])
|
|
front = _offset_geometry_z(
|
|
group, geometry, front_z
|
|
)
|
|
back = _offset_geometry_z(
|
|
group, geometry, back_z
|
|
)
|
|
flip_back = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(back, flip_back.inputs["Mesh"])
|
|
|
|
join = nodes.new("GeometryNodeJoinGeometry")
|
|
links.new(front, join.inputs["Geometry"])
|
|
links.new(flip_back.outputs["Mesh"], join.inputs["Geometry"])
|
|
links.new(flip_side.outputs["Mesh"], join.inputs["Geometry"])
|
|
merge = nodes.new("GeometryNodeMergeByDistance")
|
|
merge.mode = "ALL"
|
|
merge.inputs["Distance"].default_value = 1e-6
|
|
links.new(join.outputs["Geometry"], merge.inputs["Geometry"])
|
|
return merge.outputs["Geometry"]
|
|
|
|
|
|
def _remove_zero_thickness(
|
|
group,
|
|
zero_geometry,
|
|
shaped_geometry,
|
|
thickness,
|
|
):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
zero = math_node(nodes, "LESS_THAN")
|
|
zero.inputs[1].default_value = 1e-6
|
|
links.new(thickness, zero.inputs[0])
|
|
choose = nodes.new("GeometryNodeSwitch")
|
|
choose.input_type = "GEOMETRY"
|
|
links.new(zero.outputs[0], choose.inputs["Switch"])
|
|
links.new(shaped_geometry, choose.inputs["False"])
|
|
links.new(zero_geometry, choose.inputs["True"])
|
|
return choose.outputs["Output"]
|
|
|
|
|
|
def _position_field(group, depth_image):
|
|
nodes = group.nodes
|
|
links = group.links
|
|
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
position_texture = nodes.new("GeometryNodeImageTexture")
|
|
position_texture.interpolation = "Linear"
|
|
position_texture.extension = "EXTEND"
|
|
links.new(depth_image, position_texture.inputs["Image"])
|
|
links.new(uv.outputs["Attribute"], position_texture.inputs["Vector"])
|
|
return position_texture.outputs["Color"]
|
|
|
|
|
|
def _shade_output(group, geometry):
|
|
shade = group.nodes.new("GeometryNodeSetShadeSmooth")
|
|
shade.domain = "FACE"
|
|
shade.inputs["Shade Smooth"].default_value = True
|
|
output = group.nodes.new("NodeGroupOutput")
|
|
group.links.new(geometry, shade.inputs["Geometry"])
|
|
group.links.new(shade.outputs["Geometry"], output.inputs["Geometry"])
|