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:
2026-09-15 13:33:37 +08:00
parent 591e62085a
commit 4035f5c9e4
47 changed files with 892 additions and 570 deletions

View File

@@ -1,6 +1,7 @@
"""Build the Image Plane Geometry Nodes group."""
import bpy
from mathutils import Matrix
from ..common.nodes import prepare_node_group
from ..common.material_slots import inherit_material_slots
@@ -94,7 +95,7 @@ def build_image_plane_group():
links.new(subdivide_input.outputs["Subdivide"], short_segments.inputs[1])
vertex_counts = []
for axis, other_axis in (("X", "Y"), ("Y", "X")):
for axis, other_axis in (("X", "Z"), ("Z", "X")):
ratio = nodes.new("ShaderNodeMath")
ratio.operation = "DIVIDE"
links.new(separate_dimensions.outputs[axis], ratio.inputs[0])
@@ -118,18 +119,18 @@ def build_image_plane_group():
grid = nodes.new("GeometryNodeMeshGrid")
links.new(separate_dimensions.outputs["X"], grid.inputs["Size X"])
links.new(separate_dimensions.outputs["Y"], grid.inputs["Size Y"])
links.new(separate_dimensions.outputs["Z"], grid.inputs["Size Y"])
links.new(vertex_counts[0], grid.inputs["Vertices X"])
links.new(vertex_counts[1], grid.inputs["Vertices Y"])
position = nodes.new("GeometryNodeInputPosition")
separate_position = nodes.new("ShaderNodeSeparateXYZ")
links.new(position.outputs["Position"], separate_position.inputs[0])
uv_components = []
for axis in ("X", "Y"):
for position_axis, dimension_axis in (("X", "X"), ("Y", "Z")):
normalize = nodes.new("ShaderNodeMath")
normalize.operation = "DIVIDE"
links.new(separate_position.outputs[axis], normalize.inputs[0])
links.new(separate_dimensions.outputs[axis], normalize.inputs[1])
links.new(separate_position.outputs[position_axis], normalize.inputs[0])
links.new(separate_dimensions.outputs[dimension_axis], normalize.inputs[1])
offset_uv = nodes.new("ShaderNodeMath")
offset_uv.operation = "ADD"
offset_uv.inputs[1].default_value = 0.5
@@ -139,6 +140,21 @@ def build_image_plane_group():
links.new(uv_components[0], uv_coordinates.inputs["X"])
links.new(uv_components[1], uv_coordinates.inputs["Y"])
cube_uv_components = []
for position_axis, dimension_axis in (("X", "X"), ("Z", "Z")):
normalize = nodes.new("ShaderNodeMath")
normalize.operation = "DIVIDE"
links.new(separate_position.outputs[position_axis], normalize.inputs[0])
links.new(separate_dimensions.outputs[dimension_axis], normalize.inputs[1])
offset_uv = nodes.new("ShaderNodeMath")
offset_uv.operation = "ADD"
offset_uv.inputs[1].default_value = 0.5
links.new(normalize.outputs[0], offset_uv.inputs[0])
cube_uv_components.append(offset_uv.outputs[0])
cube_uv_coordinates = nodes.new("ShaderNodeCombineXYZ")
links.new(cube_uv_components[0], cube_uv_coordinates.inputs["X"])
links.new(cube_uv_components[1], cube_uv_coordinates.inputs["Y"])
store_grid_uv = nodes.new("GeometryNodeStoreNamedAttribute")
store_grid_uv.data_type = "FLOAT2"
store_grid_uv.domain = "CORNER"
@@ -146,6 +162,10 @@ def build_image_plane_group():
links.new(grid.outputs["Mesh"], store_grid_uv.inputs["Geometry"])
links.new(uv_coordinates.outputs["Vector"], store_grid_uv.inputs["Value"])
rotate_grid = nodes.new("GeometryNodeTransform")
rotate_grid.inputs["Rotation"].default_value = Matrix.Rotation(1.5707963267948966, 4, "X").to_euler()
links.new(store_grid_uv.outputs["Geometry"], rotate_grid.inputs["Geometry"])
center_sum = nodes.new("ShaderNodeVectorMath")
center_sum.operation = "ADD"
links.new(bounds.outputs["Min"], center_sum.inputs[0])
@@ -155,11 +175,11 @@ def build_image_plane_group():
center.inputs[3].default_value = 0.5
links.new(center_sum.outputs["Vector"], center.inputs[0])
place_grid = nodes.new("GeometryNodeTransform")
links.new(store_grid_uv.outputs["Geometry"], place_grid.inputs["Geometry"])
links.new(rotate_grid.outputs["Geometry"], place_grid.inputs["Geometry"])
links.new(center.outputs["Vector"], place_grid.inputs["Translation"])
cell_sizes = []
for index, axis in enumerate(("X", "Y")):
for index, axis in enumerate(("X", "Z")):
segments = nodes.new("ShaderNodeMath")
segments.operation = "SUBTRACT"
segments.inputs[1].default_value = 1.0
@@ -194,19 +214,19 @@ def build_image_plane_group():
cube_size = nodes.new("ShaderNodeCombineXYZ")
links.new(separate_dimensions.outputs["X"], cube_size.inputs["X"])
links.new(separate_dimensions.outputs["Y"], cube_size.inputs["Y"])
links.new(thickness_input.outputs["Thickness"], cube_size.inputs["Z"])
links.new(thickness_input.outputs["Thickness"], cube_size.inputs["Y"])
links.new(separate_dimensions.outputs["Z"], cube_size.inputs["Z"])
cube = nodes.new("GeometryNodeMeshCube")
links.new(cube_size.outputs["Vector"], cube.inputs["Size"])
links.new(vertex_counts[0], cube.inputs["Vertices X"])
links.new(vertex_counts[1], cube.inputs["Vertices Y"])
links.new(depth_vertices.outputs[0], cube.inputs["Vertices Z"])
links.new(depth_vertices.outputs[0], cube.inputs["Vertices Y"])
links.new(vertex_counts[1], cube.inputs["Vertices Z"])
store_cube_uv = nodes.new("GeometryNodeStoreNamedAttribute")
store_cube_uv.data_type = "FLOAT2"
store_cube_uv.domain = "CORNER"
store_cube_uv.inputs["Name"].default_value = "UVMap"
links.new(cube.outputs["Mesh"], store_cube_uv.inputs["Geometry"])
links.new(uv_coordinates.outputs["Vector"], store_cube_uv.inputs["Value"])
links.new(cube_uv_coordinates.outputs["Vector"], store_cube_uv.inputs["Value"])
place_cube = nodes.new("GeometryNodeTransform")
links.new(store_cube_uv.outputs["Geometry"], place_cube.inputs["Geometry"])
links.new(center.outputs["Vector"], place_cube.inputs["Translation"])