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

395 lines
11 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 type { Box2 } from '../Box2'
import type { NestFiler } from '../Filer'
import type { Point } from '../Vector2'
import { RandomIndex } from '../Random'
import { FixIndex } from '../Util'
import { Vector2 } from '../Vector2'
import { GNestConfig } from './GNestConfig'
import { NestCache } from './NestCache'
import { PartState } from './PartState'
import { Path } from './Path'
import { PathGeneratorSingle } from './PathGenerator'
const EmptyArray = []
/**
* 零件类
* 零件类可以绑定数据,也存在位置和旋转状态的信息
*
* 初始化零件:
* 传入零件的轮廓,刀半径,包围容器(或者为空?)
* 初始化用于放置的轮廓。将轮廓首点移动到0点记录移动的点P。
*
* 零件放置位置:
* 表示零件轮廓首点的位置。
*
* 零件的旋转:
* 表示零件轮廓按照首点0旋转。
*
* 还原零件的放置状态:
* 同样将零件移动到0点
* 同样将零件旋转
* 同样将零件移动到指定的位置
* 零件可能处于容器中,变换到容器坐标系
*
*/
export class Part<T = any, Matrix = any>
{
Id: number// 用于确定Part的唯一性,并且有助于在其他Work中还原
private _Holes: PartState[] = []
_RotateHoles: PartState[][] = []
// 零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态
StateIndex = 0 // 旋转状态
RotatedStates: PartState[] = []// 可能的旋转状态列表
PlacePosition: Point // 放置位置(相对于容器的位置)
HolePosition: Point//
// #region 临时数据(不会被序列化到优化线程)
UserData: T// 只用于还原零件的显示状态(或者关联到实际数据)
Parent: Part// 如果这个零件放置在了网洞中,这个Parent表示这个网洞所属的零件,我们可以得到这个零件的放置信息并且可以从Container中的ParentM得到网洞相对于零件的位置
PlaceCS: Matrix// 放置矩阵 Matrix4
PlaceIndex: number// 放置的大板索引
// #endregion
GroupMap: { [key: number]: Part[] } = {}
get State(): PartState // 零件当前的状态
{
return this.RotatedStates[this.StateIndex]
}
get Holes(): PartState[]
{
if (GNestConfig.RotateHole)
return this._RotateHoles[this.StateIndex] || EmptyArray
return this._Holes
}
// 初始化零件的各个状态,按360度旋转个数
Init(path: Path, bin: Path, rotateCount = 4): this
{
let rotations: number[] = []
let a = 2 * Math.PI / rotateCount
for (let i = 0; i < rotateCount; i++)
{
rotations.push(a * i)
}
this.Init2(path, bin, rotations)
return this
}
// 初始化零件的各个状态,按旋转角度表
Init2(path: Path, bin: Path, rotations: number[] = []): this
{
let pathP = path.OrigionMinPoint
let path_0 = PathGeneratorSingle.Allocate(path)
let pathSet = new Set<Path>()
// 初始化零件的状态集合
for (let pa of rotations)
{
let partState = new PartState()
partState.Rotation = pa
if (pa === 0)
{
partState.Contour = path_0
partState.OrigionMinPoint = pathP
partState.MinPoint = path.OrigionMinPoint
}
else
{
let path_r = new Path(path.Points, pa)
partState.Contour = PathGeneratorSingle.Allocate(path_r)
partState.Contour.Area = path_0.Area
// 避免重复的Path进入State
if (pathSet.has(partState.Contour))
continue
let p0 = path_r.OrigionMinPoint
let c = Math.cos(-pa)
let s = Math.sin(-pa)
let x1 = p0.x * c - p0.y * s
let y1 = p0.x * s + p0.y * c
partState.OrigionMinPoint = new Vector2(pathP.x + x1, pathP.y + y1)
// 计算正确的最小点
let tempPath = new Path(path.OrigionPoints, pa)
partState.MinPoint = tempPath.OrigionMinPoint
}
// 记录已有Path
pathSet.add(partState.Contour)
// 必须能放置
if (bin.GetInsideNFP(partState.Contour))
{
this.RotatedStates.push(partState)
PathGeneratorSingle.RegisterId(partState.Contour)
}
}
// 为了复用NFP,不管第0个Path是否可用,都注册它.
if (this.RotatedStates.length > 4)
PathGeneratorSingle.RegisterId(path_0)
return this
}
ParseGroup(partOther: Part, bin: Path): Part[]
{
let arr = this.GroupMap[partOther.Id]
if (arr)
return arr
arr = []
if (this.Holes.length || partOther.Holes.length)
return arr
this.GroupMap[partOther.Id] = arr
if (this.State.Contour.IsRect || partOther.State.Contour.IsRect)
return arr
if (this.State.Contour.Area > this.State.Contour.BoundingBox.area * 0.9)
return arr
if (partOther.State.Contour.Area > partOther.State.Contour.BoundingBox.area * 0.9)
return arr
for (let i = 0; i < this.RotatedStates.length; i++)
{
let s1 = this.RotatedStates[i]
for (let j = 1; j < partOther.RotatedStates.length; j++)
{
let s2 = partOther.RotatedStates[j]
let nfps = s1.Contour.GetOutsideNFP(s2.Contour)
for (let nfp of nfps)
{
for (let k = 0; k < nfp.length * 2; k++)
{
let p: Point
if (k % 2 === 0)
{
p = { ...nfp[k / 2] }
}
else
{
let p1 = nfp[FixIndex(k / 2 - 0.5, nfp)]
let p2 = nfp[FixIndex(k / 2 + 0.5, nfp)]
p = { x: p1.x + p2.x, y: p1.y + p2.y }
p.x *= 0.5
p.y *= 0.5
}
p.x *= 1e-4
p.y *= 1e-4
let newBox = s2.Contour.BoundingBox.clone().translate(p)
newBox.union(s1.Contour.BoundingBox)
if (newBox.area < (s1.Contour.Area + s2.Contour.Area) * 1.3)
{
let partGroup = new PartGroup(this, partOther, i, j, p, newBox, bin)
if (partGroup.RotatedStates.length > 0
&& !arr.some(p => p.State.Contour === partGroup.State.Contour)// 类似的
)
{
arr.push(partGroup)
return arr
}
}
}
}
}
}
return arr
}
// 添加网洞
AppendHole(path: Path)
{
let hole = new PartState()
hole.Contour = PathGeneratorSingle.Allocate(path)
PathGeneratorSingle.RegisterId(hole.Contour)
hole.OrigionMinPoint = path.OrigionMinPoint
hole.Rotation = 0
this._Holes.push(hole)
if (GNestConfig.RotateHole)
for (let i = 0; i < this.RotatedStates.length; i++)
{
let r = this.RotatedStates[i].Rotation
let arr = this._RotateHoles[i]
if (!arr)
{
arr = []
this._RotateHoles[i] = arr
}
if (r === 0)
{
hole.MinPoint = path.OrigionMinPoint
arr.push(hole)
}
else
{
let newPath = new Path(path.Points, r)
let newHole = new PartState()
newHole.Rotation = r
newHole.Contour = PathGeneratorSingle.Allocate(newPath)
PathGeneratorSingle.RegisterId(newHole.Contour)
newHole.OrigionMinPoint = newPath.OrigionMinPoint
// 计算正确的最小点
let tempPath = new Path(path.OrigionPoints, r)
newHole.MinPoint = tempPath.OrigionMinPoint
arr.push(newHole)
}
}
}
// TODO:因为现在实现的是左右翻转,所以会出现角度匹配不完全的问题(缺失上下翻转)
Mirror(doubleFace: boolean)
{
let states = this.RotatedStates
let holes = this._Holes
let roholes = this._RotateHoles
if (!doubleFace)
{
this.RotatedStates = []
this._Holes = []
this._RotateHoles = []
}
let count = states.length
for (let i = 0; i < count; i++)
{
let s = states[i]
let ns = s.Mirror()
if (ns && !this.RotatedStates.some(state => state.Contour === s.Contour))
{
this.RotatedStates.push(ns)
if (this._Holes.length > 0)
this._Holes.push(holes[i].Mirror())
if (this._RotateHoles.length > 0)
this._RotateHoles.push(roholes[i].map(s => s.Mirror()))
}
}
}
// 浅克隆
Clone()
{
let part = new Part()
part.Id = this.Id
part.UserData = this.UserData
part.RotatedStates = this.RotatedStates
part.StateIndex = this.StateIndex
part._Holes = this._Holes
part._RotateHoles = this._RotateHoles
return part
}
// 旋转起来,改变自身旋转状态(变异)
Mutate(): this
{
this.StateIndex = RandomIndex(this.RotatedStates.length, this.StateIndex)
return this
}
// #region -------------------------File-------------------------
ReadFile(file: NestFiler)
{
this.Id = file.Read()
let count = file.Read() as number
this.RotatedStates = []
for (let i = 0; i < count; i++)
{
let state = new PartState()
state.ReadFile(file)
this.RotatedStates.push(state)
}
// 无旋转网洞
count = file.Read()
this._Holes = []
for (let i = 0; i < count; i++)
{
let state = new PartState()
state.ReadFile(file)
this._Holes.push(state)
}
// 旋转网洞
count = file.Read()
this._RotateHoles = []
for (let i = 0; i < count; i++)
{
let count2 = file.Read() as number
let holes: PartState[] = []
for (let j = 0; j < count2; j++)
{
let state = new PartState()
state.ReadFile(file)
holes.push(state)
}
this._RotateHoles.push(holes)
}
}
WriteFile(file: NestFiler)
{
file.Write(this.Id)
file.Write(this.RotatedStates.length)
for (let state of this.RotatedStates)
state.WriteFile(file)
// 非旋转网洞
file.Write(this._Holes.length)
for (let hole of this._Holes)
hole.WriteFile(file)
// 写入旋转网洞
file.Write(this._RotateHoles.length)
for (let holes of this._RotateHoles)
{
file.Write(holes.length)
for (let hole of holes)
{
hole.WriteFile(file)
}
}
}
// #endregion
}
// 零件组合
export class PartGroup extends Part
{
constructor(public part1: Part,
public part2: Part,
public index1: number,
public index2: number,
public p: Point,
public box: Box2,
bin: Path,
)
{
super()
let size = box.getSize(new Vector2())
this.Init2(NestCache.CreatePath(size.x, size.y, 0), bin, [0])
}
Export(): Part[]
{
this.part1.StateIndex = this.index1
this.part2.StateIndex = this.index2
this.part1.PlacePosition = {
x: this.PlacePosition.x - this.box.min.x * 1e4,
y: this.PlacePosition.y - this.box.min.y * 1e4,
}
this.part2.PlacePosition = {
x: this.PlacePosition.x - this.box.min.x * 1e4 + this.p.x * 1e4,
y: this.PlacePosition.y - this.box.min.y * 1e4 + this.p.y * 1e4,
}
return [this.part1, this.part2]
}
}