Refactor depth surface handling and unify depth sampling rules
- Removed the `always_vertex` parameter from `build_surface_camera` to streamline depth sampling for cutouts and ensure consistent behavior across depth plane and panorama. - Updated depth sampling logic to ensure that all free boundary points, including the original frame of the depth plane, use neighboring face depth instead of their own texture sampling. - Adjusted the depth mask deletion process to operate on faces rather than points, eliminating the need for additional cleanup of dangling edges. - Ensured that the cutout rules are consistently applied across all relevant nodes, removing exceptions and simplifying the logic. - Updated tests to verify the new behavior, ensuring that cutout points follow the depth of adjacent faces and that the original frame maintains its depth sampling. - Refactored geometry handling in multiple depth-related nodes to improve clarity and maintainability.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-16
|
||||
43
openspec/changes/pin-geometry-node-smoothing/design.md
Normal file
43
openspec/changes/pin-geometry-node-smoothing/design.md
Normal file
@@ -0,0 +1,43 @@
|
||||
## Context
|
||||
|
||||
`O_AnyImage` 的顶点位置平滑只有两条路径:`tools/nodes/common/smoothing.py` 的 `expand_smooth()` 供三个深度节点组的边界平滑使用,`tools/nodes/groups/image_cutout_symmetry.py` 的 `_apply_planar_relax()` 供 Fill 与 Seam 使用。
|
||||
|
||||
两者用同一套 Blur Attribute 结构,但形态不同:`expand_smooth()` 用 Repeat 逐轮把顶点位置混向「内部模糊结果,边界点改用边界条带模糊结果」,并乘上作用于切边带的影响场;`_apply_planar_relax()` 一次性做 N 次模糊,再取平面内分量、乘固定 0.5 与轮廓/切缝的两圈 falloff 后作为位移。前者已经等于 OmooNodes `O Smooth` 的 `Pin Boundary = 1`,但两者都没有 `Pin Sharp`,`_apply_planar_relax()` 也没有边界条带分支,轮廓会被内部模糊往里拖。
|
||||
|
||||
参考实现 `O Smooth`(OmooNodes 的 `O_Essentials_GeometryNodes.blend`)把两个 pin 表达为输入:`Pin Boundary` 在「内部模糊结果」与「边界条带自身 0.5 权重模糊结果」之间选择边界点目标;`Pin Sharp` 用 `|Blur(Normal, 10)|` 压低尖锐处的平滑权重。本变更把这两个输入固定为 1,并让所有几何平滑共用这一层。
|
||||
|
||||
两处的受力点性质不同。深度三组与 Seam 的受力点是真正的网格边界(Split 切边、原图边框、对称平面的切缝),轮廓是否被拖拽、锐边是否被抹平都直接影响外形。Fill 则作用在 front 表面与它挤出的侧壁之间的衔接带:壁在平滑之后才挤出,衔接处不是网格边界,两个 pin 在这里没有对象,用力方向反而是把这一带放松成圆滑过渡。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 真正位于网格边界的平滑固定带上 `Pin Sharp = 1` 与 `Pin Boundary = 1`,且不新增 modifier 接口。
|
||||
- Fill 作为衔接带的普通平滑,作用范围比原先的两圈更宽。
|
||||
- 深度三组与 Cutout Symmetry 的平滑共用同一个构建函数,站点只提供作用范围、权重与是否 pin。
|
||||
- 保持作用范围的渐变语义与 Cutout 中轮廓、切边的权重差异。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不把 pin 应用到法线平滑、深度模糊、掩码与统计模糊等属性平滑。
|
||||
- 不改变作用范围的宽度、渐变形态与迭代次数的公开含义。
|
||||
- 不保留旧的平面内偏移平滑路径或兼容输入。
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **把平滑拆成作用范围层与 pin 层。** 作用范围层继续回答「哪些顶点允许动、每轮动多少」,由调用方传入 region 与 weight 字段;pin 层回答「边界点用哪个目标」「锐处权重要不要压低」,由共享实现的 `pin_boundary` 与 `pin_sharp` 分别生成。相比把 pin 混进各站点的影响场,这样 Cutout 的轮廓 0.1 与切边 1.0 权重、Seam 与 Fill 的 falloff 都能原样保留。
|
||||
2. **共享函数放在 `common/smoothing.py`。** 签名收敛为几何、迭代次数、region、weight、可移动分量与两个 pin 开关:Repeat 每轮按 `weight × region` 把位置混向目标,`pin_boundary` 决定边界点是否改用边界条带模糊结果,`pin_sharp` 决定是否乘锐度系数。`expand_smooth()` 不再保留独立实现,`boundary_smoothing.py` 只负责构造 Cutout 的 region 与 weight。
|
||||
3. **边界条带分支与 O Smooth 完全一致。** `Separate Geometry` 取出边界点,对位置做权重 0.5 的一步 Blur,再用 `Sample Nearest` 的索引取回,边界点目标改为该结果。边界顶点仍会沿边界移动,只避免被内部平滑拖拽收缩,因此不需要额外参数表达「冻结边界」。
|
||||
4. **锐度权重与 O Smooth 一致。** 对 `Normal` 做 10 次权重为 1 的 Blur,取长度作为逐点系数乘进权重。备选方案是用面夹角或锐边属性判断,但那需要额外的拓扑判定与属性传递;法线相干度直接复用参考实现,判定成本低且没有新增数据。
|
||||
5. **迭代机制统一为 Repeat。** Symmetry 现在是一次 N 次 Blur 再乘比例的偏移,改为与深度三组相同的逐轮混合;这样同一份实现即可覆盖两处,且 region 与 weight 在每轮都参与,收敛路径可预期。
|
||||
6. **用可移动分量保留对称平面约束。** Symmetry 的前表面折叠在对称平面上,位移若离开平面会破坏镜像焊接,因此共享函数允许调用方传入分量掩码;Fill 与 Seam 传平面内分量,深度三组保持默认全轴。该掩码表达的是几何约束,不是平滑语义。
|
||||
7. **删除旧路径而不是并存。** `_apply_planar_relax()` 与其一次性模糊的偏移实现直接移除,符合项目不保留转发函数与兼容层的要求。
|
||||
8. **Fill 拆开两个 pin,并把衔接带锚在完整边界。** 衔接处不是网格边界,`Pin Boundary` 在这里没有判断对象;但 `Pin Sharp` 仍然有效,衔接带内的尖锐处应当少动,因此两个 pin 拆成独立开关,Fill 传 `pin_boundary=False` 并保留 `pin_sharp=True`。锚点由「界外轮廓」改为 front 的完整边界带(界外轮廓 + 对称面上的切缝):只锚在界外轮廓时,对称轴上的点一旦超出渐变圈数就完全不受力,衔接在轴处断开。渐变宽度仍由 `FILL_SMOOTH_RINGS = 4` 单点控制。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [轮廓行为变化:Seam 与深度三组的轮廓不再被内部模糊拖动] → 这是本变更的目标;用行为测试锁定「范围外顶点不动」「轮廓点位移只来自边界条带分支」「Cutout 轮廓与切边权重差异保留」。
|
||||
- [Fill 范围更宽且覆盖对称轴,front 靠近边界的一段会被邻域平均拉动] → 这正是衔接圆化的目标;受力范围由 `FILL_SMOOTH_RINGS` 控制,测试锁定第三圈仍受力、配置圈数之外保持原位,对称轴上的点参与松弛。
|
||||
- [Pin Sharp 会压低 Split 台阶处的平滑权重,与去台阶目标相抵] → 已在参考实现中验证细节保留更好,保留该行为;台阶处理强度仍由 `Boundary Smooth` 迭代次数和切边完整权重控制。
|
||||
- [统一迭代机制改变 Symmetry 的收敛路径] → 保留现有对称性与闭合测试,并新增 pin 覆盖,确认镜像焊接与闭合性不被破坏。
|
||||
- [节点数量与求值成本增加] → 深度三组本来已有边界分支,只新增法线模糊;Symmetry 新增边界分支与 Repeat。共享实现避免了两套重复复杂度。
|
||||
33
openspec/changes/pin-geometry-node-smoothing/proposal.md
Normal file
33
openspec/changes/pin-geometry-node-smoothing/proposal.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## Why
|
||||
|
||||
`O_AnyImage` 的几何平滑目前只在深度三组里隐式地按边界条带处理边界点,没有任何锐边保护;`O Image Cutout Symmetry` 的 Seam 平滑还会把轮廓往里拖。OmooNodes 的 `O Smooth` 已经证明 `Pin Sharp` 与 `Pin Boundary` 能在保留必要细节的同时得到更干净的表面,本变更把这两层 pin 作为固定为 1 的内部行为引入 `O_AnyImage` 真正位于网格边界的平滑。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 位于网格边界的顶点位置平滑(`Seam Smooth` 与三个深度节点组的 `Boundary Smooth`)固定开启 `Pin Sharp = 1` 与 `Pin Boundary = 1`,在共享实现内部硬编码,不新增也不修改 modifier 接口。
|
||||
- `Pin Boundary`:边界点不再取内部模糊结果,改用边界条带自身的 0.5 权重一步模糊;轮廓只沿自身松弛,不被内部平滑拖拽或收缩,边界顶点本身仍会移动。
|
||||
- `Pin Sharp`:每点平滑权重乘上法线相干度 `|Blur(Normal, 10)|`,尖锐处少动。
|
||||
- **BREAKING**:`Seam Smooth` 与 `Boundary Smooth` 不再移动或收缩轮廓本身,轮廓改为按 `Pin Boundary` 规则处理。
|
||||
- `Fill Smooth` 改为衔接带平滑:锚点是 front 的完整边界带(外侧轮廓与对称面上的切缝),范围从两圈扩大到四圈,因此对称轴上的点同样参与;衔接处不是网格边界,因此不做 `Pin Boundary`,保留 `Pin Sharp`。
|
||||
- 作用范围语义保持不变:`Boundary Smooth`、`Fill Smooth`、`Seam Smooth` 仍是带渐变的区域,范围外顶点不动;Cutout 中原始轮廓与 Split 切边的权重差异(0.1 / 1.0)保留。
|
||||
- 抽出共享的平滑构建函数,pin 形态与普通形态共用同一个迭代循环,各站点只提供作用范围、权重与是否 pin。
|
||||
- 属性平滑(Normal Smooth、深度模糊、掩码与统计模糊)不纳入 pin,保持现有实现。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `pinned-geometry-smoothing`: 规定 `O_AnyImage` 的边界平滑固定开启 Pin Sharp 与 Pin Boundary、衔接带平滑使用普通平滑,并界定作用范围层与 pin 层的职责。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
- `tools/nodes/common/smoothing.py`:共享 pinned 平滑实现与边界字段。
|
||||
- `tools/nodes/common/boundary_smoothing.py`:深度三组的顶点平滑改走共享实现。
|
||||
- `tools/nodes/groups/image_cutout_symmetry.py`:Fill/Seam 改走共享实现,删除旧的平面内偏移路径。
|
||||
- `tools/nodes/groups/image_depth_plane.py`、`image_depth_cutout.py`、`image_depth_panorama.py`:调用点收敛到共享实现。
|
||||
- `src/anyimage/assets/O_AnyImage.blend` 与 `tests/tools/nodes` 下相关测试。
|
||||
- 公开 modifier 接口不变,不涉及依赖与打包。
|
||||
@@ -0,0 +1,105 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 边界平滑固定开启 Pin Sharp 与 Pin Boundary
|
||||
|
||||
`O Image Cutout Symmetry` 的 `Seam Smooth`,以及 `O Image Depth Plane`、`O Image Depth Cutout`、`O Image Depth Panorama` 的 `Boundary Smooth` SHALL 固定使用 `Pin Sharp = 1` 与 `Pin Boundary = 1`,且 MUST NOT 为这两个 pin 新增或修改 modifier 输入。
|
||||
|
||||
#### Scenario: 边界点按边界条带松弛
|
||||
|
||||
- **WHEN** 平滑命中边界点,包括 Split 形成的切边与对称平面的切缝
|
||||
- **THEN** 该点的目标位置取自边界条带自身权重 0.5 的一步 Blur
|
||||
- **AND** 该点不被内部 Blur 结果拖拽,轮廓不因平滑收缩
|
||||
|
||||
#### Scenario: 边界顶点仍沿边界移动
|
||||
|
||||
- **WHEN** 边界上存在阶梯状顶点
|
||||
- **THEN** 顶点沿边界移动以消除阶梯
|
||||
- **AND** 顶点不会被固定在原位
|
||||
|
||||
#### Scenario: 尖锐处少动
|
||||
|
||||
- **WHEN** 平滑范围内存在法线不连续的尖锐处
|
||||
- **THEN** 该点的平滑权重乘以 `|Blur(Normal, 10)|`
|
||||
- **AND** 尖锐处的位移小于同等权重下平坦区域的位移
|
||||
|
||||
### Requirement: Fill Smooth 平滑衔接带
|
||||
|
||||
`O Image Cutout Symmetry` 的 `Fill Smooth` SHALL 平滑 front 表面与它挤出的侧壁之间的衔接带。锚点 SHALL 是 front 的完整边界带,即外侧轮廓与对称面上的切缝,SHALL 按四圈渐变衰减并覆盖对称轴上的点。该衔接处不是网格边界,因此 MUST NOT 套用 `Pin Boundary`,但 SHALL 保留 `Pin Sharp`。
|
||||
|
||||
#### Scenario: 衔接带使用邻域平均
|
||||
|
||||
- **WHEN** `Fill Smooth` 大于 0
|
||||
- **THEN** 受力点按邻域平均松弛,不切换到边界条带目标
|
||||
|
||||
#### Scenario: 衔接带保留锐度权重
|
||||
|
||||
- **WHEN** 衔接带范围内存在法线不连续的尖锐处
|
||||
- **THEN** 该点的平滑权重乘以 `|Blur(Normal, 10)|`
|
||||
|
||||
#### Scenario: 衔接带覆盖对称轴上的点
|
||||
|
||||
- **WHEN** `Fill Smooth` 大于 0
|
||||
- **THEN** 对称轴上的点与外侧轮廓同样产生面内位移
|
||||
|
||||
#### Scenario: 衔接带比两圈更宽
|
||||
|
||||
- **WHEN** `Fill Smooth` 大于 0
|
||||
- **THEN** 从轮廓向内第三圈的点仍然受力
|
||||
- **AND** 超出配置圈数的点保持原位
|
||||
|
||||
### Requirement: 作用范围保持渐变语义
|
||||
|
||||
`Boundary Smooth`、`Fill Smooth`、`Seam Smooth` SHALL 继续表示平滑生效的渐变区域与迭代次数,落在范围外的顶点 MUST 保持原位。
|
||||
|
||||
#### Scenario: 范围外不动
|
||||
|
||||
- **WHEN** 任一平滑的迭代次数大于 0
|
||||
- **THEN** 落在渐变范围外的顶点位置与输入完全一致
|
||||
- **AND** 拓扑与 UV 保持不变
|
||||
|
||||
#### Scenario: Cutout 轮廓与切边权重差异
|
||||
|
||||
- **WHEN** Depth Cutout 同时存在原始轮廓与 Split 切边
|
||||
- **THEN** Split 切边使用完整权重
|
||||
- **AND** 原始轮廓使用 0.1 倍权重
|
||||
- **AND** Depth Plane 与 Panorama 的轮廓权重保持 1.0
|
||||
|
||||
#### Scenario: 关闭平滑
|
||||
|
||||
- **WHEN** 迭代次数为 0
|
||||
- **THEN** 输出几何与未平滑的基线完全一致
|
||||
|
||||
### Requirement: 共享平滑实现
|
||||
|
||||
`common/smoothing.py` SHALL 提供单一的平滑构建函数:pin 形态使用边界条带目标与锐度系数,普通形态只使用邻域平均目标。各节点组只提供几何、迭代次数、作用范围、权重、可移动分量与是否 pin;系统 MUST NOT 为单个节点组保留独立的平滑实现或旧偏移路径。
|
||||
|
||||
#### Scenario: 站点调用一致
|
||||
|
||||
- **WHEN** 任一节点组构建顶点位置平滑
|
||||
- **THEN** 该组只传入几何、迭代次数、作用范围、权重、允许移动的分量与是否 pin
|
||||
- **AND** 边界条带分支、锐度权重与普通邻域平均目标由共享实现生成
|
||||
|
||||
#### Scenario: 对称平面约束
|
||||
|
||||
- **WHEN** `O Image Cutout Symmetry` 应用 Fill 或 Seam 平滑
|
||||
- **THEN** 位移限制在对称平面内的分量
|
||||
- **AND** 镜像焊接与闭合性保持不变
|
||||
|
||||
### Requirement: 属性平滑保持现状
|
||||
|
||||
法线平滑、深度模糊、掩码与统计模糊 MUST NOT 纳入 pin,保持既有节点与权重。
|
||||
|
||||
#### Scenario: 属性平滑不受影响
|
||||
|
||||
- **WHEN** 构建 Depth Cutout、Depth Plane 或 Panorama
|
||||
- **THEN** 法线平滑与深度模糊的节点结构、迭代次数与权重与变更前一致
|
||||
|
||||
### Requirement: 资产与测试同步
|
||||
|
||||
源码、保存资产与测试 SHALL 保持一致;几何节点及嵌套组 MUST 保持 Capture Attribute 为零。
|
||||
|
||||
#### Scenario: 重建资产
|
||||
|
||||
- **WHEN** 执行节点资产构建并在独立 Blender 进程重新加载
|
||||
- **THEN** 资产与源码构建结果通过相同的行为验证
|
||||
- **AND** Capture Attribute 检查通过
|
||||
36
openspec/changes/pin-geometry-node-smoothing/tasks.md
Normal file
36
openspec/changes/pin-geometry-node-smoothing/tasks.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## 1. 共享实现
|
||||
|
||||
- [x] 1.1 在 `tools/nodes/common/smoothing.py` 实现 pinned 平滑构建函数:Repeat 每轮生成内部 Blur 目标、边界条带 0.5 权重目标与 `|Blur(Normal, 10)|` 锐度系数,按 `weight × region` 混向目标,并支持可移动分量掩码
|
||||
- [x] 1.2 删除 `expand_smooth()`,保留 `edge_boundary_field()` 供作用范围与边界分支使用
|
||||
- [x] 1.3 更新 `tools/nodes/common/boundary_smoothing.py`,轮廓与切边权重留在作用范围层,顶点平滑改走共享函数
|
||||
|
||||
## 2. 节点组
|
||||
|
||||
- [x] 2.1 确认三个深度节点组的顶点平滑经 `smooth_cut_boundary()` 走共享函数,调用签名不变
|
||||
- [x] 2.2 删除 `image_cutout_symmetry.py` 的 `_apply_planar_relax()` 与旧的平面内偏移路径,Fill 与 Seam 改走共享函数并传对称平面内的可移动分量
|
||||
- [x] 2.3 确认公开接口没有新增 pin 输入,法线平滑、深度模糊、掩码与统计模糊保持不变
|
||||
|
||||
## 3. 测试
|
||||
|
||||
- [x] 3.1 在 `tests/tools/nodes/test_boundary_smoothing.py` 覆盖边界条带松弛、尖锐处位移更小与可移动分量掩码,保留范围外不动、迭代 0 关闭、轮廓与切边权重差异的既有覆盖
|
||||
- [x] 3.2 确认 Fill 与 Seam 改走共享函数后镜像焊接与闭合性测试通过;`test_cutout_symmetry.py` 中 4 个失败属于 `refine-cutout-symmetry-seam` 未完成项,改动前后一致
|
||||
- [x] 3.3 运行节点与 operator 测试:919 passed(含 60 subtests),仅上述 4 个既有失败
|
||||
|
||||
## 4. 资产
|
||||
|
||||
- [x] 4.1 运行 `uv run node-group build` 更新并验证 `src/anyimage/assets/O_AnyImage.blend`
|
||||
- [x] 4.2 检查几何节点与嵌套组 Capture Attribute 为零,确认源码、资产与测试无残留旧平滑路径
|
||||
|
||||
## 5. Fill Smooth 语义调整
|
||||
|
||||
- [x] 5.1 让共享平滑支持普通形态:不接边界条带分支时只使用邻域平均目标与 region 权重,pin 形态与普通形态共用同一个迭代循环
|
||||
- [x] 5.2 `Fill Smooth` 改走衔接带形态,falloff 由两圈扩大到 `FILL_SMOOTH_RINGS = 4` 圈,Seam 保持 pin
|
||||
- [x] 5.3 覆盖普通平滑的邻域平均目标与衔接带第三圈仍受力、配置圈数之外保持原位
|
||||
- [x] 5.4 重建节点资产并运行相关测试
|
||||
|
||||
## 6. 衔接带锚点与 pin 拆分
|
||||
|
||||
- [x] 6.1 把共享平滑的 pin 拆成 `pin_boundary` 与 `pin_sharp` 两个独立开关,深度三组与 Seam 保持两者开启
|
||||
- [x] 6.2 `Fill Smooth` 保留 `Pin Sharp`、关闭 `Pin Boundary`,锚点由界外轮廓改为 front 的完整边界带,对称轴上的点因此纳入作用范围
|
||||
- [x] 6.3 更新测试:衔接带使用邻域平均但保留锐度权重、对称轴上的点产生位移,并修正 `refine-cutout-symmetry-seam` 里与旧 Fill 语义冲突的断言
|
||||
- [x] 6.4 重建节点资产并运行相关测试
|
||||
Reference in New Issue
Block a user