// 分割树切片顺序枚举实证: node scripts/analyze-split-order.mjs // 候选规则: A=遍历序c1→c2(现实现) B=遍历序c2→c1 C=min侧先(竖左/横上) D=max侧先 // 评分: 扇 polyId.idx 与玻璃 fls pid.idx 落在最终区域数内的比例 + 704/31594 样本特检 import fs from 'fs' import path from 'path' const DIR = 'D:/Code/huamenchuang/data/windoor-drawings' const bboxOf = pts => ({ x0: Math.min(...pts.map(p => p.x)), y0: Math.min(...pts.map(p => p.y)), x1: Math.max(...pts.map(p => p.x)), y1: Math.max(...pts.map(p => p.y)), }) const spAxis = sp => { const n = sp.line?.norm ?? { x: 1, y: 0 } return Math.abs(n.x) >= Math.abs(n.y) ? 'v' : 'h' } const spPos = sp => (spAxis(sp) === 'v' ? sp.line.pt.x : sp.line.pt.y) function splitPoly(region, axis, pos, rule) { const pts = region.map(s => s.ps) const n = pts.length const get = p => (axis === 'v' ? p.x : p.y) const crossings = [] for (let i = 0; i < n; i++) { const a = pts[i], b = pts[(i + 1) % n] const da = get(a) - pos, db = get(b) - pos if ((da < 0 && db > 0) || (da > 0 && db < 0)) { const t = da / (da - db) crossings.push({ i, p: axis === 'v' ? { x: pos, y: a.y + (b.y - a.y) * t } : { x: a.x + (b.x - a.x) * t, y: pos } }) } } if (crossings.length !== 2) return null const [c1, c2] = crossings if (c1.i === c2.i) return null const collect = (from, to) => { const ps = [from.p] for (let k = (from.i + 1) % n; ; k = (k + 1) % n) { ps.push(pts[k]) if (k === to.i) break } ps.push(to.p) return ps.filter((p, i) => i === 0 || Math.abs(p.x - ps[i - 1].x) > 0.01 || Math.abs(p.y - ps[i - 1].y) > 0.01) } const toSegs = ps => ps.map((p, i) => ({ ps: p, pe: ps[(i + 1) % ps.length], name: 'segment' })) let A = collect(c1, c2), B = collect(c2, c1) if (A.length < 3 || B.length < 3) return null const pa = toSegs(A), pb = toSegs(B) if (rule === 'A') return [pa, pb] if (rule === 'B') return [pb, pa] const ba = bboxOf(A), bb = bboxOf(B) const minFirst = axis === 'v' ? ba.x0 <= bb.x0 : ba.y0 <= bb.y0 return minFirst ? (rule === 'C' ? [pa, pb] : [pb, pa]) : (rule === 'C' ? [pb, pa] : [pa, pb]) } function replay(shape, rule, trace) { const base = (shape.polygon?.polygon ?? []) .map(r => r.filter(s => s?.ps && s?.pe && [s.ps.x, s.ps.y, s.pe.x, s.pe.y].every(Number.isFinite)) .map(s => ({ ps: { ...s.ps }, pe: { ...s.pe } }))) .filter(r => r.length >= 3) const regions = base.map(r => r) for (const sp of (shape.mm?.sp ?? [])) { if (!sp?.line?.pt || !sp.line.norm) continue const axis = spAxis(sp), pos = spPos(sp) let idx = sp.polyId?.idx ?? -1 const rawIdx = idx if (!Number.isInteger(idx) || idx < 0 || idx >= regions.length) idx = 0 const target = regions[idx] if (!target || target.length < 3) continue const bb = bboxOf(target.map(s => s.ps)) const lo = axis === 'v' ? bb.x0 : bb.y0, hi = axis === 'v' ? bb.x1 : bb.y1 const inside = pos > lo + 0.5 && pos < hi - 0.5 const pieces = inside ? splitPoly(target, axis, pos, rule) : null if (trace) { console.log(` sp axis=${axis} pos=${pos.toFixed(1)} polyId=${JSON.stringify(sp.polyId)} rawIdx=${rawIdx}->${idx}` + ` 目标bbox=${JSON.stringify(bb)} ${inside ? '内部✓' : '退化✗'} ${pieces ? '切分✓' : '切分✗'}`) } if (!pieces) continue regions.splice(idx, 1, pieces[0], pieces[1]) } return regions } const files = fs.readdirSync(DIR).filter(f => f.endsWith('.json')) const shapes = [] for (const f of files) { const row = JSON.parse(fs.readFileSync(path.join(DIR, f), 'utf8')) const dd = typeof row.drawing_data === 'string' ? JSON.parse(row.drawing_data) : row.drawing_data dd.sm?.forEach(shape => shapes.push({ f, shape })) } for (const rule of ['A', 'B', 'C', 'D']) { let sashOk = 0, sashN = 0, glassOk = 0, glassN = 0 for (const { f, shape } of shapes) { const regions = replay(shape, rule) for (const sub of (shape.sm ?? [])) { const idx = sub.polyId?.idx if (idx == null || idx < 0 || sub.enabled === false) continue sashN++ if (idx < regions.length) sashOk++ } for (const fl of (shape.mm?.fls?.fls ?? [])) { const idx = fl.pid?.idx if (idx == null || idx < 0) continue glassN++ if (idx < regions.length) glassOk++ } if (f === '704.json') { const r = regions[sub704Idx(shape)] console.log(` [${rule}] 704 扇挂接区域:`, r ? JSON.stringify(bboxOf(r.map(s => s.ps))) : '越界') } if (f === '31594.json') { console.log(` [${rule}] 31594 区域数:`, regions.length, '(应为 7)') if (process.argv[2] === 'trace') { console.log(` [${rule}] 31594 逐条重放:`) replay(shape, rule, true) } } } console.log(`规则${rule}: 扇引用有效 ${sashOk}/${sashN} (${(100 * sashOk / sashN).toFixed(1)}%) | 玻璃引用有效 ${glassOk}/${glassN} (${(100 * glassOk / glassN).toFixed(1)}%)`) } function sub704Idx(shape) { const sub = (shape.sm ?? []).find(s => s.enabled !== false) return sub?.polyId?.idx ?? -1 }