Initial public release
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-09-19 21:31:36 +08:00
commit 198fcbf55b
837 changed files with 67239 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-16

View File

@@ -0,0 +1,72 @@
## Context
`tools/nodes/groups/image_cutout_symmetry.py` 当前先对齐方向、按 `Offset` 与 `Scale` 成形,再用 `_delete_positive_faces` 删除越过对称面的几何,最后镜像、补侧壁并合并。
两处现状造成中缝杂乱:
- `_delete_positive_faces` 把 POINT 域的越界布尔求值到 FACE 域再取 `> 0`,等价于「任一顶点越界即删除整个面」。保留区整体退让,退让量随局部深度梯度逐处变化,切口不落在对称面上。
- `build` 里的补面边界取「只有一个相邻面的边」,没有排除切口那条。直壁模式下每条切口边生成宽度等于两倍退让量的侧壁,宽度逐边不同且平面着色;`_walls` 的平滑只按 `0.5 / 0` 比例写入新增环的对称轴坐标,并用 `(.5, 0, .5)` 掩码只移动面内分量,因此无法消除宽度差。
同一节点曾用布尔原型验证过正确构成:`C:/Users/icrdr/AppData/Local/Temp/anyimage-car-symmetry/close_projected.py` 的补面选择为「边界边且位于界内一侧」,切口依靠合并自焊,验证 `open_edges = 0`、`nonmanifold_edges = 0`。本轮用面删除加边缘归零在无布尔的条件下复现该构成。
约束:不引入布尔或裁切算子;不使用 Capture Attribute;切边平滑只改面内分量,对称轴坐标保持为零。
## Goals / Non-Goals
**Goals:** 切口严格落在对称面上并由镜像合并自焊,零厚度与非零厚度输入走同一条折叠路径;补面只覆盖外轮廓;切口环由独立参数按两圈渐变平滑;节点资产名不变。
**Non-Goals:** 精确裁切、布尔实现、细分加密、外轮廓侧壁的形态重设计、既有 `.blend` 内嵌节点的自动迁移。
## Decisions
### 开口薄片:折叠过界部分
薄片的做法是把配平到对称面上的工作量压到最小:只删除越过对称面的顶点所对应的折叠结果,不预先删除与之相连的面。
| 顶点 | 处理 |
| --- | --- |
| 越过对称面 | 保留,对称轴坐标归零 |
| 界内 | 保留,不改位置 |
| 归零后整面落在对称面上 | 该面删除 |
跨界面的顶点被拉回平面而不是被删除,因此保留的面比整面删除多(同一 33×33 薄片样例:3356 面对 3044 面),界内顶点完全不动,形变只发生在跨界面上;切口边缘写死为 0,镜像后两侧严格重合,合并即焊成接缝。
### 统一折叠,并清理内部贴平边
闭合壳体(非零厚度输入)同样折叠,折叠会在壳体边缘留下「内部贴平边」:一条落在对称面上的边仍邻接两个面,镜像后这条边会变成四面共边。这类边不是有效切口,而是折叠后两层表面叠在一起形成的非法退化,因此统一清理掉贴平边邻接的面,让切口在此重新成为边界边,再与镜像焊接。这样不按输入是否闭合分支,零厚度与非零厚度共用一条路径;测试壳体上非流形边从 4–6 条降为 0。
清理用 `InputMeshEdgeVertices` 判断边两端都在对称面上,用 `InputMeshEdgeNeighbors` 判断边仍邻接两个面,再经 `CornersOfFace` / `EdgesOfCorner` / `FieldAtIndex` 把边的非法性聚合到面并删除。布尔裁切精度最高,但既有对照测量为 5–7 倍耗时且依赖输入流形,不采用。
实现只依赖逐点与逐边求值。这里不能用「把 POINT 域布尔求值到 FACE 域再判断」的写法:函数节点链在不同域上按该元素自身的位置求值,FACE 域得到的是面中心是否越界,而不是任一顶点越界,部分越界的面会既没被删除也进不了切口环。判断面是否整体贴平面时改为「面中心贴平面且面法线沿对称轴」,避免把斜跨对称面的面一起删掉。
### 补面只覆盖不落在对称面上的边界边
边界判据保持「只有一个相邻面」,再与「顶点越出对称面容差」求交,判断按顶点而非边的平均位置。落在对称面上的切口环在合并后已是共享边,再生成侧壁只会得到退化面或三面共享边。
### 切口环与衔接带两个平滑
`Seam Smooth` 与 `Fill Smooth` 都只移动面内分量、锁定对称轴。
`Seam Smooth` 作用于折叠后的切口环,减少切边的面内量化折线,按两圈渐变衰减,并按 `pinned-geometry-smoothing` 的 `Pin Boundary` 与 `Pin Sharp` 处理。`Fill Smooth` 作用于 front 的衔接带,即界外轮廓与对称面上的切缝共同围出的边界带,按四圈渐变向内松弛,随后从平滑后的轮廓构建侧壁,因此侧壁自然跟随轮廓;衔接处不是网格边界,只保留 `Pin Sharp`。补面的细分始终存在,不再以 `Fill Smooth > 0` 为开关;`Fill Smooth = 0` 只表示不平滑。
两个平滑都在镜像之前完成,两半保持一致,且都与 Fill Sides 无关:关闭补面时 `Fill Smooth` 没有轮廓可作用,`Seam Smooth` 照常作用。
### 折叠与清理属于几何正确性,与 Fill Sides 无关
Fill Sides 关闭时跳过侧壁与侧壁平滑,折叠、贴平面片清理与切边平滑照常执行,否则两半会互相穿插。
## Risks / Trade-offs
- 跨界面被拉向对称面,界内顶点不移动 → 用样例记录越界量分布,确认参考深度切得较深时形变可接受。
- 切口环的面内折线仍是网格分辨率级近似 → 由 Seam Smooth 与上游 Boundary Smooth 共同改善,测试明确其为近似边界。
- 壳体折叠后的非法贴平边由清理移除,缝隙在有 Fill Sides 时由侧壁补上 → 测试断言无越过对称面的顶点、无四面共边、切口自焊。
- 参考深度把整片几何置于界外时输出为空 → 沿用现有空结果处理与测试。
- 源码与资产不同步 → 修改后运行 `uv run node-group build`,源码、测试与 `.blend` 一并更新。
## Migration Plan
直接替换删除与补面逻辑,不保留旧路径或开关;重新构建节点资产;不重写用户工程中的旧节点组。新增 `Seam Smooth` 的默认值为 2,与既有默认观感一致;用户工程中的旧节点实例沿用保存时的接口。回滚时恢复本次构建函数与测试并重建资产。
## Open Questions
无阻塞决策。若试作后切边形变在参考深度切入较深时仍显眼,再把归零目标改为平行于对称面的一个偏移面作为后续选项。

