Files
cut-abstractions/tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts

774 lines
21 KiB
TypeScript
Raw Normal View History

2025-07-22 18:22:31 +08:00
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
}
}