fix(engine): 放行固定宿主面与终止面的 pattern 变换
允许 hole_wizard 的 face selector 宿主面及 up_to_surface 的 face reference 终止面参与 pattern 变换,同时保留 mirror 等逐实例拓扑依赖的阻塞,并补充回归测试
This commit is contained in:
@@ -65,28 +65,58 @@ def _has_explicit_host_frame(params: dict[str, Any]) -> bool:
|
||||
return isinstance(frame, dict) and all(frame.get(key) is not None for key in ("origin_mm", "x_dir", "normal"))
|
||||
|
||||
|
||||
def _is_host_face_source(source: FeaturePlanNode) -> bool:
|
||||
"""Whether a ``face`` selector on the source is a fixed host face.
|
||||
|
||||
A patterned hole keeps its host face: the instance positions travel with
|
||||
the pattern (``_translated_node`` shifts ``positions``) while the face
|
||||
itself is resolved unchanged, so the face selector can stay untouched
|
||||
(issue #6). A face selector on anything else (for example a fillet
|
||||
selecting a face) would need per-instance edge geometry and must stay
|
||||
blocked.
|
||||
"""
|
||||
return source.atomic_id in _HOLE_ATOMICS or (
|
||||
isinstance(source.params.get("host_face"), dict)
|
||||
and source.params.get("host_face", {}).get("kind") == "face"
|
||||
)
|
||||
|
||||
|
||||
def pattern_transform_blocker(source: FeaturePlanNode) -> str | None:
|
||||
"""Return the selector dependency that cannot be transformed exactly.
|
||||
|
||||
Selectors fully captured by explicit coordinate data (a revolve axis with
|
||||
origin/direction, or a host face with a complete world frame) are
|
||||
transformed together with each patterned instance and are not blockers.
|
||||
Any remaining topology selector and selector-dependent extent stays
|
||||
blocked until its geometry transform contract is implemented.
|
||||
A patterned hole's host face and an up_to_surface extrusion's target face
|
||||
are *fixed* body faces rather than instance geometry: positions and
|
||||
profiles travel with the instance while the face itself is resolved
|
||||
unchanged (issue #6). Selectors that must follow the instance but cannot
|
||||
be translated (edges, vertices, …) stay blocked until their geometry
|
||||
transform contract is implemented.
|
||||
"""
|
||||
for selector in source.selectors or ():
|
||||
if selector.get("kind") == "axis" and _has_explicit_axis(source.params.get("axis")):
|
||||
# The axis is coordinate data; _translated_node shifts its origin.
|
||||
continue
|
||||
if selector.get("kind") == "face" and _has_explicit_host_frame(source.params):
|
||||
# The host face carries a complete world frame; positions stay local.
|
||||
if selector.get("kind") == "face" and _is_host_face_source(source):
|
||||
# 孔宿主面:主体上的固定面,不随实例平移;实例位置由 positions
|
||||
# 平移决定(_translated_node),selector 原样保留即可正确 resolve。
|
||||
continue
|
||||
return "feature selector"
|
||||
host = source.params.get("host_face")
|
||||
if host is not None and not _has_explicit_host_frame(source.params):
|
||||
return "host face selector"
|
||||
# 无 frame 的孔:宿主面以 face selector 形式给出(host_face 自身或
|
||||
# selectors 列表)→ 上面的 face 分支已放行;其它形态(无 frame 也
|
||||
# 非 face selector)仍阻塞。
|
||||
if not (isinstance(host, dict) and host.get("kind") == "face"):
|
||||
return "host face selector"
|
||||
end_condition = source.params.get("end_condition") or {}
|
||||
if isinstance(end_condition, dict) and isinstance(end_condition.get("reference"), dict):
|
||||
# up_to_surface / offset_from_surface 的终止面是主体上的固定面:
|
||||
# 不随实例平移,reference 原样保留即可正确 resolve(#5 裁剪已支持
|
||||
# 非均匀相交)。顶点/主体目标无法构造"固定终止面",仍显式阻塞。
|
||||
if end_condition["reference"].get("kind") == "face":
|
||||
return None
|
||||
return "extent target selector"
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user