feat: 增加Face偏移保持一级关系策略
This commit is contained in:
@@ -192,6 +192,8 @@ STEP/B-Rep 参数化编辑主线
|
|||||||
├── 1. 平面 Face,第一条主线
|
├── 1. 平面 Face,第一条主线
|
||||||
│ ├── [x] [偏移 -> 推拉平面 / 拉伸切除]
|
│ ├── [x] [偏移 -> 推拉平面 / 拉伸切除]
|
||||||
│ │ └── 当前 Face 移动或加料/切削,边、点和共享边一级相邻面跟随重建,复杂端盖走隔离和回滚。
|
│ │ └── 当前 Face 移动或加料/切削,边、点和共享边一级相邻面跟随重建,复杂端盖走隔离和回滚。
|
||||||
|
│ ├── [~] [偏移 + 保持关系 -> 受限拉伸/切除]
|
||||||
|
│ │ └── 偏移行新增“保持关系”建模意图:执行前要求一级相邻 Face 都能验证为平面平行/垂直关系,执行后反查关系;非平面、斜交或复杂多边界慢计划会提前阻止。
|
||||||
│ ├── [x] [面内长度 -> 改长度]
|
│ ├── [x] [面内长度 -> 改长度]
|
||||||
│ │ └── 近矩形平面 Face 沿长度方向变化,一级相邻面跟随,面积不作为驱动参数。
|
│ │ └── 近矩形平面 Face 沿长度方向变化,一级相邻面跟随,面积不作为驱动参数。
|
||||||
│ ├── [x] [面内宽度 -> 改宽度]
|
│ ├── [x] [面内宽度 -> 改宽度]
|
||||||
@@ -289,7 +291,7 @@ STEP/B-Rep 参数化编辑主线
|
|||||||
│ ├── [x] [平行/垂直 -> Face/Edge 一级事实识别]
|
│ ├── [x] [平行/垂直 -> Face/Edge 一级事实识别]
|
||||||
│ │ └── Face 和 Edge 一级事实图会记录直接邻域内的平行、垂直、斜交关系数量和摘要,供 UI 说明、计划守门和后续约束求解复用。
|
│ │ └── Face 和 Edge 一级事实图会记录直接邻域内的平行、垂直、斜交关系数量和摘要,供 UI 说明、计划守门和后续约束求解复用。
|
||||||
│ ├── [~] [平行/垂直 -> 通用可选约束]
|
│ ├── [~] [平行/垂直 -> 通用可选约束]
|
||||||
│ │ └── Edge 长度已开放受限的“保持关系”用户策略,会复用端面推拉并要求一级平面关系可验证;跨多面、跨特征的通用约束求解仍需继续泛化。
|
│ │ └── Face 偏移/拉伸和 Edge 长度已开放受限的“保持关系”用户策略,会要求一级平面关系可验证并做结果反查;跨多面、跨特征的通用约束求解仍需继续泛化。
|
||||||
│ ├── [x] [复杂曲线 Edge -> 可编辑入口守门]
|
│ ├── [x] [复杂曲线 Edge -> 可编辑入口守门]
|
||||||
│ │ └── B-spline / Bezier / 其它复杂曲线不再在扫描列表或参数表里伪装成稳定“改长度”入口。
|
│ │ └── B-spline / Bezier / 其它复杂曲线不再在扫描列表或参数表里伪装成稳定“改长度”入口。
|
||||||
│ └── [ ] [任意曲线 Edge -> 通用约束编辑]
|
│ └── [ ] [任意曲线 Edge -> 通用约束编辑]
|
||||||
@@ -1348,7 +1350,7 @@ git diff --check
|
|||||||
- `scripts/verify_selection_identity_ui.py`:验证编辑后当前拓扑 Face ID 改变时,Face / 特征标题会显示 `逻辑 Face ID(拓扑 Face ID)`,复制 ID 会优先复制可再次选择的逻辑 Face ID,避免把用户带到重建后的临时拓扑编号上。
|
- `scripts/verify_selection_identity_ui.py`:验证编辑后当前拓扑 Face ID 改变时,Face / 特征标题会显示 `逻辑 Face ID(拓扑 Face ID)`,复制 ID 会优先复制可再次选择的逻辑 Face ID,避免把用户带到重建后的临时拓扑编号上。
|
||||||
- `scripts/verify_face_feature_parameter_consistency.py`:加载默认模型 Face 594,验证轻量 `当前特征` 选择在 full feature 缓存前后都显示同一组基础 Face 参数,避免先改其它对象或触发深层识别后,同一个 Face 的属性列表突然缩水或膨胀。
|
- `scripts/verify_face_feature_parameter_consistency.py`:加载默认模型 Face 594,验证轻量 `当前特征` 选择在 full feature 缓存前后都显示同一组基础 Face 参数,避免先改其它对象或触发深层识别后,同一个 Face 的属性列表突然缩水或膨胀。
|
||||||
- `scripts/verify_face_nonrectangular_local_edit.py`:临时生成三角柱 STEP,验证非矩形平面 Face 的 `局部重建`、`缩放特征`、`移动特征`、`拉伸/切除` 语义,在中心、面内尺寸和偏移场景下都能保持单 Solid。
|
- `scripts/verify_face_nonrectangular_local_edit.py`:临时生成三角柱 STEP,验证非矩形平面 Face 的 `局部重建`、`缩放特征`、`移动特征`、`拉伸/切除` 语义,在中心、面内尺寸和偏移场景下都能保持单 Solid。
|
||||||
- `scripts/verify_face_resize_semantics.py`:加载 `cube_10mm.step`,验证 Face 的面内长度/面内宽度、中心坐标和偏移在 `当前面` 局部编辑、`整体` 所属对象编辑与 `拉伸/切除` 之间不会互相混淆;面积路径只作为后端缩放语义回归保留。
|
- `scripts/verify_face_resize_semantics.py`:加载 `cube_10mm.step`,验证 Face 的面内长度/面内宽度、中心坐标和偏移在 `当前面` 局部编辑、`整体` 所属对象编辑、`拉伸/切除` 与 `保持关系` 之间不会互相混淆;面积路径只作为后端缩放语义回归保留。
|
||||||
- `scripts/verify_face_noop_guards.py`:验证 Face 的拉伸/切除、偏移、中心、面内尺寸和壳体厚度在目标值等于当前值时都会返回 blocked,并给出“不需要修改”的说明。
|
- `scripts/verify_face_noop_guards.py`:验证 Face 的拉伸/切除、偏移、中心、面内尺寸和壳体厚度在目标值等于当前值时都会返回 blocked,并给出“不需要修改”的说明。
|
||||||
- `scripts/verify_first_level_fact_graph.py`:验证正方体平面 Face、通孔圆柱面和槽/半孔候选都会输出统一一级拓扑事实包,包含主体 Face、边界 Edge/Vertex、共享边一级相邻 Face、角色分组,以及明确不自动传播的二级/三级关系;同时验证 Face 拉伸/切除/面积/中心移动、孔径修改和槽宽修改的编辑计划都会携带这份事实。
|
- `scripts/verify_first_level_fact_graph.py`:验证正方体平面 Face、通孔圆柱面和槽/半孔候选都会输出统一一级拓扑事实包,包含主体 Face、边界 Edge/Vertex、共享边一级相邻 Face、角色分组,以及明确不自动传播的二级/三级关系;同时验证 Face 拉伸/切除/面积/中心移动、孔径修改和槽宽修改的编辑计划都会携带这份事实。
|
||||||
- `scripts/verify_feature_recognition_summary.py`:验证平面 Face、带内孔平面 Face、通孔圆柱面、槽/半孔和凸台候选都会输出统一的 `识别摘要`、候选类型、常用优先级、置信度、风险、证据项、`当前可改` 和 `当前受限修改`;全局可编辑对象扫描也会按 Face、孔、槽、凸台、圆角/倒角、壳体、Edge 的常用顺序排序。
|
- `scripts/verify_feature_recognition_summary.py`:验证平面 Face、带内孔平面 Face、通孔圆柱面、槽/半孔和凸台候选都会输出统一的 `识别摘要`、候选类型、常用优先级、置信度、风险、证据项、`当前可改` 和 `当前受限修改`;全局可编辑对象扫描也会按 Face、孔、槽、凸台、圆角/倒角、壳体、Edge 的常用顺序排序。
|
||||||
@@ -1432,7 +1434,7 @@ Face 局部移动基线验证:在 `assets/models/cube_10mm.step` 上选上表
|
|||||||
|
|
||||||
Face 一级关系基线验证:在 `assets/models/cube_10mm.step` 上选上表面 Face,`face_first_level_topology()` 会返回 `topology_relation_depth=1`、4 条边界 Edge、4 个边界 Vertex、4 个共享边相邻 Face,并明确二级/三级关系暂不递归传播。将上表面中心沿 Z 方向从 `10` 移到 `12` 后,4 个一级侧面中心 Z 从 `5` 变为 `6`,二级底面中心 Z 仍为 `0`,说明当前局部 Face 编辑只处理一级共享边拓扑。
|
Face 一级关系基线验证:在 `assets/models/cube_10mm.step` 上选上表面 Face,`face_first_level_topology()` 会返回 `topology_relation_depth=1`、4 条边界 Edge、4 个边界 Vertex、4 个共享边相邻 Face,并明确二级/三级关系暂不递归传播。将上表面中心沿 Z 方向从 `10` 移到 `12` 后,4 个一级侧面中心 Z 从 `5` 变为 `6`,二级底面中心 Z 仍为 `0`,说明当前局部 Face 编辑只处理一级共享边拓扑。
|
||||||
|
|
||||||
Face 偏移基线验证:在 `assets/models/cube_10mm.step` 上选一个平面 Face,将 `偏移` 正向或负向改变 `1mm`。`局部重建` 会走 `local-face-plane-offset-deform`,移动当前 Face 顶点并重建相邻面;`移动特征` 会走 `translate-owning-shape-from-plane-offset`,整体平移所属对象且所有面尺寸保持不变;`拉伸/切除` 会走 `push-pull-planar-face`,正向执行加料,负向执行切削。三条路径都会验证目标平面中心位置,但结果形态不同,不能混为一个“偏移”操作。
|
Face 偏移基线验证:在 `assets/models/cube_10mm.step` 上选一个平面 Face,将 `偏移` 正向或负向改变 `1mm`。`局部重建` 会走 `local-face-plane-offset-deform`,移动当前 Face 顶点并重建相邻面;`移动特征` 会走 `translate-owning-shape-from-plane-offset`,整体平移所属对象且所有面尺寸保持不变;`拉伸/切除` 会走 `push-pull-planar-face`,正向执行加料,负向执行切削;`保持关系` 会走受限的 `push-pull-planar-face-keep-relations`,要求一级相邻平面关系在执行前可验证、执行后可反查。四条路径都会验证目标平面中心位置,但结果形态不同,不能混为一个“偏移”操作。
|
||||||
|
|
||||||
Face 面积后端基线验证:面积是结果变量,不再显示为特征参数表里的驱动行;后端仍保留 `local-face-area-only-deform` 等历史回归,用来确认旧路径不会破坏 B-Rep、逻辑 ID 和结果校验。
|
Face 面积后端基线验证:面积是结果变量,不再显示为特征参数表里的驱动行;后端仍保留 `local-face-area-only-deform` 等历史回归,用来确认旧路径不会破坏 B-Rep、逻辑 ID 和结果校验。
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ FACE_CASES = (
|
|||||||
("offset", "local", "width"),
|
("offset", "local", "width"),
|
||||||
("offset", "owning", "width"),
|
("offset", "owning", "width"),
|
||||||
("offset", "push_pull", "width"),
|
("offset", "push_pull", "width"),
|
||||||
|
("offset", "keep_relations", "width"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -170,7 +171,7 @@ def main() -> int:
|
|||||||
parser.add_argument("--target-area", type=float, default=144.0)
|
parser.add_argument("--target-area", type=float, default=144.0)
|
||||||
parser.add_argument("--center-offset", default="2,0,3")
|
parser.add_argument("--center-offset", default="2,0,3")
|
||||||
parser.add_argument("--offset-distance", type=float, default=1.0)
|
parser.add_argument("--offset-distance", type=float, default=1.0)
|
||||||
parser.add_argument("--strategy", default="local", choices=["local", "owning", "push_pull"])
|
parser.add_argument("--strategy", default="local", choices=["local", "owning", "push_pull", "keep_relations"])
|
||||||
parser.add_argument("--tolerance", type=float, default=1e-5)
|
parser.add_argument("--tolerance", type=float, default=1e-5)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -182,8 +183,8 @@ def main() -> int:
|
|||||||
before = model.stats()
|
before = model.stats()
|
||||||
current_info = model.face_info(face_id)
|
current_info = model.face_info(face_id)
|
||||||
|
|
||||||
if args.strategy == "push_pull" and args.property != "offset":
|
if args.strategy in {"push_pull", "keep_relations"} and args.property != "offset":
|
||||||
raise SystemExit("--strategy push_pull is only valid with --property offset")
|
raise SystemExit("--strategy push_pull/keep_relations is only valid with --property offset")
|
||||||
|
|
||||||
if args.property == "size" and args.strategy == "local":
|
if args.property == "size" and args.strategy == "local":
|
||||||
plan = model.face_size_local_resize_plan(face_id, args.target_size, args.axis)
|
plan = model.face_size_local_resize_plan(face_id, args.target_size, args.axis)
|
||||||
@@ -259,7 +260,7 @@ def main() -> int:
|
|||||||
plan = model.face_plane_offset_owning_translation_plan(face_id, args.offset_distance)
|
plan = model.face_plane_offset_owning_translation_plan(face_id, args.offset_distance)
|
||||||
result = model.translate_face_plane_offset_owning(face_id, args.offset_distance)
|
result = model.translate_face_plane_offset_owning(face_id, args.offset_distance)
|
||||||
expected_strategy = "translate-owning-shape-from-plane-offset"
|
expected_strategy = "translate-owning-shape-from-plane-offset"
|
||||||
else:
|
elif args.property == "offset" and args.strategy == "push_pull":
|
||||||
frame = model.face_plane_offset_frame(face_id)
|
frame = model.face_plane_offset_frame(face_id)
|
||||||
if frame is None:
|
if frame is None:
|
||||||
raise SystemExit("selected Face does not have a stable plane offset frame")
|
raise SystemExit("selected Face does not have a stable plane offset frame")
|
||||||
@@ -275,12 +276,35 @@ def main() -> int:
|
|||||||
plan = model.push_pull_plan(face_id, args.offset_distance)
|
plan = model.push_pull_plan(face_id, args.offset_distance)
|
||||||
result = model.push_pull_face(face_id, args.offset_distance)
|
result = model.push_pull_face(face_id, args.offset_distance)
|
||||||
expected_strategy = "push-pull-planar-face"
|
expected_strategy = "push-pull-planar-face"
|
||||||
|
else:
|
||||||
|
frame = model.face_plane_offset_frame(face_id)
|
||||||
|
if frame is None:
|
||||||
|
raise SystemExit("selected Face does not have a stable plane offset frame")
|
||||||
|
current_center = current_info.get("area_center") or current_info.get("bbox_center")
|
||||||
|
if not isinstance(current_center, tuple) or len(current_center) != 3:
|
||||||
|
raise SystemExit("selected Face does not have a stable center")
|
||||||
|
_origin, direction, _position = frame
|
||||||
|
target_center = (
|
||||||
|
float(current_center[0]) + direction[0] * args.offset_distance,
|
||||||
|
float(current_center[1]) + direction[1] * args.offset_distance,
|
||||||
|
float(current_center[2]) + direction[2] * args.offset_distance,
|
||||||
|
)
|
||||||
|
plan = model.push_pull_keep_relations_plan(face_id, args.offset_distance)
|
||||||
|
result = model.push_pull_face_keep_relations(face_id, args.offset_distance)
|
||||||
|
expected_strategy = "push-pull-planar-face-keep-relations"
|
||||||
|
|
||||||
resolved_strategy = str(plan.get("resize_strategy", ""))
|
resolved_strategy = str(plan.get("resize_strategy", ""))
|
||||||
if args.property == "offset" and args.strategy == "push_pull":
|
if args.property == "offset" and args.strategy == "push_pull":
|
||||||
resolved_strategy = "push-pull-planar-face"
|
resolved_strategy = "push-pull-planar-face"
|
||||||
if resolved_strategy != expected_strategy:
|
if resolved_strategy != expected_strategy:
|
||||||
raise SystemExit(f"expected {expected_strategy}, got {resolved_strategy or '<none>'}")
|
raise SystemExit(f"expected {expected_strategy}, got {resolved_strategy or '<none>'}")
|
||||||
|
if args.property == "offset" and args.strategy == "keep_relations":
|
||||||
|
if plan.get("face_push_pull_planar_relation_constraint_requested") is not True:
|
||||||
|
raise SystemExit(f"keep-relations plan should record requested relation constraint: {plan}")
|
||||||
|
if plan.get("face_push_pull_planar_constraint_status") != "ready":
|
||||||
|
raise SystemExit(f"keep-relations plan should be ready on cube Face: {plan}")
|
||||||
|
if "Planar relation check: ok" not in result:
|
||||||
|
raise SystemExit(f"keep-relations result should include planar relation check, got: {result}")
|
||||||
if "Face result check:" not in result:
|
if "Face result check:" not in result:
|
||||||
raise SystemExit(f"Face edit result message should include a result check, got: {result}")
|
raise SystemExit(f"Face edit result message should include a result check, got: {result}")
|
||||||
if "First-level check:" not in result:
|
if "First-level check:" not in result:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ if str(PROJECT_ROOT) not in sys.path:
|
|||||||
|
|
||||||
EXPECTED_FACE_ISOLATED_OPERATIONS = {
|
EXPECTED_FACE_ISOLATED_OPERATIONS = {
|
||||||
"push_pull_face",
|
"push_pull_face",
|
||||||
|
"push_pull_face_keep_relations",
|
||||||
"move_face_plane_offset_local",
|
"move_face_plane_offset_local",
|
||||||
"resize_face_area_local",
|
"resize_face_area_local",
|
||||||
"resize_face_area",
|
"resize_face_area",
|
||||||
@@ -122,6 +123,7 @@ def _assert_same(label: str, actual: set[str], expected: set[str]) -> None:
|
|||||||
|
|
||||||
PROPERTY_FACE_ACTION_TO_ISOLATED_OPERATION = {
|
PROPERTY_FACE_ACTION_TO_ISOLATED_OPERATION = {
|
||||||
"push_pull_face": "push_pull_face",
|
"push_pull_face": "push_pull_face",
|
||||||
|
"push_pull_face_keep_relations": "push_pull_face_keep_relations",
|
||||||
"move_selected_face_plane_position_local": "move_face_plane_offset_local",
|
"move_selected_face_plane_position_local": "move_face_plane_offset_local",
|
||||||
"resize_face_width_local": "resize_face_size_local",
|
"resize_face_width_local": "resize_face_size_local",
|
||||||
"resize_face_height_local": "resize_face_size_local",
|
"resize_face_height_local": "resize_face_size_local",
|
||||||
@@ -151,6 +153,8 @@ PROPERTY_FACE_ACTIONS_ALLOWED_IN_MAIN_THREAD = {
|
|||||||
|
|
||||||
|
|
||||||
ACTION_IMPLEMENTATION_METHOD = {
|
ACTION_IMPLEMENTATION_METHOD = {
|
||||||
|
"push_pull_face": "_push_pull_face_action",
|
||||||
|
"push_pull_face_keep_relations": "_push_pull_face_action",
|
||||||
"resize_face_width_local": "_resize_face_size_local",
|
"resize_face_width_local": "_resize_face_size_local",
|
||||||
"resize_face_height_local": "_resize_face_size_local",
|
"resize_face_height_local": "_resize_face_size_local",
|
||||||
"resize_face_width_owning_scale": "_resize_face_size_owning_scale",
|
"resize_face_width_owning_scale": "_resize_face_size_owning_scale",
|
||||||
|
|||||||
@@ -491,6 +491,19 @@ def main() -> int:
|
|||||||
_assert_label(plane_specs, "local_face_width", "面内长度")
|
_assert_label(plane_specs, "local_face_width", "面内长度")
|
||||||
_assert_label(plane_specs, "local_face_height", "面内宽度")
|
_assert_label(plane_specs, "local_face_height", "面内宽度")
|
||||||
_assert_label(plane_specs, "face_target_normal_position", "偏移")
|
_assert_label(plane_specs, "face_target_normal_position", "偏移")
|
||||||
|
offset_keep_relations = _scope_mode(plane_specs, "face_target_normal_position", "keep_relations")
|
||||||
|
if str(offset_keep_relations.get("label") or "") != "保持关系":
|
||||||
|
raise SystemExit(f"Face plane-offset keep-relations scope should be labelled clearly: {offset_keep_relations}")
|
||||||
|
if str(offset_keep_relations.get("action") or "") != "push_pull_face_keep_relations":
|
||||||
|
raise SystemExit(f"Face plane-offset keep-relations scope should use its own action: {offset_keep_relations}")
|
||||||
|
if not bool(offset_keep_relations.get("enabled", False)):
|
||||||
|
raise SystemExit(f"Face plane-offset keep-relations scope should be available on simple planar Face: {offset_keep_relations}")
|
||||||
|
_assert_scoped_hint_fragments(
|
||||||
|
plane_specs,
|
||||||
|
"face_target_normal_position",
|
||||||
|
"keep_relations",
|
||||||
|
("一级", "平行/垂直", "会直接阻止"),
|
||||||
|
)
|
||||||
_assert_actionable_rows_first(
|
_assert_actionable_rows_first(
|
||||||
plane_info,
|
plane_info,
|
||||||
(
|
(
|
||||||
@@ -575,7 +588,7 @@ def main() -> int:
|
|||||||
mode,
|
mode,
|
||||||
("5%", "5 倍", "会被阻止"),
|
("5%", "5 倍", "会被阻止"),
|
||||||
)
|
)
|
||||||
for mode in ("push_pull", "local", "owning"):
|
for mode in ("push_pull", "keep_relations", "local", "owning"):
|
||||||
_assert_scoped_hint_fragments(
|
_assert_scoped_hint_fragments(
|
||||||
plane_specs,
|
plane_specs,
|
||||||
"face_target_normal_position",
|
"face_target_normal_position",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ def _point3(value: object, operation: str) -> tuple[float, float, float]:
|
|||||||
def _execute(model: StepModel, operation: str, args: list[object]) -> str:
|
def _execute(model: StepModel, operation: str, args: list[object]) -> str:
|
||||||
if operation == "push_pull_face":
|
if operation == "push_pull_face":
|
||||||
return model.push_pull_face(int(args[0]), float(args[1]))
|
return model.push_pull_face(int(args[0]), float(args[1]))
|
||||||
|
if operation == "push_pull_face_keep_relations":
|
||||||
|
return model.push_pull_face_keep_relations(int(args[0]), float(args[1]))
|
||||||
if operation == "move_face_plane_offset_local":
|
if operation == "move_face_plane_offset_local":
|
||||||
return model.move_face_plane_offset_local(int(args[0]), float(args[1]))
|
return model.move_face_plane_offset_local(int(args[0]), float(args[1]))
|
||||||
if operation == "resize_face_area_local":
|
if operation == "resize_face_area_local":
|
||||||
|
|||||||
+180
-2
@@ -258,6 +258,116 @@ class OperationMixin:
|
|||||||
blockers.append(f"{operation_label}缺少直接相邻 Face 角色,不能确定一级联动范围。")
|
blockers.append(f"{operation_label}缺少直接相邻 Face 角色,不能确定一级联动范围。")
|
||||||
return blockers
|
return blockers
|
||||||
|
|
||||||
|
def _face_push_pull_planar_relation_constraint_fields(
|
||||||
|
self,
|
||||||
|
face_id: int,
|
||||||
|
plan: dict[str, object],
|
||||||
|
) -> dict[str, object]:
|
||||||
|
relation_status = str(plan.get("first_level_planar_relation_status") or "")
|
||||||
|
relation_scope = str(plan.get("first_level_planar_relation_scope") or "")
|
||||||
|
relation_count = int(plan.get("first_level_planar_relation_count", 0) or 0)
|
||||||
|
parallel_count = int(plan.get("first_level_planar_relation_parallel_count", 0) or 0)
|
||||||
|
perpendicular_count = int(plan.get("first_level_planar_relation_perpendicular_count", 0) or 0)
|
||||||
|
angled_count = int(plan.get("first_level_planar_relation_angled_count", 0) or 0)
|
||||||
|
adjacent_count = int(plan.get("first_level_adjacent_face_count", 0) or 0)
|
||||||
|
subject_count = int(plan.get("same_domain_face_count", 0) or 0)
|
||||||
|
non_planar_face_ids = _int_values(plan.get("first_level_planar_relation_non_planar_face_ids"))
|
||||||
|
observed_modes = tuple(str(item) for item in plan.get("first_level_planar_relation_observed_modes", ()) or ())
|
||||||
|
rows = tuple(row for row in plan.get("first_level_planar_relation_rows", ()) or () if isinstance(row, dict))
|
||||||
|
|
||||||
|
blockers: list[str] = []
|
||||||
|
warnings: list[str] = []
|
||||||
|
if relation_status != "ready" or relation_scope != "face":
|
||||||
|
blockers.append("当前 Face 的一级平面关系还不可验证,不能使用“保持关系”建模意图。")
|
||||||
|
if subject_count <= 0:
|
||||||
|
blockers.append("没有识别到当前 Face 的同域主体区域,不能判断哪些面应一起参与保持关系。")
|
||||||
|
if adjacent_count <= 0:
|
||||||
|
blockers.append("没有识别到共享边一级相邻 Face,保持关系没有可验证对象。")
|
||||||
|
if relation_count <= 0:
|
||||||
|
blockers.append("当前一级邻域没有识别到平行或垂直平面关系。")
|
||||||
|
if non_planar_face_ids:
|
||||||
|
blockers.append(
|
||||||
|
"一级相邻 Face 中包含非平面 Face "
|
||||||
|
f"{tuple(non_planar_face_ids)};当前“保持关系”只支持平面邻域。"
|
||||||
|
)
|
||||||
|
if angled_count > 0:
|
||||||
|
blockers.append("一级相邻平面中存在斜交关系;当前“保持关系”只支持平行/垂直关系。")
|
||||||
|
if parallel_count + perpendicular_count <= 0:
|
||||||
|
blockers.append("没有可保持的一级平行/垂直关系。")
|
||||||
|
|
||||||
|
expected_planar_adjacent = max(adjacent_count - len(non_planar_face_ids), 0)
|
||||||
|
if relation_count < max(expected_planar_adjacent, 1):
|
||||||
|
blockers.append(
|
||||||
|
"一级平面关系数量少于相邻平面数量,说明还有相邻面关系无法判断;"
|
||||||
|
"当前版本会先阻止,避免只移动局部面后破坏周边结构。"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not blockers:
|
||||||
|
warnings.append("将执行后反查一级相邻平面关系;如果关系丢失,模型会回滚。")
|
||||||
|
|
||||||
|
if blockers:
|
||||||
|
summary = (
|
||||||
|
"Face保持关系:当前不能稳定保持一级平面关系;"
|
||||||
|
f"相邻 Face {adjacent_count},平行 {parallel_count},垂直 {perpendicular_count},"
|
||||||
|
f"斜交 {angled_count},非平面 {len(non_planar_face_ids)}。"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
summary = (
|
||||||
|
"Face保持关系:可按拉伸/切除执行并反查一级平面关系;"
|
||||||
|
f"平行 {parallel_count},垂直 {perpendicular_count}。"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"face_push_pull_planar_relation_constraint_requested": True,
|
||||||
|
"face_push_pull_planar_relation_constraint_label": "保持一级平面关系",
|
||||||
|
"face_push_pull_planar_constraint_status": "blocked" if blockers else "ready",
|
||||||
|
"face_push_pull_planar_constraint_risk": "blocked" if blockers else ("medium" if warnings else "low"),
|
||||||
|
"face_push_pull_planar_constraint_summary": summary,
|
||||||
|
"face_push_pull_planar_constraint_relation_count": relation_count,
|
||||||
|
"face_push_pull_planar_constraint_parallel_count": parallel_count,
|
||||||
|
"face_push_pull_planar_constraint_perpendicular_count": perpendicular_count,
|
||||||
|
"face_push_pull_planar_constraint_angled_count": angled_count,
|
||||||
|
"face_push_pull_planar_constraint_adjacent_face_count": adjacent_count,
|
||||||
|
"face_push_pull_planar_constraint_subject_face_count": subject_count,
|
||||||
|
"face_push_pull_planar_constraint_non_planar_face_ids": tuple(non_planar_face_ids),
|
||||||
|
"face_push_pull_planar_constraint_observed_modes": observed_modes,
|
||||||
|
"face_push_pull_planar_constraint_rows": rows,
|
||||||
|
"face_push_pull_planar_constraint_blockers": ";".join(blockers),
|
||||||
|
"face_push_pull_planar_constraint_warnings": ";".join(warnings),
|
||||||
|
"face_push_pull_planar_constraint_note": (
|
||||||
|
"Face保持关系当前只作为一级平面关系守门:执行前要求直接相邻 Face 的平行/垂直关系可验证,"
|
||||||
|
"执行后在结果 Face 上反查;二级/三级传播仍不自动处理。"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def push_pull_keep_relations_plan(self, face_id: int, distance: float) -> dict[str, object]:
|
||||||
|
plan = self.push_pull_plan(face_id, distance)
|
||||||
|
constraint_fields = self._face_push_pull_planar_relation_constraint_fields(face_id, plan)
|
||||||
|
plan.update(constraint_fields)
|
||||||
|
plan["resize_strategy"] = "push-pull-planar-face-keep-relations"
|
||||||
|
plan["edit_strategy_label"] = "保持一级平面关系的拉伸/切除"
|
||||||
|
plan["edit_semantics"] = (
|
||||||
|
"沿当前 Face 法向拉伸/切除,同时要求当前 Face 与共享边一级相邻平面的平行/垂直关系"
|
||||||
|
"在执行前可验证,并在执行后反查。"
|
||||||
|
)
|
||||||
|
if plan["status"] != "blocked" and constraint_fields["face_push_pull_planar_constraint_status"] == "blocked":
|
||||||
|
blockers = str(constraint_fields.get("face_push_pull_planar_constraint_blockers") or "")
|
||||||
|
plan["status"] = "blocked"
|
||||||
|
plan["risk"] = "blocked"
|
||||||
|
plan["message"] = blockers or str(constraint_fields["face_push_pull_planar_constraint_summary"])
|
||||||
|
existing_blockers = str(plan.get("blockers") or "").strip()
|
||||||
|
plan["blockers"] = ";".join(item for item in (existing_blockers, blockers) if item)
|
||||||
|
elif plan["status"] != "blocked":
|
||||||
|
warnings = [
|
||||||
|
item for item in str(plan.get("warnings") or "").split(";") if item
|
||||||
|
]
|
||||||
|
constraint_warning = str(constraint_fields.get("face_push_pull_planar_constraint_warnings") or "").strip()
|
||||||
|
if constraint_warning:
|
||||||
|
warnings.append(constraint_warning)
|
||||||
|
plan["warnings"] = ";".join(warnings)
|
||||||
|
if plan["risk"] == "low":
|
||||||
|
plan["message"] = str(constraint_fields["face_push_pull_planar_constraint_summary"])
|
||||||
|
return plan
|
||||||
|
|
||||||
def _multistep_prismatic_plan_blocker(self, face_id: int, operation_label: str) -> str:
|
def _multistep_prismatic_plan_blocker(self, face_id: int, operation_label: str) -> str:
|
||||||
try:
|
try:
|
||||||
feature = self.feature_info(face_id)
|
feature = self.feature_info(face_id)
|
||||||
@@ -8189,6 +8299,57 @@ class OperationMixin:
|
|||||||
part.shape = shape
|
part.shape = shape
|
||||||
self.refresh_topology()
|
self.refresh_topology()
|
||||||
|
|
||||||
|
def _face_push_pull_planar_relation_result_summary_or_raise(
|
||||||
|
self,
|
||||||
|
plan: dict[str, object],
|
||||||
|
matched_face_id: int,
|
||||||
|
facts: dict[str, object],
|
||||||
|
) -> str:
|
||||||
|
if plan.get("face_push_pull_planar_relation_constraint_requested") is not True:
|
||||||
|
return ""
|
||||||
|
if str(plan.get("face_push_pull_planar_constraint_status") or "") != "ready":
|
||||||
|
raise RuntimeError("Face planar relation check was requested but the original constraint plan is not ready.")
|
||||||
|
|
||||||
|
actual_status = str(facts.get("first_level_planar_relation_status") or "")
|
||||||
|
actual_relation_count = int(facts.get("first_level_planar_relation_count", 0) or 0)
|
||||||
|
actual_parallel = int(facts.get("first_level_planar_relation_parallel_count", 0) or 0)
|
||||||
|
actual_perpendicular = int(facts.get("first_level_planar_relation_perpendicular_count", 0) or 0)
|
||||||
|
actual_angled = int(facts.get("first_level_planar_relation_angled_count", 0) or 0)
|
||||||
|
actual_adjacent = int(facts.get("first_level_fact_adjacent_face_count", 0) or 0)
|
||||||
|
actual_non_planar = _int_values(facts.get("first_level_planar_relation_non_planar_face_ids"))
|
||||||
|
|
||||||
|
expected_relation_count = int(plan.get("face_push_pull_planar_constraint_relation_count", 0) or 0)
|
||||||
|
expected_parallel = int(plan.get("face_push_pull_planar_constraint_parallel_count", 0) or 0)
|
||||||
|
expected_perpendicular = int(plan.get("face_push_pull_planar_constraint_perpendicular_count", 0) or 0)
|
||||||
|
expected_adjacent = int(plan.get("face_push_pull_planar_constraint_adjacent_face_count", 0) or 0)
|
||||||
|
|
||||||
|
failures: list[str] = []
|
||||||
|
if actual_status != "ready":
|
||||||
|
failures.append(f"结果 Face {matched_face_id} 的一级平面关系不是 ready")
|
||||||
|
if actual_adjacent < expected_adjacent:
|
||||||
|
failures.append(f"一级相邻 Face 数减少 {actual_adjacent}/{expected_adjacent}")
|
||||||
|
if actual_relation_count < expected_relation_count:
|
||||||
|
failures.append(f"一级平面关系数减少 {actual_relation_count}/{expected_relation_count}")
|
||||||
|
if actual_parallel < expected_parallel:
|
||||||
|
failures.append(f"平行关系数减少 {actual_parallel}/{expected_parallel}")
|
||||||
|
if actual_perpendicular < expected_perpendicular:
|
||||||
|
failures.append(f"垂直关系数减少 {actual_perpendicular}/{expected_perpendicular}")
|
||||||
|
if actual_angled > 0:
|
||||||
|
failures.append(f"结果一级邻域出现 {actual_angled} 条斜交关系")
|
||||||
|
if actual_non_planar:
|
||||||
|
failures.append(f"结果一级邻域出现非平面 Face {tuple(actual_non_planar)}")
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Face保持关系反查失败,模型已恢复到修改前状态。"
|
||||||
|
+ ";".join(failures)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
" Planar relation check: ok, "
|
||||||
|
f"face={matched_face_id}, relations={actual_relation_count}, "
|
||||||
|
f"parallel={actual_parallel}, perpendicular={actual_perpendicular}, angled={actual_angled}."
|
||||||
|
)
|
||||||
|
|
||||||
def _checked_face_edit_result_summary(self, plan: dict[str, object]) -> str:
|
def _checked_face_edit_result_summary(self, plan: dict[str, object]) -> str:
|
||||||
part_id = _int_or_none(plan.get("part_id"))
|
part_id = _int_or_none(plan.get("part_id"))
|
||||||
part = self.part_by_id(part_id) if part_id is not None else None
|
part = self.part_by_id(part_id) if part_id is not None else None
|
||||||
@@ -8369,12 +8530,18 @@ class OperationMixin:
|
|||||||
f"required_after>={minimum_fact_adjacent}, expected_before={expected_fact_adjacent}, "
|
f"required_after>={minimum_fact_adjacent}, expected_before={expected_fact_adjacent}, "
|
||||||
f"actual_after={fact_adjacent}."
|
f"actual_after={fact_adjacent}."
|
||||||
)
|
)
|
||||||
|
planar_relation_summary = self._face_push_pull_planar_relation_result_summary_or_raise(
|
||||||
|
plan,
|
||||||
|
matched_face_id,
|
||||||
|
facts,
|
||||||
|
)
|
||||||
first_level_summary = (
|
first_level_summary = (
|
||||||
" First-level check: "
|
" First-level check: "
|
||||||
f"boundary_edges={boundary_edges}, boundary_vertices={boundary_vertices}, "
|
f"boundary_edges={boundary_edges}, boundary_vertices={boundary_vertices}, "
|
||||||
f"adjacent_faces={adjacent_faces}, inner_wires={inner_boundary_wires}; "
|
f"adjacent_faces={adjacent_faces}, inner_wires={inner_boundary_wires}; "
|
||||||
f"{area_summary}"
|
f"{area_summary}"
|
||||||
f"fact_subject_faces={fact_subject}, fact_included_faces={fact_included}."
|
f"fact_subject_faces={fact_subject}, fact_included_faces={fact_included}."
|
||||||
|
f"{planar_relation_summary}"
|
||||||
)
|
)
|
||||||
return self._face_edit_result_summary(plan, check) + first_level_summary
|
return self._face_edit_result_summary(plan, check) + first_level_summary
|
||||||
|
|
||||||
@@ -10183,8 +10350,9 @@ class OperationMixin:
|
|||||||
raise RuntimeError(f"{transform_kind} edge-length transform produced an empty shape.")
|
raise RuntimeError(f"{transform_kind} edge-length transform produced an empty shape.")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def push_pull_face(self, face_id: int, distance: float) -> str:
|
def push_pull_face(self, face_id: int, distance: float, *, plan: dict[str, object] | None = None) -> str:
|
||||||
plan = self.push_pull_plan(face_id, distance)
|
if plan is None:
|
||||||
|
plan = self.push_pull_plan(face_id, distance)
|
||||||
if plan["status"] == "blocked":
|
if plan["status"] == "blocked":
|
||||||
raise ValueError(str(plan["message"]))
|
raise ValueError(str(plan["message"]))
|
||||||
face = self.faces[face_id]
|
face = self.faces[face_id]
|
||||||
@@ -10434,6 +10602,16 @@ class OperationMixin:
|
|||||||
f"risk={plan['risk']}. {result_check}"
|
f"risk={plan['risk']}. {result_check}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def push_pull_face_keep_relations(self, face_id: int, distance: float) -> str:
|
||||||
|
plan = self.push_pull_keep_relations_plan(face_id, distance)
|
||||||
|
if plan["status"] == "blocked":
|
||||||
|
raise ValueError(str(plan["message"]))
|
||||||
|
result = self.push_pull_face(face_id, distance, plan=plan)
|
||||||
|
return (
|
||||||
|
f"{result} "
|
||||||
|
f"Keep-relation intent: {plan.get('face_push_pull_planar_constraint_summary', '')}"
|
||||||
|
)
|
||||||
|
|
||||||
def _cylindrical_cap_local_shell_rebuild_shape(
|
def _cylindrical_cap_local_shell_rebuild_shape(
|
||||||
self,
|
self,
|
||||||
face_id: int,
|
face_id: int,
|
||||||
|
|||||||
@@ -91,6 +91,19 @@ INFO_GROUPS: list[tuple[str, list[str]]] = [
|
|||||||
"first_level_planar_relation_observed_modes",
|
"first_level_planar_relation_observed_modes",
|
||||||
"first_level_planar_relation_summary",
|
"first_level_planar_relation_summary",
|
||||||
"first_level_planar_relation_note",
|
"first_level_planar_relation_note",
|
||||||
|
"face_push_pull_planar_relation_constraint_requested",
|
||||||
|
"face_push_pull_planar_relation_constraint_label",
|
||||||
|
"face_push_pull_planar_constraint_status",
|
||||||
|
"face_push_pull_planar_constraint_summary",
|
||||||
|
"face_push_pull_planar_constraint_relation_count",
|
||||||
|
"face_push_pull_planar_constraint_parallel_count",
|
||||||
|
"face_push_pull_planar_constraint_perpendicular_count",
|
||||||
|
"face_push_pull_planar_constraint_angled_count",
|
||||||
|
"face_push_pull_planar_constraint_adjacent_face_count",
|
||||||
|
"face_push_pull_planar_constraint_non_planar_face_ids",
|
||||||
|
"face_push_pull_planar_constraint_observed_modes",
|
||||||
|
"face_push_pull_planar_constraint_blockers",
|
||||||
|
"face_push_pull_planar_constraint_warnings",
|
||||||
"edge_length_planar_relation_constraint_requested",
|
"edge_length_planar_relation_constraint_requested",
|
||||||
"edge_length_planar_relation_constraint_label",
|
"edge_length_planar_relation_constraint_label",
|
||||||
"edge_length_planar_constraint_status",
|
"edge_length_planar_constraint_status",
|
||||||
@@ -566,6 +579,19 @@ INFO_LABELS = {
|
|||||||
"first_level_planar_relation_observed_modes": "已识别平面关系",
|
"first_level_planar_relation_observed_modes": "已识别平面关系",
|
||||||
"first_level_planar_relation_summary": "一级平面关系摘要",
|
"first_level_planar_relation_summary": "一级平面关系摘要",
|
||||||
"first_level_planar_relation_note": "一级平面关系说明",
|
"first_level_planar_relation_note": "一级平面关系说明",
|
||||||
|
"face_push_pull_planar_relation_constraint_requested": "Face保持关系已请求",
|
||||||
|
"face_push_pull_planar_relation_constraint_label": "Face保持关系约束",
|
||||||
|
"face_push_pull_planar_constraint_status": "Face保持关系状态",
|
||||||
|
"face_push_pull_planar_constraint_summary": "Face保持关系摘要",
|
||||||
|
"face_push_pull_planar_constraint_relation_count": "Face保持关系数",
|
||||||
|
"face_push_pull_planar_constraint_parallel_count": "Face保持平行关系数",
|
||||||
|
"face_push_pull_planar_constraint_perpendicular_count": "Face保持垂直关系数",
|
||||||
|
"face_push_pull_planar_constraint_angled_count": "Face保持斜交关系数",
|
||||||
|
"face_push_pull_planar_constraint_adjacent_face_count": "Face保持相邻面数",
|
||||||
|
"face_push_pull_planar_constraint_non_planar_face_ids": "Face保持非平面相邻面",
|
||||||
|
"face_push_pull_planar_constraint_observed_modes": "Face保持已识别关系",
|
||||||
|
"face_push_pull_planar_constraint_blockers": "Face保持关系阻止原因",
|
||||||
|
"face_push_pull_planar_constraint_warnings": "Face保持关系警告",
|
||||||
"edge_length_planar_relation_constraint_requested": "已请求保持一级平面关系",
|
"edge_length_planar_relation_constraint_requested": "已请求保持一级平面关系",
|
||||||
"edge_length_planar_relation_constraint_label": "一级平面关系约束",
|
"edge_length_planar_relation_constraint_label": "一级平面关系约束",
|
||||||
"edge_length_planar_constraint_status": "端面推拉约束状态",
|
"edge_length_planar_constraint_status": "端面推拉约束状态",
|
||||||
|
|||||||
@@ -347,6 +347,12 @@ class WindowActionMixin:
|
|||||||
self.statusBar().showMessage("未选择可修复对象")
|
self.statusBar().showMessage("未选择可修复对象")
|
||||||
|
|
||||||
def push_pull_face(self) -> None:
|
def push_pull_face(self) -> None:
|
||||||
|
self._push_pull_face_action(keep_relations=False)
|
||||||
|
|
||||||
|
def push_pull_face_keep_relations(self) -> None:
|
||||||
|
self._push_pull_face_action(keep_relations=True)
|
||||||
|
|
||||||
|
def _push_pull_face_action(self, *, keep_relations: bool) -> None:
|
||||||
if self.model is None:
|
if self.model is None:
|
||||||
return
|
return
|
||||||
if self._edit_busy("请等待当前编辑完成后再执行新的拉伸/切除。"):
|
if self._edit_busy("请等待当前编辑完成后再执行新的拉伸/切除。"):
|
||||||
@@ -360,10 +366,11 @@ class WindowActionMixin:
|
|||||||
QMessageBox.critical(self, "距离无效", "请输入数字形式的面移动距离。")
|
QMessageBox.critical(self, "距离无效", "请输入数字形式的面移动距离。")
|
||||||
return
|
return
|
||||||
face_id = self.selected_face_id
|
face_id = self.selected_face_id
|
||||||
plan = self._push_pull_plan_for_action(face_id, distance)
|
plan = self._push_pull_plan_for_action(face_id, distance, keep_relations=keep_relations)
|
||||||
|
operation_name = "拉伸/切除平面(保持关系)" if keep_relations else "拉伸/切除平面"
|
||||||
|
|
||||||
if plan["status"] == "blocked":
|
if plan["status"] == "blocked":
|
||||||
self._show_blocked_plan_message("拉伸/切除平面", plan, "拉伸/切除平面已阻止")
|
self._show_blocked_plan_message(operation_name, plan, "拉伸/切除平面已阻止")
|
||||||
return
|
return
|
||||||
if plan["risk"] != "low":
|
if plan["risk"] != "low":
|
||||||
warnings = str(plan.get("warnings", ""))
|
warnings = str(plan.get("warnings", ""))
|
||||||
@@ -383,6 +390,7 @@ class WindowActionMixin:
|
|||||||
f"目标法向位置: {_format_value(plan.get('target_plane_position'))}\n"
|
f"目标法向位置: {_format_value(plan.get('target_plane_position'))}\n"
|
||||||
f"执行策略: {_format_value(plan.get('edit_strategy_label'))}\n"
|
f"执行策略: {_format_value(plan.get('edit_strategy_label'))}\n"
|
||||||
f"执行影响: {_format_value(plan.get('edit_semantics'))}\n"
|
f"执行影响: {_format_value(plan.get('edit_semantics'))}\n"
|
||||||
|
f"关系约束: {_format_value(plan.get('face_push_pull_planar_constraint_summary', ''))}\n"
|
||||||
f"风险: {plan['risk']}\n\n"
|
f"风险: {plan['risk']}\n\n"
|
||||||
f"{warnings_line}"
|
f"{warnings_line}"
|
||||||
f"{plan['message']}\n\n"
|
f"{plan['message']}\n\n"
|
||||||
@@ -395,18 +403,23 @@ class WindowActionMixin:
|
|||||||
if result != QMessageBox.StandardButton.Yes:
|
if result != QMessageBox.StandardButton.Yes:
|
||||||
self.statusBar().showMessage("已取消拉伸/切除平面")
|
self.statusBar().showMessage("已取消拉伸/切除平面")
|
||||||
return
|
return
|
||||||
isolation = self._isolation_for_plan(plan, "push_pull_face", [face_id, distance])
|
if keep_relations:
|
||||||
|
isolation = self._isolation_for_plan(plan, "push_pull_face_keep_relations", [face_id, distance])
|
||||||
|
else:
|
||||||
|
isolation = self._isolation_for_plan(plan, "push_pull_face", [face_id, distance])
|
||||||
if isolation is None:
|
if isolation is None:
|
||||||
self._show_push_pull_preview(face_id, distance, plan=plan)
|
self._show_push_pull_preview(face_id, distance, plan=plan)
|
||||||
else:
|
else:
|
||||||
self.clear_edit_preview(render=False)
|
self.clear_edit_preview(render=False)
|
||||||
|
|
||||||
def action():
|
def action():
|
||||||
|
if keep_relations:
|
||||||
|
return self.model.push_pull_face_keep_relations(face_id, distance)
|
||||||
return self.model.push_pull_face(face_id, distance)
|
return self.model.push_pull_face(face_id, distance)
|
||||||
|
|
||||||
self._run_edit_action(
|
self._run_edit_action(
|
||||||
action,
|
action,
|
||||||
operation_name="拉伸/切除平面",
|
operation_name=operation_name,
|
||||||
target=f"Face {face_id}",
|
target=f"Face {face_id}",
|
||||||
parameters={
|
parameters={
|
||||||
"part_id": plan.get("part_id"),
|
"part_id": plan.get("part_id"),
|
||||||
@@ -449,6 +462,31 @@ class WindowActionMixin:
|
|||||||
"cylindrical_cap_extension_kind": plan.get("cylindrical_cap_extension_kind"),
|
"cylindrical_cap_extension_kind": plan.get("cylindrical_cap_extension_kind"),
|
||||||
"cylindrical_cap_extension_method": plan.get("cylindrical_cap_extension_method"),
|
"cylindrical_cap_extension_method": plan.get("cylindrical_cap_extension_method"),
|
||||||
"cap_extra_adjacent_face_count": plan.get("cap_extra_adjacent_face_count"),
|
"cap_extra_adjacent_face_count": plan.get("cap_extra_adjacent_face_count"),
|
||||||
|
"face_push_pull_planar_relation_constraint_requested": plan.get(
|
||||||
|
"face_push_pull_planar_relation_constraint_requested"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_relation_constraint_label": plan.get(
|
||||||
|
"face_push_pull_planar_relation_constraint_label"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_status": plan.get("face_push_pull_planar_constraint_status"),
|
||||||
|
"face_push_pull_planar_constraint_summary": plan.get("face_push_pull_planar_constraint_summary"),
|
||||||
|
"face_push_pull_planar_constraint_relation_count": plan.get(
|
||||||
|
"face_push_pull_planar_constraint_relation_count"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_parallel_count": plan.get(
|
||||||
|
"face_push_pull_planar_constraint_parallel_count"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_perpendicular_count": plan.get(
|
||||||
|
"face_push_pull_planar_constraint_perpendicular_count"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_angled_count": plan.get(
|
||||||
|
"face_push_pull_planar_constraint_angled_count"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_adjacent_face_count": plan.get(
|
||||||
|
"face_push_pull_planar_constraint_adjacent_face_count"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_constraint_blockers": plan.get("face_push_pull_planar_constraint_blockers"),
|
||||||
|
"face_push_pull_planar_constraint_warnings": plan.get("face_push_pull_planar_constraint_warnings"),
|
||||||
},
|
},
|
||||||
target_kind="face",
|
target_kind="face",
|
||||||
target_id=face_id,
|
target_id=face_id,
|
||||||
@@ -700,16 +738,51 @@ class WindowActionMixin:
|
|||||||
)
|
)
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
def _push_pull_plan_for_action(self, face_id: int, distance: float) -> dict[str, object]:
|
def _push_pull_plan_for_action(
|
||||||
|
self,
|
||||||
|
face_id: int,
|
||||||
|
distance: float,
|
||||||
|
*,
|
||||||
|
keep_relations: bool = False,
|
||||||
|
) -> dict[str, object]:
|
||||||
quick_plan = self._quick_push_pull_plan(face_id, distance)
|
quick_plan = self._quick_push_pull_plan(face_id, distance)
|
||||||
if quick_plan.get("status") == "blocked":
|
if quick_plan.get("status") == "blocked":
|
||||||
return quick_plan
|
return quick_plan
|
||||||
if self.model is None:
|
if self.model is None:
|
||||||
return quick_plan
|
return quick_plan
|
||||||
if self._should_defer_push_pull_model_plan(face_id, distance, quick_plan):
|
if self._should_defer_push_pull_model_plan(face_id, distance, quick_plan):
|
||||||
|
if keep_relations:
|
||||||
|
blocked_plan = dict(quick_plan)
|
||||||
|
message = (
|
||||||
|
"当前 Face 属于复杂大 STEP 的多边界平面区域;“保持关系”需要在界面提交前完成一级平面关系验证。"
|
||||||
|
"为避免长时间卡住,当前版本暂不支持这种组合。请先选择简单平面 Face,"
|
||||||
|
"或改用普通“拉伸/切除”。"
|
||||||
|
)
|
||||||
|
blocked_plan.update(
|
||||||
|
{
|
||||||
|
"status": "blocked",
|
||||||
|
"risk": "blocked",
|
||||||
|
"message": message,
|
||||||
|
"blockers": message,
|
||||||
|
"resize_strategy": "push-pull-planar-face-keep-relations",
|
||||||
|
"edit_strategy_label": "保持一级平面关系的拉伸/切除",
|
||||||
|
"edit_semantics": (
|
||||||
|
"沿当前 Face 法向拉伸/切除,同时要求一级相邻平面关系可验证。"
|
||||||
|
),
|
||||||
|
"face_push_pull_planar_relation_constraint_requested": True,
|
||||||
|
"face_push_pull_planar_relation_constraint_label": "保持一级平面关系",
|
||||||
|
"face_push_pull_planar_constraint_status": "blocked",
|
||||||
|
"face_push_pull_planar_constraint_summary": "Face保持关系:当前复杂多边界平面需要完整验证,暂未开放。",
|
||||||
|
"face_push_pull_planar_constraint_blockers": message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return blocked_plan
|
||||||
return self._deferred_push_pull_model_plan(quick_plan)
|
return self._deferred_push_pull_model_plan(quick_plan)
|
||||||
try:
|
try:
|
||||||
model_plan = self.model.push_pull_plan(face_id, distance)
|
if keep_relations:
|
||||||
|
model_plan = self.model.push_pull_keep_relations_plan(face_id, distance)
|
||||||
|
else:
|
||||||
|
model_plan = self.model.push_pull_plan(face_id, distance)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
blocked_plan = dict(quick_plan)
|
blocked_plan = dict(quick_plan)
|
||||||
blocked_plan.update(
|
blocked_plan.update(
|
||||||
@@ -1453,6 +1526,7 @@ class WindowActionMixin:
|
|||||||
risk = str(plan.get("risk", ""))
|
risk = str(plan.get("risk", ""))
|
||||||
isolated_geometry_operations = {
|
isolated_geometry_operations = {
|
||||||
"push_pull_face",
|
"push_pull_face",
|
||||||
|
"push_pull_face_keep_relations",
|
||||||
"move_face_plane_offset_local",
|
"move_face_plane_offset_local",
|
||||||
"resize_face_area_local",
|
"resize_face_area_local",
|
||||||
"resize_face_area",
|
"resize_face_area",
|
||||||
|
|||||||
@@ -3594,6 +3594,24 @@ class WindowStateMixin:
|
|||||||
"target_transform": "plane_target_position_to_offset",
|
"target_transform": "plane_target_position_to_offset",
|
||||||
"transform_context": {"current_plane_position": current_plane_position},
|
"transform_context": {"current_plane_position": current_plane_position},
|
||||||
},
|
},
|
||||||
|
"keep_relations": {
|
||||||
|
"label": "保持关系",
|
||||||
|
"action": "push_pull_face_keep_relations",
|
||||||
|
"target_attr": "offset_input",
|
||||||
|
"enabled": current_plane_position is not None,
|
||||||
|
"enabled_tip": (
|
||||||
|
"输入偏移的目标位置;程序会沿当前 Face 法向拉伸/切除,"
|
||||||
|
"并要求一级相邻平面的平行/垂直关系可验证。"
|
||||||
|
),
|
||||||
|
"disabled_tip": "当前平面缺少稳定移动方向或基准点,不能保持一级平面关系。",
|
||||||
|
"range_hint": (
|
||||||
|
"这是带约束守门的拉伸/切除:只支持直接相邻平面关系清楚、且关系为平行/垂直的一级邻域;"
|
||||||
|
"遇到非平面相邻面、斜交关系或复杂多边界慢计划会直接阻止。"
|
||||||
|
f"{face_offset_hard_limit}"
|
||||||
|
),
|
||||||
|
"target_transform": "plane_target_position_to_offset",
|
||||||
|
"transform_context": {"current_plane_position": current_plane_position},
|
||||||
|
},
|
||||||
"local": {
|
"local": {
|
||||||
"label": "局部重建",
|
"label": "局部重建",
|
||||||
"action": "move_selected_face_plane_position_local",
|
"action": "move_selected_face_plane_position_local",
|
||||||
|
|||||||
Reference in New Issue
Block a user