feat: 渲染视觉与图纸对齐(源站原生扇内结构全量消费: 扇内分割/裙板/格条/编号, inferSashPanels 工具+三渲染器接入+锚点归属匹配+对话框ResizeObserver)

This commit is contained in:
HHY
2026-10-05 07:15:07 -07:00
parent cbc96dd166
commit 888a8803af
5 changed files with 315 additions and 8 deletions
+51
View File
@@ -67,6 +67,7 @@ export interface SashShape {
openToward?: string // inward/outward
rt?: number | boolean // 圆弧半径(弧顶扇)
sashes?: SashEntry[] // DoubleSash 的子扇
mm?: { sp?: SplitLine[]; dbs?: unknown; fls?: { fls?: any[] }; phs?: unknown[]; lpfs?: unknown[] } // 扇内分割/玻璃/格条(源站原生)
[k: string]: unknown
}
@@ -480,3 +481,53 @@ export function inferSashGlyphs(shape: FrameShape, replayed?: ReplayResult | Seg
}
return { regions, sashes: glyphs }
}
/** 扇内子格(源站原生结构消费):扇自身 mm.sp 内部分割 + mm.fls 玻璃挂接 + mm.dbs.bars 源站格条。
* 扇无基底 polygon(几何=挂接区域矩形),以 rect 构造临时 shape 复用 replayShape 全语义重放。 */
export interface SashPanel {
x0: number; y0: number; x1: number; y1: number
glass: boolean // true=玻璃格(挂 fls 或无分割默认) / false=实板(门裙板等)
glassText?: string // 玻璃编号(ser.text)
glassSpec?: string // 玻璃配置(spet.sp)
deco?: { tp: string; gp: number; hc: number } // 源站格条(dbs.bars: tp=风格, gp=回纹尺寸 mm, hc=主格等分数)
}
export function inferSashPanels(sash: SashShape, rect: { x0: number; y0: number; x1: number; y1: number }): SashPanel[] {
const mm = ((sash as Record<string, unknown>).mm ?? {}) as { sp?: SplitLine[]; fls?: { fls?: any[] }; dbs?: unknown }
const sps = mm.sp ?? []
const fls = mm.fls?.fls ?? []
if (!sps.length) {
const fl = fls[0]
return [{ ...rect, glass: true, glassText: fl?.ser?.text, glassSpec: fl?.spet?.sp }]
}
const ring = [
[{ ps: { x: rect.x0, y: rect.y0 }, pe: { x: rect.x1, y: rect.y0 } },
{ ps: { x: rect.x1, y: rect.y0 }, pe: { x: rect.x1, y: rect.y1 } },
{ ps: { x: rect.x1, y: rect.y1 }, pe: { x: rect.x0, y: rect.y1 } },
{ ps: { x: rect.x0, y: rect.y1 }, pe: { x: rect.x0, y: rect.y0 } }],
]
const fake = { ...(sash as Record<string, unknown>), polygon: { type: 'polygon', polygon: ring, spLine: null, virtual: false }, mm } as unknown as FrameShape
const rp = replayShape(fake)
const decos: { pid: { idx: number; pos: number }; tp: string; gp: number; hc: number }[] = []
const barsRaw = (mm.dbs && typeof mm.dbs === 'object' ? (mm.dbs as { bars?: unknown[] }).bars : (mm.dbs as unknown[])) ?? []
for (const entry of barsRaw as unknown[]) {
try {
const pair = entry as [string, { db?: { tp?: string; gp?: number } } | undefined]
const pid = typeof pair[0] === 'string' ? JSON.parse(pair[0]) : pair[0]
const db = (pair[1]?.db ?? pair[1]) as { tp?: string; gp?: number; hc?: number } | undefined
if (pid && Number.isInteger(pid.idx)) decos.push({ pid, tp: String(db?.tp ?? ''), gp: Number(db?.gp) || 80, hc: Number(db?.hc) || 2 })
} catch { /* 坏数据忽略 */ }
}
return rp.regions.map((segs, i) => {
const bb = bboxOf(regionPoints(segs))
const ref = rp.leafRefs[i] ?? { idx: -1, pos: -1 }
const fl = fls.find(f => { const p = f?.pid ?? {}; return p.idx === ref.idx && p.pos === ref.pos })
?? (fls.length === 1 && (fls[0]?.pid?.idx ?? -1) < 0 ? fls[0] : undefined)
const isPanel = String(fl?.type ?? '') === 'Panel'
const deco = decos.find(d => d.pid.idx === ref.idx && d.pid.pos === ref.pos)
return { x0: bb.x0, y0: bb.y0, x1: bb.x1, y1: bb.y1,
glass: isPanel ? false : Boolean(fl) || fls.length === 0,
glassText: fl?.ser?.text, glassSpec: fl?.spet?.sp,
deco: deco ? { tp: deco.tp, gp: deco.gp, hc: deco.hc } : undefined }
})
}