diff --git a/CHANGELOG b/CHANGELOG index 6b8e44c..4b742d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # CHANGELOG +- [2026-09-21 10:30] FEAT: v1.5 3D 门窗开合动画——①扇推断逻辑抽取为共享模块 inferSashGlyphs(types/drawing.ts), 2D 符号与 3D 动画共用; ②WindoorThreed 重写: 每个扇构建独立网格组(玻璃+扇框), 平开扇绕铰链旋转(od 定铰链侧, 门75°/平开32°/双向门40°), 推拉扇沿轨道平移(双扇前后轨交替), 区域归属扇的不再重复渲染静态玻璃; ③开合控制条: 开合度滑杆+自动演示(正弦摆动)+全开/全关, 平滑插值动画; 组合画布 3D 预览自动获得开合能力 (Files: frontend/src/types/drawing.ts, frontend/src/components/WindoorThreed.vue, frontend/src/components/WindoorCanvas.vue) - [2026-09-21 00:40] FEAT: v1.4 旋转编辑+方向核验+双向门符号。①组合画布旋转编辑: 选中单元⟲/⟳90°旋转(绕几何中心, 分割线法向/开向角度同步旋转)与水平镜像, 工具栏按钮+Del删除; Node数学验证(宽高互换/法向转轴/可逆性✓); ②数据方向核验: 以31729(扇上固定下)与31740(裙板下)真实图纸比对, 确认2D渲染Y翻转方向正确(模型+y=屏幕上); ③2D渲染补双向开启门虚线交叉符号(wtf/withTurningFrame) (Files: frontend/src/components/CombineCanvas.vue, frontend/src/components/WindoorCanvas.vue, frontend/src/views/CombineView.vue) - [2026-09-21 00:10] FEAT: v1.3 样板组合升级为画布形式——交互对齐原站画图编辑器(库面板可拖拽+shapeManager按位添加)。新组件 CombineCanvas: 拖拽样板入库投放(落点即中心)/自由拖移(同uid单元整体移动)/边缘吸附对齐(红色辅助线,12px容差)/点选高亮/底边对齐/Del删除/平移缩放; CombineView 重构为画布交互(拖放+点击双入口), 保留2D编辑与3D预览切换、另存我的样板(导出时剥离内部标记字段); 原"顺序拼接"模式由画布自由摆放取代 (Files: frontend/src/components/CombineCanvas.vue, frontend/src/views/CombineView.vue) - [2026-09-20 23:40] FEAT: v1.2 样板组合工场+组合3D渲染+菜单分组。①样板组合(/combine): 样板库作为子组件点击加入, 多单元紧贴排布(平移归一), 等高对齐(参考首单元缩放), 删除/左右调序, 组合结果为标准多形状 drawing_data(与渲染/报价/AI炫图/3D全链路兼容), 可另存为我的样板; ②组合预览支持 2D/3D 双模式(Three.js 直接渲染组合 sm[]); ③侧边栏菜单分组: 销售业务/设计制图/生产供应/系统; 算法经 Node 几何验证(6形状无缝拼接/高度0误差) (Files: frontend/src/views/CombineView.vue, frontend/src/views/LayoutView.vue, frontend/src/router/index.ts) diff --git a/frontend/src/components/WindoorCanvas.vue b/frontend/src/components/WindoorCanvas.vue index 1f1d15e..6f12f2c 100644 --- a/frontend/src/components/WindoorCanvas.vue +++ b/frontend/src/components/WindoorCanvas.vue @@ -37,7 +37,7 @@ import { onMounted, ref, watch } from 'vue' import { ElMessage } from 'element-plus' import { - bboxOf, parseDrawing, regionPoints, replayShape, spAxis, spPos, + bboxOf, inferSashGlyphs, parseDrawing, regionPoints, replayShape, spAxis, spPos, type DrawingData, type FrameShape, type Pt, type Segment, type SashShape, type SplitLine, } from '../types/drawing' @@ -245,68 +245,8 @@ function drawFrame( ctx.strokeRect(x0 + inset, y0 + inset, (x1 - x0) - inset * 2, (y1 - y0) - inset * 2) } - // 扇(推断: 有效bd → polyId区域(双扇对半) → 帧bbox按扇数均分) - const regionBBs = regions.map(pts => bboxOf(pts)) - type Glyph = { x0: number; y0: number; x1: number; y1: number; od: number; openToward?: string; isDoor?: boolean; wtf?: boolean } - const glyphs: Glyph[] = [] - const bdRect = (bd: NonNullable) => { - const r = { - x0: Math.min(bd.from.x, bd.to.x), y0: Math.min(bd.from.y, bd.to.y), - x1: Math.max(bd.from.x, bd.to.x), y1: Math.max(bd.from.y, bd.to.y), - } - return !bd.hidden && r.x1 - r.x0 > 10 && r.y1 - r.y0 > 10 ? r : null - } - const pushGlyphs = (r: { x0: number; y0: number; x1: number; y1: number }, entries: any[], sub: SashShape) => { - for (const e of entries) { - glyphs.push({ - x0: r.x0, y0: r.y0, x1: r.x1, y1: r.y1, - od: (e.od ?? sub.od ?? 90) as number, - openToward: (e.openToward ?? sub.openToward) as string | undefined, - isDoor: Boolean(e.isDoor ?? sub.isDoor), - wtf: Boolean(e.wtf ?? sub.wtf ?? sub.withTurningFrame), - }) - } - } - const unmatched: { sub: SashShape; entries: any[] }[] = [] - for (const sub of (shape.sm ?? []) as SashShape[]) { - const raw = (sub.sashes?.length ? sub.sashes : [sub]) as any[] - const entries = raw.filter(e => e && e.enabled !== false) - if (!entries.length) continue - const rect = sub.bd ? bdRect(sub.bd) : null - if (rect) { pushGlyphs(rect, entries, sub); continue } - const idx = sub.polyId?.idx - const rb = idx != null && regionBBs[idx] ? regionBBs[idx] : null - if (rb) { - if (entries.length === 2) { - const w = rb.x1 - rb.x0, h = rb.y1 - rb.y0 - const halves = w >= h - ? [{ ...rb, x1: (rb.x0 + rb.x1) / 2 }, { ...rb, x0: (rb.x0 + rb.x1) / 2 }] - : [{ ...rb, y1: (rb.y0 + rb.y1) / 2 }, { ...rb, y0: (rb.y0 + rb.y1) / 2 }] - entries.forEach((e, k) => pushGlyphs(halves[k], [e], sub)) - } else { - pushGlyphs(rb, entries, sub) - } - } else { - unmatched.push({ sub, entries }) - } - } - if (unmatched.length && regions.length) { - const fb = bboxOf(regions.flat()) - const K = unmatched.reduce((s, u) => s + u.entries.length, 0) - const colW = (fb.x1 - fb.x0) / K - let i = 0 - for (const u of unmatched) { - for (const e of u.entries) { - glyphs.push({ - x0: fb.x0 + i * colW, x1: fb.x0 + (i + 1) * colW, y0: fb.y0, y1: fb.y1, - od: (e.od ?? u.sub.od ?? 90) as number, - openToward: (e.openToward ?? u.sub.openToward) as string | undefined, - isDoor: Boolean(e.isDoor ?? u.sub.isDoor), - }) - i++ - } - } - } + // 扇(推断: 有效bd → polyId区域(双扇对半) → 帧bbox按扇数均分) — 共享推断模块 + const glyphs = inferSashGlyphs(shape, rp.regions).sashes for (const g of glyphs) { const bx0 = tx(g.x0), bx1 = tx(g.x1), by0 = ty(g.y1), by1 = ty(g.y0) const w = bx1 - bx0, h = by1 - by0 diff --git a/frontend/src/components/WindoorThreed.vue b/frontend/src/components/WindoorThreed.vue index a54a715..fd7fab7 100644 --- a/frontend/src/components/WindoorThreed.vue +++ b/frontend/src/components/WindoorThreed.vue @@ -5,23 +5,36 @@ {{ info }} 左键旋转 · 滚轮缩放 · 右键平移 +
+ 开合 + + 自动演示 + {{ openPct > 0 ? '全关' : '全开' }} +