View File

@@ -0,0 +1,28 @@
## Why
`O Image Cutout Symmetry` 在补面之前按「任一顶点越过对称面即删除整个面」切掉界外几何,保留区因此整体退让,退让量随局部深度梯度逐处不同。补面随后不得不把这条不共面的切口也连起来,生成的侧壁宽度逐边变化且平面着色,Fill Smooth 只柔化新增环的面内位置,无法消除宽度差;对称对象在中缝处出现一圈明显杂乱的面。
## What Changes
- 对称节点的切口统一为「把越过对称面的顶点折叠到对称面,删除整体贴平的面与内部贴平边」,使零厚度与非零厚度输入的切边落在同一位置。
- 补面只连接不落在对称面上的边界边。落在对称面上的切口由镜像后的合并直接焊成接缝,不再生成侧壁。
- 新增 `Seam Smooth` 并重定义 `Fill Smooth`:两者都只移动面内分量、锁定对称轴;`Seam Smooth` 按两圈渐变松弛切口环,`Fill Smooth` 按四圈渐变松弛 front 的衔接带,锚点为界外轮廓与对称面上的切缝,因此对称轴上的点同样参与。
- 补面细分始终存在,不再以 `Fill Smooth > 0` 为开关;`Fill Smooth` 平滑界外轮廓,侧壁从平滑后的轮廓构建。
- 不引入布尔、裁切算子、细分或新增采样参数。
## Capabilities
### New Capabilities
- `cutout-symmetry-seam`: 对称节点切口与补面的边界语义,包括切口落在对称面上、镜像自焊、补面只覆盖外轮廓,以及独立的切边平滑参数。
### Modified Capabilities
无。`openspec/specs` 尚无可修改的已归档规格,已完成的 `compose-depth-cutout-symmetry` 仍未归档。
## Impact
- `tools/nodes/groups/image_cutout_symmetry.py`:删除、吸附与补面选择。
- `tests/tools/nodes/test_cutout_symmetry.py`:切口位置、封闭性与属性断言。
- `src/anyimage/assets/O_AnyImage.blend`:重新构建节点资产。
- 不改动 `O Image Depth Cutout`、Cutout 创建入口、材质或依赖。

