Files
cut-abstractions/tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts
2025-07-22 18:22:31 +08:00

774 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}