diff --git a/Tools/ModelTranslator/qr_bridge.py b/Tools/ModelTranslator/qr_bridge.py index f20c173a..48503d7f 100644 --- a/Tools/ModelTranslator/qr_bridge.py +++ b/Tools/ModelTranslator/qr_bridge.py @@ -4,6 +4,9 @@ import glob import os QR_TIMEOUT = 120.0 # 引擎轮询超时(秒) +QR_INPUT_CAP = 50000 # quad 后端 QR 前预 collapse 的中等密度目标面数 +QR_HOLE_MAX_SIDES = 6 # 补洞上限边数:只补 <=6 边的薄部位小洞 +QR_CATASTROPHIC_TOL = 12 # 软水密门容忍:补洞后仍多出超此数的开边/非流形才判灾难性 def find_qr_engine(): @@ -65,8 +68,14 @@ def parse_progress(text): def topology_acceptable(src_boundary, src_nonmanifold, - out_boundary, out_nonmanifold): - """QR 输出拓扑是否可接受:不比输入引入更多开边(孔洞)/非流形边。 - QR 引擎在激进减面下会非确定性地打出孔洞——封闭输入(0/0)时要求输出也 0/0, - 输入本身开放(壳)时按其基线放行;否则判破损,交上层回退 collapse。""" - return out_boundary <= src_boundary and out_nonmanifold <= src_nonmanifold + out_boundary, out_nonmanifold, tol=0): + """QR 输出拓扑是否可接受:不比输入多出超过 tol 的开边(孔洞)/非流形边。 + tol=0(默认)为零容忍严格门;quad 后端补小洞后用 tol=QR_CATASTROPHIC_TOL 只挡灾难性破损。""" + return (out_boundary <= src_boundary + tol + and out_nonmanifold <= src_nonmanifold + tol) + + +def precollapse_target(face_count, cap=QR_INPUT_CAP): + """QR 前预 collapse 目标:面数 > cap 返回 cap(先减到中等密度),否则 None(跳过)。 + 高密度网格直接 QR 必破洞,先 collapse 到中等密度给 QR 一个稳定输入。""" + return cap if face_count > cap else None diff --git a/Tools/ModelTranslator/tests/test_qr_bridge.py b/Tools/ModelTranslator/tests/test_qr_bridge.py index 97155762..71c7d7fc 100644 --- a/Tools/ModelTranslator/tests/test_qr_bridge.py +++ b/Tools/ModelTranslator/tests/test_qr_bridge.py @@ -141,5 +141,38 @@ class TestTopologyAcceptable(unittest.TestCase): self.assertTrue(qb.topology_acceptable(10, 2, 8, 1)) +class TestTopologyTolerance(unittest.TestCase): + def test_tol_allows_small_increase(self): + # 封闭输入 + QR 多出 3 开边,容忍 12 内 -> 接受 + self.assertTrue(qb.topology_acceptable(0, 0, 3, 0, tol=12)) + + def test_tol_rejects_large_increase(self): + self.assertFalse(qb.topology_acceptable(0, 0, 13, 0, tol=12)) + + def test_tol_applies_to_nonmanifold(self): + self.assertTrue(qb.topology_acceptable(0, 0, 0, 12, tol=12)) + self.assertFalse(qb.topology_acceptable(0, 0, 0, 13, tol=12)) + + def test_default_tol_zero_strict(self): + # 默认 tol=0:现有严格语义不变 + self.assertFalse(qb.topology_acceptable(0, 0, 1, 0)) + self.assertTrue(qb.topology_acceptable(0, 0, 0, 0)) + + +class TestPrecollapseTarget(unittest.TestCase): + def test_above_cap_returns_cap(self): + self.assertEqual(qb.precollapse_target(1000000, 50000), 50000) + + def test_below_cap_returns_none(self): + self.assertIsNone(qb.precollapse_target(40000, 50000)) + + def test_at_cap_returns_none(self): + self.assertIsNone(qb.precollapse_target(50000, 50000)) + + def test_default_cap(self): + self.assertEqual(qb.precollapse_target(60000), qb.QR_INPUT_CAP) + self.assertIsNone(qb.precollapse_target(40000)) + + if __name__ == "__main__": unittest.main()