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:
@@ -1,5 +1,6 @@
|
||||
"""Mirror completed depth geometry and fill matching boundaries."""
|
||||
import bpy
|
||||
from mathutils import Matrix
|
||||
|
||||
from ..common.nodes import (
|
||||
interface_socket, group_input, float_input, bool_input, vector_input,
|
||||
@@ -7,6 +8,7 @@ from ..common.nodes import (
|
||||
)
|
||||
from ..common.depth_surface import _math
|
||||
from ..common.normal_map import ROTATION_ATTRIBUTE, FACE_ATTRIBUTE, AXIS_ATTRIBUTE
|
||||
from ..common.direction import symmetry_legacy_direction_to_canonical
|
||||
|
||||
NODE_GROUP_NAME = "O Image Cutout Symmetry"
|
||||
|
||||
@@ -14,9 +16,10 @@ NODE_GROUP_NAME = "O Image Cutout Symmetry"
|
||||
def _align_front(group, geometry, controls):
|
||||
"""Align existing geometry with the fixed XY symmetry plane."""
|
||||
nodes, links = group.nodes, group.links
|
||||
direction = symmetry_legacy_direction_to_canonical(group, controls['Direction'])
|
||||
align = nodes.new('FunctionNodeAlignEulerToVector')
|
||||
align.axis = 'Z'
|
||||
links.new(controls['Direction'], align.inputs['Vector'])
|
||||
align.axis = 'Y'
|
||||
links.new(direction, align.inputs['Vector'])
|
||||
position = nodes.new('GeometryNodeInputPosition')
|
||||
rotate = nodes.new('ShaderNodeVectorRotate')
|
||||
rotate.rotation_type = 'EULER_XYZ'
|
||||
@@ -24,15 +27,19 @@ def _align_front(group, geometry, controls):
|
||||
links.new(position.outputs[0], rotate.inputs['Vector'])
|
||||
links.new(align.outputs['Rotation'], rotate.inputs['Rotation'])
|
||||
offset = nodes.new('ShaderNodeCombineXYZ')
|
||||
links.new(controls['Offset'], offset.inputs['Z'])
|
||||
offset_value = nodes.new('ShaderNodeMath')
|
||||
offset_value.operation = 'MULTIPLY'
|
||||
offset_value.inputs[1].default_value = -1.0
|
||||
links.new(controls['Offset'], offset_value.inputs[0])
|
||||
links.new(offset_value.outputs[0], offset.inputs['Y'])
|
||||
offset_position = nodes.new('ShaderNodeVectorMath')
|
||||
offset_position.operation = 'ADD'
|
||||
links.new(rotate.outputs[0], offset_position.inputs[0])
|
||||
links.new(offset.outputs[0], offset_position.inputs[1])
|
||||
scale_vector = nodes.new('ShaderNodeCombineXYZ')
|
||||
scale_vector.inputs['X'].default_value = 1
|
||||
scale_vector.inputs['Y'].default_value = 1
|
||||
links.new(controls['Scale'], scale_vector.inputs['Z'])
|
||||
scale_vector.inputs['Z'].default_value = 1
|
||||
links.new(controls['Scale'], scale_vector.inputs['Y'])
|
||||
scale = nodes.new('ShaderNodeVectorMath')
|
||||
scale.operation = 'MULTIPLY'
|
||||
links.new(offset_position.outputs[0], scale.inputs[0])
|
||||
@@ -48,32 +55,32 @@ def _align_front(group, geometry, controls):
|
||||
return store.outputs['Geometry']
|
||||
|
||||
|
||||
def _delete_negative_faces(group, front, tolerance):
|
||||
"""Discard faces touching negative Z without leaving wires or isolated vertices."""
|
||||
def _delete_positive_faces(group, front, tolerance):
|
||||
"""Discard faces touching positive Y without leaving wires or isolated vertices."""
|
||||
position = group.nodes.new('GeometryNodeInputPosition')
|
||||
components = group.nodes.new('ShaderNodeSeparateXYZ')
|
||||
group.links.new(position.outputs[0], components.inputs[0])
|
||||
negative = _math(group, 'LESS_THAN', components.outputs['Z'], tolerance)
|
||||
point_negative = evaluate_field(group, negative, 'FLOAT', 'POINT')
|
||||
face_negative = evaluate_field(group, point_negative, 'FLOAT', 'FACE')
|
||||
positive = _math(group, 'GREATER_THAN', components.outputs['Y'], tolerance)
|
||||
point_positive = evaluate_field(group, positive, 'FLOAT', 'POINT')
|
||||
face_positive = evaluate_field(group, point_positive, 'FLOAT', 'FACE')
|
||||
delete = group.nodes.new('GeometryNodeDeleteGeometry')
|
||||
delete.domain = 'FACE'
|
||||
delete.mode = 'ALL'
|
||||
group.links.new(front, delete.inputs['Geometry'])
|
||||
group.links.new(_math(group, 'GREATER_THAN', face_negative, 0), delete.inputs['Selection'])
|
||||
group.links.new(_math(group, 'GREATER_THAN', face_positive, 0), delete.inputs['Selection'])
|
||||
return delete.outputs[0]
|
||||
|
||||
|
||||
def _mirror(group, geometry):
|
||||
mirror = group.nodes.new('GeometryNodeTransform')
|
||||
mirror.inputs['Scale'].default_value = (1, 1, -1)
|
||||
mirror.inputs['Scale'].default_value = (1, -1, 1)
|
||||
group.links.new(geometry, mirror.inputs['Geometry'])
|
||||
flip = group.nodes.new('GeometryNodeFlipFaces')
|
||||
group.links.new(mirror.outputs[0], flip.inputs['Mesh'])
|
||||
return flip.outputs[0]
|
||||
|
||||
|
||||
def _extrude_boundary(group, geometry, selection, z_factor):
|
||||
def _extrude_boundary(group, geometry, selection, y_factor):
|
||||
"""Extrude selected edges to a fraction of their current height."""
|
||||
nodes, links = group.nodes, group.links
|
||||
extrude = nodes.new('GeometryNodeExtrudeMesh')
|
||||
@@ -84,7 +91,7 @@ def _extrude_boundary(group, geometry, selection, z_factor):
|
||||
position = nodes.new('GeometryNodeInputPosition')
|
||||
scale = nodes.new('ShaderNodeVectorMath')
|
||||
scale.operation = 'MULTIPLY'
|
||||
scale.inputs[1].default_value = (1, 1, z_factor)
|
||||
scale.inputs[1].default_value = (1, y_factor, 1)
|
||||
links.new(position.outputs[0], scale.inputs[0])
|
||||
place = nodes.new('GeometryNodeSetPosition')
|
||||
links.new(extrude.outputs['Mesh'], place.inputs['Geometry'])
|
||||
@@ -128,7 +135,7 @@ def _walls(group, front, boundary, smooth, tolerance, *, curved):
|
||||
links.new(position.outputs[0], difference.inputs[1])
|
||||
mask = nodes.new('ShaderNodeVectorMath')
|
||||
mask.operation = 'MULTIPLY'
|
||||
mask.inputs[1].default_value = (.5, .5, 0)
|
||||
mask.inputs[1].default_value = (.5, 0, .5)
|
||||
links.new(difference.outputs[0], mask.inputs[0])
|
||||
place = nodes.new('GeometryNodeSetPosition')
|
||||
links.new(geometry, place.inputs['Geometry'])
|
||||
@@ -171,7 +178,7 @@ def build_image_cutout_symmetry_group():
|
||||
links.new(bounds.outputs['Min'], size.inputs[0])
|
||||
links.new(bounds.outputs['Max'], size.inputs[1])
|
||||
tolerance = _math(group, 'MULTIPLY', _math(group, 'MAXIMUM', size.outputs['Value'], 1e-8), 1e-7)
|
||||
front = _delete_negative_faces(group, _align_front(group, inputs['Geometry'], inputs), tolerance)
|
||||
front = _delete_positive_faces(group, _align_front(group, inputs['Geometry'], inputs), tolerance)
|
||||
shade = nodes.new('GeometryNodeSetShadeSmooth')
|
||||
shade.domain = 'FACE'
|
||||
links.new(front, shade.inputs['Geometry'])
|
||||
@@ -194,7 +201,10 @@ def build_image_cutout_symmetry_group():
|
||||
merge = nodes.new('GeometryNodeMergeByDistance')
|
||||
links.new(tolerance, merge.inputs['Distance'])
|
||||
links.new(join.outputs[0], merge.inputs['Geometry'])
|
||||
geometry = store_float_attribute(group, merge.outputs[0], AXIS_ATTRIBUTE, 0., domain='FACE')
|
||||
links.new(geometry, nodes.new('NodeGroupOutput').inputs['Geometry'])
|
||||
geometry = store_float_attribute(group, merge.outputs[0], AXIS_ATTRIBUTE, 1., domain='FACE')
|
||||
orient = nodes.new('GeometryNodeTransform')
|
||||
orient.inputs['Rotation'].default_value = Matrix.Rotation(1.5707963267948966, 4, 'Z').to_euler()
|
||||
links.new(geometry, orient.inputs['Geometry'])
|
||||
links.new(orient.outputs[0], nodes.new('NodeGroupOutput').inputs['Geometry'])
|
||||
prepare_node_group(group)
|
||||
return group
|
||||
|
||||
Reference in New Issue
Block a user