import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' import type { Curve2d } from '../../common/base/CAD.js' import { Arc2d, CADExt, Line2d, Point2d } from '../../common/base/CAD.js' import { equal } from '../../common/base/MathComm.js' import { PlaceSpace,PlaceBlock,TextureType } from '../../confClass.js' /** 生成矩形空间的相关算法 */ export class SpacePlus { /** 纹路:0正文 1反纹 */ static texture :TextureType = 0 // = TextureType.NORMAL_TEXTURE // 正纹 /** 设置空间的方向,横向或竖向 */ static setSpaceStyle(block: PlaceBlock) { if (block.texture == TextureType.NORMAL_TEXTURE) // 正纹 { this.texture = 0 } else if (block.texture == TextureType.REVERSE_TEXTURE) // 反纹 { this.texture = 1 } else // 可翻转 { if (block.cutWidth < block.cutLength) { this.texture = 0 } else { this.texture = 1 } } } /** 复制轮廓 */ static copyBorder(curves: Curve2d[]): Curve2d[] { const newCurves: Curve2d[] = [] for (let i = 0; i < curves.length; i++) { const curve = curves[i] const sp = curve.StartPoint const ep = curve.EndPoint if (curve instanceof Arc2d) { const arc = new Arc2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y), curve.Bul) newCurves.push(arc) } else { // 后面会修改line里头的起点终点,所以必须重新初始化, 确保不能影响原来的轮廓 border . const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) newCurves.push(newLine) } } return newCurves } /** 从轮廓中分析空间 */ static borderToSpace(orgcurves: Curve2d[]) { const curves = this.copyBorder(orgcurves) // 1.新建线段列表 不影响源数据 // 弧线转成 直线 const lines: Line2d[] = [] for (let i = 0; i < curves.length;) { const curve = curves[i] const sp = curve.StartPoint const ep = curve.EndPoint if (curve instanceof Arc2d) { const len = Math.abs(curve.m_Radius * curve.m_AllAngle) // 圆弧长度 if (len > 200) // 圆弧长大于 200 则拆分 { const count = Math.ceil(len / 100) const newArcs = CADExt.SplitArc(curve, count) curves.splice(i, 1) for (let j = newArcs.length - 1; j >= 0; j--) { curves.splice(i, 0, newArcs[j]) } continue } else // 圆弧转换 直线 { if (curve.Bul == 0) { curve.Parse() } const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) // 外凸 直接转 if (curve.Bul >= 0) { newLine.canotSplit = true // 不能拆分 lines.push(newLine) } else // 内凹 算要算一下 弧形外接 矩形面积 { const pts:any[] = [] pts.push({ x: curve.StartPoint.m_X, y: curve.StartPoint.m_Y, bul: curve.Bul }) pts.push({ x: curve.EndPoint.m_X, y: curve.EndPoint.m_Y, bul: 0 }) const pl = PolylineHelper.create(pts) const box = pl.BoundingBox this.setDirect(newLine) const fx = newLine.fx const newPoints :any[] = [] newPoints.push({ x: sp.m_X, y: sp.m_Y }) switch (fx) { case 0: newPoints.push({ x: box.min.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.min.y }) break case 1: newPoints.push({ x: box.max.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.max.y }) break case 2: newPoints.push({ x: box.max.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.max.y }) break case 3: newPoints.push({ x: box.min.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.min.y }) break case 4: newPoints.push({ x: box.min.x, y: sp.m_Y }) newPoints.push({ x: box.min.x, y: box.max.y }) newPoints.push({ x: ep.m_X, y: box.max.y }) break case 5: newPoints.push({ x: sp.m_X, y: box.min.y }) newPoints.push({ x: box.min.x, y: box.min.y }) newPoints.push({ x: box.min.x, y: ep.m_Y }) break case 6: newPoints.push({ x: box.max.x, y: sp.m_Y }) newPoints.push({ x: box.max.x, y: box.min.y }) newPoints.push({ x: ep.m_X, y: box.min.y }) break case 7: newPoints.push({ x: sp.m_X, y: box.max.y }) newPoints.push({ x: box.max.x, y: box.max.y }) newPoints.push({ x: box.max.x, y: ep.m_Y }) break default: } newPoints.push({ x: ep.m_X, y: ep.m_Y }) for (let n = 0; n < newPoints.length - 1; n++) { const m = n + 1 const newLine = new Line2d(new Point2d(newPoints[n].x, newPoints[n].y), new Point2d(newPoints[m].x, newPoints[m].y)) lines.push(newLine) } } } } else { // 后面会修改line里头的起点终点,所以必须重新初始化 ,确保不能影响原来的轮廓 border . const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) lines.push(newLine) } i++ } // 2 轮廓需要够大 let minX = 1000 let minY = 1000 let maxX = -1000 let maxY = -1000 for (const line of lines) { if (line.StartPoint.m_X < minX) minX = line.StartPoint.m_X if (line.StartPoint.m_X > maxX) maxX = line.StartPoint.m_X if (line.StartPoint.m_Y < minY) minY = line.StartPoint.m_Y if (line.StartPoint.m_Y > maxY) maxY = line.StartPoint.m_Y if (line.EndPoint.m_X < minX) minX = line.EndPoint.m_X if (line.EndPoint.m_X > maxX) maxX = line.EndPoint.m_X if (line.EndPoint.m_Y < minY) minY = line.EndPoint.m_Y if (line.EndPoint.m_Y > maxY) maxY = line.EndPoint.m_Y } if (maxX - minX < 50) return // 太小了,就不要分析 if (maxY - minY < 50) return if ((maxX - minX) * (maxY - minY) < 10000) return [] // 3.将长斜线 ,转成 多段 for (let i = 0; i < lines.length;) { const line = lines[i] if (this.isXL(line)) { if (line.canotSplit) // 不能拆分 { } else { const sp = line.StartPoint const ep = line.EndPoint if (line.m_Length > 300 && Math.abs(sp.m_X - ep.m_X) > 50 && Math.abs(sp.m_X - ep.m_X) > 50) // 够长,截断 { const cx = (line.StartPoint.m_X + line.EndPoint.m_X) / 2 const cy = (line.StartPoint.m_Y + line.EndPoint.m_Y) / 2 line.EndPoint = new Point2d(cx, cy) line.Parse() const newLine = new Line2d(new Point2d(cx, cy), new Point2d(ep.m_X, ep.m_Y)) lines.splice(i + 1, 0, newLine) continue } } } i++ } // 3.斜线 -> 横平竖直 for (let i = 0; i < lines.length; i++) { this.turnToLine(lines, i) } // 6.将line 标识方向 for (let i = lines.length - 1; i >= 0; i--) { if (this.setDirect(lines[i]) == -1) lines.splice(i, 1) } const tmpSpaces = [] // 7.开始分析空间 this.parseSpace(lines, tmpSpaces) // 8.合并空间 this.mergeSpaces(tmpSpaces) // 9 生成 const spaces: PlaceSpace[] = [] for (const tmp of tmpSpaces) { const space = PlaceSpace.create(tmp.x1, tmp.y1, tmp.x2, tmp.y2) spaces.push(space) } return spaces } /** 闭合(逆时针)的线段内分析空间 */ private static parseSpace(lines: Line2d[], spaces: any[]) { const childs = this.resetLines(lines) if (childs.length > 1) // 分叉 { for (const child of childs) { this.parseSpace(child, spaces) } return } // 没有分支,才开始真正计算 lines = childs[0] if (lines.length < 4) return // 至少有4条 /** * 思路:在线段里头 找到一段连续且成矩形的3条直线,即可组成一个矩形 * 然后:去掉(或缩短)这3条直线,再重新找 */ let space for (let i = 0; i < lines.length; i++) { space = this.createSpace(lines, i) if (space) break } // 没找到任何空间, 直接退出 if (!space) return // 找到了,继续往下找 spaces.push(space) this.parseSpace(lines, spaces) } /** 分析空间大的保留,小的合并 */ static mergeSpaces(spaces: any[]) { const minW = 50 // 空间最小宽度 const minL = 200 const avgW = 60 // 不损失 合并空间 for (let i = 0; i < spaces.length;) { const space1 = spaces[i] let hasMerge = false // 是否合并一个空间 for (let j = i + 1; j < spaces.length;) { const space2 = spaces[j] hasMerge = mergeSpace(space1, space2) // space2 是否被合并 if (hasMerge) { spaces.splice(j, 1) break } j++ } if (hasMerge) break i++ } // 小空间 委屈合并 for (let i = 0; i < spaces.length;) { const space1 = spaces[i] if (big(space1) == false) // 小空间,找别人合并 { mergeSpace2(i) spaces.splice(i, 1)// 删除小空间 continue } i++ } /** 空间 大否 */ function big(space) { let w = space.x2 - space.x1 if (w < minW) return false let l = space.y2 - space.y1 if (l < minW) return false if (w > l) [w, l] = [l, w] if (w >= minW && l >= minL) return true if (w >= avgW && l >= avgW) return true return false } /** 不损失 合并空间 */ function mergeSpace(sp1, sp2) { // 上下 宽一样 if (equal(sp1.x1, sp2.x1) && equal(sp1.x2, sp2.x2) && (equal(sp1.y2, sp2.y1) || equal(sp2.y2, sp1.y1))) { sp1.y1 = Math.min(sp1.y1, sp2.y1) sp1.y2 = Math.max(sp1.y2, sp2.y2) return true } // 左右 高一样 if (equal(sp1.y1, sp2.y1) && equal(sp1.y2, sp2.y2) && (equal(sp1.x2, sp2.x1) || equal(sp2.x2, sp1.x1))) { sp1.x1 = Math.min(sp1.x1, sp2.x1) sp1.x2 = Math.max(sp1.x2, sp2.x2) return true } return false } /** 损失 合并空间,四周找一个空间,合并后效果最好的 */ function mergeSpace2(index: number) { const cur = spaces[index] const canSpaces = [] for (let i = 0; i < spaces.length; i++) { if (i == index) continue const oth = spaces[i] let x1, y1, x2, y2 // 右边的 if (equal(cur.x2, oth.x1) && cur.y1 < oth.y2 && cur.y2 > oth.y1) { x1 = cur.x1 y1 = Math.max(cur.y1, oth.y1) x2 = oth.x2 y2 = Math.min(cur.y2, oth.y2) } // 左边的 else if (equal(cur.x1, oth.x2) && cur.y1 < oth.y2 && cur.y2 > oth.y1) { x1 = oth.x1 y1 = Math.max(cur.y1, oth.y1) x2 = cur.x2 y2 = Math.min(cur.y2, oth.y2) } // 下边的 else if (equal(cur.y1, oth.y2) && cur.x1 < oth.x2 && cur.x2 > oth.x1) { x1 = Math.max(cur.x1, oth.x1) y1 = oth.y1 x2 = Math.min(cur.x2, oth.x2) y2 = cur.y2 } // 上边的 else if (equal(cur.y2, oth.y1) && cur.x1 < oth.x2 && cur.x2 > oth.x1) { x1 = Math.max(cur.x1, oth.x1) y1 = cur.y1 x2 = Math.min(cur.x2, oth.x2) y2 = oth.y2 } else { continue } // oth 原来面积 const size_org = (oth.x2 - oth.x1) * (oth.y2 - oth.y1) const size_new = (x2 - x1) * (y2 - y1) const size_plus = size_new - size_org if (size_plus < 0) continue // 合并 面积更小,就不要合并 const space = { x1, y1, x2, y2 } canSpaces.push({ size_plus, space, i }) } if (canSpaces.length == 0) return // 没有可以合并的 // 按增大面积 排序 canSpaces.sort((a, b) => b.size_plus - a.size_plus) // 取效果最好的。 const newSpace = canSpaces[0].space // 替换 oth , spaces.splice(canSpaces[0].i, 1, newSpace) } } /** 整理多段线 */ private static resetLines(lines: Line2d[]): Line2d[][] { for (let i = 0; i < lines.length;) { const lp = lines[i] let n = i + 1 if (n == lines.length) n = 0 const ln = lines[n] if (lp.m_Length < 0.001) // 太短了,移除 { lines.splice(i, 1) i = 0 continue } if (lp.fx == ln.fx) // 同向, { lp.EndPoint = ln.EndPoint lp.Parse() lines.splice(n, 1) i = 0 continue } if (lp.fx % 2 == ln.fx % 2) // 反向 { lp.EndPoint = ln.EndPoint lp.Parse() this.setDirect(lp) lines.splice(n, 1) if ((lp.fx == 0 && lp.EndPoint.m_X < lp.StartPoint.m_X) || (lp.fx == 1 && lp.EndPoint.m_Y < lp.StartPoint.m_Y) || (lp.fx == 2 && lp.EndPoint.m_X > lp.StartPoint.m_X) || (lp.fx == 3 && lp.EndPoint.m_Y > lp.StartPoint.m_Y)) // 这很奇葩,该空间 不能分析,直接清除所有线段 { lines.splice(0, lines.length) return } i = 0 continue } i++ } // 判断 有向下的线,与向上的线 重叠。 则要分开; 类似 与 管 的下面 2个口 let line0: Line2d let line1: Line2d let i = -1 let j = -1 let x = 0 for (i = 0; i < lines.length; i++) { if (lines[i].fx == 3) { x = lines[i].StartPoint.m_X j = lines.findIndex(t => t.fx == 1 && t.StartPoint.m_X == x) if (j >= 0) { line0 = lines[i] line1 = lines[j] break } } } if (!line0 || !line1) return [lines] // 没找到 const rt = [] const si = Math.min(i, j) const ei = Math.max(i, j) const lines_s: Line2d[] = [] const lines_x: Line2d[] = [] for (let i = si + 1; i <= ei - 1; i++) { lines_s.push(lines[i]) } for (let i = ei + 1; i <= lines.length + si - 1; i++) { const ri = i % lines.length lines_x.push(lines[ri]) } if (lines_s.length >= 3) { const pe = lines_s[lines_s.length - 1].EndPoint const ps = lines_s[0].StartPoint const newLine = new Line2d(new Point2d(pe.m_X, pe.m_Y), new Point2d(ps.m_X, ps.m_Y)) this.setDirect(newLine) lines_s.push(newLine) rt.push(lines_s) } if (lines_x.length >= 3) { const pe = lines_x[lines_x.length - 1].EndPoint const ps = lines_x[0].StartPoint const newLine = new Line2d(new Point2d(pe.m_X, pe.m_Y), new Point2d(ps.m_X, ps.m_Y)) this.setDirect(newLine) lines_x.push(newLine) rt.push(lines_x) } return rt } /** line如果是斜线,转换成直线 */ private static turnToLine(lines: Line2d[], i: number) { if (!this.isXL(lines[i])) return const sp = lines[i].StartPoint const ep = lines[i].EndPoint const sx = sp.m_X const sy = sp.m_Y const ex = ep.m_X const ey = ep.m_Y let cx = 0 let cy = 0 // ↖ 换成 ←↑ if (ex < sx && ey > sy) { cx = ex cy = sy } else if (ex < sx && ey < sy) // ↙ 换成 ←↓ { cx = sx cy = ey } else if (ex > sx && ey < sy) // ↘ 换成 →↓ { cx = ex cy = sy } else if (ex > sx && ey > sy) // ↗ 换成 →↑ { cx = sx cy = ey } if (cx == 0 && cy == 0) return const line1 = new Line2d(new Point2d(sx, sy), new Point2d(cx, cy)) const line2 = new Line2d(new Point2d(cx, cy), new Point2d(ex, ey)) lines.splice(i, 1, line1, line2) } /** 线段设置方向 */ private static setDirect(line: Line2d) { let fx = -1 // 向右 0,向上 1,向左 2,向下 3 ,右上 4,左上5,左下6,右下 7 if (line.EndPoint.m_X > line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) { fx = 0 } else if (line.EndPoint.m_X < line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) { fx = 2 } else if (line.EndPoint.m_Y > line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) { fx = 1 } else if (line.EndPoint.m_Y < line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) { fx = 3 } else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) { fx = 4 } else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) { fx = 5 } else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) { fx = 6 } else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) { fx = 7 } line.fx = fx return fx } /** 斜线 */ private static isXL(line: Curve2d) { return !equal(line.StartPoint.m_X, line.EndPoint.m_X) && !equal(line.StartPoint.m_Y, line.EndPoint.m_Y) } /** 3条线,方向持续, 组成空间 */ private static createSpace(lines: Line2d[], i: number) { let j = i + 1 let n = i + 2 if (j >= lines.length) j = j - lines.length if (n >= lines.length) n = n - lines.length const line1 = lines[i] const line2 = lines[j] const line3 = lines[n] const fx1 = line1.fx if (line2.fx != (fx1 + 1) % 4) return if (line3.fx != (fx1 + 2) % 4) return // 安装板的纹路,进行开始计算空间,横的,还是竖的 if (fx1 % 2 != this.texture) return let x1, y1, x2, y2 let sp, ep if (fx1 == 0) { x1 = Math.max(line1.StartPoint.m_X, line3.EndPoint.m_X) y1 = line2.StartPoint.m_Y x2 = line2.EndPoint.m_X y2 = line2.EndPoint.m_Y sp = new Point2d(x1, y1) ep = new Point2d(x1, y2) } else if (fx1 == 1) { x1 = line2.EndPoint.m_X y1 = Math.max(line1.StartPoint.m_Y, line3.EndPoint.m_Y) x2 = line2.StartPoint.m_X y2 = line2.StartPoint.m_Y sp = new Point2d(x2, y1) ep = new Point2d(x1, y1) } else if (fx1 == 2) { x1 = line2.EndPoint.m_X y1 = line2.EndPoint.m_Y x2 = Math.min(line1.StartPoint.m_X, line3.EndPoint.m_X) y2 = line2.StartPoint.m_Y sp = new Point2d(x2, y2) ep = new Point2d(x2, y1) } else if (fx1 == 3) { x1 = line2.StartPoint.m_X y1 = line2.StartPoint.m_Y x2 = line2.EndPoint.m_X y2 = Math.min(line1.StartPoint.m_Y, line3.EndPoint.m_Y) sp = new Point2d(x1, y2) ep = new Point2d(x2, y2) } else { return } // 在新空间内,不能出现其他的线段, for (let o = 0; o < lines.length; o++) { if (o == i || o == j || o == n) continue const oth = lines[o] if (oth.EndPoint.m_X > x1 + 0.001 && oth.EndPoint.m_X < x2 - 0.001 && oth.EndPoint.m_Y > y1 + 0.001 && oth.EndPoint.m_Y < y2 - 0.001) return } // 缩短line1, line3 line1.EndPoint = sp line1.Parse() this.setDirect(line1) line3.StartPoint = ep line3.Parse() this.setDirect(line3) // 删除 line2 ,添加 新连接线 const newline = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) this.setDirect(newline) lines.splice(j, 1, newline) const space = { x1, y1, x2, y2 } return space } /** 反转 */ static reverseCurves(curves: Curve2d[]): Curve2d[] { const newCurs: Curve2d[] = [] for (let i = curves.length - 1; i >= 0; i--) { const cur = curves[i] const sp = cur.StartPoint const ep = cur.EndPoint if (cur instanceof Arc2d) { const newArc = new Arc2d(ep, sp, -cur.Bul) newCurs.push(newArc) } else { const newline = new Line2d(ep, sp) newCurs.push(newline) } } return newCurs } }