feat(node_assets): add image plane geometry nodes and validation scripts
- Implemented `image_plane.py` to build the Image Plane Geometry Nodes group with customizable parameters for geometry, subdivide, and thickness. - Created `image_plane_validation.py` to validate the functionality of the image plane, ensuring correct geometry generation and material assignment. - Developed `material_layers.py` to construct shader node groups for image materials, including depth and normal handling. - Authored `plane_layouts.py` to define node arrangements for image geometry assets. - Introduced `shadeless.py` to create a shadeless shader node group that displays color without scene lighting while preserving shadows and reflections.
This commit is contained in:
@@ -12,14 +12,14 @@ sys.path.insert(
|
||||
str(next((PROJECT_ROOT / "src" / "anyimage" / "wheels").glob("blendjob-*.whl"))),
|
||||
)
|
||||
|
||||
from build_cutout_modifiers import (
|
||||
from cutout import (
|
||||
CUTOUT_NODE_GROUP_NAMES,
|
||||
build_cutout_group,
|
||||
)
|
||||
from build_depth_plane import build_depth_plane_group
|
||||
from build_material_layers import build_material_layer_group
|
||||
from build_mesh_plane import build_mesh_plane_group
|
||||
from build_shadeless import build_shadeless_group
|
||||
from depth_plane import build_depth_plane_group
|
||||
from material_layers import build_material_layer_group
|
||||
from image_plane import build_image_plane_group
|
||||
from shadeless import build_shadeless_group
|
||||
OUTPUT_PATH = (
|
||||
PROJECT_ROOT
|
||||
/ "src"
|
||||
@@ -32,8 +32,8 @@ OUTPUT_PATH = (
|
||||
def main():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
mesh_plane = build_mesh_plane_group()
|
||||
build_depth_plane_group(mesh_plane)
|
||||
image_plane = build_image_plane_group()
|
||||
build_depth_plane_group(image_plane)
|
||||
build_material_layer_group()
|
||||
build_material_layer_group(depth_plane=True)
|
||||
build_shadeless_group()
|
||||
@@ -35,7 +35,7 @@ def input_names(group):
|
||||
|
||||
def main():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
mesh_plane = load_node_group("O Mesh Plane")
|
||||
image_plane = load_node_group("O Image Plane")
|
||||
depth_plane = load_node_group(DEPTH_PLANE_NODE_GROUP_NAME)
|
||||
image_layer = load_node_group(IMAGE_MATERIAL_NODE_GROUP_NAME)
|
||||
depth_layer = load_node_group(DEPTH_MATERIAL_NODE_GROUP_NAME)
|
||||
@@ -44,7 +44,7 @@ def main():
|
||||
name: load_node_group(name) for name in set(CUTOUT_NODE_GROUP_NAMES.values())
|
||||
}
|
||||
expected_names = {
|
||||
"O Mesh Plane",
|
||||
"O Image Plane",
|
||||
DEPTH_PLANE_NODE_GROUP_NAME,
|
||||
IMAGE_MATERIAL_NODE_GROUP_NAME,
|
||||
DEPTH_MATERIAL_NODE_GROUP_NAME,
|
||||
@@ -54,10 +54,10 @@ def main():
|
||||
with bpy.data.libraries.load(str(node_asset_path()), link=False) as (source, _):
|
||||
assert set(source.node_groups) == expected_names
|
||||
|
||||
assert mesh_plane.name == "O Mesh Plane"
|
||||
assert mesh_plane.bl_idname == "GeometryNodeTree"
|
||||
assert mesh_plane.is_modifier
|
||||
assert input_names(mesh_plane) == [
|
||||
assert image_plane.name == "O Image Plane"
|
||||
assert image_plane.bl_idname == "GeometryNodeTree"
|
||||
assert image_plane.is_modifier
|
||||
assert input_names(image_plane) == [
|
||||
"Geometry",
|
||||
"Subdivide",
|
||||
"Thickness",
|
||||
@@ -66,19 +66,28 @@ def main():
|
||||
assert depth_plane.is_modifier
|
||||
assert input_names(depth_plane) == [
|
||||
"Geometry",
|
||||
"Mode",
|
||||
"Subdivide",
|
||||
"Thickness",
|
||||
"Thickness",
|
||||
"Depth Scale",
|
||||
"Reference Depth",
|
||||
"Normal Smooth",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
]
|
||||
thickness_inputs = [
|
||||
item for item in depth_plane.interface.items_tree
|
||||
if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name == "Thickness"
|
||||
]
|
||||
assert len({item.identifier for item in thickness_inputs}) == 2
|
||||
assert all(item.default_value == 0 and item.subtype == "DISTANCE" for item in thickness_inputs)
|
||||
depth_panels = [
|
||||
item for item in depth_plane.interface.items_tree if item.item_type == "PANEL"
|
||||
]
|
||||
assert [panel.name for panel in depth_panels] == ["Options", "Data"]
|
||||
assert [item.name for item in depth_panels[0].interface_items] == [
|
||||
"Reference Depth"
|
||||
"Reference Depth", "Normal Smooth"
|
||||
]
|
||||
assert [item.name for item in depth_panels[1].interface_items] == [
|
||||
"Uniform Scale",
|
||||
@@ -119,7 +128,7 @@ def main():
|
||||
assert input_names(group)[0] == "Geometry"
|
||||
|
||||
public_groups = {
|
||||
mesh_plane,
|
||||
image_plane,
|
||||
depth_plane,
|
||||
image_layer,
|
||||
depth_layer,
|
||||
@@ -10,7 +10,7 @@ from cutout_shapes import (
|
||||
_build_depth_surface, _shade_output,
|
||||
)
|
||||
from cutout_shape_layouts import LAYOUTS
|
||||
from cutout_depth_surface_layout import POSITIONS, INPUT_REGIONS, layout_geometry_references
|
||||
from depth_cutout_layout import POSITIONS, INPUT_REGIONS, layout_geometry_references
|
||||
from cutout_layout import layout_stage, finish_layout
|
||||
from cutout_volume import build_volume_fields, shift_stage
|
||||
from cutout_rounding import build_volume_rounding
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Construct the planar and depth-based Cutout shape stages."""
|
||||
from cutout_depth_surface import store_surface_camera, remove_surface_attributes, split_depth_surface, thicken_depth_surface
|
||||
from depth_surface import project_depth_surface, store_surface_camera, remove_surface_attributes, split_depth_surface, thicken_depth_surface
|
||||
from anyimage.operators.cutout_tool.shape import BALLOON_ATTRIBUTE_NAME
|
||||
|
||||
|
||||
@@ -561,53 +561,11 @@ def _build_depth_surface(
|
||||
geometry, camera_position = store_surface_camera(
|
||||
group, geometry, original_camera, split
|
||||
)
|
||||
scaled = nodes.new("ShaderNodeVectorMath")
|
||||
scaled.operation = "SCALE"
|
||||
scaled.location = (3100.0, -100.0)
|
||||
links.new(camera_position, scaled.inputs[0])
|
||||
links.new(uniform_scale, scaled.inputs[3])
|
||||
separate = nodes.new("ShaderNodeSeparateXYZ")
|
||||
separate.location = (3300.0, -100.0)
|
||||
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
|
||||
relative_depth = _math(nodes, "SUBTRACT", (3500.0, -100.0))
|
||||
links.new(reference_depth, relative_depth.inputs[0])
|
||||
links.new(separate.outputs["Z"], relative_depth.inputs[1])
|
||||
front = _math(nodes, "MULTIPLY", (3700.0, -100.0))
|
||||
links.new(relative_depth.outputs[0], front.inputs[0])
|
||||
links.new(depth_scale, front.inputs[1])
|
||||
|
||||
negative_y = _math(nodes, "MULTIPLY", (3500.0, -600.0))
|
||||
negative_y.inputs[1].default_value = -1.0
|
||||
links.new(separate.outputs["Y"], negative_y.inputs[0])
|
||||
projected = nodes.new("ShaderNodeCombineXYZ")
|
||||
projected.location = (3700.0, -450.0)
|
||||
links.new(separate.outputs["X"], projected.inputs["X"])
|
||||
links.new(negative_y.outputs[0], projected.inputs["Y"])
|
||||
current_position = nodes.new("GeometryNodeInputPosition")
|
||||
current_position.location = (3500.0, -850.0)
|
||||
blend = nodes.new("ShaderNodeMix")
|
||||
blend.data_type = "VECTOR"
|
||||
blend.clamp_factor = False
|
||||
blend.location = (3900.0, -450.0)
|
||||
links.new(depth_scale, blend.inputs[0])
|
||||
links.new(current_position.outputs["Position"], blend.inputs[4])
|
||||
links.new(projected.outputs["Vector"], blend.inputs[5])
|
||||
blended_axes = nodes.new("ShaderNodeSeparateXYZ")
|
||||
blended_axes.location = (4100.0, -500.0)
|
||||
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
|
||||
surface_position = nodes.new("ShaderNodeCombineXYZ")
|
||||
surface_position.location = (4100.0, -200.0)
|
||||
links.new(blended_axes.outputs["X"], surface_position.inputs["X"])
|
||||
links.new(blended_axes.outputs["Y"], surface_position.inputs["Y"])
|
||||
links.new(front.outputs[0], surface_position.inputs["Z"])
|
||||
project_geometry = nodes.new("GeometryNodeSetPosition")
|
||||
project_geometry.location = (4400.0, 400.0)
|
||||
links.new(geometry, project_geometry.inputs["Geometry"])
|
||||
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
||||
|
||||
geometry = project_geometry.outputs["Geometry"]
|
||||
geometry = project_depth_surface(
|
||||
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale
|
||||
)
|
||||
shaped = thicken_depth_surface(group, geometry, thickness)
|
||||
geometry = _remove_zero_thickness(
|
||||
group, project_geometry.outputs["Geometry"], shaped, thickness, (11800.0, 400.0)
|
||||
group, geometry, shaped, thickness, (11800.0, 400.0)
|
||||
)
|
||||
return remove_surface_attributes(group, geometry)
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import bpy
|
||||
|
||||
from geometry_layouts import layout_geometry
|
||||
from plane_layouts import layout_geometry
|
||||
from cutout_layout import finish_layout
|
||||
from depth_plane_camera import build_camera_plane
|
||||
from depth_plane_layout import layout_camera_plane
|
||||
|
||||
|
||||
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
|
||||
@@ -36,7 +39,7 @@ def _math(nodes, operation, location):
|
||||
return node
|
||||
|
||||
|
||||
def build_depth_plane_group(mesh_plane_group):
|
||||
def build_depth_plane_group(image_plane_group):
|
||||
group = bpy.data.node_groups.get(DEPTH_PLANE_NODE_GROUP_NAME)
|
||||
if group is not None:
|
||||
return group
|
||||
@@ -47,14 +50,21 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
group.is_modifier = True
|
||||
geometry = _interface_socket(group, "Geometry", "INPUT", "NodeSocketGeometry")
|
||||
geometry.structure_type = "SINGLE"
|
||||
mode = _interface_socket(group, "Mode", "INPUT", "NodeSocketMenu")
|
||||
subdivide = _interface_socket(group, "Subdivide", "INPUT", "NodeSocketInt")
|
||||
subdivide.default_value = 6
|
||||
subdivide.min_value = 0
|
||||
subdivide.max_value = 10
|
||||
thickness = _interface_socket(group, "Thickness", "INPUT", "NodeSocketFloat")
|
||||
thickness.default_value = 0.1
|
||||
thickness.default_value = 0.0
|
||||
thickness.min_value = 0.0
|
||||
thickness.subtype = "DISTANCE"
|
||||
thickness.description = "Base thickness used only by Relief mode"
|
||||
camera_thickness = _interface_socket(group, "Camera Thickness", "INPUT", "NodeSocketFloat")
|
||||
camera_thickness.default_value = 0.0
|
||||
camera_thickness.min_value = 0.0
|
||||
camera_thickness.subtype = "DISTANCE"
|
||||
camera_thickness.description = "Inward shell distance used only by Camera mode"
|
||||
depth_scale = _interface_socket(group, "Depth Scale", "INPUT", "NodeSocketFloat")
|
||||
depth_scale.default_value = 1.0
|
||||
depth_scale.min_value = 0.0
|
||||
@@ -69,6 +79,11 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
base_plane_depth.default_value = 1.0
|
||||
base_plane_depth.min_value = 0.0
|
||||
base_plane_depth.subtype = "DISTANCE"
|
||||
normal_smooth = _interface_socket(
|
||||
group, "Normal Smooth", "INPUT", "NodeSocketInt", parent=options,
|
||||
)
|
||||
normal_smooth.default_value, normal_smooth.min_value, normal_smooth.max_value = 50, 0, 50
|
||||
normal_smooth.description = "Smooth the Camera shell thickness direction"
|
||||
data = group.interface.new_panel(name="Data", default_closed=True)
|
||||
uniform_scale = _interface_socket(
|
||||
group,
|
||||
@@ -109,12 +124,12 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
)
|
||||
group_output = nodes.new("NodeGroupOutput")
|
||||
group_output.location = (1550.0, 500.0)
|
||||
mesh_plane = nodes.new("GeometryNodeGroup")
|
||||
mesh_plane.node_tree = mesh_plane_group
|
||||
mesh_plane.location = (-1600.0, 500.0)
|
||||
links.new(geometry_input.outputs["Geometry"], mesh_plane.inputs["Geometry"])
|
||||
links.new(geometry_input.outputs["Subdivide"], mesh_plane.inputs["Subdivide"])
|
||||
links.new(shape_input.outputs["Thickness"], mesh_plane.inputs["Thickness"])
|
||||
image_plane = nodes.new("GeometryNodeGroup")
|
||||
image_plane.node_tree = image_plane_group
|
||||
image_plane.location = (-1600.0, 500.0)
|
||||
links.new(geometry_input.outputs["Geometry"], image_plane.inputs["Geometry"])
|
||||
links.new(geometry_input.outputs["Subdivide"], image_plane.inputs["Subdivide"])
|
||||
links.new(shape_input.outputs["Thickness"], image_plane.inputs["Thickness"])
|
||||
|
||||
positive_half = _math(nodes, "MULTIPLY", (-1300.0, 300.0))
|
||||
positive_half.inputs[1].default_value = 0.5
|
||||
@@ -124,7 +139,7 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
links.new(positive_half.outputs[0], translation.inputs["Z"])
|
||||
place_block = nodes.new("GeometryNodeTransform")
|
||||
place_block.location = (-900.0, 500.0)
|
||||
links.new(mesh_plane.outputs["Geometry"], place_block.inputs["Geometry"])
|
||||
links.new(image_plane.outputs["Geometry"], place_block.inputs["Geometry"])
|
||||
links.new(translation.outputs["Vector"], place_block.inputs["Translation"])
|
||||
|
||||
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
@@ -141,7 +156,7 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
depth_components.location = (-450.0, -350.0)
|
||||
links.new(texture.outputs["Color"], depth_components.inputs["Vector"])
|
||||
metric_depth = _math(nodes, "MULTIPLY", (-200.0, -350.0))
|
||||
links.new(depth_components.outputs["X"], metric_depth.inputs[0])
|
||||
links.new(depth_components.outputs["Z"], metric_depth.inputs[0])
|
||||
links.new(image_input.outputs["Uniform Scale"], metric_depth.inputs[1])
|
||||
relative_depth = _math(nodes, "SUBTRACT", (50.0, -350.0))
|
||||
links.new(shape_input.outputs["Reference Depth"], relative_depth.inputs[0])
|
||||
@@ -205,8 +220,35 @@ def build_depth_plane_group(mesh_plane_group):
|
||||
links.new(shade_smooth.outputs["Geometry"], group_output.inputs["Geometry"])
|
||||
group.color_tag = "GEOMETRY"
|
||||
group.description = (
|
||||
"Build a single-sided depth terrain block with a fixed base."
|
||||
"Build a fixed-base relief or camera-projected image shell."
|
||||
)
|
||||
group.use_fake_user = True
|
||||
layout_geometry(group)
|
||||
for node in nodes:
|
||||
node.location.x += 3750
|
||||
node.location.y += 2400
|
||||
previous = set(nodes)
|
||||
camera = build_camera_plane(group, image_plane_group)
|
||||
layout_camera_plane(group, previous)
|
||||
nodes.remove(shade_smooth)
|
||||
# Align the Relief and Camera branch ends in menu order.
|
||||
set_position.location.x = camera.node.location.x + 40
|
||||
selector = nodes.new("GeometryNodeMenuSwitch")
|
||||
selector.data_type = "GEOMETRY"
|
||||
for item, name in zip(selector.enum_items, ("Relief", "Camera")):
|
||||
item.name = name
|
||||
selector.location = (6500, 1300)
|
||||
controls = _group_input(nodes, (6250, 1550), {"Mode"})
|
||||
links.new(controls.outputs["Mode"], selector.inputs["Menu"])
|
||||
links.new(set_position.outputs["Geometry"], selector.inputs["Relief"])
|
||||
links.new(camera, selector.inputs["Camera"])
|
||||
mode.default_value = "Relief"
|
||||
smooth = nodes.new("GeometryNodeSetShadeSmooth")
|
||||
smooth.domain = "FACE"
|
||||
smooth.location = (6750, 1300)
|
||||
links.new(selector.outputs[0], smooth.inputs["Geometry"])
|
||||
group_output.location = (7000, 1300)
|
||||
links.new(smooth.outputs["Geometry"], group_output.inputs["Geometry"])
|
||||
finish_layout(group)
|
||||
camera_thickness.name = "Thickness"
|
||||
return group
|
||||
44
tools/node_assets/depth_plane_camera.py
Normal file
44
tools/node_assets/depth_plane_camera.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Build the camera-projected branch of the complete-image depth plane."""
|
||||
|
||||
from node_graph import group_input as _group_input
|
||||
from depth_surface import project_depth_surface, thicken_depth_surface, remove_surface_attributes
|
||||
|
||||
|
||||
def build_camera_plane(group, image_plane_group):
|
||||
nodes, links = group.nodes, group.links
|
||||
controls = _group_input(nodes, (-300, 0), {"Geometry", "Subdivide"})
|
||||
plane = nodes.new("GeometryNodeGroup")
|
||||
plane.node_tree = image_plane_group
|
||||
plane.inputs["Thickness"].default_value = 0
|
||||
links.new(controls.outputs["Geometry"], plane.inputs["Geometry"])
|
||||
links.new(controls.outputs["Subdivide"], plane.inputs["Subdivide"])
|
||||
texture_input = _group_input(nodes, (-300, 700), {"Depth Image", "Uniform Scale"})
|
||||
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
||||
uv.data_type = "FLOAT_VECTOR"
|
||||
uv.inputs["Name"].default_value = "UVMap"
|
||||
texture = nodes.new("GeometryNodeImageTexture")
|
||||
texture.interpolation, texture.extension = "Linear", "EXTEND"
|
||||
links.new(uv.outputs["Attribute"], texture.inputs["Vector"])
|
||||
links.new(texture_input.outputs["Depth Image"], texture.inputs["Image"])
|
||||
depth_input = _group_input(nodes, (500, 1000), {"Reference Depth", "Depth Scale"})
|
||||
surface = project_depth_surface(
|
||||
group, plane.outputs["Geometry"], texture.outputs["Color"],
|
||||
texture_input.outputs["Uniform Scale"], depth_input.outputs["Reference Depth"],
|
||||
depth_input.outputs["Depth Scale"],
|
||||
)
|
||||
shell_input = _group_input(nodes, (2000, 500), {"Camera Thickness", "Normal Smooth"})
|
||||
shell = thicken_depth_surface(
|
||||
group, surface, shell_input.outputs["Camera Thickness"],
|
||||
normal_smooth=shell_input.outputs["Normal Smooth"],
|
||||
)
|
||||
positive = nodes.new("ShaderNodeMath")
|
||||
positive.operation = "GREATER_THAN"
|
||||
links.new(shell_input.outputs["Camera Thickness"], positive.inputs[0])
|
||||
positive.inputs[1].default_value = 0
|
||||
choose = nodes.new("GeometryNodeSwitch")
|
||||
choose.input_type = "GEOMETRY"
|
||||
links.new(positive.outputs[0], choose.inputs["Switch"])
|
||||
links.new(surface, choose.inputs["False"])
|
||||
links.new(shell, choose.inputs["True"])
|
||||
clean = remove_surface_attributes(group, choose.outputs["Output"])
|
||||
return clean
|
||||
82
tools/node_assets/depth_plane_layout.py
Normal file
82
tools/node_assets/depth_plane_layout.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Place projection fields above the rectangular surface and shell stages."""
|
||||
|
||||
from cutout_layout import route_geometry, layout_stage
|
||||
|
||||
|
||||
def layout_camera_plane(group, previous):
|
||||
stage = layout_stage(group, previous, {
|
||||
"Group": (0, 0),
|
||||
"Named Attribute": (-240, 760),
|
||||
"Image Texture": (0, 760),
|
||||
"Vector Math": (220, 760),
|
||||
"Separate XYZ": (440, 760),
|
||||
"Math": (660, 980),
|
||||
"Math.001": (1320, 980),
|
||||
"Math.002": (660, 620),
|
||||
"Combine XYZ": (880, 760),
|
||||
"Position": (880, 420),
|
||||
"Mix": (1100, 760),
|
||||
"Separate XYZ.001": (1320, 760),
|
||||
"Combine XYZ.001": (1540, 880),
|
||||
"Set Position": (1760, 0),
|
||||
"Position.001": (1760, 400),
|
||||
"Store Named Attribute": (2020, 0),
|
||||
"Vector Math.001": (2020, 400),
|
||||
"Set Position.001": (2280, 0),
|
||||
"Index": (2280, 400),
|
||||
"Store Named Attribute.001": (2540, 0),
|
||||
"Combine XYZ.002": (3970, 820),
|
||||
"Integer Math": (3750, 1220),
|
||||
"Named Attribute.001": (3500, 1220),
|
||||
"Integer Math.001": (3750, 980),
|
||||
"Named Attribute.002": (3500, 980),
|
||||
"Extrude Mesh": (2820, 0),
|
||||
"Combine XYZ.003": (2540, 440),
|
||||
"Separate Geometry": (3090, 0),
|
||||
"Combine XYZ.004": (2820, -350),
|
||||
"Set Position.002": (3090, -350),
|
||||
"Flip Faces": (3370, -350),
|
||||
"Flip Faces.001": (3370, 0),
|
||||
"Join Geometry": (3650, 0),
|
||||
"Set Position.003": (3970, 0),
|
||||
"Merge by Distance": (4230, 0),
|
||||
"Normal": (1540, -340),
|
||||
"Blur Attribute": (1760, -340),
|
||||
"Vector Math.003": (2020, -340),
|
||||
"Store Named Attribute.002": (2020, 0),
|
||||
"Vector Math.004": (4990, 700),
|
||||
"Named Attribute.004": (4710, 1080),
|
||||
"Position.002": (4230, 700),
|
||||
"Separate XYZ.002": (4450, 700),
|
||||
"Math.003": (4710, 700),
|
||||
"Vector Math.002": (5210, 700),
|
||||
"Named Attribute.003": (4990, 1080),
|
||||
"Set Position.004": (5430, 0),
|
||||
"Position.003": (3500, 620),
|
||||
"Separate XYZ.003": (3750, 620),
|
||||
"Math.004": (5430, 500),
|
||||
"Switch": (5700, 0),
|
||||
"Remove Named Attribute": (5960, 0),
|
||||
"Set Shade Smooth": (6220, 0),
|
||||
"Group Output": (6480, 0),
|
||||
}, (
|
||||
(("Group",), (-250, 0)),
|
||||
(("Image Texture",), (-240, 1100)),
|
||||
(("Vector Math",), (0, 1100)),
|
||||
(("Math",), (440, 1120)),
|
||||
(("Math.001", "Mix"), (880, 1160)),
|
||||
(("Math.003",), (4450, 1040)),
|
||||
(("Math.004",), (5210, 500)),
|
||||
(("Blur Attribute",), (1540, -540)),
|
||||
))
|
||||
for name, node in stage.items():
|
||||
if node.location.x >= 2020 and name not in {
|
||||
"Store Named Attribute.002", "Vector Math.003"
|
||||
}:
|
||||
node.location.x += 260
|
||||
base = route_geometry(group, stage["Store Named Attribute.001"].outputs["Geometry"],
|
||||
((2770, 180), (3600, 180)))
|
||||
group.links.new(base, stage["Join Geometry"].inputs["Geometry"])
|
||||
bypass = route_geometry(group, stage["Set Position"].outputs["Geometry"],
|
||||
((1970, -760), (5650, -760)))
|
||||
group.links.new(bypass, stage["Switch"].inputs["False"])
|
||||
@@ -8,6 +8,7 @@ TEMPORARY_ATTRIBUTES = {
|
||||
"camera": ("FLOAT_VECTOR", "POINT"),
|
||||
"surface_position": ("FLOAT_VECTOR", "POINT"),
|
||||
"source_vertex": ("INT", "POINT"),
|
||||
"surface_normal": ("FLOAT_VECTOR", "POINT"),
|
||||
}
|
||||
|
||||
|
||||
@@ -168,11 +169,13 @@ def store_surface_camera(group, geometry, original, strength):
|
||||
return geometry, _read(group, "camera")
|
||||
|
||||
|
||||
def thicken_depth_surface(group, geometry, thickness):
|
||||
def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
|
||||
"""Connect caps with single quads by source vertex and cap identity."""
|
||||
nodes, links = group.nodes, group.links
|
||||
front_geometry = geometry
|
||||
position = nodes.new("GeometryNodeInputPosition")
|
||||
geometry = _store(group, geometry, "surface_position", position.outputs[0])
|
||||
surface_store = geometry.node
|
||||
flattened = nodes.new("ShaderNodeVectorMath")
|
||||
flattened.operation = "MULTIPLY"
|
||||
flattened.inputs[1].default_value = (1.0, 1.0, 0.0)
|
||||
@@ -249,4 +252,80 @@ def thicken_depth_surface(group, geometry, thickness):
|
||||
local_axes = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(local_position.outputs[0], local_axes.inputs[0])
|
||||
links.new(local_axes.outputs["Z"], identity.inputs["Z"])
|
||||
if normal_smooth is not None:
|
||||
normal = nodes.new("GeometryNodeInputNormal")
|
||||
blur = nodes.new("GeometryNodeBlurAttribute")
|
||||
blur.data_type = "FLOAT_VECTOR"
|
||||
links.new(normal_smooth, blur.inputs["Iterations"])
|
||||
links.new(normal.outputs[0], blur.inputs["Value"])
|
||||
normalize = nodes.new("ShaderNodeVectorMath")
|
||||
normalize.operation = "NORMALIZE"
|
||||
links.new(blur.outputs[0], normalize.inputs[0])
|
||||
front_geometry = _store(group, front_geometry, "surface_normal", normalize.outputs[0])
|
||||
links.new(front_geometry, surface_store.inputs["Geometry"])
|
||||
normal_offset = nodes.new("ShaderNodeVectorMath")
|
||||
normal_offset.operation = "SCALE"
|
||||
links.new(_read(group, "surface_normal"), normal_offset.inputs[0])
|
||||
links.new(displacement.inputs["Z"].links[0].from_socket, normal_offset.inputs[3])
|
||||
links.new(normal_offset.outputs[0], restored.inputs[1])
|
||||
nodes.remove(displacement)
|
||||
return result.outputs["Geometry"]
|
||||
|
||||
|
||||
def _projection_math(nodes, operation, location):
|
||||
node = nodes.new("ShaderNodeMath")
|
||||
node.operation = operation
|
||||
node.location = location
|
||||
return node
|
||||
|
||||
|
||||
def project_depth_surface(
|
||||
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale
|
||||
):
|
||||
"""Interpolate the rectangular plane into the model camera coordinates."""
|
||||
nodes, links = group.nodes, group.links
|
||||
scaled = nodes.new("ShaderNodeVectorMath")
|
||||
scaled.operation = "SCALE"
|
||||
scaled.location = (3100.0, -100.0)
|
||||
links.new(camera_position, scaled.inputs[0])
|
||||
links.new(uniform_scale, scaled.inputs[3])
|
||||
separate = nodes.new("ShaderNodeSeparateXYZ")
|
||||
separate.location = (3300.0, -100.0)
|
||||
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
|
||||
relative_depth = _projection_math(nodes, "SUBTRACT", (3500.0, -100.0))
|
||||
links.new(reference_depth, relative_depth.inputs[0])
|
||||
links.new(separate.outputs["Z"], relative_depth.inputs[1])
|
||||
front = _projection_math(nodes, "MULTIPLY", (3700.0, -100.0))
|
||||
links.new(relative_depth.outputs[0], front.inputs[0])
|
||||
links.new(depth_scale, front.inputs[1])
|
||||
|
||||
negative_y = _projection_math(nodes, "MULTIPLY", (3500.0, -600.0))
|
||||
negative_y.inputs[1].default_value = -1.0
|
||||
links.new(separate.outputs["Y"], negative_y.inputs[0])
|
||||
projected = nodes.new("ShaderNodeCombineXYZ")
|
||||
projected.location = (3700.0, -450.0)
|
||||
links.new(separate.outputs["X"], projected.inputs["X"])
|
||||
links.new(negative_y.outputs[0], projected.inputs["Y"])
|
||||
current_position = nodes.new("GeometryNodeInputPosition")
|
||||
current_position.location = (3500.0, -850.0)
|
||||
blend = nodes.new("ShaderNodeMix")
|
||||
blend.data_type = "VECTOR"
|
||||
blend.clamp_factor = False
|
||||
blend.location = (3900.0, -450.0)
|
||||
links.new(depth_scale, blend.inputs[0])
|
||||
links.new(current_position.outputs["Position"], blend.inputs[4])
|
||||
links.new(projected.outputs["Vector"], blend.inputs[5])
|
||||
blended_axes = nodes.new("ShaderNodeSeparateXYZ")
|
||||
blended_axes.location = (4100.0, -500.0)
|
||||
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
|
||||
surface_position = nodes.new("ShaderNodeCombineXYZ")
|
||||
surface_position.location = (4100.0, -200.0)
|
||||
links.new(blended_axes.outputs["X"], surface_position.inputs["X"])
|
||||
links.new(blended_axes.outputs["Y"], surface_position.inputs["Y"])
|
||||
links.new(front.outputs[0], surface_position.inputs["Z"])
|
||||
project_geometry = nodes.new("GeometryNodeSetPosition")
|
||||
project_geometry.location = (4400.0, 400.0)
|
||||
links.new(geometry, project_geometry.inputs["Geometry"])
|
||||
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
||||
|
||||
return project_geometry.outputs["Geometry"]
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Build the Mesh Plane Geometry Nodes group."""
|
||||
"""Build the Image Plane Geometry Nodes group."""
|
||||
|
||||
import bpy
|
||||
|
||||
from geometry_layouts import layout_geometry
|
||||
from plane_layouts import layout_geometry
|
||||
|
||||
|
||||
MESH_PLANE_GROUP_NAME = "O Mesh Plane"
|
||||
IMAGE_PLANE_GROUP_NAME = "O Image Plane"
|
||||
|
||||
|
||||
def new_socket(interface, name, in_out, socket_type, parent=None):
|
||||
@@ -54,8 +54,8 @@ def localize_dimensions(group):
|
||||
)
|
||||
|
||||
|
||||
def build_mesh_plane_group():
|
||||
group = bpy.data.node_groups.new(MESH_PLANE_GROUP_NAME, "GeometryNodeTree")
|
||||
def build_image_plane_group():
|
||||
group = bpy.data.node_groups.new(IMAGE_PLANE_GROUP_NAME, "GeometryNodeTree")
|
||||
group.is_modifier = True
|
||||
interface = group.interface
|
||||
geometry = new_socket(interface, "Geometry", "INPUT", "NodeSocketGeometry")
|
||||
@@ -15,9 +15,9 @@ from anyimage.common.object import (
|
||||
modifier_input_identifier,
|
||||
set_modifier_input,
|
||||
)
|
||||
from anyimage.operators.convert_to_plane.mesh_plane import (
|
||||
add_mesh_plane_processing,
|
||||
create_mesh_plane_mesh,
|
||||
from anyimage.operators.convert_to_plane.image_plane import (
|
||||
add_image_plane_processing,
|
||||
create_image_plane_mesh,
|
||||
)
|
||||
|
||||
|
||||
@@ -29,15 +29,15 @@ def evaluated_mesh(obj):
|
||||
|
||||
def main():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
mesh = create_mesh_plane_mesh("Mesh Plane", (-1.0, 1.0, -0.5, 0.5))
|
||||
mesh = create_image_plane_mesh("Image Plane", (-1.0, 1.0, -0.5, 0.5))
|
||||
assert len(mesh.polygons) == 1
|
||||
material = bpy.data.materials.new("Mesh Plane Material")
|
||||
material = bpy.data.materials.new("Image Plane Material")
|
||||
mesh.materials.append(material)
|
||||
obj = bpy.data.objects.new("Mesh Plane", mesh)
|
||||
obj = bpy.data.objects.new("Image Plane", mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
modifier = add_mesh_plane_processing(obj)
|
||||
modifier = add_image_plane_processing(obj)
|
||||
group = modifier.node_group
|
||||
assert group.name == "O Mesh Plane"
|
||||
assert group.name == "O Image Plane"
|
||||
assert group.is_modifier
|
||||
inputs = {
|
||||
item.name: item
|
||||
@@ -3,7 +3,7 @@
|
||||
from node_layout import arrange_nodes
|
||||
|
||||
LAYOUTS = {
|
||||
"O Mesh Plane": (
|
||||
"O Image Plane": (
|
||||
{
|
||||
"Bounding Box": (-2320, 440),
|
||||
"Vector Math": (-2100, 650),
|
||||
Reference in New Issue
Block a user