View File

@@ -0,0 +1,104 @@
## ADDED Requirements
### Requirement: 切口落在对称面上
对称节点 SHALL 让切口边缘落在对称面上,零厚度与非零厚度输入走同一条折叠路径:越过对称面的顶点 SHALL 拉回对称面,只删除因此整体落在对称面上的面,并删除折叠后仍邻接两个面的内部贴平边及其邻接面。保留几何中不得存在越过对称面的顶点,也不得留下整体落在对称面上的面或四面共边。
#### Scenario: 折叠过界部分
- **WHEN** 输入几何的一部分穿过对称面
- **THEN** 跨界面的面保留,越界顶点对称轴坐标归零,界内顶点位置不变
- **AND** 保留几何中不存在越过对称面的顶点
#### Scenario: 清理内部贴平边
- **WHEN** 折叠后出现落在对称面上、且仍邻接两个面的边
- **THEN** 这条边及其邻接面被删除,切口重新成为边界边
- **AND** 输出中不存在四面共边
#### Scenario: 不同厚度得到同一切边位置
- **WHEN** 同一 Cutout 分别以零厚度与非零厚度连接到对称节点
- **THEN** 两种情况都折叠并把切口边缘归零,切边落在对称面上
- **AND** 两侧顶点关于对称面对称,且都不越过对称面的几何尺度派生容差
#### Scenario: 裁切后不留平面面片
- **WHEN** 切口边缘归零使某个面完全落到对称面上,或输入本身存在这类面
- **THEN** 该面不出现在输出中
#### Scenario: 几何全部位于界外
- **WHEN** 输入几何整体越过对称面
- **THEN** 输出为空几何,节点不报错
### Requirement: 接缝由镜像合并自焊
对称节点 SHALL 在镜像之前完成切口归零,使两侧切口顶点重合,并 SHALL 在合并后形成共享边。开启 Fill Sides 时输出网格 SHALL 封闭,且边与顶点流形。
#### Scenario: 开启补面的封闭结果
- **WHEN** Fill Sides 开启且输入包含跨对称面的几何
- **THEN** 输出的每条边恰好邻接两个面,顶点与边满足流形性,面绕序一致
- **AND** 两侧顶点关于对称面对称
#### Scenario: 关闭补面
- **WHEN** Fill Sides 关闭
- **THEN** 切口仍吸附到对称面并完成自焊
- **AND** 开边只出现在没有侧壁的外轮廓处
### Requirement: 补面只覆盖不落在对称面上的边界边
Fill Sides 生成的侧壁 SHALL 只连接不落在对称面上的边界边,落在对称面上的切口环 SHALL 不生成侧壁几何。补面选择 SHALL 按顶点是否越过对称面容差判断。
#### Scenario: 切口处不生成侧壁
- **WHEN** Fill Sides 开启且切口环顶点归零后落在对称面上
- **THEN** 输出中不存在顶点全部落在对称面上的补面面片
- **AND** 切口环在合并后为共享边
#### Scenario: 外轮廓仍生成侧壁
- **WHEN** 输入外轮廓的边界顶点不落在对称面上
- **THEN** 这些边界按现有直壁或平滑侧壁构成连接
- **AND** Fill Sides 关闭时不生成侧壁
### Requirement: 切边平滑独立控制
`Seam Smooth` SHALL 控制切口环的面内平滑次数,`Fill Smooth` SHALL 控制 front 衔接带的面内平滑次数。两者都只移动面内分量并锁定对称轴坐标。侧壁细分 SHALL 始终存在,不以 `Fill Smooth > 0` 为开关。`Seam Smooth` 为 0 SHALL 保持原始切口,`Fill Smooth` 为 0 SHALL 保持原始轮廓;两个参数 SHALL 互不影响,且都与 Fill Sides 独立。
`Seam Smooth` 按切口环的两圈渐变衰减,切口环按 `pinned-geometry-smoothing` 的 `Pin Boundary` 与 `Pin Sharp` 处理。`Fill Smooth` 的锚点 SHALL 是 front 的完整边界带,即界外轮廓与对称面上的切缝,SHALL 按四圈渐变衰减并覆盖对称轴上的点;衔接处不是网格边界,因此 MUST NOT 套用 `Pin Boundary`,只保留 `Pin Sharp`。
#### Scenario: 切边平滑保持切口落在对称面
- **WHEN** Seam Smooth 大于 0
- **THEN** 切口环顶点的对称轴坐标保持在容差内,镜像对称关系成立
- **AND** 平滑位移从切口环起按两圈渐变衰减,两圈之外保留表面顶点位置不变
#### Scenario: 两个平滑参数互不影响
- **WHEN** 只改变 Fill Smooth 或只改变 Seam Smooth
- **THEN** 前者只改变衔接带及其侧壁,后者只改变切口环及其两圈渐变范围
- **AND** Seam Smooth 为 0 时切口保持原始形态,Fill Smooth 为 0 时轮廓保持原始形态且侧壁仍保持细分
#### Scenario: 衔接带覆盖对称轴
- **WHEN** Fill Smooth 大于 0
- **THEN** 界外轮廓与对称轴上的点都产生面内位移
- **AND** 位移从完整边界带向内按四圈渐变衰减
#### Scenario: 关闭补面仍可平滑切边
- **WHEN** Fill Sides 关闭且 Seam Smooth 大于 0
- **THEN** 切口环仍按该参数平滑,侧壁不生成,Fill Smooth 无可作用的轮廓
### Requirement: 节点不引入布尔与裁切
对称节点 SHALL 沿用现有节点集合完成切口,不新增布尔、裁切或细分节点。
#### Scenario: 检查构建结果
- **WHEN** 检查构建出的 `O Image Cutout Symmetry` 节点组
- **THEN** 节点图中不存在布尔类节点
- **AND** 接口在原有输入之外只新增 Seam Smooth

