Refactor geometry generation to standardize Blender object coordinates
- Unified the canonical local frame for generated geometries (Plane, Depth Plane, Relief Plane, Cutout, Depth Cutout) to align with Blender's object coordinate system, setting X as horizontal, Z as up, and +Y as depth. - Removed the `Depth Axis` parameter from `O Image Cutout` and `O Image Depth Cutout`, simplifying the geometry generation process. - Updated the depth direction defaults to use +Y instead of -Z for `O Image Relief Plane` and Depth Symmetry. - Adjusted the geometry nodes to ensure thickness, projection, and shell displacements operate along the Y-axis. - Implemented new capabilities for handling object space normals to align with the new canonical frame. - Enhanced the Depth Symmetry functionality to mirror across the canonical Y-axis and perform a final rotation to maintain the correct orientation. - Added tests to validate the new coordinate system and ensure compatibility with existing assets.
This commit is contained in:
@@ -57,39 +57,39 @@ 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"].default_value = (0.0, 1.0, 0.0)
|
||||
# Keep distinct front and back vertices before overriding their Y 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):
|
||||
def _offset_geometry_y(group, geometry, y_value):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
offset = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(z_value, offset.inputs["Z"])
|
||||
links.new(y_value, offset.inputs["Y"])
|
||||
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):
|
||||
def _set_layer_y(group, geometry, extrude, front_y, back_y):
|
||||
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"])
|
||||
choose_y = nodes.new("GeometryNodeSwitch")
|
||||
choose_y.input_type = "FLOAT"
|
||||
links.new(extrude.outputs["Top"], choose_y.inputs["Switch"])
|
||||
links.new(front_y, choose_y.inputs["False"])
|
||||
links.new(back_y, choose_y.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"])
|
||||
links.new(choose_y.outputs["Output"], combine.inputs["Y"])
|
||||
links.new(separate.outputs["Z"], 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"])
|
||||
@@ -100,11 +100,11 @@ def _set_layer_z(group, geometry, extrude, front_z, back_z):
|
||||
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
|
||||
front = _offset_geometry_y(
|
||||
group, geometry, front_y
|
||||
)
|
||||
back = _offset_geometry_z(
|
||||
group, geometry, back_z
|
||||
back = _offset_geometry_y(
|
||||
group, geometry, back_y
|
||||
)
|
||||
flip_back = nodes.new("GeometryNodeFlipFaces")
|
||||
links.new(back, flip_back.inputs["Mesh"])
|
||||
|
||||
@@ -290,7 +290,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
row = nodes.new("FunctionNodeIntegerMath")
|
||||
row.operation = "MULTIPLY"
|
||||
row.inputs[1].default_value = 4096
|
||||
links.new(layer_axes.outputs["Y"], row.inputs[0])
|
||||
links.new(layer_axes.outputs["Z"], row.inputs[0])
|
||||
vertex = nodes.new("FunctionNodeIntegerMath")
|
||||
vertex.operation = "ADD"
|
||||
links.new(layer_axes.outputs["X"], vertex.inputs[0])
|
||||
@@ -303,7 +303,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
extrude.mode = "FACES"
|
||||
extrude.inputs["Individual"].default_value = False
|
||||
extrusion_offset = nodes.new("ShaderNodeCombineXYZ")
|
||||
extrusion_offset.inputs["Z"].default_value = -1.0
|
||||
extrusion_offset.inputs["Y"].default_value = -1.0
|
||||
links.new(extrusion_offset.outputs[0], extrude.inputs["Offset"])
|
||||
links.new(saved, extrude.inputs["Mesh"])
|
||||
sides = nodes.new("GeometryNodeSeparateGeometry")
|
||||
@@ -311,7 +311,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
links.new(extrude.outputs["Mesh"], sides.inputs["Geometry"])
|
||||
links.new(extrude.outputs["Side"], sides.inputs["Selection"])
|
||||
offset = nodes.new("ShaderNodeCombineXYZ")
|
||||
offset.inputs["Z"].default_value = -1.0
|
||||
offset.inputs["Y"].default_value = -1.0
|
||||
back = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(saved, back.inputs["Geometry"])
|
||||
links.new(offset.outputs[0], back.inputs["Offset"])
|
||||
@@ -337,9 +337,9 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
if weld_zero_thickness:
|
||||
positive = _math(group, "GREATER_THAN", source_thickness, 1e-12)
|
||||
flattened = nodes.new("ShaderNodeCombineXYZ")
|
||||
for axis in ("X", "Y"):
|
||||
for axis in ("X", "Z"):
|
||||
links.new(layer_axes.outputs[axis], flattened.inputs[axis])
|
||||
links.new(_math(group, "MULTIPLY", positive, layer_axes.outputs["Z"]), flattened.inputs["Z"])
|
||||
links.new(_math(group, "MULTIPLY", positive, layer_axes.outputs["Y"]), flattened.inputs["Y"])
|
||||
collapse = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(construction, collapse.inputs["Geometry"])
|
||||
links.new(flattened.outputs[0], collapse.inputs["Position"])
|
||||
@@ -349,8 +349,8 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
|
||||
merge.inputs["Distance"].default_value = 0.1
|
||||
links.new(construction, merge.inputs["Geometry"])
|
||||
displacement = nodes.new("ShaderNodeCombineXYZ")
|
||||
distance = _math(group, "MULTIPLY", layer_axes.outputs["Z"], source_thickness)
|
||||
links.new(distance, displacement.inputs["Z"])
|
||||
distance = _math(group, "MULTIPLY", layer_axes.outputs["Y"], source_thickness)
|
||||
links.new(distance, displacement.inputs["Y"])
|
||||
restored = nodes.new("ShaderNodeVectorMath")
|
||||
restored.operation = "ADD"
|
||||
links.new(source_position, restored.inputs[0])
|
||||
@@ -401,18 +401,19 @@ def project_depth_surface(
|
||||
separate = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
|
||||
relative_depth = _projection_math(nodes, "SUBTRACT")
|
||||
links.new(reference_depth, relative_depth.inputs[0])
|
||||
links.new(separate.outputs["Z"], relative_depth.inputs[1])
|
||||
links.new(separate.outputs["Z"], relative_depth.inputs[0])
|
||||
links.new(reference_depth, relative_depth.inputs[1])
|
||||
front = _projection_math(nodes, "MULTIPLY")
|
||||
links.new(relative_depth.outputs[0], front.inputs[0])
|
||||
links.new(depth_scale, front.inputs[1])
|
||||
|
||||
negative_y = _projection_math(nodes, "MULTIPLY")
|
||||
negative_y.inputs[1].default_value = -1.0
|
||||
links.new(separate.outputs["Y"], negative_y.inputs[0])
|
||||
negative_z = _projection_math(nodes, "MULTIPLY")
|
||||
negative_z.inputs[1].default_value = -1.0
|
||||
links.new(separate.outputs["Y"], negative_z.inputs[0])
|
||||
projected = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(separate.outputs["X"], projected.inputs["X"])
|
||||
links.new(negative_y.outputs[0], projected.inputs["Y"])
|
||||
links.new(front.outputs[0], projected.inputs["Y"])
|
||||
links.new(negative_z.outputs[0], projected.inputs["Z"])
|
||||
current_position = nodes.new("GeometryNodeInputPosition")
|
||||
blend = nodes.new("ShaderNodeMix")
|
||||
blend.data_type = "VECTOR"
|
||||
@@ -424,8 +425,8 @@ def project_depth_surface(
|
||||
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
|
||||
surface_position = nodes.new("ShaderNodeCombineXYZ")
|
||||
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"])
|
||||
links.new(front.outputs[0], surface_position.inputs["Y"])
|
||||
links.new(blended_axes.outputs["Z"], surface_position.inputs["Z"])
|
||||
project_geometry = nodes.new("GeometryNodeSetPosition")
|
||||
links.new(geometry, project_geometry.inputs["Geometry"])
|
||||
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
||||
|
||||
33
tools/nodes/common/direction.py
Normal file
33
tools/nodes/common/direction.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Build nodes that decode legacy Direction parameters into canonical space."""
|
||||
|
||||
|
||||
def legacy_direction_to_canonical(group, legacy):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
axes = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(legacy, axes.inputs[0])
|
||||
negative_x = nodes.new("ShaderNodeMath")
|
||||
negative_x.operation = "MULTIPLY"
|
||||
negative_x.inputs[1].default_value = -1.0
|
||||
links.new(axes.outputs["X"], negative_x.inputs[0])
|
||||
negative_y = nodes.new("ShaderNodeMath")
|
||||
negative_y.operation = "MULTIPLY"
|
||||
negative_y.inputs[1].default_value = -1.0
|
||||
links.new(axes.outputs["Y"], negative_y.inputs[0])
|
||||
direction = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(negative_x.outputs[0], direction.inputs["X"])
|
||||
links.new(axes.outputs["Z"], direction.inputs["Y"])
|
||||
links.new(negative_y.outputs[0], direction.inputs["Z"])
|
||||
return direction.outputs["Vector"]
|
||||
|
||||
|
||||
def symmetry_legacy_direction_to_canonical(group, legacy):
|
||||
nodes = group.nodes
|
||||
links = group.links
|
||||
axes = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(legacy, axes.inputs[0])
|
||||
direction = nodes.new("ShaderNodeCombineXYZ")
|
||||
links.new(axes.outputs["X"], direction.inputs["X"])
|
||||
links.new(axes.outputs["Z"], direction.inputs["Y"])
|
||||
links.new(axes.outputs["Y"], direction.inputs["Z"])
|
||||
return direction.outputs["Vector"]
|
||||
@@ -6,7 +6,7 @@ These FACE attributes stay on the output mesh for the material consumer.
|
||||
"""
|
||||
from mathutils import Matrix
|
||||
|
||||
from anyimage.operators.cutout_tool.shape import CUTOUT_Z_TO_X
|
||||
from anyimage.common.object import blender_frame_rotation
|
||||
|
||||
ROTATION_ATTRIBUTE = 'o_depth_rotation'
|
||||
FACE_ATTRIBUTE = 'o_depth_face'
|
||||
@@ -38,28 +38,32 @@ def transform_object_normal_color(group, color, strength):
|
||||
decode.inputs[1].default_value = (2., 2., 2.)
|
||||
decode.inputs[2].default_value = (-1., -1., -1.)
|
||||
links.new(color, decode.inputs[0])
|
||||
base = nodes.new('ShaderNodeVectorRotate')
|
||||
base.rotation_type = 'EULER_XYZ'
|
||||
base.inputs['Rotation'].default_value = Matrix(blender_frame_rotation()).to_euler()
|
||||
links.new(decode.outputs[0], base.inputs['Vector'])
|
||||
rotate = nodes.new('ShaderNodeVectorRotate')
|
||||
rotate.rotation_type = 'EULER_XYZ'
|
||||
rotate.invert = True
|
||||
links.new(decode.outputs[0], rotate.inputs['Vector'])
|
||||
links.new(base.outputs[0], rotate.inputs['Vector'])
|
||||
links.new(rotation.outputs['Vector'], rotate.inputs['Rotation'])
|
||||
separate = nodes.new('ShaderNodeSeparateXYZ')
|
||||
links.new(rotate.outputs[0], separate.inputs[0])
|
||||
back = math('COMPARE', face.outputs['Fac'], 2.)
|
||||
# Reflect the normal's Z component on the back.
|
||||
difference = math('MULTIPLY', separate.outputs['Z'], -2.)
|
||||
# Reflect the normal's Y component on the back.
|
||||
difference = math('MULTIPLY', separate.outputs['Y'], -2.)
|
||||
correction = nodes.new('ShaderNodeMath')
|
||||
correction.operation = 'MULTIPLY_ADD'
|
||||
links.new(back, correction.inputs[0])
|
||||
links.new(difference, correction.inputs[1])
|
||||
links.new(separate.outputs['Z'], correction.inputs[2])
|
||||
links.new(separate.outputs['Y'], correction.inputs[2])
|
||||
combine = nodes.new('ShaderNodeCombineXYZ')
|
||||
links.new(separate.outputs['X'], combine.inputs['X'])
|
||||
links.new(separate.outputs['Y'], combine.inputs['Y'])
|
||||
links.new(correction.outputs[0], combine.inputs['Z'])
|
||||
links.new(correction.outputs[0], combine.inputs['Y'])
|
||||
links.new(separate.outputs['Z'], combine.inputs['Z'])
|
||||
axis_rotation = nodes.new('ShaderNodeVectorMath')
|
||||
axis_rotation.operation = 'SCALE'
|
||||
axis_rotation.inputs[0].default_value = Matrix(CUTOUT_Z_TO_X).to_euler()
|
||||
axis_rotation.inputs[0].default_value = Matrix.Rotation(1.5707963267948966, 4, 'Z').to_euler()
|
||||
links.new(axis.outputs['Fac'], axis_rotation.inputs['Scale'])
|
||||
orient = nodes.new('ShaderNodeVectorRotate')
|
||||
orient.rotation_type = 'EULER_XYZ'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Build the direction-adjusted reference depth field."""
|
||||
from .nodes import math_node
|
||||
from .direction import legacy_direction_to_canonical
|
||||
|
||||
|
||||
def reference_plane_depth(group, direction, reference_depth):
|
||||
@@ -8,23 +9,24 @@ def reference_plane_depth(group, direction, reference_depth):
|
||||
normal = nodes.new("ShaderNodeVectorMath")
|
||||
normal.operation = "NORMALIZE"
|
||||
links.new(direction, normal.inputs[0])
|
||||
remapped = legacy_direction_to_canonical(group, normal.outputs["Vector"])
|
||||
normal_components = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(normal.outputs["Vector"], normal_components.inputs["Vector"])
|
||||
links.new(remapped, normal_components.inputs["Vector"])
|
||||
position = nodes.new("GeometryNodeInputPosition")
|
||||
position_components = nodes.new("ShaderNodeSeparateXYZ")
|
||||
links.new(position.outputs["Position"], position_components.inputs["Vector"])
|
||||
x_term = math_node(nodes, "MULTIPLY")
|
||||
links.new(position_components.outputs["X"], x_term.inputs[0])
|
||||
links.new(normal_components.outputs["X"], x_term.inputs[1])
|
||||
y_term = math_node(nodes, "MULTIPLY")
|
||||
links.new(position_components.outputs["Y"], y_term.inputs[0])
|
||||
links.new(normal_components.outputs["Y"], y_term.inputs[1])
|
||||
z_term = math_node(nodes, "MULTIPLY")
|
||||
links.new(position_components.outputs["Z"], z_term.inputs[0])
|
||||
links.new(normal_components.outputs["Z"], z_term.inputs[1])
|
||||
point_dot = math_node(nodes, "ADD")
|
||||
links.new(x_term.outputs[0], point_dot.inputs[0])
|
||||
links.new(y_term.outputs[0], point_dot.inputs[1])
|
||||
links.new(z_term.outputs[0], point_dot.inputs[1])
|
||||
divide = math_node(nodes, "DIVIDE")
|
||||
links.new(point_dot.outputs[0], divide.inputs[0])
|
||||
links.new(normal_components.outputs["Z"], divide.inputs[1])
|
||||
links.new(normal_components.outputs["Y"], divide.inputs[1])
|
||||
plane = math_node(nodes, "ADD")
|
||||
links.new(reference_depth, plane.inputs[0])
|
||||
links.new(divide.outputs[0], plane.inputs[1])
|
||||
|
||||
Reference in New Issue
Block a user