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
+69 -4
View File
@@ -59,8 +59,8 @@ let ro: ResizeObserver | null = null
import { ElMessage } from 'element-plus'
import * as THREE from 'three'
import {
bboxOf, inferSashGlyphs, parseDrawing, regionPoints, replayShape,
type DecBar, type DrawingData, type Pt,
bboxOf, inferSashGlyphs, inferSashPanels, parseDrawing, regionPoints, replayShape,
type DecBar, type DrawingData, type FrameShape, type Pt, type SashShape,
} from '../types/drawing'
import { profileGeo } from '../three/profileGeo'
@@ -256,6 +256,14 @@ function build(drawing: DrawingData) {
for (const shape of drawing.sm) {
const { regions, sashes } = inferSashGlyphs(shape)
if (!regions.length) continue
// 收集 Sash 级形状, 供扇内子格(裙板/格条)锚点归属匹配
const sashShapes3d: SashShape[] = []
const collectSash3d = (s: FrameShape | SashShape) => {
if (!['Frame', 'Slide'].includes(String(s.type))) sashShapes3d.push(s as SashShape)
;(((s as FrameShape).sm ?? []) as (FrameShape | SashShape)[]).forEach(x => collectSash3d(x))
;(((s as SashShape).sashes ?? []) as SashShape[]).forEach(x => collectSash3d(x))
}
collectSash3d(shape)
const cm = shape.cm ?? {}
const gm = cm.g
? new THREE.MeshPhysicalMaterial({
@@ -271,16 +279,22 @@ function build(drawing: DrawingData) {
const regionBBs: BBox[] = regions.map(r => bboxOf(regionPoints(r)))
const used = new Set<number>()
const panels: { rect: BBox; sash: (typeof sashes)[number]; ri: number }[] = []
// 先统计各区域覆盖扇数: 多扇共享(推拉/对开门)时保留静态底玻璃, 独占区域才跳过
const coverCount = new Map<number, number>()
for (const s of sashes) {
const sb: BBox = { x0: s.x0, y0: s.y0, x1: s.x1, y1: s.y1 }
let best = -1, bestA = 0
regionBBs.forEach((rb, ri) => {
if (used.has(ri)) return
const a = overlapArea(sb, rb)
if (a > bestA) { bestA = a; best = ri }
})
if (best >= 0 && bestA > 0) { used.add(best); panels.push({ rect: regionBBs[best], sash: s, ri: best }) }
if (best >= 0 && bestA > 0) {
coverCount.set(best, (coverCount.get(best) ?? 0) + 1)
// 扇面板用 glyph 自身矩形(多扇可共享同一区域)
panels.push({ rect: sb, sash: s, ri: best })
}
}
for (const [ri, n] of coverCount) if (n === 1) used.add(ri)
/** 装饰格条(自研 decs 扩展): 玻璃前表面加横竖格条体 */
const addDecBars = (parent: THREE.Group, dec: DecBar, w: number, h: number, mx: number, my: number, z: number) => {
@@ -333,6 +347,57 @@ function build(drawing: DrawingData) {
barY(group, sashMat, h, t, DEPTH * 0.7, w / 2 - t / 2, 0, 0)
const dec = (shape.decs ?? []).find(d => d.ri === ri)
if (dec) addDecBars(group, dec, w, h, 0, 0, 0.011)
// 扇内子格(源站原生): 裙板实板 + dbs 格条(锚点归属匹配扇形状: sp 锚点落在该扇矩形内)
const gRect = { x0: s.x0, y0: s.y0, x1: s.x1, y1: s.y1 }
const matched = sashShapes3d.find(sh => (sh.mm?.sp ?? []).some(sp => {
const pt = sp.line?.pt
return pt && pt.x >= gRect.x0 && pt.x <= gRect.x1 && pt.y >= gRect.y0 && pt.y <= gRect.y1
})) ?? sashShapes3d[k % Math.max(1, sashShapes3d.length)]
if (matched) {
for (const p of inferSashPanels(matched, gRect)) {
const pw = (p.x1 - p.x0) * S, ph = (p.y1 - p.y0) * S
// 子格中心 → group 局部坐标(group 原点=扇矩形中心)
const lmx = ((p.x0 + p.x1) / 2 - gRect.x1 / 2 - gRect.x0 / 2) * S
const lmy = (gRect.y1 / 2 + gRect.y0 / 2 - (p.y0 + p.y1) / 2) * S
if (![pw, ph, lmx, lmy].every(Number.isFinite) || pw <= 0.01 || ph <= 0.01) continue
if (!p.glass) {
const board = new THREE.Mesh(new THREE.BoxGeometry(pw, ph, 0.012),
new THREE.MeshStandardMaterial({ color: 0xd9cdb8, roughness: 0.7, metalness: 0.05 }))
board.position.set(lmx, lmy, 0.004)
group.add(board)
} else if (p.deco) {
// hc 等分主格 + gp 内框 + 四角回纹块(3D 条体)
const bw = Math.max(0.006, 0.014 * S)
const inner = Math.max(0.015, p.deco.gp * S)
const barMat = new THREE.MeshStandardMaterial({ color: 0x6b563c, roughness: 0.5, metalness: 0.25 })
const iw = pw - 2 * inner, ih = ph - 2 * inner
if (iw > 0.02 && ih > 0.02) {
// 内圈框
barX(group, barMat, iw, bw, DEPTH * 0.2, lmx, lmy + ih / 2, 0.008, 1)
barX(group, barMat, iw, bw, DEPTH * 0.2, lmx, lmy - ih / 2, 0.008, 1)
barY(group, barMat, ih, bw, DEPTH * 0.2, lmx - iw / 2, lmy, 0.008, 1)
barY(group, barMat, ih, bw, DEPTH * 0.2, lmx + iw / 2, lmy, 0.008, 1)
// 主格等分
const hc = Math.max(2, p.deco.hc)
for (let i = 1; i < hc; i++) {
barY(group, barMat, ih, bw, DEPTH * 0.2, lmx - iw / 2 + (iw * i) / hc, lmy, 0.008, 1)
barX(group, barMat, iw, bw, DEPTH * 0.2, lmx, lmy + ih / 2 - (ih * i) / hc, 0.008, 1)
}
// 四角回纹块(嵌套双框)
const qs = Math.min(inner * 0.72, iw * 0.14, ih * 0.14)
for (const [sx, sy] of [[-1, 1], [1, 1], [-1, -1], [1, -1]] as const) {
const cx2 = lmx + sx * (iw / 2 - qs / 2), cy2 = lmy + sy * (ih / 2 - qs / 2)
for (const f of [1, 0.55]) {
barX(group, barMat, qs * f, bw, DEPTH * 0.2, cx2, cy2 + sy * (qs * f) / 2, 0.008, 1)
barX(group, barMat, qs * f, bw, DEPTH * 0.2, cx2, cy2 - sy * (qs * f) / 2, 0.008, 1)
barY(group, barMat, qs * f, bw, DEPTH * 0.2, cx2 + sx * (qs * f) / 2, cy2, 0.008, 1)
barY(group, barMat, qs * f, bw, DEPTH * 0.2, cx2 - sx * (qs * f) / 2, cy2, 0.008, 1)
}
}
}
}
}
}
group.position.set(cxr, cyr, 0)
sashRoot.add(group)