View File

@@ -0,0 +1,23 @@
## 1. 切口与补面构建
- [x] 1.1 用「越界顶点折叠到对称面」替换 `_delete_positive_faces`,删除整体贴平面的面
- [x] 1.2 删除折叠后仍邻接两个面的内部贴平边及其邻接面
- [x] 1.3 把补面边界判据改为「只有一个相邻面且不落在对称面上」
- [x] 1.4 新增 Seam Smooth 按两圈渐变平滑切口环,Fill Smooth 收窄为侧壁内部
- [x] 1.5 保持 FACE、ROTATION、AXIS 属性写入点与最终朝向不变
- [x] 1.6 零厚度与非零厚度输入走同一条折叠路径,不按闭合性分支
## 2. 节点测试
- [x] 2.1 断言输出不存在越过对称面的顶点、整体贴平面的面或四面共边
- [x] 2.2 断言 Fill Sides 开启时网格封闭、边与顶点流形、镜像成立,并覆盖零厚度与非零厚度输入
- [x] 2.3 断言 Fill Sides 关闭时切口自焊,落在对称面上的边没有开边
- [x] 2.4 断言 Seam Smooth 大于 0 时切口对称轴坐标不漂移,且按两圈渐变影响切口邻域
- [x] 2.5 断言节点组不含布尔节点,接口只新增 Seam Smooth
## 3. 验证
- [x] 3.1 运行 `uv run node-group build --skip-tests` 并确认资产重载校验通过
- [x] 3.2 更新节点测试后运行 `pytest tests/tools/nodes/test_cutout_symmetry.py`,全部通过
- [x] 3.3 运行受影响的 Cutout 对象创建测试
- [x] 3.4 记录样例上折叠前越界量分布,作为折面跨度的验证证据