- modal_stack.gd 新增 has_open_excluding(skip):排除式开态查询(地图屏系登记册模态成员=常态 屏态,其上角标必须可点;只读查询零副作用,登记序/开合记账语义零改动) - board_zone_view.gd 战斗屏 HUD 右下角+level_map_screen.gd 地图屏顶栏 GP 徽右——各一枚「暂停」 角标(debug 角标同形制:UiKit.button 紧凑豁免宽度基准+FS_HINT 字号降档;可见性/置灰归组合根, 信号连接归组合根 ADR-0001;非对局期显式不可见防菜单误现——P1 丁) - main.gd 接线:两角标 pressed→_on_pause_badge_pressed(守卫兜底处理器:他层开着即拒开—— 常态防线=置灰,探针 emit 绕过 disabled 时由此兜住;地图屏开态经 has_open_excluding 不计自身) +_sync_pause_entry 单一同步点(可见性=_level_active∧屏态;置灰=has_open)挂模态开合单口与 屏级互斥口/进局/离局/续玩五路径 - ui_struct_probe:pause 结构断言+只读性契约(游戏语义信号清单巡检——pressed/toggled/text/ value/item/gui_input/focus/closed 十二名,白名单外零连接,合法连接恰 2=关闭钮+closed 外发) +零动效契约(子树零 AnimationPlayer+源码零 create_tween——R5/P4 甲)【首跑 L1 修正:全集 巡检把 Godot 容器布局生命线连接(child_order_changed/item_rect_changed 等引擎自动脏标记) 误判非法——收敛为信号名清单,任务书 v1.1「巡检信号名清单落码列全」本意】 - ui_input_probe case⑯:暂停层输入锁语义(角标真点击 press+release 开层→锁期挖掘零穿透→ 「继续」关层→同格挖掘重新到达;十七断言 PASS)【首跑 L1 修正:域恰 2 枚时 flag_t 带旗+dig_t 揭开后零无旗格可取——case 挪全 case 之尾+前沿移位扩大可挖域兜底(板面扰动零波及)】 - ui_click_probe:战斗屏角标开合/守卫断言(图例开着=置灰+emit 兜底拒开)+地图屏角标一次性 case(选路前开/关,地图屏选路态零扰动);ui_contrast_probe 增钥石徽深墨/烛光黄对(12 对全绿) - smoke_all._pause_overlay_projection:暂停层投影==MetaProgression 访问器逐项(余额徽/锻造三行/ 工坊六行解锁态/天赋树概览——隔离档 smoke_meta 复用,+5 check) - 任务书回填 T0 基线注记(冒烟 783/0·run_all 17 套·交集核对零命中,HEAD=44328b1 时点) - T2 闸读数:冒烟 788/0(基线 783/0,+5 本批)· ui_input 十七断言 PASS · ui_struct 全绿 · ui_contrast 12 对 PASS · ui_click 问题=0 · ui_layout_probe 退出码 0;纯呈现零数值=零 MC
419 lines
17 KiB
GDScript
419 lines
17 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# UI 结构探针(ui-struct-probe mini-loop 2026-10-05——计划书 .zcode/plans/ui-struct-probe-miniloop-plan-2026-10-05.md T0/T1)
|
||
# 全 UI 屏结构断言一次跑完:逐屏实例化(菜单簇真驱动 + 冒险簇真流推进 + 兑付屏真实 open 面合成喂入),
|
||
# 断言族:
|
||
# ①overlap 同屏内可见未禁用按钮两两 global_rect 不相交(互压=红;层间模态盖底层系设计内——各屏采样以屏根为界天然排除)
|
||
# ②equal_width 同一直接父容器内 ≥2 可见按钮宽度差 >1px=红(容器纪律:组内等宽;钮高族
|
||
# equal_height 2026-10-06 增——同容器高度差 >1px=红,纪律=ui_kit BUTTON_MIN_H)
|
||
# ③offscreen 可见件 global_rect 完全落在视口外=红
|
||
# ④zero_size 可见件宽或高 ≤0=红
|
||
# ⑤start 专项 标题屏左列(start_box)与右列(start_right)矩形不相交(M2 双列契约——叠放缺陷回归闸)
|
||
# 豁免:WL 常量表按「屏名/规则名/件名」登记合法例外(登记须注理由于行尾注释)。
|
||
# 运行:Godot_console.exe --headless --path . res://smoke/ui_struct_probe.tscn
|
||
extends Node
|
||
|
||
const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd")
|
||
|
||
# 豁免清单(空 = 零豁免起步;红灯逐条核对后合法者入表并注理由)
|
||
const WL := {}
|
||
|
||
var problems: Array = []
|
||
|
||
|
||
func _ready() -> void:
|
||
await get_tree().process_frame # autoload 就绪
|
||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||
add_child(main)
|
||
await get_tree().process_frame
|
||
|
||
# ---- 菜单簇(开始层可见态;逐屏开→采样→关,模态恢复走组合根真实路径)----
|
||
ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_menu", true, false, true)
|
||
_settle()
|
||
await _audit("start", [main._start_overlay])
|
||
_start_columns_contract(main)
|
||
|
||
main._open_settings()
|
||
_settle()
|
||
await _audit("settings", [main.settings_overlay])
|
||
main._on_settings_closed()
|
||
_settle()
|
||
|
||
var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix")
|
||
if campbtn != null:
|
||
campbtn.pressed.emit()
|
||
_settle()
|
||
await _audit("camp", [main.camp_overlay])
|
||
main._on_camp_closed()
|
||
_settle()
|
||
else:
|
||
problems.append("[camp] 营地入口按钮缺失(驱动中断)")
|
||
|
||
main.confirm_dialog.open("结构探针采样", "确认")
|
||
_settle()
|
||
await _audit("confirm", [main.confirm_dialog])
|
||
main.confirm_dialog.close()
|
||
_settle()
|
||
|
||
main._show_debug_overlay()
|
||
_settle()
|
||
await _audit("debug", [main.debug_overlay])
|
||
main._close_debug_overlay()
|
||
_settle()
|
||
|
||
# ---- 冒险簇(真流推进:开冒险→地图选路态→选战斗节点→战斗 HUD)----
|
||
ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_adv", true, false, true)
|
||
main._start_adventure_from_menu()
|
||
_settle(4)
|
||
await _audit("level_map", [main.level_screen])
|
||
|
||
var battle_id := _pick_battle_node(main)
|
||
if battle_id >= 0:
|
||
main.level_screen.route_chosen.emit(battle_id)
|
||
_settle(6)
|
||
_audit("battle_hud", [main.board_zone, main.info_panel, main.stage])
|
||
_standeefit_contract(main)
|
||
# ---- 暂停层(迭代 17 卡③):组合根真实路径开层→结构断言→关闭。附只读性契约
|
||
#(R4——子树信号连接白名单巡检)与零动效契约(R5/P4 甲——零 AnimationPlayer/零 tween)。
|
||
# 巡检信号名清单=子树全控件的 get_signal_list() 全集(不放行任何未列名信号——新连接
|
||
# 一律红,须先入册审查);白名单={视图自身 _emit_closed, 组合根 _on_pause_closed}。
|
||
main._open_pause_layer()
|
||
_settle()
|
||
await _audit("pause", [main.pause_overlay])
|
||
_pause_readonly_contract(main)
|
||
_pause_zero_motion_contract(main)
|
||
main._on_pause_closed()
|
||
_settle()
|
||
if main.pause_overlay.visible:
|
||
problems.append("[pause] 关闭后层未隐藏(模态栈单口失守)")
|
||
else:
|
||
problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.level_screen._cand_ids))
|
||
|
||
# ---- 兑付屏(真实 open 面 + 合成最小数据——结构断言只看矩形/宽度/可见性)----
|
||
main._end_overlay.open_victory("结构探针·胜利演算", "返回", Rect2(200, 200, 400, 300), "GP 0")
|
||
_settle()
|
||
await _audit("battle_end", [main._end_overlay])
|
||
main._end_overlay.close()
|
||
_settle()
|
||
|
||
main.curio_overlay.open_pick("结构探针·三选一", [
|
||
{"id": "probe_heal", "text": "回血\n恢复 3 点"},
|
||
{"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"},
|
||
{"id": "probe_pierce", "text": "穿甲\n无视护甲"},
|
||
])
|
||
_settle()
|
||
await _audit("curio_pick", [main.curio_overlay])
|
||
main.curio_overlay.visible = false
|
||
_settle()
|
||
|
||
main.shop_overlay.open_shop([
|
||
{"product_id": "probe_p1", "display_name": "探针商品甲", "attr": "atk", "flat_value": 1, "price": 10, "purchased": 0},
|
||
{"product_id": "probe_p2", "display_name": "探针商品乙", "attr": "hp", "flat_value": 2, "price": 20, "purchased": 0},
|
||
], 100)
|
||
_settle()
|
||
await _audit("shop", [main.shop_overlay])
|
||
|
||
# ---- ⑦resume 专项:续玩停点恢复(2026-10-06 用户实机 main.gd:717 报错回归闸)----
|
||
# 三选一停点续玩分支在 level_started 之前同步 curio_choice_presented.emit(adventure_runner
|
||
# resume 提前 return)——GP 总结 lambda 求值时 runner 尚未赋值,修复前 717 炸 null。
|
||
ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_resume", true)
|
||
main.save_manager.set_breakpoint(_resume_bp_fixture())
|
||
main._refresh_resume_button()
|
||
main.runner = null # 全新启动等价态(真机=菜单直进续玩,无前置冒险——runner 从未赋值;探针此前推流会残留旧 runner)
|
||
main._resume_from_menu()
|
||
_settle(8)
|
||
if not main._end_overlay.visible:
|
||
problems.append("[resume] 续玩恢复后胜利总结面未开(三选一停点分支中断)")
|
||
else:
|
||
var found := {"ok": false}
|
||
_find_label_text(main._end_overlay, "合计 24", found)
|
||
if not found.ok:
|
||
problems.append("[resume] 胜利总结面 GP 行未见快照合计「合计 24」(runner 缺位守卫缺位——717 回归)")
|
||
var s: String = main._gp_summary_text({"gp_dropped": 0})
|
||
if not s.contains("合计 24"):
|
||
problems.append("[resume] _gp_summary_text runner=null 守卫未取快照 gp(%s)" % s)
|
||
# 恢复现场(resume 改写了 main 的 adventure/save 态——后续无屏依赖,仅为整洁)
|
||
main._level_active = false
|
||
|
||
# ---- 判定 ----
|
||
if problems.is_empty():
|
||
print("[ui_struct_probe] PASS — 全屏结构断言零红")
|
||
get_tree().quit(0)
|
||
else:
|
||
for p in problems:
|
||
print("[ui_struct_probe] FAIL — %s" % p)
|
||
get_tree().quit(1)
|
||
|
||
|
||
# ⑦resume 夹具:最小合法断点快照——bonus_pending 非空=三选一停点分支(合计 gp=24 供守卫兜底断言)
|
||
func _resume_bp_fixture() -> Dictionary:
|
||
return {
|
||
"master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 0,
|
||
"level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [],
|
||
"bonus_aux_carry": [], "keystone_drops": [], "bonus_taken": {},
|
||
"bonus_pending": {"candidates": [{"kind": "heal", "tier": "common"}], "payload": {"gp_dropped": 0}},
|
||
}
|
||
|
||
|
||
# 递归找含目标子串的 Label(⑦resume 断言读数面)
|
||
func _find_label_text(node: Node, needle: String, found: Dictionary) -> void:
|
||
for ch in node.get_children():
|
||
if ch is Label and String(ch.text).contains(needle):
|
||
found.ok = true
|
||
return
|
||
_find_label_text(ch, needle, found)
|
||
|
||
|
||
func _pick_battle_node(main: Node) -> int:
|
||
# 入口/兑付停点走 level_screen.open_choices 直传(不写 _pending_candidates)——
|
||
# 候选从视图态取:_cand_ids × runner.nodes() 的 type(编号解析=node_id_of 单一出口)
|
||
var ls: Node = main.level_screen
|
||
if ls._runner == null:
|
||
return -1
|
||
for nd in ls._runner.nodes():
|
||
if String(nd.get("type", "")) in ["battle", "boss"]:
|
||
var nid: int = ls._runner.node_id_of(nd)
|
||
if nid in ls._cand_ids:
|
||
return nid
|
||
return -1
|
||
|
||
|
||
# ⑧pause 只读性契约(迭代 17 卡③ R4):暂停层子树全控件 × 游戏语义信号连接巡检——清单外零连接。
|
||
# 巡检信号名清单(任务书 v1.1「巡检信号名清单落码时列全进探针」)= 玩家交互可携带写意图的通道
|
||
# 全集(按钮按下/拨杆/文本/滑值/选项/键盘焦点)+ 本层外发信号 closed;引擎内部布局生命线信号
|
||
# 不计(child_order_changed/item_rect_changed/visibility_changed/size_flags_changed/
|
||
# minimum/maximum_size_changed 等系 Godot 容器系统对每控件自动建立的脏标记连接,与写通路无关
|
||
#——首跑实测踩中:全集巡检把布局生命线误判为非法连接)。清单外任何连接(尤其指向
|
||
# 购买/解锁/写入路径的)一律红,须先入册审查再改清单。
|
||
const _PAUSE_READONLY_SIGNALS: Array[String] = [
|
||
"pressed", "button_down", "button_up", "toggled",
|
||
"text_changed", "text_submitted", "value_changed", "item_selected",
|
||
"gui_input", "focus_entered", "focus_exited", "closed",
|
||
]
|
||
|
||
|
||
func _pause_readonly_contract(main: Node) -> void:
|
||
var overlay: Control = main.pause_overlay
|
||
var stack: Array = [overlay]
|
||
var conn_count := 0
|
||
while not stack.is_empty():
|
||
var n: Node = stack.pop_back()
|
||
for sig_name in _PAUSE_READONLY_SIGNALS:
|
||
for conn in n.get_signal_connection_list(sig_name):
|
||
conn_count += 1
|
||
var cb: Callable = conn["callable"]
|
||
var host: Object = cb.get_object()
|
||
var m := String(cb.get_method())
|
||
if host == overlay and m == "_emit_closed":
|
||
continue
|
||
if host == main and m == "_on_pause_closed":
|
||
continue
|
||
problems.append("[pause][readonly] 非法信号连接 %s.%s → %s.%s(零写通路巡检——白名单=视图 _emit_closed/组合根 _on_pause_closed)"
|
||
% [n.name, sig_name, host, m])
|
||
for ch in n.get_children():
|
||
stack.append(ch)
|
||
if conn_count != 2:
|
||
problems.append("[pause][readonly] 信号连接数=%d 应=2(关闭钮 pressed+closed 外发——多连接=写通路嫌疑)" % conn_count)
|
||
|
||
|
||
# ⑨pause 零动效契约(迭代 17 卡③ R5/P4 甲——零动效直开直关路径存在断言):
|
||
# 子树零 AnimationPlayer + 视图源码零 tween/动效包装调用(源码 token 扫描=ui_contrast 静态段同手法)
|
||
func _pause_zero_motion_contract(main: Node) -> void:
|
||
var overlay: Control = main.pause_overlay
|
||
var stack: Array = [overlay]
|
||
while not stack.is_empty():
|
||
var n: Node = stack.pop_back()
|
||
if n is AnimationPlayer:
|
||
problems.append("[pause][motion] 子树含 AnimationPlayer(R5 零动效破)")
|
||
for ch in n.get_children():
|
||
stack.append(ch)
|
||
var src := FileAccess.get_file_as_string("res://presentation/views/pause_overlay.gd")
|
||
for token in ["create_tween", "UiKit.motion"]:
|
||
if src.contains(token):
|
||
problems.append("[pause][motion] 视图源码含「%s」(R5 零动效直开直关破——P4 甲)" % token)
|
||
|
||
|
||
# ⑥standee 专项:B 区站桩内容矩形 ⊆ 252px 舞台矩形(内缩 8px)且不被裁切
|
||
# (UI 设计对齐批 N2 2026-10-06——名行基线离盒底 1px 同型高危写法返修回归闸;
|
||
# 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘)
|
||
func _standeefit_contract(main: Node) -> void:
|
||
var st: Node = main.stage
|
||
if st == null or st.hero_standee == null:
|
||
problems.append("[standee] 舞台/主角站桩缺失(断言中断)")
|
||
return
|
||
var stage_gr: Rect2 = st.get_global_rect()
|
||
var bodies: Array = [st.hero_standee]
|
||
for c in st._cards:
|
||
bodies.append(c)
|
||
for body in bodies:
|
||
if body == null or not is_instance_valid(body):
|
||
continue
|
||
var content := Rect2(st.world_to_shell(body.position), body.content_rect().size)
|
||
if not stage_gr.encloses(content):
|
||
problems.append("[standee] %s 内容矩形 %s 越出舞台矩形 %s(N2 站桩出框回归)"
|
||
% [body.name if body.name != "" else "standee", content, stage_gr])
|
||
elif content.end.y > stage_gr.end.y - 8.0:
|
||
problems.append("[standee] %s 内容底缘 %s 距舞台底 <8px(N2 纵向余量回归,舞台底=%s)"
|
||
% [body.name if body.name != "" else "standee", content.end.y, stage_gr.end.y])
|
||
|
||
|
||
func _settle(frames := 4) -> void:
|
||
for i in range(frames):
|
||
await get_tree().process_frame
|
||
await get_tree().process_frame # 布局排序系帧尾 call_deferred——末帧后再保一帧
|
||
|
||
|
||
# ---- 采样与断言 ----
|
||
|
||
func _visible_children(root: Control) -> Array:
|
||
var out: Array = []
|
||
var stack: Array = [root]
|
||
while not stack.is_empty():
|
||
var c: Control = stack.pop_back()
|
||
if c == null:
|
||
continue
|
||
if c.is_visible_in_tree():
|
||
out.append(c)
|
||
for ch in c.get_children():
|
||
if ch is Control:
|
||
stack.append(ch)
|
||
return out
|
||
|
||
|
||
func _buttons(root: Control) -> Array:
|
||
var out: Array = []
|
||
for c in _visible_children(root):
|
||
if c is Button and not c.disabled:
|
||
out.append(c)
|
||
return out
|
||
|
||
|
||
func _wl(screen: String, rule: String, ctl: Control) -> bool:
|
||
var s: Dictionary = WL.get(screen, {})
|
||
if s.is_empty():
|
||
return false
|
||
var names: Array = s.get(rule, [])
|
||
if names.is_empty():
|
||
return false
|
||
return String(ctl.name) in names
|
||
|
||
|
||
func _force_layout(root: Control) -> void:
|
||
# modal 屏首次 visible 后容器排序不自动传播(size 已算而 position 停 0——4.7 行为)——
|
||
# 采样前对子树全部 Container 强制 queue_sort
|
||
var stack: Array = [root]
|
||
while not stack.is_empty():
|
||
var c: Control = stack.pop_back()
|
||
if c is Container:
|
||
c.queue_sort()
|
||
for ch in c.get_children():
|
||
if ch is Control:
|
||
stack.append(ch)
|
||
|
||
|
||
func _in_scroll(c: Control) -> bool:
|
||
var p := c.get_parent()
|
||
while p is Control:
|
||
if p is ScrollContainer:
|
||
return true
|
||
p = p.get_parent()
|
||
return false
|
||
|
||
|
||
func _audit(screen: String, roots: Array) -> void:
|
||
for r in roots:
|
||
if r != null and is_instance_valid(r):
|
||
_force_layout(r)
|
||
await _settle(1)
|
||
if OS.get_environment("STRUCT_PROBE_DEBUG") == "1":
|
||
for r in roots:
|
||
if r != null and is_instance_valid(r):
|
||
_dump(r, 0)
|
||
var all_ctls: Array = []
|
||
var all_buttons: Array = []
|
||
for r in roots:
|
||
if r == null or not is_instance_valid(r):
|
||
problems.append("[%s] 采样根缺失" % screen)
|
||
return
|
||
all_ctls += _visible_children(r)
|
||
all_buttons += _buttons(r)
|
||
var vp := get_viewport().get_visible_rect()
|
||
# ①overlap:操作钮两两不相交
|
||
for i in range(all_buttons.size()):
|
||
for j in range(i + 1, all_buttons.size()):
|
||
var a: Control = all_buttons[i]
|
||
var b: Control = all_buttons[j]
|
||
if _wl(screen, "overlap", a) or _wl(screen, "overlap", b):
|
||
continue
|
||
if a.get_global_rect().intersects(b.get_global_rect()):
|
||
problems.append("[%s][overlap] %s 与 %s 矩形相交(%s ∩ %s)" % [
|
||
screen, a.name, b.name, a.get_global_rect(), b.get_global_rect()])
|
||
# ②equal_width + equal_height:同一直接父容器内 ≥2 可见按钮宽度/高度差 >1px
|
||
#(钮高族 2026-10-06 用户令「对齐好、不要细」——主钮 26px 对常规 38px 不齐回归闸;纪律=ui_kit BUTTON_MIN_H)
|
||
var groups := {}
|
||
for b in all_buttons:
|
||
var k: Node = b.get_parent()
|
||
if not groups.has(k):
|
||
groups[k] = []
|
||
groups[k].append(b)
|
||
for k in groups:
|
||
var g: Array = groups[k]
|
||
if g.size() < 2:
|
||
continue
|
||
var w0: float = (g[0] as Control).size.x
|
||
var h0: float = (g[0] as Control).size.y
|
||
var diff := false
|
||
var diff_h := false
|
||
for b in g:
|
||
if _wl(screen, "equal_width", b):
|
||
diff = false
|
||
diff_h = false
|
||
break
|
||
if absf((b as Control).size.x - w0) > 1.0:
|
||
diff = true
|
||
if absf((b as Control).size.y - h0) > 1.0:
|
||
diff_h = true
|
||
if diff:
|
||
var names: Array = []
|
||
var widths: Array = []
|
||
for x in g:
|
||
names.append(String((x as Control).name))
|
||
widths.append((x as Control).size.x)
|
||
problems.append("[%s][equal_width] 同容器按钮宽度不一(%s——%s)" % [screen, str(names), str(widths)])
|
||
if diff_h:
|
||
var names_h: Array = []
|
||
var heights: Array = []
|
||
for x in g:
|
||
names_h.append(String((x as Control).name))
|
||
heights.append((x as Control).size.y)
|
||
problems.append("[%s][equal_height] 同容器按钮高度不一(%s——%s)" % [screen, str(names_h), str(heights)])
|
||
# ③offscreen + ④zero_size(ScrollContainer 内容坐标系超出=可滚动设计内——豁免)
|
||
for c in all_ctls:
|
||
if _in_scroll(c):
|
||
continue
|
||
var gr: Rect2 = c.get_global_rect()
|
||
if not _wl(screen, "offscreen", c) and not gr.intersects(vp):
|
||
problems.append("[%s][offscreen] %s 完全出视口(%s)" % [screen, c.name, gr])
|
||
if not _wl(screen, "zero_size", c) and (c.size.x <= 0.0 or c.size.y <= 0.0):
|
||
problems.append("[%s][zero_size] %s 零尺寸(%s)" % [screen, c.name, c.size])
|
||
|
||
|
||
func _dump(c: Control, depth: int) -> void:
|
||
var ind := " ".repeat(depth)
|
||
print("[dump] %s%s(%s) pos=%s gpos=%s size=%s vis=%s" % [ind, c.name, c.get_class(),
|
||
c.position, c.get_global_position(), c.size, c.is_visible_in_tree()])
|
||
for ch in c.get_children():
|
||
if ch is Control:
|
||
_dump(ch, depth + 1)
|
||
|
||
|
||
# ⑤start 专项:左列/右列矩形不相交(M2 双列契约——装配件命名 start_box/start_right 为探针锚点)
|
||
func _start_columns_contract(main: Node) -> void:
|
||
var box: Control = main._start_overlay.find_child("start_box", true, false)
|
||
var right: Control = main._start_overlay.find_child("start_right", true, false)
|
||
if box == null or right == null:
|
||
problems.append("[start][columns] 左列/右列节点缺失(start_box/start_right——装配命名失守)")
|
||
return
|
||
if box.get_global_rect().intersects(right.get_global_rect()):
|
||
problems.append("[start][columns] 左列 %s 与右列 %s 相交(双列叠放回归——M2 缺陷闸)" % [
|
||
box.get_global_rect(), right.get_global_rect()])
|