Files
cut-abstractions/tests/dev1/dataHandle/common/core/Container.ts

400 lines
10 KiB
TypeScript
Raw Normal View History

2025-07-22 18:22:31 +08:00
import { ClipType, PolyFillType } from 'js-angusj-clipper/web'
import type { Paths } from 'js-angusj-clipper/web'
import type { SubjectInput } from 'js-angusj-clipper/web/clipFunctions'
import { Box2 } from '../Box2'
import { clipperCpp } from '../ClipperCpp'
import type { CompareVectorFn } from '../ComparePoint'
import { ComparePoint } from '../ComparePoint'
import { ConvexHull2D } from '../ConvexHull2D'
import type { NestFiler } from '../Filer'
import type { Point } from '../Vector2'
import { equaln } from '../Util'
import { Vector2 } from '../Vector2'
import { PlaceType } from '../../confClass' //'../../vo/enums/PlaceType'
import { NestCache } from './NestCache'
import type { Part } from './Part'
import { PartGroup } from './Part'
import type { Path } from './Path'
import { Area, TranslatePath } from './Path'
/**
* ,,
*/
interface PartPlacedContainerState
{
p: Point
area?: number
hull?: Point[]
box?: Box2
}
/**
* ,
*
*
*
*/
export class Container
{
ParentId: number = -1// 容器bin来源 -1表示默认的bin,大于等于0表示来自板件网洞
ChildrenIndex: number = 0// 网洞的索引位置
ParentM: Point// 在旋转网洞时,和非旋转网洞时表现不一样. (网洞相对于父零件的位置,如果是使用旋转网洞则表示网洞的最下角位置)
PlacedParts: Part[] = []
// 放置状态
PlacedArea = 0
PlacedBox: Box2
PlacedHull: Point[]
StatusKey: string
CompartePoint: CompareVectorFn
constructor(protected BinPath?: Path, private _PlaceType = PlaceType.Box, compare = 'xy')
{
if (BinPath)
this.StatusKey = `${this.BinPath.Id.toString()},${this._PlaceType}`
this.CompartePoint = ComparePoint(compare)
}
get UseRatio(): number
{
return this.PlacedBox.area / this.BinPath.Area
}
private _NotPuts: Set<number>[] = []// 已经无法放置的pathId
private PrePut(part: Part): PartPlacedContainerState
{
// 无法容纳
if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
return
let cacheKey = `${this.StatusKey},${part.State.Contour.Id}`
let cacheP = NestCache.PositionCache[cacheKey]
// 读取缓存位置
if (cacheP)
{
NestCache.count1++
return this.Calc(part, cacheP)
}
let binNfp = this.BinPath.GetInsideNFP(part.State.Contour)
if (!binNfp || binNfp.length === 0)
return
// 首个
if (this.PlacedParts.length === 0)
{
let p = this.GetFarLeftP(binNfp)
NestCache.PositionCache[cacheKey] = p
return this.Calc(part, p)
}
// 快速退出
let noSet = NestCache.NoPutCache[this.StatusKey]
if (noSet)
this._NotPuts.push(noSet)
for (let set of this._NotPuts)
{
if (set.has(part.State.Contour.Id))
{
NestCache.count2++
return
}
}
let finalNfp = this.GetNFPs(part, binNfp)
if (!finalNfp || finalNfp.length === 0)
{
if (noSet)
noSet.add(part.State.Contour.Id)
else
{
noSet = new Set([part.State.Contour.Id])
NestCache.NoPutCache[this.StatusKey] = noSet
}
return
}
// 选择合适的放置点
let minArea: number = Number.POSITIVE_INFINITY
let translate: Point
let bestBox: Box2
let bestHull: Point[]
let tempVec = new Vector2()
for (let nfp of finalNfp)
{
for (let p of nfp)
{
tempVec.set(p.x * 1e-4, p.y * 1e-4)
switch (this._PlaceType)
{
case PlaceType.Box:
{
let box2 = part.State.Contour.BoundingBox.clone()
box2.translate(tempVec)
let rectBox = this.PlacedBox.clone().union(box2)
let size = rectBox.getSize()
let area = size.x * size.y
if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
{
translate = p
minArea = area
bestBox = rectBox
}
break
}
case PlaceType.Hull:
{
let pts = TranslatePath(part.State.Contour.Points, tempVec)
let nhull = ConvexHull2D([...pts, ...this.PlacedHull])
let area = Math.abs(Area(nhull))
if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
{
translate = p
minArea = area
bestHull = nhull
}
break
}
case PlaceType.Gravity:
{
if (!translate || this.CompartePoint(p, translate) === -1)
translate = p
break
}
default:
break
}
}
}
if (translate)
{
NestCache.PositionCache[cacheKey] = translate
if (!bestBox)
bestBox = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate({ x: translate.x * 1e-4, y: translate.y * 1e-4 }))
return { p: translate, area: minArea, box: bestBox, hull: bestHull }
}
}
private Calc(part: Part, p: Point): PartPlacedContainerState
{
let d: PartPlacedContainerState = { p }
let m: Point = { x: p.x * 1e-4, y: p.y * 1e-4 }
if (this.PlacedBox)
d.box = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate(m))
else
d.box = part.State.Contour.BoundingBox.clone().translate(m)
// 凸包
if (this._PlaceType === PlaceType.Hull)
{
if (!this.PlacedHull)
{
d.hull = TranslatePath(part.State.Contour.Points, m)
d.area = part.State.Contour.Area
}
else
{
d.hull = ConvexHull2D([...this.PlacedHull, ...TranslatePath(part.State.Contour.Points, m)])
d.area = Area(d.hull)
}
d.area = Math.abs(d.area)
}
else
{
d.area = d.box.area
}
return d
}
PutPart(p: Part, greedy = false): boolean
{
let bestD: PartPlacedContainerState
if (greedy)
{
let bestI: number
for (let i = 0; i < p.RotatedStates.length; i++)
{
p.StateIndex = i
let d = this.PrePut(p)
if (d && (!bestD || bestD.area > d.area
|| (
this._PlaceType === PlaceType.Hull
&& equaln(bestD.area, d.area, 0.1)
&& d.box.area < bestD.box.area
)
))
{
bestD = d
bestI = i
}
}
if (bestD)
p.StateIndex = bestI
}
else
bestD = this.PrePut(p)
if (bestD)
{
p.PlacePosition = bestD.p
this.PlacedBox = bestD.box ?? this.PlacedBox
this.PlacedHull = bestD.hull
this.AppendPart(p, false)
return true
}
return false
}
protected GetNFPs(part: Part, binNfp: Point[][]): Paths
{
// 合并(零件和所有已经放置零件的NFP)
let nfps: SubjectInput[] = []
for (let placedPart of this.PlacedParts)
{
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour)
if (!nfp)
return
for (let n of nfp)
{
let nnfp = TranslatePath(n, placedPart.PlacePosition)
nfps.push({ data: nnfp, closed: true })
}
}
// 合并nfp
let combinedNfp = clipperCpp.lib.clipToPaths({
subjectInputs: nfps,
clipType: ClipType.Union,
subjectFillType: PolyFillType.NonZero,
})
combinedNfp = clipperCpp.lib.cleanPolygons(combinedNfp, 100)
if (combinedNfp.length === 0)
return
// 减去nfp
let finalNfp = clipperCpp.lib.clipToPaths({
subjectInputs: [{ data: binNfp, closed: true }],
clipInputs: [{ data: combinedNfp }],
clipType: ClipType.Difference,
subjectFillType: PolyFillType.NonZero,
})
finalNfp = clipperCpp.lib.cleanPolygons(finalNfp, 100)
return finalNfp
}
/**
* Part添加的Placed列表
* @param part ,
* @param [calc] ?
*/
private AppendPart(part: Part, calc = true): void
{
this.StatusKey += `,${part.State.Contour.Id}`
this.PlacedParts.push(part)
this.PlacedArea += part.State.Contour.Area
let m = { x: part.PlacePosition.x * 1e-4, y: part.PlacePosition.y * 1e-4 }
if (calc)
{
// 凸包
if (this._PlaceType === PlaceType.Hull)
{
if (!this.PlacedHull)
this.PlacedHull = TranslatePath(part.State.Contour.Points, m)
else
this.PlacedHull = ConvexHull2D(this.PlacedHull.concat(TranslatePath(part.State.Contour.Points, m)))
}
}
if (calc || this._PlaceType !== PlaceType.Box)
{
if (this.PlacedBox)
this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(m))
else
this.PlacedBox = part.State.Contour.BoundingBox.clone().translate(m)
}
}
/**
*
*/
protected GetFarLeftP(nfp: Point[][]): Point
{
let leftP: Point
for (let path of nfp)
{
for (let p of path)
{
if (!leftP || this.CompartePoint(p, leftP) === -1)
leftP = p
}
}
return leftP
}
// #region -------------------------File-------------------------
// 对象从文件中读取数据,初始化自身
ReadFile(file: NestFiler, parts: Part[])
{
this.ParentId = file.Read()
this.ChildrenIndex = file.Read()
this.ParentM = file.Read()
let count = file.Read() as number
this.PlacedParts = []
for (let i = 0; i < count; i++)
{
let index = file.Read() as number
let part = parts[index]
part.StateIndex = file.Read()
part.PlacePosition = file.Read()
this.PlacedParts.push(part)
}
if (!this.PlacedBox)
this.PlacedBox = new Box2()
this.PlacedBox.min.fromArray(file.Read())
this.PlacedBox.max.fromArray(file.Read())
return this
}
// 对象将自身数据写入到文件.
WriteFile(file: NestFiler)
{
file.Write(this.ParentId)
file.Write(this.ChildrenIndex)
file.Write(this.ParentM)
let parts: Part[] = []
for (let p of this.PlacedParts)
{
if (p instanceof PartGroup)
parts.push(...p.Export())
else
parts.push(p)
}
file.Write(parts.length)
for (let p of parts)
{
file.Write(p.Id)
file.Write(p.StateIndex)
file.Write(p.PlacePosition)
}
if (!this.PlacedBox)
this.PlacedBox = new Box2()
file.Write(this.PlacedBox.min.toArray())
file.Write(this.PlacedBox.max.toArray())
}
// #endregion
}