Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
b7399fd6e7 | |||
![]() |
87e2804d1f | ||
cf9913469f | |||
e8ae91bb2a | |||
595675a08a | |||
![]() |
d9a3368185 | ||
![]() |
2105c4b656 | ||
![]() |
1adee71c7a | ||
![]() |
92b49c7035 | ||
![]() |
fd68920e01 | ||
![]() |
1954f8d612 | ||
![]() |
4473a9af41 | ||
![]() |
d73c260fb8 | ||
![]() |
79c3284ced | ||
![]() |
c43224ed4a |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,3 +1,2 @@
|
||||
{
|
||||
"recommendations": ["Orta.vscode-jest"]
|
||||
}
|
33
.vscode/launch.json
vendored
Normal file
33
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Vitest Browser",
|
||||
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
||||
"console": "integratedTerminal",
|
||||
"args": [
|
||||
"--inspect-brk",
|
||||
"--browser",
|
||||
"--no-file-parallelism"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"name": "Attach to Vitest Browser",
|
||||
"port": 9229
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug Vitest Browser",
|
||||
"configurations": [
|
||||
"Attach to Vitest Browser",
|
||||
"Run Vitest Browser"
|
||||
],
|
||||
"stopAll": true
|
||||
}
|
||||
]
|
||||
}
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -1,3 +1,2 @@
|
||||
{
|
||||
"jest.useJest30": true
|
||||
}
|
25
README.md
25
README.md
@@ -0,0 +1,25 @@
|
||||
## 生产接口协议
|
||||
本项目使用typescript编写,IDE推荐使用vscode。
|
||||
|
||||
### 术语表
|
||||
| 中文 | CAD | MES | IMES | 备注 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 房名 | RoomName | roomName | roomName | |
|
||||
| 柜名 | CabinetName | boxName | bodyName | |
|
||||
| 小板名 | BoardName | blockName | blockName | |
|
||||
| 材质 | Material | material | material | |
|
||||
| 大板名 | 无 | boardName | goodsName | |
|
||||
| 余料 | 无 | scrap | remain | |
|
||||
| 排单 | 无 | planOrder |planOrder | |
|
||||
|
||||
|
||||
### 编译与发布
|
||||
更新 package.json 版本号
|
||||
```shell
|
||||
pnpm clean
|
||||
pnpm build
|
||||
pnpm release
|
||||
```
|
||||
|
||||
### 开发建议
|
||||
MES与IMES存在不少命名上的差异,可以考虑 接口类型独立, 参数与配置单独创建类型
|
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': '@swc/jest',
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx']
|
||||
};
|
33
package.json
33
package.json
@@ -1,28 +1,39 @@
|
||||
{
|
||||
"name": "cut-abstractions",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"version": "0.1.5",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"files": ["dist/**/*"],
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
"exports": {
|
||||
".": "./dist/**/*"
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"release": "pnpm pack --pack-destination ./dist-packs",
|
||||
"clean":"rimraf ./dist",
|
||||
"test": "jest"
|
||||
"clean": "rimraf ./dist",
|
||||
"test": "vitest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@9.1.1+sha1.09ada6cd05003e0ced25fb716f9fda4063ec2e3b",
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.2",
|
||||
"@swc/core": "^1.12.4",
|
||||
"@swc/jest": "^0.2.38",
|
||||
"jest": "^30.0.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"cadapi": "http://gitea.cf/MES-FE/webcad-api/archive/0.0.60.tar.gz",
|
||||
"js-angusj-clipper": "^1.0.4",
|
||||
"playwright": "^1.54.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^22.16.3",
|
||||
"three": "^0.178.0"
|
||||
}
|
||||
}
|
||||
|
3341
pnpm-lock.yaml
generated
3341
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
45
samples/WorkerHelper.ts
Normal file
45
samples/WorkerHelper.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import path from 'path';
|
||||
import { Worker as NodeWorker } from 'worker_threads';
|
||||
|
||||
export class UniversalWorker {
|
||||
private worker: NodeWorker | Worker;
|
||||
|
||||
constructor(workerPath: string | URL) {
|
||||
if (typeof window !== 'undefined' && 'Worker' in window) {
|
||||
// 浏览器环境
|
||||
this.worker = new Worker(workerPath);
|
||||
} else {
|
||||
// Node.js 环境
|
||||
// const resolvedPath = require.resolve(workerPath);
|
||||
this.worker = new NodeWorker(workerPath, { eval: false });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
postMessage(message: any): void {
|
||||
this.worker.postMessage(message);
|
||||
}
|
||||
|
||||
onMessage(callback: (data: any) => void): void {
|
||||
if ('onmessage' in this.worker) {
|
||||
this.worker.onmessage = (event) => callback(event.data);
|
||||
} else {
|
||||
this.worker.on('message', callback);
|
||||
}
|
||||
}
|
||||
|
||||
onError(callback: (err: Error) => void): void {
|
||||
if ('onerror' in this.worker) {
|
||||
this.worker.onerror = (event) => callback(new Error(event.message));
|
||||
} else {
|
||||
this.worker.on('error', callback);
|
||||
}
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
this.worker.terminate();
|
||||
}
|
||||
getWorker(){
|
||||
return this.worker
|
||||
}
|
||||
}
|
152
samples/businessCapability/blockDetailHelper.ts
Normal file
152
samples/businessCapability/blockDetailHelper.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { FaceType, PlaceBlock, PlaceBlockDetail, PlaceBorderContour, PlaceMaterial, PlaceStyle, SizeExpand } from "../confClass"
|
||||
import { BlockDetailHelperBase } from "../handleAbility/blockDetailHelperBase"
|
||||
// import { PolylineHelper } from "../handleAbility/common/LayoutEngine/PolylineHelper"
|
||||
import { KnifeHelper } from "../handleAbility/knifeHelper"
|
||||
|
||||
|
||||
/** 小板明细 相关的计算 孔 造型 以及轮廓 */
|
||||
export class BlockDetailHelper extends BlockDetailHelperBase {
|
||||
/** 预铣(板件外扩)值 */
|
||||
preMillingSize = 0;
|
||||
|
||||
constructor(config?: any) {
|
||||
super()
|
||||
if(config){
|
||||
this.updateConfig(config)
|
||||
}
|
||||
}
|
||||
updateConfig(config) {
|
||||
if (config) {
|
||||
let keys = Object.keys(config)
|
||||
for (const key of keys) {
|
||||
if (Reflect.has(this, key)) {
|
||||
this[key] = config[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/** 小板初始化:1各种轮廓|走刀路径|,2尺寸扩展后的轮廓与走刀路径 */
|
||||
initBlock(block: PlaceBlock, pm: PlaceMaterial) {
|
||||
if (block.blockDetail == null) {
|
||||
// 异常情况
|
||||
console.error(`【initBlock()】${block.blockNo}没用板件明细,请检查`);
|
||||
return
|
||||
}
|
||||
if (block.blockDetail.borderContour) return // 已初始化
|
||||
|
||||
// const pm = block.placeMaterial
|
||||
|
||||
const bd = block.blockDetail
|
||||
|
||||
if (pm == null) {
|
||||
return // 异常
|
||||
}
|
||||
const cutKnifeR = pm.diameter / 2
|
||||
const cutGap = pm.cutKnifeGap
|
||||
// 开料偏移值 ,矩形波倒角
|
||||
bd.offsetKnifeRadius = pm.diameter / 2
|
||||
bd.isOffsetRounding = true // 默认要倒角
|
||||
bd.preMillingSize = pm.preMillingSize
|
||||
|
||||
if (bd.points.length == 0) // 矩形板,设置不倒角
|
||||
{
|
||||
bd.isOffsetRounding = false// sysConfig.RegularBlockFilletCurve;
|
||||
}
|
||||
else // 异形板
|
||||
{
|
||||
// 异形板倒角
|
||||
bd.isOffsetRounding = true // sysConfig.isUnRegularBlockChamfer
|
||||
}
|
||||
|
||||
// 初始化 小板基础轮廓
|
||||
// 1.原始轮廓,成品轮廓
|
||||
|
||||
const border_final = this.createFinalBorder(bd, block)
|
||||
// 2.开料轮廓 <不含预铣>
|
||||
const border_org = this.createOrgBorder(bd)
|
||||
|
||||
bd.borderContour = new PlaceBorderContour(PlaceStyle.FRONT, border_final, border_org)
|
||||
|
||||
// 3.开料轮廓_带预铣, 外扩尺寸
|
||||
bd.borderContour.borderPreMilling = border_org
|
||||
bd.preMillingExpandSize = new SizeExpand()
|
||||
// console.log('开料轮廓_带预铣, 外扩尺寸', block, bd.preMillingExpandSize, sysConfig.preMillingSize)
|
||||
if (this.preMillingSize > 0.0001) {
|
||||
const rt = this.creatOrgBorderWithPrevCut(block, border_org, this.sysConfig.preMillingSize)
|
||||
bd.borderContour.borderPreMilling = rt.newBorders
|
||||
bd.preMillingExpandSize = rt.newSizeExpand
|
||||
// console.log('开料轮廓_带预铣, 外扩尺寸==》after', block, rt.newSizeExpand)
|
||||
}
|
||||
|
||||
// 4. 板内走刀路径,板内空间
|
||||
const innerGroup = this.analyeInners(bd, cutKnifeR, cutGap)
|
||||
|
||||
bd.borderContour.borderModelThrough = innerGroup.borders_inner_org
|
||||
bd.borderContour.borderModelThroughR = innerGroup.borders_inner_r
|
||||
bd.borderContour.cutLinesModelThrough = innerGroup.borders_inner_cut
|
||||
bd.borderContour.borderInnerPlace = innerGroup.borders_inner_place
|
||||
bd.borderContour.blockInnerSpace = innerGroup.spaces
|
||||
|
||||
// 5 造型外扩轮廓, 外扩尺寸
|
||||
const { pls_model, sizeout: sizeout_model } = this.createPolyline_model(block)
|
||||
bd.borderContour.polylinesOutModel = pls_model
|
||||
bd.modelExpandSize = sizeout_model
|
||||
|
||||
// 6. 2V刀路 外扩轮廓,外扩尺寸
|
||||
const { pls_2v, sizeout_2v } = this.createPolyline_2vModel(block)
|
||||
bd.borderContour.polylines2vModel = pls_2v
|
||||
bd.vKnifeModelExpandSize = sizeout_2v
|
||||
|
||||
// 7.同刀辅助 外扩尺寸
|
||||
let isUseSameKnifeToHelpCut = this.sysConfig.helpCutStyle || 0
|
||||
let useSameKnifeToHelpCutGap = this.sysConfig.helpCutGap || 0
|
||||
const isSameKnifeToCut = isUseSameKnifeToHelpCut && useSameKnifeToHelpCutGap > 0
|
||||
if (isSameKnifeToCut == false || bd.isUseSameKnifeToHelpCut == false) {
|
||||
// 未启动同刀辅助, 或 该小板 不需要同刀辅助
|
||||
bd.useSameKnifeToHelpCutGap = 0
|
||||
bd.sameKnfieHelpExpandSize = new SizeExpand()
|
||||
}
|
||||
else {
|
||||
const gap = useSameKnifeToHelpCutGap
|
||||
bd.useSameKnifeToHelpCutGap = gap
|
||||
bd.sameKnfieHelpExpandSize = new SizeExpand({ left: gap, right: gap, under: gap, upper: gap })
|
||||
}
|
||||
// 2V刀路,预洗,同刀辅助开料,出板造型刀
|
||||
this.resetBlock(block, pm)
|
||||
}
|
||||
|
||||
/** 二维刀路初始化 */
|
||||
init2VModel(blockDetail: PlaceBlockDetail, isCNC = false, _knifeHelper: KnifeHelper) {
|
||||
for (let model of blockDetail.models) {
|
||||
if (!model.isVKnifeModel)
|
||||
continue
|
||||
let vModels: any = []
|
||||
model.VLines = vModels
|
||||
let isFaceB = model.face == FaceType.BACK
|
||||
if (model.pointList.length < 1)
|
||||
continue
|
||||
let ps = model.pointList.map((t) => { return { x: t.pointX, y: t.pointY, bul: t.curve } })
|
||||
let pl = PolylineHelper.create(ps)
|
||||
if (model.VLines?.length > 0)
|
||||
return // 已经分析了
|
||||
model.VLines = []
|
||||
for (let os of model.offsetList) {
|
||||
let knife1 = isCNC ? null : _knifeHelper.getModelKnifeByName(os.name) // getModelKnifeByName(os.name)
|
||||
let knifeR = os.radius
|
||||
let knifeId = knife1 ? knife1.knifeId : -1
|
||||
try {
|
||||
let vps_1 = PolylineHelper.getVModelPoints_offset(pl, os.offset, os.depth, os.angle)
|
||||
let vLine = { isFaceB, name: os.name, value: os.offset, knife: knife1, knifeId, knifeRadius: knifeR, depth: os.depth, points: vps_1, offset: os }
|
||||
vModels.push(vLine) // 偏移路径
|
||||
model.VLines.push(vLine)
|
||||
}
|
||||
catch (err) {
|
||||
console.log('v型刀走刀路径算法出错。' + err)
|
||||
}
|
||||
}
|
||||
model.VLines = vModels
|
||||
}
|
||||
}
|
||||
|
||||
}
|
283
samples/businessCapability/businessRectOptimizeMachine.ts
Normal file
283
samples/businessCapability/businessRectOptimizeMachine.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { ConfigBase } from "../../src/models/config";
|
||||
import { confItem, HoleArrange, Knife, PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass";
|
||||
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
|
||||
|
||||
|
||||
/** 配置列表 */
|
||||
export const confList: confItem[] = [
|
||||
{
|
||||
key: 'isCutProcess',
|
||||
label: '开料机(雕刻机)加工',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isCutAndCNCProcess',
|
||||
label: '开料机CNC组合',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'isCustomized',
|
||||
label: '定制加工',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'cutBoardBorder',
|
||||
label: '总修边宽度',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
key: 'blockKnifeLineSpacing',
|
||||
label: '刀路间距',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
key: 'isDoubleFaceBlockFirst',
|
||||
label: '双面开料优先排版',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isRectPlace',
|
||||
label: '新优化规则排版',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
// yuLiaoBoardDo2FaceBlock
|
||||
key: 'isDoubleFaceBlockInRemain',
|
||||
label: '余料板允许排入双面加工的小板',
|
||||
value: true
|
||||
},
|
||||
]
|
||||
/**
|
||||
* 新优化
|
||||
* 调用流程
|
||||
* 1、准备工作 加载配置 + 加载刀库 + 设置 回调函数
|
||||
* 2、调用 startPlaceThreed
|
||||
* 入参:data (数据源 必须包含 小板数据列表和小板明细数据列表)
|
||||
* pm 优化的材质
|
||||
* pm.diameter = knife?.diameter
|
||||
pm.cutKnifeName = knife.knifeName
|
||||
*/
|
||||
export class BusinessRectOptimizeMachine extends ConfigBase {
|
||||
/** 多线程数据集 */
|
||||
workerList: WorkerItemType[] = []
|
||||
|
||||
/** 优化回调 */
|
||||
placeCallBack: Function = () => { }
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
|
||||
}
|
||||
/** 设置优化回调 */
|
||||
setPlaceTaskCallBackFun(func: Function) {
|
||||
this.placeCallBack = func
|
||||
}
|
||||
/**开始优化线程 输入的数据没有初始化
|
||||
* @param data 数据源 包含小板列表和小板明细列表 data内需要包含blockList(小板列表) 和 blockDetailList(小板明细列表)
|
||||
* @param pm 优化的材质 包含该材质的可用开料大板信息 需包含开料刀的信息 pm.diameter pm.cutKnifeName
|
||||
*/
|
||||
startPlaceThreed(data, pm: PlaceMaterial) {
|
||||
let { blockList, blockDetailList } = data
|
||||
let bList: any = []
|
||||
|
||||
blockList.map(e => {
|
||||
if (e.goodsId == pm.goodsId) {
|
||||
bList[e.blockNo] = e
|
||||
let detail = blockDetailList.find(x => x.blockId == e.blockId)
|
||||
if (!Reflect.has(bList[e.blockNo], 'blockDetail')) {
|
||||
bList[e.blockNo].blockDetail = new PlaceBlockDetail(detail)
|
||||
}
|
||||
// bList[e.blockNo].isTurnFaceToPlace = !this.getDoFace(bList[e.blockNo], this.processMode())
|
||||
// 是否翻面后续处理
|
||||
bList[e.blockNo].isTurnFaceToPlace = true
|
||||
pm.blockList.push(e)
|
||||
}
|
||||
})
|
||||
pm.cutBorder = this.cutBoardBorder
|
||||
pm.cutKnifeGap = this.blockKnifeLineSpacing
|
||||
|
||||
/** 小板 */
|
||||
let bans: xbang[] = []
|
||||
|
||||
// 实际开料大板的列表
|
||||
let big_Bang: Big_bang[] = []
|
||||
let big_BangSL: number[] = []
|
||||
|
||||
|
||||
let border = this.cutBoardBorder
|
||||
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
|
||||
|
||||
// 余料板 以及实际开料大板
|
||||
for (const cuttingBoard of pm.remainBoardList) {
|
||||
big_Bang.push({ w: cuttingBoard.width - border * 2 + borderOff * 2, l: cuttingBoard.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
big_BangSL.push(cuttingBoard.count || 999)
|
||||
}
|
||||
// big_Bang = []
|
||||
// big_BangSL = []
|
||||
// 母板 兜底的
|
||||
big_Bang.push({ w: pm.width - border * 2 + borderOff * 2, l: pm.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
// 生成小板
|
||||
for (let key in bList) {
|
||||
let b = bList[key]
|
||||
|
||||
let bid = b.blockNo
|
||||
|
||||
let width = b.placeFullWidth
|
||||
let length = b.placeFullLength
|
||||
let line = this.toLine(b.texture)
|
||||
|
||||
bans.push({
|
||||
l: length,
|
||||
w: width,
|
||||
line,
|
||||
face: this.toface(b),
|
||||
id: bid,
|
||||
bno: b.blockNo,
|
||||
holeFaceCount: 3,
|
||||
isRect: !b.isUnRegular,
|
||||
hasHole: false,
|
||||
isdtwosided: true,
|
||||
})
|
||||
}
|
||||
|
||||
let bestCount = 0
|
||||
if (bans.length == 0) // 没有板了
|
||||
{
|
||||
let best = []
|
||||
let yl: Big_bang[] = []
|
||||
let fit = 0
|
||||
let resObj = {
|
||||
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
|
||||
info: {
|
||||
times: -1,
|
||||
type: 'noBan'
|
||||
}
|
||||
}
|
||||
this.placeCallBack(resObj)
|
||||
return
|
||||
}
|
||||
let xyhcs = 50
|
||||
if (bans.length > 1000) { xyhcs = 40 }
|
||||
else if (bans.length > 1500) { xyhcs = 30 }
|
||||
else if (bans.length > 2000) { xyhcs = 25 }
|
||||
else if (bans.length > 4000) { xyhcs = 20 }
|
||||
else if (bans.length > 6000) { xyhcs = 15 }
|
||||
else if (bans.length > 10000) { xyhcs = 10 }
|
||||
else if (bans.length > 15000) { xyhcs = 5 }
|
||||
else if (bans.length > 20000) { xyhcs = 1 }
|
||||
let isDoubleFaceBlockFirst = this.isDoubleFaceBlockFirst // 双面加工排前面
|
||||
let gap = this.blockKnifeLineSpacing
|
||||
// this.bestfit = 0
|
||||
|
||||
for (let j = 0; j < 1; j++) {
|
||||
|
||||
let w = new Worker(new URL('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker', import.meta.url), { type: 'module' })
|
||||
const data = {
|
||||
type: 'start',
|
||||
data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.isRectPlace, this.isDoubleFaceBlockInRemain],
|
||||
}
|
||||
let item: WorkerItemType = {
|
||||
w: w,
|
||||
goodsId: pm.goodsId,
|
||||
pm,
|
||||
status: 'start'
|
||||
}
|
||||
if (this.workerList.findIndex(e => e.goodsId == item.goodsId) == -1) {
|
||||
this.workerList.push(item)
|
||||
}
|
||||
|
||||
|
||||
let workItem = this.workerList.find(e => e.goodsId == pm.goodsId)
|
||||
if (workItem && workItem != undefined) {
|
||||
workItem.w?.postMessage(data)
|
||||
|
||||
workItem.w.onmessage = async (d) => {
|
||||
let [best, yl, fit, info] = d.data as [any[], Big_bang[], number, any]
|
||||
|
||||
switch (info.type) {
|
||||
case 'loop':
|
||||
let resObj = {
|
||||
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
|
||||
info
|
||||
}
|
||||
this.placeCallBack(resObj)
|
||||
break;
|
||||
case 'stop':
|
||||
console.error('stop =》dataHandleBase', info, this.workerList)
|
||||
this.terminateWorker({ goodsId: pm.goodsId })
|
||||
break;
|
||||
case 'isStop':
|
||||
// console.error('isStop', info)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log('新算法线程启动', pm.goodsId, this.workerList)
|
||||
}
|
||||
}
|
||||
/** 停止优化 */
|
||||
async stopPlaceTask() {
|
||||
|
||||
for (const key in this.workerList) {
|
||||
await this.terminateWorker({ key })
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取加工面
|
||||
* @param block
|
||||
* @param processMode
|
||||
* @returns
|
||||
*/
|
||||
getDoFace(block: PlaceBlock, processMode: number = 0): boolean {
|
||||
// 模式0: 开料机处理排钻, 造型, 采用大孔面作为正面的模式. 正面多加工, cnc多加工
|
||||
if (processMode == 0) {
|
||||
// 先考虑 设计开料面
|
||||
if (block.placeHole == HoleArrange.FRONT)
|
||||
return true
|
||||
if (block.placeHole == HoleArrange.BACK)
|
||||
return false
|
||||
// 造型单面 作为开料面
|
||||
if (block.modelCountFront() > 0 && block.modelCountBack() == 0)
|
||||
return true
|
||||
if (block.modelCountFront() == 0 && block.modelCountBack() > 0)
|
||||
return false
|
||||
|
||||
// 优先考虑 大孔面 多孔面
|
||||
return this.getHoleFaceMore(block)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
processMode() {
|
||||
let processMode = 0;
|
||||
if (this.isCutProcess) processMode = 0;
|
||||
if (this.isCutAndCNCProcess) processMode = 1;
|
||||
if (this.isCustomized) processMode = 2;
|
||||
return processMode;
|
||||
}
|
||||
/**
|
||||
* 正纹 = 0, 可翻转 = 1, 反纹 = 2;
|
||||
* 正文 Positive = 0, 反纹 Reverse = 1, 可翻转 CanReversal = 2,
|
||||
*/
|
||||
toLine(texture): LineType {
|
||||
if (texture == 0)
|
||||
return LineType.Positive
|
||||
if (texture == 1)
|
||||
return LineType.CanReversal
|
||||
return LineType.Reverse
|
||||
}
|
||||
/** 小板加工面:Positive=0正面 Reverse=1反面 Arbitrary=2任意 */
|
||||
toface(block: PlaceBlock): ComposingType {
|
||||
let turnF = block.isTurnFaceToPlace
|
||||
if (this.isTurnFaceToPlace)
|
||||
turnF = !turnF
|
||||
if (!turnF)
|
||||
return ComposingType.Positive
|
||||
return ComposingType.Reverse
|
||||
}
|
||||
}
|
0
samples/businessCapability/业务功能目录.md
Normal file
0
samples/businessCapability/业务功能目录.md
Normal file
2467
samples/confClass.ts
2467
samples/confClass.ts
File diff suppressed because it is too large
Load Diff
33
samples/demoDataHandleServer.ts
Normal file
33
samples/demoDataHandleServer.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Processor, ProcessorCollection, ProcessorModule } from "../src/device";
|
||||
|
||||
|
||||
export class ProcessorManager<T, R> implements ProcessorCollection<T, R> {
|
||||
private processors = new Map<string, Processor<T, R>>();
|
||||
private currentProcessor?: Processor<T, R>;
|
||||
|
||||
constructor(){
|
||||
|
||||
}
|
||||
/** 注册模块流程 */
|
||||
registerProcessor(name: string, processor: Processor<T, R>): this {
|
||||
this.processors.set(name, processor);
|
||||
return this;
|
||||
}
|
||||
/** 使用处理器 */
|
||||
useProcessor(name: string): Processor<T, R> {
|
||||
const processor = this.processors.get(name);
|
||||
if (!processor) {
|
||||
throw new Error(`Processor ${name} not found`);
|
||||
}
|
||||
this.currentProcessor = processor;
|
||||
return processor;
|
||||
}
|
||||
/** 获取处理器 */
|
||||
getProcessor(name: string): Processor<T, R> | undefined {
|
||||
return this.processors.get(name);
|
||||
}
|
||||
/** 获取正在使用的处理器 */
|
||||
getCurrentProcessor(): Processor<T, R> | undefined {
|
||||
return this.currentProcessor;
|
||||
}
|
||||
}
|
111
samples/demoDatahandle/demoDataHandle1.ts
Normal file
111
samples/demoDatahandle/demoDataHandle1.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
import { ProcessorModule, StepControllerProcessor, Processor } from "../../src/device";
|
||||
import { RectOptimizeMachineModule } from "../moduleManager/module_RectOptimizeMachine";
|
||||
import { ToolsModule } from "../moduleManager/module_tools";
|
||||
import { CheckMaterial } from "../moduleManager/module_checkMaterial";
|
||||
import { CheckBlocks } from "../moduleManager/module_checkBlocks";
|
||||
import { ResetModelContour } from "../moduleManager/module_resetModelContour";
|
||||
import { AutoCalcCutOrder } from "../moduleManager/module_autoCalcCutOrder";
|
||||
import { HandleMaterialPlaceResult } from "../moduleManager/module_handleMaterialPlaceResult";
|
||||
import { handlePlaceResultToPlaceMaterial } from "../moduleManager/module_handlePlaceResultToPlaceMaterial";
|
||||
// import { Init2VModel } from "../moduleManager/module_init2VModel";
|
||||
/**
|
||||
* demo 开料机处理器
|
||||
*
|
||||
*
|
||||
*/
|
||||
export class demoHandleGroupCutting {
|
||||
processorName = "cutting"
|
||||
processor: StepControllerProcessor<any, any>
|
||||
constructor() {
|
||||
// 主线程
|
||||
const demoCallbackModule: ProcessorModule<any, any> = {
|
||||
moduleName: "callbackStyle",
|
||||
process(input, next, context) {
|
||||
|
||||
console.log("做优化");
|
||||
const _input = input
|
||||
const _next = next
|
||||
const _context = context
|
||||
// 可以在这里调用异步操作
|
||||
|
||||
Reflect.set(context, 'CallBack', callBack1)
|
||||
// 决定是否调用 next
|
||||
this.onMessage('测试传消息给处理器');
|
||||
|
||||
function callBack1(v) {
|
||||
console.log('接收到其它模块回传的数据');
|
||||
}
|
||||
|
||||
// 调用 next 继续流程
|
||||
return next(input);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.processor = new StepControllerProcessor<any, any>();
|
||||
this.processor.setOnMessageFunc(this.getMessageByModules)
|
||||
|
||||
this.processor.use([
|
||||
// demoCallbackModule,
|
||||
ToolsModule, // 刀库
|
||||
// CheckMaterial,
|
||||
// CheckBlocks,
|
||||
// ResetModelContour,
|
||||
// Init2VModel,
|
||||
RectOptimizeMachineModule, // 优化
|
||||
// {
|
||||
// moduleName: "final",
|
||||
// process(input, next,context) {
|
||||
// // 不调用 next,终止流程
|
||||
// console.log('结束了')
|
||||
// return next(input);
|
||||
// }
|
||||
// }
|
||||
])
|
||||
|
||||
|
||||
}
|
||||
// 获取到模块的消息
|
||||
async getMessageByModules(data) {
|
||||
console.log('getMessageByModules', data);
|
||||
this.processor = new StepControllerProcessor<any, any>();
|
||||
switch (data.moduleName) {
|
||||
// 处理优化模块的消息
|
||||
case 'RectOptimizeMachine':
|
||||
|
||||
// 处理优化数据
|
||||
this.processor.use([HandleMaterialPlaceResult,
|
||||
handlePlaceResultToPlaceMaterial,
|
||||
AutoCalcCutOrder])
|
||||
this.processor.setOnMessageFunc(this.getMessageByModules)
|
||||
let bList = []
|
||||
data.pm.blockList.forEach(b => {
|
||||
bList[b.blockNo] = b
|
||||
});
|
||||
let params = {
|
||||
bList: bList,
|
||||
best:data.result[0],
|
||||
yl:data.result[1],
|
||||
pm:data.pm,
|
||||
width:data.pm.width,
|
||||
length:data.pm.length
|
||||
}
|
||||
let context = {
|
||||
MaterialPlaceSource:params
|
||||
}
|
||||
let res = await this.processor.process(data.input,context)
|
||||
|
||||
console.log(res);
|
||||
|
||||
// this.processor.use(handlePlaceResultToPlaceMaterial)
|
||||
// let params1 = res
|
||||
// let res1 = await this.processor.process(res)
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
18
samples/demoDatahandle/demoDataHandle2.ts
Normal file
18
samples/demoDatahandle/demoDataHandle2.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DeviceBase } from "../../src/device";
|
||||
import { ConfigBase } from "../../src/models/config";
|
||||
import { BusinessRectOptimizeMachine } from "../businessCapability/businessRectOptimizeMachine";
|
||||
|
||||
/**
|
||||
* 某个处理器 一个 功能 和 业务功能的集合
|
||||
* 可以包含多个业务功能和 基础功能
|
||||
*
|
||||
*
|
||||
*/
|
||||
export class demoHandleGroup extends DeviceBase {
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import { _knifeType } from "./confClass"
|
||||
import { _knifeType, Knife } from "./confClass"
|
||||
|
||||
/** demo 刀具数据 */
|
||||
export const knifeData :_knifeType= {
|
||||
export const knifeData :Knife= {
|
||||
"isEnabled": true,
|
||||
"axleId": 2,
|
||||
"knifeId": 2,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
|
||||
import { ConfigBase } from "../src/base";
|
||||
import { ConfigBase } from "../src";
|
||||
import { ParserBase } from "../src/parsers";
|
||||
import { _knifeType, CodeParams } from "./confClass";
|
||||
import { knifeData, knifeData1, knifeData2 } from "./demoKnives";
|
||||
@@ -18,6 +18,8 @@ export class DemoParser extends ParserBase {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
const gcodeActions = new GCodeAction();
|
||||
this.codeManager['FSTART'] = {
|
||||
name: 'FileStart',
|
||||
|
1746
samples/handleAbility/RectOptimizeWorker/RectOptimizeMachine.ts
Normal file
1746
samples/handleAbility/RectOptimizeWorker/RectOptimizeMachine.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
import type { Big_bang, xbang } from './bang'
|
||||
import { RectOptimizeMachine } from './RectOptimizeMachine'
|
||||
// import {Worker} from "worker_threads"
|
||||
import { Worker as NodeWorker } from 'worker_threads';
|
||||
const ctx: NodeWorker | Worker = self as any
|
||||
if (typeof window !== 'undefined' && 'Worker' in window) {
|
||||
ctx.addEventListener('message', async (event) => {
|
||||
let m = new RectOptimizeMachine()
|
||||
m.CallBack = async (best, fit, arg, info) => {
|
||||
|
||||
ctx.postMessage([best, fit, arg, info])
|
||||
}
|
||||
if (event.data.type == 'start') {
|
||||
/**
|
||||
* blockList 小板列表
|
||||
* boardList 大板(N个元素,前N-1个元素表示余料板且余料板须为矩形,第N个元素表示大板)
|
||||
* boardCount 余料板数量(bigBang中前N-1个元素对应的数量,如果bigBang中只有一个元素即只有大板没有余料板,则为空数组)
|
||||
* optimizeTimes 新优化次数
|
||||
* isDoubleFaceBlockFirst 双面加工的小板是否优先排入
|
||||
* gap 排版缝隙 = 开料刀直径 + 缝隙
|
||||
* gzpb 规则排版
|
||||
* isDoubleFaceBlockInRemain 余料板是否排入双面加工的小板
|
||||
*/
|
||||
let [blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain] = (event.data.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean]
|
||||
|
||||
m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain)
|
||||
} else {
|
||||
const info = {
|
||||
type: 'isStop',
|
||||
}
|
||||
await m.Stop(info)
|
||||
ctx.postMessage([[], null, null, info])
|
||||
ctx?.terminate()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default {} as typeof Worker & (new () => Worker)
|
115
samples/handleAbility/RectOptimizeWorker/bang.ts
Normal file
115
samples/handleAbility/RectOptimizeWorker/bang.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
|
||||
export type Con = YH_bang[];//单块1220*2440的板结果 Container
|
||||
export type Inv = Con[]; //个体:优化结果 包含多个大板 Individual
|
||||
|
||||
/**纹路类型 Positive=0正纹 Reverse=1反纹 CanReversal=2可翻转 */
|
||||
export enum LineType
|
||||
{
|
||||
/**正纹 */
|
||||
Positive = 0,
|
||||
/**反纹 */
|
||||
Reverse = 1,
|
||||
/**可翻转 */
|
||||
CanReversal = 2,
|
||||
}
|
||||
|
||||
/**优化的大板 */
|
||||
export interface YH_bang
|
||||
{
|
||||
/**板ID */
|
||||
bangid: number;
|
||||
/**纹路 */
|
||||
line: LineType;
|
||||
x: number;
|
||||
y: number;
|
||||
/**排版高 */
|
||||
pbg: number;
|
||||
/**排版宽 */
|
||||
pbk: number;
|
||||
|
||||
ishb?: boolean;//是否参与合并的板
|
||||
hb?: number[]; //合在并的板
|
||||
isgr?: boolean; //是否关连
|
||||
gr?: number[];//关联的板的集合
|
||||
grid?: number; //跟别的板关联的ID
|
||||
isbig?: boolean;//是否为合并的大板
|
||||
isqg?: boolean;//是否被切掉的板
|
||||
}
|
||||
|
||||
/**版面类型: Positive=0正面 Reverse=1反面 Arbitrary=2任意面 */
|
||||
export enum ComposingType
|
||||
{
|
||||
/**正面 */
|
||||
Positive = 0,
|
||||
/**反面 */
|
||||
Reverse = 1,
|
||||
/**任意面 */
|
||||
Arbitrary = 2
|
||||
}
|
||||
|
||||
/**孔类型 None=0无 Positive=1正面 Reverse=2反面 Two=3正反 */
|
||||
export enum HoleType
|
||||
{
|
||||
/**无 */
|
||||
None = 0,
|
||||
/**正面 */
|
||||
Positive = 1,
|
||||
/**反面 */
|
||||
Reverse = 2,
|
||||
/**正反 */
|
||||
Two = 3
|
||||
}
|
||||
|
||||
/** 小板 */
|
||||
export interface xbang
|
||||
{
|
||||
/**长 */
|
||||
l: number;
|
||||
/**宽 */
|
||||
w: number;
|
||||
/**纹路 */
|
||||
line: LineType;
|
||||
/**排版面 */
|
||||
face: ComposingType;
|
||||
/**小板ID */
|
||||
id: number;
|
||||
/**小板编号 */
|
||||
bno: string;
|
||||
/**孔面: 0无孔 1正面有孔 2反面有孔 3正反面都有孔 */
|
||||
holeFaceCount: HoleType;
|
||||
/**是矩形 */
|
||||
isRect?: boolean;
|
||||
/**有孔 */
|
||||
hasHole?: boolean;
|
||||
/**false单面 true双面 */
|
||||
isdtwosided?: boolean;
|
||||
}
|
||||
|
||||
/** 大板 */
|
||||
export class Big_bang //待优化的板
|
||||
{
|
||||
l: number; //长
|
||||
w: number; //宽
|
||||
x: number; //x
|
||||
y: number; //y
|
||||
}
|
||||
|
||||
|
||||
export enum BlockRegion
|
||||
{
|
||||
/** 左下 = 0 */
|
||||
LEFT_BOTTOM = 0,
|
||||
/** 右下 = 1 */
|
||||
RIGHT_BOTTOM = 1,
|
||||
/** 右上 = 2 */
|
||||
RIGHT_TOP = 2,
|
||||
/** 左上 = 3 */
|
||||
LEFT_TOP = 3,
|
||||
}
|
||||
|
||||
export class WorkerItemType {
|
||||
w?: Worker
|
||||
goodsId?: string | number
|
||||
pm?: any
|
||||
status?: 'start' | 'stop'
|
||||
}
|
1
samples/handleAbility/RectOptimizeWorker/新优化.md
Normal file
1
samples/handleAbility/RectOptimizeWorker/新优化.md
Normal file
@@ -0,0 +1 @@
|
||||
# 新优化
|
13
samples/handleAbility/ability_1.ts
Normal file
13
samples/handleAbility/ability_1.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ConfigBase } from "../../src/models/config";
|
||||
|
||||
/** 基础功能1 -- 新优化 */
|
||||
export class BlockPlace extends ConfigBase{
|
||||
// 优化过程中的回调函数
|
||||
callBack?:Function
|
||||
constructor(){
|
||||
super()
|
||||
this.name = 'XXX'
|
||||
this.version = '20250707'
|
||||
}
|
||||
|
||||
}
|
132
samples/handleAbility/blockDetailHelperBase.ts
Normal file
132
samples/handleAbility/blockDetailHelperBase.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { FaceType, PlaceBlock, PlaceBlockDetail, PlaceBorderContour, PlaceMaterial, PlaceStyle, SizeExpand } from "../confClass"
|
||||
import { Arc2d, Curve2d, Line2d } from "./common/base/CAD"
|
||||
import { PolylineHelper } from "./common/LayoutEngine/PolylineHelper"
|
||||
import { KnifeHelper } from "./knifeHelper"
|
||||
|
||||
/** 小板明细 相关的计算 孔 造型 以及轮廓 */
|
||||
export class BlockDetailHelperBase {
|
||||
|
||||
/** 造型轮廓(含封边),扣除封边, 变成开料坐标 */
|
||||
resetModelContour(bd: PlaceBlockDetail) {
|
||||
let ox = bd.offsetX
|
||||
let oy = bd.offsetY
|
||||
for (let m of bd.models) {
|
||||
if (m.hasContour()) {
|
||||
let ptsArr = m.originModeling.outline.map(e => e.pts)
|
||||
for (let pt of ptsArr) {
|
||||
// 23.8.5 发现矩形的挖穿轮廓坐标是不含封边的
|
||||
pt.x -= ox
|
||||
pt.y -= oy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 原始轮廓,成品轮廓
|
||||
createFinalBorder(bd: PlaceBlockDetail, block: PlaceBlock): Curve2d[] {
|
||||
const orgPoints = bd.orgPoints
|
||||
const orgBorderCurveList = new Array<Curve2d>()
|
||||
if (orgPoints && orgPoints.length > 1) // 异形
|
||||
{
|
||||
|
||||
const count = orgPoints.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p0 = orgPoints[i]
|
||||
const p1 = i == count - 1 ? orgPoints[0] : orgPoints[i + 1]
|
||||
const sideHoleCount = bd.holeListSide.filter(t => t.faceId == i).length
|
||||
if (p0.curve == 0) // 直线
|
||||
{
|
||||
const newLine = Line2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY)
|
||||
newLine.tagData = p0.sealSize
|
||||
newLine.tagData2 = sideHoleCount
|
||||
orgBorderCurveList.push(newLine)
|
||||
p0.radius = 0
|
||||
}
|
||||
else // 曲线
|
||||
{
|
||||
const crc = Arc2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY, p0.curve)
|
||||
crc.tagData = p0.sealSize
|
||||
crc.tagData2 = sideHoleCount
|
||||
p0.radius = crc.m_Radius
|
||||
orgBorderCurveList.push(crc)
|
||||
}
|
||||
}
|
||||
}
|
||||
else // 矩形板
|
||||
{
|
||||
const w = block.width
|
||||
const l = block.length
|
||||
|
||||
const line0 = Line2d.New(0, 0, w, 0)
|
||||
line0.tagData = block.sealBottom
|
||||
line0.tagData2 = block.holeCountBottom() || 0
|
||||
|
||||
const line1 = Line2d.New(w, 0, w, l)
|
||||
line1.tagData = block.sealRight
|
||||
line1.tagData2 = block.holeCountRight() || 0
|
||||
|
||||
const line2 = Line2d.New(w, l, 0, l)
|
||||
line2.tagData = block.sealTop
|
||||
line2.tagData2 = block.holeCountTop() || 0
|
||||
|
||||
const line3 = Line2d.New(0, l, 0, 0)
|
||||
line3.tagData = block.sealLeft
|
||||
line3.tagData2 = block.holeCountLeft() || 0
|
||||
|
||||
orgBorderCurveList.push(line0)
|
||||
orgBorderCurveList.push(line1)
|
||||
orgBorderCurveList.push(line2)
|
||||
orgBorderCurveList.push(line3)
|
||||
}
|
||||
return orgBorderCurveList
|
||||
}
|
||||
/** 创建 开料轮廓不含预铣 */
|
||||
createOrgBorder(bd: PlaceBlockDetail): Curve2d[] {
|
||||
const borders = new Array<Curve2d>()
|
||||
if (bd.points && bd.points.length > 1) // 异形
|
||||
{
|
||||
const count = bd.points.length
|
||||
for (let i = 0; i < count - 1; i++) // 异形点(无封边,起点终点 是重复的)
|
||||
{
|
||||
const p0 = bd.points[i]
|
||||
const p1 = i == count - 1 ? bd.points[0] : bd.points[i + 1]
|
||||
const sideHoleCount = bd.holeListSide.filter(t => t.faceId == i).length
|
||||
if (p0.curve == 0) // 直线
|
||||
{
|
||||
const newLine = Line2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY)
|
||||
newLine.tagData = p0.sealSize
|
||||
newLine.tagData2 = sideHoleCount
|
||||
borders.push(newLine)
|
||||
p0.radius = 0
|
||||
}
|
||||
else // 曲线
|
||||
{
|
||||
const crc = Arc2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY, p0.curve)
|
||||
crc.tagData = p0.sealSize
|
||||
crc.tagData2 = sideHoleCount
|
||||
p0.radius = crc.m_Radius
|
||||
if (p0.radius < 2) {
|
||||
p0.curve = 0
|
||||
p0.radius = 0
|
||||
}
|
||||
borders.push(crc)
|
||||
}
|
||||
}
|
||||
}
|
||||
else // 矩形板
|
||||
{
|
||||
const w = bd.cutWidth
|
||||
const l = bd.cutLength
|
||||
const line0 = Line2d.New(0, 0, w, 0)
|
||||
const line1 = Line2d.New(w, 0, w, l)
|
||||
const line2 = Line2d.New(w, l, 0, l)
|
||||
const line3 = Line2d.New(0, l, 0, 0)
|
||||
|
||||
borders.push(line0)
|
||||
borders.push(line1)
|
||||
borders.push(line2)
|
||||
borders.push(line3)
|
||||
}
|
||||
|
||||
return borders
|
||||
}
|
||||
}
|
4
samples/handleAbility/blockHelper.ts
Normal file
4
samples/handleAbility/blockHelper.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/** 小板相关的计算 */
|
||||
export class BlockHelper{
|
||||
|
||||
}
|
365
samples/handleAbility/common/ArrayExt.js
Normal file
365
samples/handleAbility/common/ArrayExt.js
Normal file
@@ -0,0 +1,365 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.arrayRemoveDuplicateBySort = exports.GroupItem = exports.ArrayExt = exports.List = void 0;
|
||||
var List = /** @class */ (function (_super) {
|
||||
__extends(List, _super);
|
||||
function List() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
/** 返回符合条件的第一个元素 */
|
||||
List.prototype.first = function (fn) {
|
||||
if (this.length == 0)
|
||||
return null;
|
||||
for (var _i = 0, _a = this; _i < _a.length; _i++) {
|
||||
var item = _a[_i];
|
||||
if (fn(item))
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/** 返回符合条件的最后一元素 */
|
||||
List.prototype.last = function (fn) {
|
||||
if (this.length == 0)
|
||||
return null;
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
if (fn(this[i]))
|
||||
return this[i];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/** 最大值 */
|
||||
List.prototype.max = function (fn) {
|
||||
var maxV;
|
||||
for (var _i = 0, _a = this; _i < _a.length; _i++) {
|
||||
var i = _a[_i];
|
||||
var v = fn(i);
|
||||
if (maxV == null) {
|
||||
maxV = fn(i);
|
||||
}
|
||||
else {
|
||||
if (v > maxV) {
|
||||
maxV = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxV;
|
||||
};
|
||||
/** 最小值 */
|
||||
List.prototype.min = function (fn) {
|
||||
var minV;
|
||||
for (var _i = 0, _a = this; _i < _a.length; _i++) {
|
||||
var i = _a[_i];
|
||||
var v = fn(i);
|
||||
if (minV == null) {
|
||||
minV = fn(i);
|
||||
}
|
||||
else {
|
||||
if (v < minV) {
|
||||
minV = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return minV;
|
||||
};
|
||||
/** 累加 */
|
||||
List.prototype.sum = function (fn) {
|
||||
var v = 0;
|
||||
for (var _i = 0, _a = this; _i < _a.length; _i++) {
|
||||
var t = _a[_i];
|
||||
v = v + fn(t);
|
||||
}
|
||||
return v;
|
||||
};
|
||||
/** 平均值 */
|
||||
List.prototype.avg = function (fn) {
|
||||
if (this.length == 0)
|
||||
return 0;
|
||||
var sum = this.sum(fn);
|
||||
return sum / this.length;
|
||||
};
|
||||
/** 满足条件的元素数量 */
|
||||
List.prototype.count = function (fn) {
|
||||
if (this.length == 0)
|
||||
return 0;
|
||||
var c = 0;
|
||||
for (var _i = 0, _a = this; _i < _a.length; _i++) {
|
||||
var item = _a[_i];
|
||||
if (fn(item))
|
||||
c = c + 1;
|
||||
}
|
||||
return c;
|
||||
};
|
||||
List.prototype.FindMax = function (fn) {
|
||||
return this.reduce(function (a, b) { return fn(a) > fn(b) ? a : b; });
|
||||
};
|
||||
return List;
|
||||
}(Array));
|
||||
exports.List = List;
|
||||
var ArrayExt = /** @class */ (function () {
|
||||
function ArrayExt() {
|
||||
}
|
||||
/** 返回满足条件的元素数量 */
|
||||
ArrayExt.count = function (list, fn) {
|
||||
if (list.length == 0)
|
||||
return 0;
|
||||
var c = 0;
|
||||
for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {
|
||||
var item = list_1[_i];
|
||||
if (fn(item))
|
||||
c = c + 1;
|
||||
}
|
||||
return c;
|
||||
};
|
||||
/** 移除 */
|
||||
ArrayExt.remove = function (list, obj) {
|
||||
var index = list.findIndex(function (t) { return t == obj; });
|
||||
if (index == -1)
|
||||
return;
|
||||
list.splice(index, 1);
|
||||
};
|
||||
/** 返回符合条件的第一个元素 */
|
||||
ArrayExt.first = function (list, fn, orderFn1, orderFn2) {
|
||||
if (orderFn1 === void 0) { orderFn1 = null; }
|
||||
if (orderFn2 === void 0) { orderFn2 = null; }
|
||||
if (list.length == 0)
|
||||
return null;
|
||||
if (orderFn1 == null) {
|
||||
for (var _i = 0, list_2 = list; _i < list_2.length; _i++) {
|
||||
var item = list_2[_i];
|
||||
if (fn(item))
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var minValue1;
|
||||
var minValue2;
|
||||
var minItem;
|
||||
for (var _a = 0, list_3 = list; _a < list_3.length; _a++) {
|
||||
var item = list_3[_a];
|
||||
if (fn(item) == false)
|
||||
continue;
|
||||
var v1 = orderFn1(item);
|
||||
var v2 = orderFn2 != null ? orderFn2(item) : 0;
|
||||
if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2)) {
|
||||
minValue1 = v1;
|
||||
minValue2 = v2;
|
||||
minItem = item;
|
||||
}
|
||||
}
|
||||
return minItem;
|
||||
};
|
||||
/** 返回符合条件的最后一元素 */
|
||||
ArrayExt.last = function (list, fn, orderFn1, orderFn2) {
|
||||
if (orderFn1 === void 0) { orderFn1 = null; }
|
||||
if (orderFn2 === void 0) { orderFn2 = null; }
|
||||
if (list.length == 0)
|
||||
return null;
|
||||
if (orderFn1 == null) {
|
||||
for (var i = list.length - 1; i >= 0; i--) {
|
||||
if (fn(list[i]))
|
||||
return list[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//
|
||||
var maxValue1;
|
||||
var maxValue2;
|
||||
var maxItem;
|
||||
for (var _i = 0, list_4 = list; _i < list_4.length; _i++) {
|
||||
var item = list_4[_i];
|
||||
if (fn(item) == false)
|
||||
continue;
|
||||
var v1 = orderFn1(item);
|
||||
var v2 = orderFn2 ? orderFn2(item) : 0;
|
||||
if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2)) {
|
||||
maxValue1 = v1;
|
||||
maxValue2 = v2;
|
||||
maxItem = item;
|
||||
}
|
||||
}
|
||||
return maxItem;
|
||||
};
|
||||
/** 取最大值 */
|
||||
ArrayExt.max = function (list, fn, whereF, defaultV) {
|
||||
if (whereF === void 0) { whereF = null; }
|
||||
if (defaultV === void 0) { defaultV = null; }
|
||||
var maxV;
|
||||
for (var _i = 0, list_5 = list; _i < list_5.length; _i++) {
|
||||
var i = list_5[_i];
|
||||
if (whereF && whereF(i) == false)
|
||||
continue;
|
||||
var v = fn(i);
|
||||
if (maxV == undefined) {
|
||||
maxV = fn(i);
|
||||
}
|
||||
else {
|
||||
if (v > maxV) {
|
||||
maxV = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxV != undefined)
|
||||
return maxV;
|
||||
return defaultV;
|
||||
};
|
||||
/** 最小值 */
|
||||
ArrayExt.min = function (list, fn, whereF, defaultV) {
|
||||
if (whereF === void 0) { whereF = null; }
|
||||
if (defaultV === void 0) { defaultV = null; }
|
||||
var minV;
|
||||
for (var _i = 0, list_6 = list; _i < list_6.length; _i++) {
|
||||
var i = list_6[_i];
|
||||
if (whereF && whereF(i) == false)
|
||||
continue;
|
||||
var v = fn(i);
|
||||
if (minV == undefined) {
|
||||
minV = v;
|
||||
}
|
||||
else {
|
||||
if (v < minV) {
|
||||
minV = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minV != undefined)
|
||||
return minV;
|
||||
return defaultV;
|
||||
};
|
||||
/** 累加 */
|
||||
ArrayExt.sum = function (list, fn, wn) {
|
||||
var v = 0;
|
||||
for (var _i = 0, list_7 = list; _i < list_7.length; _i++) {
|
||||
var t = list_7[_i];
|
||||
if (wn && wn(t) == false)
|
||||
continue;
|
||||
v = v + fn(t);
|
||||
}
|
||||
return v;
|
||||
};
|
||||
/** 平均值 */
|
||||
ArrayExt.avg = function (list, fn) {
|
||||
if (this.length == 0)
|
||||
return 0;
|
||||
var sum = ArrayExt.sum(list, fn);
|
||||
return sum / this.length;
|
||||
};
|
||||
/** 排序 */
|
||||
ArrayExt.sortBy = function (list, fn, fn2) {
|
||||
if (fn2 === void 0) { fn2 = null; }
|
||||
if (fn2 == null)
|
||||
return list.sort(function (a, b) { return fn(a) - fn(b); });
|
||||
else
|
||||
return list.sort(function (a, b) { return fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b); });
|
||||
};
|
||||
/** 降序 排序 */
|
||||
ArrayExt.sortByDescending = function (list, fn) {
|
||||
list.sort(function (a, b) { return fn(b) - fn(a); });
|
||||
};
|
||||
/** 排序成新的数组 */
|
||||
ArrayExt.orderBy = function (list, fn, fn2) {
|
||||
if (fn2 === void 0) { fn2 = null; }
|
||||
var newList = list.concat([]);
|
||||
if (fn2 == null)
|
||||
return newList.sort(function (a, b) { return fn(a) - fn(b); });
|
||||
else
|
||||
return newList.sort(function (a, b) { return fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b); });
|
||||
};
|
||||
/** 降序成新的数组 */
|
||||
ArrayExt.orderByDescending = function (list, fn, fn2) {
|
||||
if (fn2 === void 0) { fn2 = null; }
|
||||
var newList = list.concat([]);
|
||||
if (fn2 == null)
|
||||
return list.sort(function (a, b) { return fn(b) - fn(a); });
|
||||
else
|
||||
return list.sort(function (a, b) { return fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a); });
|
||||
};
|
||||
/** 分组 */
|
||||
ArrayExt.groupBy = function (list, fn) {
|
||||
var groups = new Array();
|
||||
var _loop_1 = function (item) {
|
||||
var key = fn(item);
|
||||
var group = groups.find(function (t) { return t.key == key; });
|
||||
if (group == null) {
|
||||
group = new GroupItem(key);
|
||||
groups.push(group);
|
||||
}
|
||||
group.push(item);
|
||||
};
|
||||
for (var _i = 0, list_8 = list; _i < list_8.length; _i++) {
|
||||
var item = list_8[_i];
|
||||
_loop_1(item);
|
||||
}
|
||||
return groups;
|
||||
};
|
||||
/**
|
||||
* 选择
|
||||
* let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
|
||||
*/
|
||||
ArrayExt.Select = function (list, fn) {
|
||||
var newList = new Array();
|
||||
for (var _i = 0, list_9 = list; _i < list_9.length; _i++) {
|
||||
var t = list_9[_i];
|
||||
newList.push(fn(t));
|
||||
}
|
||||
return newList;
|
||||
};
|
||||
/** 过来,并按顺序排序 */
|
||||
ArrayExt.where = function (list, whereFn, orderfn1, orderfn2) {
|
||||
if (orderfn1 === void 0) { orderfn1 = null; }
|
||||
if (orderfn2 === void 0) { orderfn2 = null; }
|
||||
var newList = list.filter(whereFn);
|
||||
if (orderfn1 == null && orderfn2 == null)
|
||||
return newList;
|
||||
return ArrayExt.sortBy(newList, orderfn1, orderfn2);
|
||||
};
|
||||
return ArrayExt;
|
||||
}());
|
||||
exports.ArrayExt = ArrayExt;
|
||||
var GroupItem = /** @class */ (function () {
|
||||
function GroupItem(k) {
|
||||
this.key = k;
|
||||
this.list = [];
|
||||
}
|
||||
Object.defineProperty(GroupItem.prototype, "count", {
|
||||
get: function () { return this.list.length; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
GroupItem.prototype.push = function (d) {
|
||||
this.list.push(d);
|
||||
};
|
||||
return GroupItem;
|
||||
}());
|
||||
exports.GroupItem = GroupItem;
|
||||
/**
|
||||
* 对排序好的数组进行去重操作
|
||||
* @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
|
||||
* @returns {Array<T>} 返回自身
|
||||
*/
|
||||
function arrayRemoveDuplicateBySort(arr, checkFuction) {
|
||||
if (checkFuction === void 0) { checkFuction = checkEqual; }
|
||||
if (arr.length < 2)
|
||||
return arr;
|
||||
var j = 1;
|
||||
for (var i = 1, l = arr.length; i < l; i++)
|
||||
if (!checkFuction(arr[j - 1], arr[i]))
|
||||
arr[j++] = arr[i];
|
||||
arr.length = j;
|
||||
return arr;
|
||||
}
|
||||
exports.arrayRemoveDuplicateBySort = arrayRemoveDuplicateBySort;
|
392
samples/handleAbility/common/ArrayExt.ts
Normal file
392
samples/handleAbility/common/ArrayExt.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
export class List<Item> extends Array<Item>
|
||||
{
|
||||
/** 返回符合条件的第一个元素 */
|
||||
first(fn: (item: Item) => boolean): Item
|
||||
{
|
||||
if (this.length == 0)
|
||||
return null
|
||||
for (const item of this)
|
||||
{
|
||||
if (fn(item))
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 返回符合条件的最后一元素 */
|
||||
last(fn: (t: Item) => boolean): Item
|
||||
{
|
||||
if (this.length == 0)
|
||||
return null
|
||||
for (let i = this.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (fn(this[i]))
|
||||
return this[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 最大值 */
|
||||
max<T>(fn: (item: Item) => T): T
|
||||
{
|
||||
let maxV: T
|
||||
for (const i of this)
|
||||
{
|
||||
let v = fn(i)
|
||||
if (maxV == null)
|
||||
{
|
||||
maxV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v > maxV)
|
||||
{
|
||||
maxV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxV
|
||||
}
|
||||
|
||||
/** 最小值 */
|
||||
min<T>(fn: (item: Item) => T): T
|
||||
{
|
||||
let minV: T
|
||||
for (const i of this)
|
||||
{
|
||||
let v = fn(i)
|
||||
if (minV == null)
|
||||
{
|
||||
minV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v < minV)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return minV
|
||||
}
|
||||
|
||||
/** 累加 */
|
||||
sum(fn: (item: Item) => number): number
|
||||
{
|
||||
let v: number = 0
|
||||
for (const t of this)
|
||||
{
|
||||
v = v + fn(t)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
/** 平均值 */
|
||||
avg(fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let sum = this.sum(fn)
|
||||
return sum / this.length
|
||||
}
|
||||
|
||||
/** 满足条件的元素数量 */
|
||||
count(fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let c = 0
|
||||
for (const item of this)
|
||||
{
|
||||
if (fn(item))
|
||||
c = c + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
FindMax<T>(fn: (item: Item) => T): Item
|
||||
{
|
||||
return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b)
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayExt
|
||||
{
|
||||
/** 返回满足条件的元素数量 */
|
||||
static count<Item>(list: Item[], fn: (item: Item) => boolean): number
|
||||
{
|
||||
if (list.length == 0)
|
||||
return 0
|
||||
let c = 0
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item))
|
||||
c = c + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
/** 移除 */
|
||||
static remove<Item>(list: Item[], obj: Item)
|
||||
{
|
||||
let index = list.findIndex(t => t == obj)
|
||||
if (index == -1)
|
||||
return
|
||||
list.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 返回符合条件的第一个元素 */
|
||||
static first<Item>(list: Item[], fn: (item: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
|
||||
{
|
||||
if (list.length == 0)
|
||||
return null
|
||||
if (orderFn1 == null)
|
||||
{
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item))
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
let minValue1: number
|
||||
let minValue2: number
|
||||
let minItem: Item
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item) == false)
|
||||
continue
|
||||
let v1 = orderFn1(item)
|
||||
let v2 = orderFn2 != null ? orderFn2(item) : 0
|
||||
if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2))
|
||||
{
|
||||
minValue1 = v1
|
||||
minValue2 = v2
|
||||
minItem = item
|
||||
}
|
||||
}
|
||||
return minItem
|
||||
}
|
||||
|
||||
/** 返回符合条件的最后一元素 */
|
||||
static last<Item>(list: Item[], fn: (t: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
|
||||
{
|
||||
if (list.length == 0)
|
||||
return null
|
||||
if (orderFn1 == null)
|
||||
{
|
||||
for (let i = list.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (fn(list[i]))
|
||||
return list[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//
|
||||
let maxValue1: number
|
||||
let maxValue2: number
|
||||
let maxItem: Item
|
||||
for (const item of list)
|
||||
{
|
||||
if (fn(item) == false)
|
||||
continue
|
||||
let v1 = orderFn1(item)
|
||||
let v2 = orderFn2 ? orderFn2(item) : 0
|
||||
if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2))
|
||||
{
|
||||
maxValue1 = v1
|
||||
maxValue2 = v2
|
||||
maxItem = item
|
||||
}
|
||||
}
|
||||
return maxItem
|
||||
}
|
||||
|
||||
/** 取最大值 */
|
||||
static max<T1, T2>(list: T1[], fn: (item: T1) => T2, whereF: (item: T1) => boolean = null, defaultV: T2 = null): T2
|
||||
{
|
||||
let maxV: T2
|
||||
for (const i of list)
|
||||
{
|
||||
if (whereF && whereF(i) == false)
|
||||
continue
|
||||
let v = fn(i)
|
||||
if (maxV == undefined)
|
||||
{
|
||||
maxV = fn(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v > maxV)
|
||||
{
|
||||
maxV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxV != undefined)
|
||||
return maxV
|
||||
return defaultV
|
||||
}
|
||||
|
||||
/** 最小值 */
|
||||
static min<Item, T>(list: Item[], fn: (item: Item) => T, whereF: (item: Item) => boolean = null, defaultV: T = null): T
|
||||
{
|
||||
let minV: T
|
||||
for (const i of list)
|
||||
{
|
||||
if (whereF && whereF(i) == false)
|
||||
continue
|
||||
let v = fn(i)
|
||||
if (minV == undefined)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v < minV)
|
||||
{
|
||||
minV = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minV != undefined)
|
||||
return minV
|
||||
return defaultV
|
||||
}
|
||||
|
||||
/** 累加 */
|
||||
static sum<Item>(list: Item[], fn: (item: Item) => number, wn?: (item: Item) => boolean): number
|
||||
{
|
||||
let v: number = 0
|
||||
for (const t of list)
|
||||
{
|
||||
if (wn && wn(t) == false)
|
||||
continue
|
||||
v = v + fn(t)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
/** 平均值 */
|
||||
static avg<Item>(list: Item[], fn: (item: Item) => number): number
|
||||
{
|
||||
if (this.length == 0)
|
||||
return 0
|
||||
let sum = ArrayExt.sum(list, fn)
|
||||
return sum / this.length
|
||||
}
|
||||
|
||||
/** 排序 */
|
||||
static sortBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null)
|
||||
{
|
||||
if (fn2 == null)
|
||||
return list.sort((a: Item, b: Item): number => fn(a) - fn(b))
|
||||
else
|
||||
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
|
||||
}
|
||||
|
||||
/** 降序 排序 */
|
||||
static sortByDescending<Item>(list: Item[], fn: (item: Item) => number)
|
||||
{
|
||||
list.sort((a: Item, b: Item): number => fn(b) - fn(a))
|
||||
}
|
||||
|
||||
/** 排序成新的数组 */
|
||||
static orderBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.concat([])
|
||||
if (fn2 == null)
|
||||
return newList.sort((a: Item, b: Item): number => fn(a) - fn(b))
|
||||
else
|
||||
return newList.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
|
||||
}
|
||||
|
||||
/** 降序成新的数组 */
|
||||
static orderByDescending<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.concat([])
|
||||
if (fn2 == null)
|
||||
return list.sort((a: Item, b: Item): number => fn(b) - fn(a))
|
||||
else
|
||||
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a))
|
||||
}
|
||||
|
||||
/** 分组 */
|
||||
static groupBy<Item, gT>(list: Item[], fn: (item: Item) => gT): GroupItem<gT, Item>[]
|
||||
{
|
||||
let groups = new Array<GroupItem<gT, Item>>()
|
||||
|
||||
for (const item of list)
|
||||
{
|
||||
let key = fn(item)
|
||||
let group = groups.find(t => t.key == key)
|
||||
if (group == null)
|
||||
{
|
||||
group = new GroupItem(key)
|
||||
groups.push(group)
|
||||
}
|
||||
group.push(item)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择
|
||||
* let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
|
||||
*/
|
||||
static Select<Item, rT>(list: Item[], fn: (item: Item) => rT): rT[]
|
||||
{
|
||||
let newList = new Array<rT>()
|
||||
for (const t of list)
|
||||
{
|
||||
newList.push(fn(t))
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
/** 过来,并按顺序排序 */
|
||||
static where<Item>(list: Item[], whereFn: (item: Item) => boolean, orderfn1: (item: Item) => number = null, orderfn2: (item: Item) => number = null): Item[]
|
||||
{
|
||||
let newList = list.filter(whereFn)
|
||||
if (orderfn1 == null && orderfn2 == null)
|
||||
return newList
|
||||
return ArrayExt.sortBy(newList, orderfn1, orderfn2)
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupItem<gT, Item>
|
||||
{
|
||||
key: gT
|
||||
get count() { return this.list.length }
|
||||
list: Item[]
|
||||
|
||||
constructor(k: gT)
|
||||
{
|
||||
this.key = k
|
||||
this.list = []
|
||||
}
|
||||
|
||||
push(d: Item)
|
||||
{
|
||||
this.list.push(d)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对排序好的数组进行去重操作
|
||||
* @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
|
||||
* @returns {Array<T>} 返回自身
|
||||
*/
|
||||
export function arrayRemoveDuplicateBySort<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
|
||||
{
|
||||
if (arr.length < 2)
|
||||
return arr
|
||||
let j = 1
|
||||
for (let i = 1, l = arr.length; i < l; i++)
|
||||
if (!checkFuction(arr[j - 1], arr[i]))
|
||||
arr[j++] = arr[i]
|
||||
arr.length = j
|
||||
return arr
|
||||
}
|
141
samples/handleAbility/common/Box2.js
Normal file
141
samples/handleAbility/common/Box2.js
Normal file
@@ -0,0 +1,141 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Box2 = void 0;
|
||||
var Vector2_js_1 = require("./Vector2.js");
|
||||
var Box2 = /** @class */ (function () {
|
||||
function Box2(min, max) {
|
||||
if (min === void 0) { min = new Vector2_js_1.Vector2(+Number.POSITIVE_INFINITY, +Number.POSITIVE_INFINITY); }
|
||||
if (max === void 0) { max = new Vector2_js_1.Vector2(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); }
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
Object.defineProperty(Box2.prototype, "area", {
|
||||
/** 获取面积 */
|
||||
get: function () {
|
||||
return (this.max.x - this.min.x) * (this.max.y - this.min.y);
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/** */
|
||||
Box2.prototype.set = function (min, max) {
|
||||
this.min.copy(min);
|
||||
this.max.copy(max);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.setFromPoints = function (points) {
|
||||
this.makeEmpty();
|
||||
for (var _i = 0, points_1 = points; _i < points_1.length; _i++) {
|
||||
var p = points_1[_i];
|
||||
this.expandByPoint(p);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.setFromCenterAndSize = function (center, size) {
|
||||
var v1 = Box2._setFromCenterAndSize_v1;
|
||||
var halfSize = v1.copy(size).multiplyScalar(0.5);
|
||||
this.min.copy(center).sub(halfSize);
|
||||
this.max.copy(center).add(halfSize);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.clone = function () {
|
||||
return new this.constructor().copy(this);
|
||||
};
|
||||
Box2.prototype.copy = function (box) {
|
||||
this.min.copy(box.min);
|
||||
this.max.copy(box.max);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.makeEmpty = function () {
|
||||
this.min.x = this.min.y = +Number.POSITIVE_INFINITY;
|
||||
this.max.x = this.max.y = Number.NEGATIVE_INFINITY;
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.isEmpty = function () {
|
||||
// this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes
|
||||
return (this.max.x < this.min.x) || (this.max.y < this.min.y);
|
||||
};
|
||||
Box2.prototype.getCenter = function (result) {
|
||||
if (result === void 0) { result = new Vector2_js_1.Vector2(); }
|
||||
return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5);
|
||||
};
|
||||
Box2.prototype.getSize = function (result) {
|
||||
if (result === void 0) { result = new Vector2_js_1.Vector2(); }
|
||||
return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min);
|
||||
};
|
||||
Box2.prototype.expandByPoint = function (point) {
|
||||
this.min.min(point);
|
||||
this.max.max(point);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.expandByVector = function (vector) {
|
||||
this.min.sub(vector);
|
||||
this.max.add(vector);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.expandByScalar = function (scalar) {
|
||||
this.min.addScalar(-scalar);
|
||||
this.max.addScalar(scalar);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.containsPoint = function (point) {
|
||||
if (point.x < this.min.x || point.x > this.max.x
|
||||
|| point.y < this.min.y || point.y > this.max.y) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
Box2.prototype.containsBox = function (box) {
|
||||
if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x)
|
||||
&& (this.min.y <= box.min.y) && (box.max.y <= this.max.y)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Box2.prototype.getParameter = function (point, result) {
|
||||
if (result === void 0) { result = new Vector2_js_1.Vector2(); }
|
||||
// This can potentially have a divide by zero if the box
|
||||
// has a size dimension of 0.
|
||||
return result.set((point.x - this.min.x) / (this.max.x - this.min.x), (point.y - this.min.y) / (this.max.y - this.min.y));
|
||||
};
|
||||
Box2.prototype.intersectsBox = function (box) {
|
||||
// using 6 splitting planes to rule out intersections.
|
||||
if (box.max.x < this.min.x || box.min.x > this.max.x
|
||||
|| box.max.y < this.min.y || box.min.y > this.max.y) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
Box2.prototype.clampPoint = function (point, result) {
|
||||
if (result === void 0) { result = new Vector2_js_1.Vector2(); }
|
||||
return result.copy(point).clamp(this.min, this.max);
|
||||
};
|
||||
Box2.prototype.distanceToPoint = function (point) {
|
||||
var v1 = Box2._distanceToPoint_v1;
|
||||
var clampedPoint = v1.copy(point).clamp(this.min, this.max);
|
||||
return clampedPoint.sub(point).length();
|
||||
};
|
||||
Box2.prototype.intersect = function (box) {
|
||||
this.min.max(box.min);
|
||||
this.max.min(box.max);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.union = function (box) {
|
||||
this.min.min(box.min);
|
||||
this.max.max(box.max);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.translate = function (offset) {
|
||||
this.min.add(offset);
|
||||
this.max.add(offset);
|
||||
return this;
|
||||
};
|
||||
Box2.prototype.equals = function (box) {
|
||||
return box.min.equals(this.min) && box.max.equals(this.max);
|
||||
};
|
||||
Box2._setFromCenterAndSize_v1 = new Vector2_js_1.Vector2();
|
||||
Box2._distanceToPoint_v1 = new Vector2_js_1.Vector2();
|
||||
return Box2;
|
||||
}());
|
||||
exports.Box2 = Box2;
|
||||
;
|
183
samples/handleAbility/common/Box2.ts
Normal file
183
samples/handleAbility/common/Box2.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Vector2 } from './Vector2.js'
|
||||
import type { Point } from './Point.js'
|
||||
|
||||
export class Box2
|
||||
{
|
||||
min: Vector2
|
||||
max: Vector2
|
||||
constructor(min = new Vector2(+Number.POSITIVE_INFINITY, +Number.POSITIVE_INFINITY), max = new Vector2(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY))
|
||||
{
|
||||
this.min = min
|
||||
this.max = max
|
||||
}
|
||||
|
||||
/** 获取面积 */
|
||||
get area(): number
|
||||
{
|
||||
return (this.max.x - this.min.x) * (this.max.y - this.min.y)
|
||||
}
|
||||
|
||||
/** */
|
||||
set(min: Vector2, max: Vector2): Box2
|
||||
{
|
||||
this.min.copy(min)
|
||||
this.max.copy(max)
|
||||
return this
|
||||
}
|
||||
|
||||
setFromPoints(points: Iterable<Point>): Box2
|
||||
{
|
||||
this.makeEmpty()
|
||||
for (let p of points)
|
||||
{
|
||||
this.expandByPoint(p)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private static _setFromCenterAndSize_v1 = new Vector2()
|
||||
setFromCenterAndSize(center: Vector2, size: Vector2): Box2
|
||||
{
|
||||
const v1 = Box2._setFromCenterAndSize_v1
|
||||
const halfSize = v1.copy(size).multiplyScalar(0.5)
|
||||
this.min.copy(center).sub(halfSize)
|
||||
this.max.copy(center).add(halfSize)
|
||||
return this
|
||||
}
|
||||
|
||||
clone(): Box2
|
||||
{
|
||||
return new (this.constructor as any)().copy(this)
|
||||
}
|
||||
|
||||
copy(box: Box2): Box2
|
||||
{
|
||||
this.min.copy(box.min)
|
||||
this.max.copy(box.max)
|
||||
return this
|
||||
}
|
||||
|
||||
makeEmpty(): Box2
|
||||
{
|
||||
this.min.x = this.min.y = +Number.POSITIVE_INFINITY
|
||||
this.max.x = this.max.y = Number.NEGATIVE_INFINITY
|
||||
return this
|
||||
}
|
||||
|
||||
isEmpty(): boolean
|
||||
{
|
||||
// this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes
|
||||
return (this.max.x < this.min.x) || (this.max.y < this.min.y)
|
||||
}
|
||||
|
||||
getCenter(result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5)
|
||||
}
|
||||
|
||||
getSize(result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min)
|
||||
}
|
||||
|
||||
expandByPoint(point: Point): Box2
|
||||
{
|
||||
this.min.min(point)
|
||||
this.max.max(point)
|
||||
return this
|
||||
}
|
||||
|
||||
expandByVector(vector: Vector2): Box2
|
||||
{
|
||||
this.min.sub(vector)
|
||||
this.max.add(vector)
|
||||
return this
|
||||
}
|
||||
|
||||
expandByScalar(scalar: number): Box2
|
||||
{
|
||||
this.min.addScalar(-scalar)
|
||||
this.max.addScalar(scalar)
|
||||
return this
|
||||
}
|
||||
|
||||
containsPoint(point: Vector2): boolean
|
||||
{
|
||||
if (point.x < this.min.x || point.x > this.max.x
|
||||
|| point.y < this.min.y || point.y > this.max.y)
|
||||
{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
containsBox(box: Box2): boolean
|
||||
{
|
||||
if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x)
|
||||
&& (this.min.y <= box.min.y) && (box.max.y <= this.max.y))
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
getParameter(point: Vector2, result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
// This can potentially have a divide by zero if the box
|
||||
// has a size dimension of 0.
|
||||
return result.set(
|
||||
(point.x - this.min.x) / (this.max.x - this.min.x),
|
||||
(point.y - this.min.y) / (this.max.y - this.min.y),
|
||||
)
|
||||
}
|
||||
|
||||
intersectsBox(box: Box2): boolean
|
||||
{
|
||||
// using 6 splitting planes to rule out intersections.
|
||||
if (box.max.x < this.min.x || box.min.x > this.max.x
|
||||
|| box.max.y < this.min.y || box.min.y > this.max.y)
|
||||
{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
clampPoint(point: Vector2, result: Vector2 = new Vector2()): Vector2
|
||||
{
|
||||
return result.copy(point).clamp(this.min, this.max)
|
||||
}
|
||||
|
||||
private static _distanceToPoint_v1 = new Vector2()
|
||||
distanceToPoint(point: Vector2): number
|
||||
{
|
||||
const v1 = Box2._distanceToPoint_v1
|
||||
const clampedPoint = v1.copy(point).clamp(this.min, this.max)
|
||||
return clampedPoint.sub(point).length()
|
||||
}
|
||||
|
||||
intersect(box: Box2): Box2
|
||||
{
|
||||
this.min.max(box.min)
|
||||
this.max.min(box.max)
|
||||
return this
|
||||
}
|
||||
|
||||
union(box: Box2): Box2
|
||||
{
|
||||
this.min.min(box.min)
|
||||
this.max.max(box.max)
|
||||
return this
|
||||
}
|
||||
|
||||
translate(offset: Point): Box2
|
||||
{
|
||||
this.min.add(offset)
|
||||
this.max.add(offset)
|
||||
return this
|
||||
}
|
||||
|
||||
equals(box: Box2): boolean
|
||||
{
|
||||
return box.min.equals(this.min) && box.max.equals(this.max)
|
||||
}
|
||||
};
|
25
samples/handleAbility/common/ClipperCpp.ts
Normal file
25
samples/handleAbility/common/ClipperCpp.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as clipperLib from 'js-angusj-clipper/web/index.js'
|
||||
|
||||
// nodejs style require
|
||||
|
||||
export const clipperCpp: { lib?: clipperLib.ClipperLibWrapper } = {}
|
||||
export function InitClipperCpp(): Promise<void>
|
||||
{
|
||||
if (clipperCpp.lib)
|
||||
return
|
||||
if (!globalThis.document)
|
||||
globalThis.document = {} as any
|
||||
return new Promise((res, rej) =>
|
||||
{
|
||||
clipperLib.loadNativeClipperLibInstanceAsync(
|
||||
// let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
|
||||
// clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
|
||||
clipperLib.NativeClipperLibRequestedFormat.WasmOnly,
|
||||
).then((c) =>
|
||||
{
|
||||
clipperCpp.lib = c
|
||||
res()
|
||||
console.log('载入成功!')
|
||||
})
|
||||
})
|
||||
}
|
31
samples/handleAbility/common/Filer.ts
Normal file
31
samples/handleAbility/common/Filer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* CAD文件数据
|
||||
*/
|
||||
export class NestFiler
|
||||
{
|
||||
private readIndex: number = 0
|
||||
constructor(public _datas: any[] = []) { }
|
||||
|
||||
Clear()
|
||||
{
|
||||
this._datas.length = 0
|
||||
return this.Reset()
|
||||
}
|
||||
|
||||
Reset()
|
||||
{
|
||||
this.readIndex = 0
|
||||
return this
|
||||
}
|
||||
|
||||
Write(data: any)
|
||||
{
|
||||
this._datas.push(data)
|
||||
return this
|
||||
}
|
||||
|
||||
Read(): any
|
||||
{
|
||||
return this._datas[this.readIndex++]
|
||||
}
|
||||
}
|
255
samples/handleAbility/common/LayoutEngine/Curves2Parts.ts
Normal file
255
samples/handleAbility/common/LayoutEngine/Curves2Parts.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import type { PolylineProps } from 'cadapi'
|
||||
import { Circle, Polyline, Polyline2Points } from 'cadapi'
|
||||
import { EndType, JoinType } from 'js-angusj-clipper/web'
|
||||
import type { Box3, Vector3 } from 'three'
|
||||
import { Vector2 } from 'three'
|
||||
import { clipperCpp } from '../ClipperCpp'
|
||||
import type { Point } from '../Point'
|
||||
import { Path, PathScale } from '../core/Path'
|
||||
import type { IOffset } from './Simplify2'
|
||||
import { SimplifyDouglasPeucker } from './Simplify2'
|
||||
|
||||
/** 内外接多边形 */
|
||||
export function Circle2Points(circle: Circle, knifRadius: number, splitSize = 10, outside = false): Point[] {
|
||||
let radius = circle.Radius
|
||||
const an = Math.PI * 2 / splitSize
|
||||
|
||||
if (outside)
|
||||
radius = radius / Math.cos(an / 2) + knifRadius
|
||||
else
|
||||
radius -= knifRadius
|
||||
|
||||
const cenP = circle.Center
|
||||
const pts: Vector3[] = []
|
||||
for (let i = 0; i < splitSize; i++)
|
||||
pts.push(polar(cenP.clone(), an * i, radius))
|
||||
|
||||
return pts as Point[]
|
||||
}
|
||||
|
||||
export function Curve2Path(curve: Polyline, outside = false): Path {
|
||||
if (curve.IsClockWise)
|
||||
curve.Reverse()
|
||||
const w = new CurveWrap(curve, 3, outside)
|
||||
return new Path(outside ? w.GetOutsidePoints() : w.GetInsidePoints())
|
||||
}
|
||||
|
||||
export class CurveWrap {
|
||||
BoundingBox: Box3
|
||||
|
||||
Area: number
|
||||
|
||||
SimplyPolyline: Polyline
|
||||
SimplyOffset: IOffset
|
||||
Used = false
|
||||
Holes: CurveWrap[] = []
|
||||
|
||||
Points: Point[]
|
||||
|
||||
constructor(public Curve: Polyline | Circle, public KnifRadius: number, public IsOutside: boolean) {
|
||||
this.BoundingBox = Curve.BoundingBox
|
||||
|
||||
if (Curve instanceof Polyline) {
|
||||
const pts = Polyline2Points(Curve, IsOutside, 0)[1]
|
||||
/**
|
||||
* 精简算法SimplifyDouglasPeucker 会导致轮廓变大,
|
||||
* 修改成直接取点 陈雄 QQ聊天记录 23.9.18
|
||||
* 23.10.9 by lrx
|
||||
*/
|
||||
|
||||
const [spts, offset] = SimplifyDouglasPeucker(pts, KnifRadius ** 2 + KnifRadius)
|
||||
if (spts.length !== pts.length && spts.length > 2) {
|
||||
this.SimplyOffset = offset
|
||||
|
||||
this.SimplyPolyline = Path2Polyline(spts)
|
||||
this.Curve = this.SimplyPolyline// 保险起见,也更新它
|
||||
this.Area = this.SimplyPolyline.Area
|
||||
}
|
||||
else// 此处更新多段线
|
||||
{ this.Curve = Path2Polyline(pts) }
|
||||
this.Points = spts
|
||||
|
||||
// 以下修改后的
|
||||
// this.Curve = Path2Polyline(pts);
|
||||
// this.Points = pts;
|
||||
}
|
||||
|
||||
if (this.Area === undefined)
|
||||
this.Area = this.Curve.Area
|
||||
}
|
||||
|
||||
ContainsCurve(curve: CurveWrap): boolean {
|
||||
if (this.SimplyPolyline)
|
||||
return this.SimplyPolyline.PtInCurve(curve.Curve.StartPoint)
|
||||
return this.Curve.PtInCurve(curve.Curve.StartPoint)
|
||||
}
|
||||
|
||||
GetOutsidePoints(): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, this.KnifRadius, 10, true)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = this.KnifRadius
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.positiveOffset
|
||||
|
||||
if (offset > 0) {
|
||||
let pts = pl.GetStretchPoints() as Point[]
|
||||
pts = clipperCpp.lib.offsetToPaths({
|
||||
delta: offset * 1e4,
|
||||
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
try {
|
||||
PathScale(pts, 1e-4)
|
||||
}
|
||||
catch {
|
||||
console.log('err')
|
||||
}
|
||||
return pts
|
||||
}
|
||||
else { return this.Points }
|
||||
}
|
||||
}
|
||||
|
||||
GetInsidePoints(): Point[] {
|
||||
return this.GetInsidePoints2(this.KnifRadius)
|
||||
}
|
||||
|
||||
GetInsidePoints2(d: number): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, d, 10, false)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = -d
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.negativeOffset
|
||||
|
||||
if (offset < -0.01) {
|
||||
const pls = pl.GetOffsetCurves(offset)
|
||||
if (pls.length)
|
||||
return pls[0].GetStretchPoints()
|
||||
}
|
||||
else { return this.Points }
|
||||
}
|
||||
}
|
||||
|
||||
/** 引入Polyline 已经含刀半径, 获得精简后的点阵 */
|
||||
GetOrgPoints(outside = true): Point[] {
|
||||
if (this.Curve instanceof Circle) {
|
||||
const pts = Circle2Points(this.Curve, 0, 10, outside)
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
const pl = this.SimplyPolyline || this.Curve
|
||||
let offset = 0
|
||||
if (this.SimplyOffset)
|
||||
offset += this.SimplyOffset.positiveOffset
|
||||
|
||||
if (offset > 0) {
|
||||
let pts = pl.GetStretchPoints() as Point[]
|
||||
pts = clipperCpp.lib.offsetToPaths({
|
||||
delta: offset * 1e4,
|
||||
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
try {
|
||||
PathScale(pts, 1e-4)
|
||||
}
|
||||
catch {
|
||||
console.log('err')
|
||||
}
|
||||
return pts
|
||||
}
|
||||
else {
|
||||
return this.Points
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 多段线 转点整 已弃用,整合到CAD api 23.11.2 */
|
||||
// export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: number): [Polyline, Point[]]
|
||||
// {
|
||||
// let pts: Point[] = [];
|
||||
|
||||
// if (!outside) knifRadius = -knifRadius;
|
||||
// if (pl.IsClockWise) pl.Reverse();
|
||||
// for (let i = 0; i < pl.EndParam; i++)
|
||||
// {
|
||||
// pts.push(pl.GetPointAtParam(i));
|
||||
|
||||
// let bul = pl.GetBulgeAt(i);
|
||||
// if (bul !== 0)
|
||||
// {
|
||||
// let arc = pl.GetCurveAtIndex(i) as Arc;
|
||||
|
||||
// let allAngle = arc.AllAngle;
|
||||
// let arcLength = arc.Length;
|
||||
|
||||
// // let splitCount = Math.round(allAngle / 0.4);
|
||||
// // if (arcLength < 300)
|
||||
// // splitCount = 2;
|
||||
// // else
|
||||
// // splitCount = Math.max(arcLength / 200, splitCount,2);
|
||||
|
||||
// let minCount = Math.floor(allAngle * 4 / Math.PI);
|
||||
// let splitCount = Math.round(allAngle / 0.4);
|
||||
// if (arcLength < 300)
|
||||
// splitCount = Math.max(2, minCount);
|
||||
// else
|
||||
// splitCount = Math.max(Math.floor(arcLength / 200), splitCount,2, minCount);
|
||||
|
||||
// let radius = arc.Radius;
|
||||
// if (outside === bul > 0)
|
||||
// radius = radius / Math.cos(allAngle / (splitCount * 2));
|
||||
|
||||
// let cp = arc.Center;
|
||||
// for (let j = outside ? 0.5 : 0; j < splitCount; j++)
|
||||
// {
|
||||
// let a = arc.GetAngleAtParam(j * (1 / splitCount));
|
||||
// let p = polar(cp.clone(), a, radius);
|
||||
// pts.push(p);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (knifRadius !== 0)
|
||||
// {
|
||||
// pts = clipperCpp.lib.offsetToPaths({
|
||||
// delta: knifRadius * 1e4,
|
||||
// offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }]
|
||||
// })[0];
|
||||
// PathScale(pts, 1e-4);
|
||||
// }
|
||||
// return [pl, pts];
|
||||
// }
|
||||
|
||||
export function Path2Polyline(path: Point[]): Polyline {
|
||||
const pl = new Polyline()
|
||||
pl.LineData = path.map((p) => {
|
||||
return { pt: new Vector2(p.x, p.y), bul: 0 }
|
||||
})
|
||||
pl.CloseMark = true
|
||||
return pl
|
||||
}
|
||||
|
||||
export function Points2Polyline(pts: any[]): Polyline {
|
||||
const lined: PolylineProps[] = []
|
||||
const count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p0 = pts[i]
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul })
|
||||
}
|
||||
const pls = new Polyline(lined)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
export function polar<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T {
|
||||
v.x += Math.cos(an) * dis
|
||||
v.y += Math.sin(an) * dis
|
||||
return v
|
||||
}
|
368
samples/handleAbility/common/LayoutEngine/PolylineHelper.ts
Normal file
368
samples/handleAbility/common/LayoutEngine/PolylineHelper.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
import type { PolylineProps } from 'cadapi'
|
||||
import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi'
|
||||
import type { Box3 } from 'three'
|
||||
import { Vector2, Vector3 } from 'three'
|
||||
import { arrayRemoveDuplicateBySort } from '../ArrayExt'
|
||||
import type { Curve2d } from '../base/CAD'
|
||||
import { Arc2d, Point2d, copyTextToClipboard } from '../base/CAD'
|
||||
import { CurveWrap } from './Curves2Parts'
|
||||
|
||||
|
||||
export class PolylineHelper {
|
||||
/** 创建闭合多段线 */
|
||||
static create(pts: any[], closeMark = false): Polyline {
|
||||
let lined: PolylineProps[] = []
|
||||
let count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
let p0 = pts[i]
|
||||
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul || 0 })
|
||||
}
|
||||
let pls = new Polyline(lined)
|
||||
pls.CloseMark = closeMark
|
||||
return pls
|
||||
}
|
||||
|
||||
static createByCurve2d(curs: Curve2d[], closeMark = true): Polyline {
|
||||
let lined: PolylineProps[] = []
|
||||
for (let cur of curs) {
|
||||
let x = cur.StartPoint.m_X
|
||||
let y = cur.StartPoint.m_Y
|
||||
let bul = 0
|
||||
if (cur instanceof Arc2d)
|
||||
bul = cur.Bul || 0
|
||||
lined.push({ pt: new Vector2(x, y), bul })
|
||||
}
|
||||
let pls = new Polyline(lined)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
static createByPts(pts: any[], buls: number[], closeMark = false): Polyline {
|
||||
let plps: PolylineProps[] = []
|
||||
let count = pts.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
let p0 = pts[i]
|
||||
plps.push({ pt: new Vector2(p0.x, p0.y), bul: buls[i] })
|
||||
}
|
||||
let pls = new Polyline(plps)
|
||||
pls.CloseMark = closeMark
|
||||
return pls
|
||||
}
|
||||
|
||||
static getSimplePoints(pts: any[], offset: number): any[] {
|
||||
let pl = PolylineHelper.create(pts)
|
||||
pl.CloseMark = true
|
||||
let cureW = new CurveWrap(pl, offset, true)
|
||||
let pts2 = cureW.GetOutsidePoints()
|
||||
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
return pts2
|
||||
}
|
||||
|
||||
static createByWidthLength(w: number, l: number): Polyline {
|
||||
let plps: PolylineProps[] = []
|
||||
plps.push({ pt: new Vector2(0, 0), bul: 0 })
|
||||
plps.push({ pt: new Vector2(w, 0), bul: 0 })
|
||||
plps.push({ pt: new Vector2(w, l), bul: 0 })
|
||||
plps.push({ pt: new Vector2(0, l), bul: 0 })
|
||||
let pls = new Polyline(plps)
|
||||
pls.CloseMark = true
|
||||
return pls
|
||||
}
|
||||
|
||||
/** 多段线,添加位置移动 返回新多段线 */
|
||||
static moveTo(pl: Polyline, x: number, y: number): Polyline {
|
||||
let lindData = pl.LineData
|
||||
let pos = pl.Position
|
||||
|
||||
let newPts: PolylineProps[] = []
|
||||
for (let p of lindData) {
|
||||
let nx = p.pt.x + pos.x + x
|
||||
let ny = p.pt.y + pos.y + y
|
||||
if (ny < 7.9) {
|
||||
// console.log('修边小于 7.9????', ny)
|
||||
}
|
||||
let bul = p.bul
|
||||
newPts.push({ pt: new Vector2(nx, ny), bul })
|
||||
}
|
||||
let npl = new Polyline(newPts)
|
||||
npl.CloseMark = pl.CloseMark
|
||||
return npl
|
||||
}
|
||||
|
||||
/** 重设 多段线的几点 */
|
||||
static resetPosition(pl: Polyline): Polyline {
|
||||
let lindData = pl.LineData
|
||||
let pos = pl.Position
|
||||
|
||||
let newPts: PolylineProps[] = []
|
||||
for (let p of lindData) {
|
||||
let nx = p.pt.x + pos.x
|
||||
let ny = p.pt.y + pos.y
|
||||
let bul = p.bul
|
||||
newPts.push({ pt: new Vector2(nx, ny), bul })
|
||||
}
|
||||
let npl = new Polyline(newPts)
|
||||
npl.CloseMark = pl.CloseMark
|
||||
return npl
|
||||
}
|
||||
|
||||
/** 获得v型刀走刀路径 */o
|
||||
static getVModelPoints(pl: Polyline, depth: number, ang: number): any[] {
|
||||
// let ang = Math.PI * (0.5 * angle) / 180 ;
|
||||
let ps = []
|
||||
let bx = pl.Position.x
|
||||
let by = pl.Position.y
|
||||
if (ang > 0.01) {
|
||||
let rt = VKnifToolPath(pl, depth, ang / 2)
|
||||
ps = rt.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: t.pt.z, bul: t.bul, r: 0 } })
|
||||
}
|
||||
else {
|
||||
ps = pl.LineData.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: 0, bul: t.bul, r: 0 } })
|
||||
}
|
||||
for (let i = 0; i < ps.length; i++) {
|
||||
let p = ps[i]
|
||||
if (p.bul == 0)
|
||||
continue
|
||||
let p2 = (i == ps.length - 1 ? ps[0] : ps[i + 1])
|
||||
let r = this.getArcRadius(p.x, p.y, p2.x, p2.y, p.bul)
|
||||
p.r = r
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
static ConverPolyLin2Circle(polyline: Polyline, fuzz = 0.1): Circle | undefined {
|
||||
let box = polyline.BoundingBox
|
||||
let size = box.getSize(new Vector3())
|
||||
if (!this.equaln(size.x, size.y, fuzz))// 盒子四方
|
||||
return undefined
|
||||
|
||||
let circleLength = 2 * Math.PI * size.x
|
||||
if (!this.equaln(circleLength, polyline.Length, fuzz * 2))
|
||||
return undefined
|
||||
|
||||
let circleArea = Math.PI * size.x * size.x
|
||||
if (!this.equaln(circleArea, polyline.Area, fuzz * 2))
|
||||
return undefined
|
||||
|
||||
let r = size.x// 必须备份(因为我们要复用这个vector变量)
|
||||
return new Circle(box.getCenter(size), r)
|
||||
}
|
||||
// 有问题
|
||||
static getVModelPoints_offset(pl: Polyline, offset: number, depth: number, angle: number) {
|
||||
let npl = offset == 0 ? pl : pl.GetOffsetCurves(offset)[0]
|
||||
// if(offset != 0)
|
||||
// {
|
||||
// ClipboardTest.write2PolyLine(pl,npl);
|
||||
// }
|
||||
return PolylineHelper.getVModelPoints(npl, depth, angle)
|
||||
}
|
||||
|
||||
static getArcRadius(x1: number, y1: number, x2: number, y2: number, bul: number): number {
|
||||
let bul2 = Math.abs(bul)
|
||||
let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2
|
||||
return 0.5 * d * (1 + bul2 ** 2) / bul2
|
||||
}
|
||||
|
||||
// 圆 转 多段线
|
||||
static cicleToPolyline(c: Circle): Polyline {
|
||||
let arcs = c.GetSplitCurves([0, 0.5])
|
||||
let pl = Polyline.FastCombine(arcs)
|
||||
return pl
|
||||
}
|
||||
|
||||
/** 判断多段线是否 重叠 */
|
||||
static isIntersect(pl1: Polyline, pl2: Polyline): boolean {
|
||||
let box1 = this.getBox(pl1)
|
||||
let box2 = this.getBox(pl2)
|
||||
|
||||
if (!box1.intersectsBox(box2))
|
||||
return false // 肯定不相交
|
||||
|
||||
let ipts = pl1.IntersectWith(pl2, 0)
|
||||
if (ipts.length === 0) {
|
||||
if (pl1.Area > pl2.Area)// 缓存面积
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(pl1, pl2))
|
||||
return true // 包含
|
||||
}
|
||||
else {
|
||||
if (isTargetCurInOrOnSourceCur(pl2, pl1))
|
||||
return true // 包含
|
||||
}
|
||||
return false
|
||||
}
|
||||
else {
|
||||
return true // 有交点 一定有交集
|
||||
}
|
||||
}
|
||||
|
||||
// 多段线 圆弧合并
|
||||
static mergeCurve(pl2: Polyline): Polyline {
|
||||
const curves = pl2.Explode()
|
||||
arrayRemoveDuplicateBySort(curves, (c1, c2) => {
|
||||
return c1.Join(c2) === Status.True
|
||||
})
|
||||
|
||||
return Polyline.FastCombine(curves)
|
||||
}
|
||||
|
||||
/**
|
||||
* pl2 包含在pl1 内
|
||||
*
|
||||
*/
|
||||
// static isInside(pl1:Polyline,pl2:Polyline):boolean
|
||||
// {
|
||||
// let box1 = this.getBox(pl1);
|
||||
// let box2 = this.getBox(pl2);
|
||||
// if (!box1.intersectsBox(box2)) return false; //肯定不相交
|
||||
|
||||
// let ipts = pl1.IntersectWith(pl2, 0);
|
||||
// if (ipts.length > 0) return true; //有交点 一定有交集
|
||||
// }
|
||||
|
||||
/**
|
||||
* 两片板的干涉检查
|
||||
*
|
||||
* @param pl1
|
||||
* @param pls1_inner
|
||||
* @param pls1_model
|
||||
* @param pl2
|
||||
* @param pls2_inner
|
||||
* @param pls2_model
|
||||
* @returns
|
||||
*/
|
||||
static isOverLap(pl1: Polyline, pls1_inner: Polyline[], pls1_model: Polyline[], pl2: Polyline, pls2_inner: Polyline[], pls2_model: Polyline[]) {
|
||||
// 是否干涉, 被包含在造型洞,不算干涉
|
||||
let isOverlap = this.boxIsOverlap(pl1, pls1_inner, pl2, pls2_inner)
|
||||
|
||||
if (isOverlap)
|
||||
return true
|
||||
|
||||
// 造型 ,2v 刀路 是否干涉
|
||||
for (let pl1_model of pls1_model) {
|
||||
if (pl1_model.IntersectWith(pl2, 0).length > 0)
|
||||
return true
|
||||
for (let pl2_inner of pls2_inner) {
|
||||
if (pl1_model.IntersectWith(pl2_inner, 0).length > 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for (let pl2_model of pls2_model) {
|
||||
if (pl2_model.IntersectWith(pl1, 0).length > 0)
|
||||
return true
|
||||
for (let pl1_inner of pls1_inner) {
|
||||
if (pl2_model.IntersectWith(pl1_inner, 0).length > 0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private static boxIsOverlap(pl1: Polyline, pls1_inner: Polyline[], pl2: Polyline, pls2_inner: Polyline[]) {
|
||||
let box1 = this.getBox(pl1)
|
||||
let box2 = this.getBox(pl2)
|
||||
|
||||
if (!box1.intersectsBox(box2))
|
||||
return false // 肯定不相交
|
||||
|
||||
let ipts = pl1.IntersectWith(pl2, 0)
|
||||
if (ipts.length > 0)
|
||||
return true // 有交点 一定有交集
|
||||
|
||||
if (pl1.Area > pl2.Area)// 缓存面积
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(pl1, pl2)) // pl1包含 pl2
|
||||
{
|
||||
for (let mpl of pls1_inner) // 如果pl1有造型洞包含pl2, 则表示不干涉,返回false
|
||||
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(mpl, pl2) == true)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isTargetCurInOrOnSourceCur(pl2, pl1)) // pl2包含 pl1
|
||||
{
|
||||
for (let mpl of pls2_inner) // 如果pl2有造型洞包含pl1, 则表示不干涉,返回false
|
||||
|
||||
{
|
||||
if (isTargetCurInOrOnSourceCur(mpl, pl1) == true)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/** 判断 点是否在多段线内 */
|
||||
static isPointInPolyline(pl1: Polyline, x: number, y: number): boolean {
|
||||
return pl1.PtInCurve(new Vector3(x, y, 0))
|
||||
}
|
||||
|
||||
static getBox(pl1: Polyline): Box3 {
|
||||
if (!pl1.box_tp)
|
||||
pl1.box_tp = pl1.BoundingBox
|
||||
|
||||
return pl1.box_tp as Box3
|
||||
}
|
||||
|
||||
static getArea(pl1: Polyline): number {
|
||||
if (!pl1.area_tp)
|
||||
pl1.area_tp = pl1.Area
|
||||
|
||||
return pl1.area_tp as number
|
||||
}
|
||||
|
||||
static getPath(pl: Polyline): Path2D {
|
||||
let path = new Path2D()
|
||||
let p0 = pl.LineData[0].pt
|
||||
path.moveTo(p0.x, p0.y)
|
||||
|
||||
for (let i = 0; i < pl.LineData.length; i++) {
|
||||
let p0 = pl.LineData[i].pt
|
||||
let p1 = (i == pl.LineData.length - 1) ? pl.LineData[0].pt : pl.LineData[i + 1].pt
|
||||
let bul = pl.LineData[i].bul
|
||||
if (bul == 0) {
|
||||
path.lineTo(p1.x, p1.y)
|
||||
}
|
||||
else {
|
||||
let arc = new Arc2d(new Point2d(p0.x, p0.y), new Point2d(p1.x, p1.y), bul)
|
||||
path.arc(arc.m_Center.m_X, arc.m_Center.m_Y, arc.m_Radius, arc.m_StartAngle, arc.m_EndAngle, bul < 0)
|
||||
}
|
||||
}
|
||||
path.closePath()
|
||||
return path
|
||||
}
|
||||
|
||||
static equaln(v1: number, v2: number, fuzz = 1e-5) {
|
||||
return Math.abs(v1 - v2) <= fuzz
|
||||
}
|
||||
|
||||
static toClipboard(en: Polyline | any) {
|
||||
let f = new CADFiler()
|
||||
f.Write(1)// 实体个数
|
||||
f.WriteObject(en)
|
||||
|
||||
copyTextToClipboard(f.ToString())
|
||||
}
|
||||
|
||||
static getStrPLs(ens: any[]) {
|
||||
if (ens.length == 0)
|
||||
return ''
|
||||
let f = new CADFiler()
|
||||
f.Write(ens.length)// 实体个数
|
||||
for (let en of ens)
|
||||
f.WriteObject(en)
|
||||
|
||||
return f.ToString()
|
||||
}
|
||||
}
|
85
samples/handleAbility/common/LayoutEngine/Simplify2.ts
Normal file
85
samples/handleAbility/common/LayoutEngine/Simplify2.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Vector2 } from "../Vector2"
|
||||
|
||||
interface P {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface IOffset {
|
||||
negativeOffset: number
|
||||
positiveOffset: number
|
||||
}
|
||||
|
||||
/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */
|
||||
function GetSqSegDist(p: P, p1: P, p2: P): number {
|
||||
let x = p1.x
|
||||
let y = p1.y
|
||||
let dx = p2.x - x
|
||||
let dy = p2.y - y
|
||||
|
||||
if (dx !== 0 || dy !== 0)// 不是0长度线
|
||||
{
|
||||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
|
||||
if (t > 1) {
|
||||
x = p2.x
|
||||
y = p2.y
|
||||
}
|
||||
else if (t > 0) {
|
||||
x += dx * t
|
||||
y += dy * t
|
||||
}
|
||||
}
|
||||
dx = p.x - x
|
||||
dy = p.y - y
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
function CrossVector2(a: P, b: P)
|
||||
{
|
||||
return a.x * b.y - a.y * b.x
|
||||
}
|
||||
|
||||
// Ramer-Douglas-Peucker algorithm
|
||||
function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void {
|
||||
let maxSqDist = 0
|
||||
let index: number
|
||||
const fp = points[first]
|
||||
const lp = points[last]
|
||||
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
const p = points[i]
|
||||
const sqDist = GetSqSegDist(p, fp, lp)
|
||||
if (sqDist > maxSqDist) {
|
||||
index = i
|
||||
maxSqDist = sqDist
|
||||
}
|
||||
}
|
||||
|
||||
if (maxSqDist > sqTolerance) {
|
||||
if (index - first > 1)
|
||||
SimplifyDPStep(points, first, index, sqTolerance, simplified, offset)
|
||||
simplified.push(points[index])
|
||||
if (last - index > 1)
|
||||
SimplifyDPStep(points, index, last, sqTolerance, simplified, offset)
|
||||
}
|
||||
else {
|
||||
// 记录偏移
|
||||
const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize()
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
const p = points[i]
|
||||
const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y })
|
||||
offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist)
|
||||
offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ramer-Douglas-Peucker 算法
|
||||
export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] {
|
||||
const last = points.length - 1
|
||||
const simplified: P[] = [points[0]]
|
||||
const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 }
|
||||
SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset)
|
||||
simplified.push(points[last])
|
||||
return [simplified, offset]
|
||||
}
|
5
samples/handleAbility/common/Point.ts
Normal file
5
samples/handleAbility/common/Point.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Point
|
||||
{
|
||||
x: number
|
||||
y: number
|
||||
}
|
38
samples/handleAbility/common/Util.ts
Normal file
38
samples/handleAbility/common/Util.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/** 判断两个值是否相等 */
|
||||
export function equaln(v1: number, v2: number, fuzz = 1e-5)
|
||||
{
|
||||
return Math.abs(v1 - v2) <= fuzz
|
||||
}
|
||||
|
||||
/** 修正数组索引号 */
|
||||
export function FixIndex(index: number, arr: Array<any> | number)
|
||||
{
|
||||
let count = (Array.isArray(arr)) ? arr.length : arr
|
||||
if (index < 0)
|
||||
return count + index
|
||||
else if (index >= count)
|
||||
return index - count
|
||||
else
|
||||
return index
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数组最大元素的索引号
|
||||
* @param compart t2大于t1返回t2
|
||||
* @returns 索引
|
||||
*/
|
||||
export function Max<T>(arr: T[], compart: (t1: T, t2: T) => boolean): number
|
||||
{
|
||||
let best: T = arr[0]
|
||||
let bestIndex = 0
|
||||
for (let i = 1; i < arr.length; i++)
|
||||
{
|
||||
let t1 = arr[i]
|
||||
if (compart(best, t1))
|
||||
{
|
||||
best = t1
|
||||
bestIndex = i
|
||||
}
|
||||
}
|
||||
return bestIndex
|
||||
}
|
335
samples/handleAbility/common/Vector2.ts
Normal file
335
samples/handleAbility/common/Vector2.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import type { Point } from './Point.js'
|
||||
|
||||
/** 二维向量 */
|
||||
export class Vector2
|
||||
{
|
||||
x: number
|
||||
y: number
|
||||
readonly isVector2: boolean = true
|
||||
constructor(x: number = 0, y: number = 0)
|
||||
{
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
|
||||
get width(): number { return this.x }
|
||||
set width(value: number) { this.x = value }
|
||||
get height(): number { return this.y }
|
||||
set height(value: number) { this.y = value }
|
||||
set(x: number, y: number): Vector2
|
||||
{
|
||||
this.x = x
|
||||
this.y = y
|
||||
return this
|
||||
}
|
||||
|
||||
setScalar(scalar: number): Vector2
|
||||
{
|
||||
this.x = scalar
|
||||
this.y = scalar
|
||||
return this
|
||||
}
|
||||
|
||||
setX(x: number): Vector2
|
||||
{
|
||||
this.x = x
|
||||
return this
|
||||
}
|
||||
|
||||
setY(y: number): Vector2
|
||||
{
|
||||
this.y = y
|
||||
return this
|
||||
}
|
||||
|
||||
setComponent(index: number, value: number): Vector2
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: this.x = value; break
|
||||
case 1: this.y = value; break
|
||||
default: throw new Error(`index is out of range: ${index}`)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
getComponent(index: number): number
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return this.x
|
||||
case 1: return this.y
|
||||
default: throw new Error(`index is out of range: ${index}`)
|
||||
}
|
||||
}
|
||||
|
||||
clone(): Vector2
|
||||
{
|
||||
return new (this.constructor as any)().copy(this)
|
||||
}
|
||||
|
||||
copy(v: Vector2): Vector2
|
||||
{
|
||||
this.x = v.x
|
||||
this.y = v.y
|
||||
return this
|
||||
}
|
||||
|
||||
add(v: Point): Vector2
|
||||
{
|
||||
this.x += v.x
|
||||
this.y += v.y
|
||||
return this
|
||||
}
|
||||
|
||||
addScalar(s: number): Vector2
|
||||
{
|
||||
this.x += s
|
||||
this.y += s
|
||||
return this
|
||||
}
|
||||
|
||||
addVectors(a: Vector2, b: Vector2): Vector2
|
||||
{
|
||||
this.x = a.x + b.x
|
||||
this.y = a.y + b.y
|
||||
return this
|
||||
}
|
||||
|
||||
addScaledVector(v: Vector2, s: number): Vector2
|
||||
{
|
||||
this.x += v.x * s
|
||||
this.y += v.y * s
|
||||
return this
|
||||
}
|
||||
|
||||
sub(v: Vector2): Vector2
|
||||
{
|
||||
this.x -= v.x
|
||||
this.y -= v.y
|
||||
return this
|
||||
}
|
||||
|
||||
subScalar(s: number): Vector2
|
||||
{
|
||||
this.x -= s
|
||||
this.y -= s
|
||||
return this
|
||||
}
|
||||
|
||||
subVectors(a: Vector2, b: Vector2): Vector2
|
||||
{
|
||||
this.x = a.x - b.x
|
||||
this.y = a.y - b.y
|
||||
return this
|
||||
}
|
||||
|
||||
multiply(v: Vector2): Vector2
|
||||
{
|
||||
this.x *= v.x
|
||||
this.y *= v.y
|
||||
return this
|
||||
}
|
||||
|
||||
multiplyScalar(scalar: number): Vector2
|
||||
{
|
||||
if (Number.isFinite(scalar))
|
||||
{
|
||||
this.x *= scalar
|
||||
this.y *= scalar
|
||||
} else
|
||||
{
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
divide(v: Vector2): Vector2
|
||||
{
|
||||
this.x /= v.x
|
||||
this.y /= v.y
|
||||
return this
|
||||
}
|
||||
|
||||
divideScalar(scalar: number): Vector2
|
||||
{
|
||||
return this.multiplyScalar(1 / scalar)
|
||||
}
|
||||
|
||||
min(v: Point): Vector2
|
||||
{
|
||||
this.x = Math.min(this.x, v.x)
|
||||
this.y = Math.min(this.y, v.y)
|
||||
return this
|
||||
}
|
||||
|
||||
max(v: Point): Vector2
|
||||
{
|
||||
this.x = Math.max(this.x, v.x)
|
||||
this.y = Math.max(this.y, v.y)
|
||||
return this
|
||||
}
|
||||
|
||||
clamp(min: Vector2, max: Vector2): Vector2
|
||||
{
|
||||
// This function assumes min < max, if this assumption isn't true it will not operate correctly
|
||||
this.x = Math.max(min.x, Math.min(max.x, this.x))
|
||||
this.y = Math.max(min.y, Math.min(max.y, this.y))
|
||||
return this
|
||||
}
|
||||
|
||||
private static clampScalar_min = new Vector2()
|
||||
private static clampScalar_max = new Vector2()
|
||||
clampScalar(minVal: number, maxVal: number): Vector2
|
||||
{
|
||||
const min: Vector2 = Vector2.clampScalar_min.set(minVal, minVal)
|
||||
const max: Vector2 = Vector2.clampScalar_max.set(maxVal, maxVal)
|
||||
return this.clamp(min, max)
|
||||
}
|
||||
|
||||
clampLength(min: number, max: number): Vector2
|
||||
{
|
||||
const length: number = this.length()
|
||||
return this.multiplyScalar(Math.max(min, Math.min(max, length)) / length)
|
||||
}
|
||||
|
||||
floor(): Vector2
|
||||
{
|
||||
this.x = Math.floor(this.x)
|
||||
this.y = Math.floor(this.y)
|
||||
return this
|
||||
}
|
||||
|
||||
ceil(): Vector2
|
||||
{
|
||||
this.x = Math.ceil(this.x)
|
||||
this.y = Math.ceil(this.y)
|
||||
return this
|
||||
}
|
||||
|
||||
round(): Vector2
|
||||
{
|
||||
this.x = Math.round(this.x)
|
||||
this.y = Math.round(this.y)
|
||||
return this
|
||||
}
|
||||
|
||||
roundToZero(): Vector2
|
||||
{
|
||||
this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x)
|
||||
this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y)
|
||||
return this
|
||||
}
|
||||
|
||||
negate(): Vector2
|
||||
{
|
||||
this.x = -this.x
|
||||
this.y = -this.y
|
||||
return this
|
||||
}
|
||||
|
||||
dot(v: Vector2): number
|
||||
{
|
||||
return this.x * v.x + this.y * v.y
|
||||
}
|
||||
|
||||
lengthSq(): number
|
||||
{
|
||||
return this.x * this.x + this.y * this.y
|
||||
}
|
||||
|
||||
length(): number
|
||||
{
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y)
|
||||
}
|
||||
|
||||
lengthManhattan(): number
|
||||
{
|
||||
return Math.abs(this.x) + Math.abs(this.y)
|
||||
}
|
||||
|
||||
normalize(): Vector2
|
||||
{
|
||||
return this.divideScalar(this.length())
|
||||
}
|
||||
|
||||
angle(): number
|
||||
{
|
||||
// computes the angle in radians with respect to the positive x-axis
|
||||
let angle: number = Math.atan2(this.y, this.x)
|
||||
if (angle < 0)
|
||||
angle += 2 * Math.PI
|
||||
return angle
|
||||
}
|
||||
|
||||
distanceTo(v: Vector2): number
|
||||
{
|
||||
return Math.sqrt(this.distanceToSquared(v))
|
||||
}
|
||||
|
||||
distanceToSquared(v: Vector2): number
|
||||
{
|
||||
const dx: number = this.x - v.x; const dy: number = this.y - v.y
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
distanceToManhattan(v: Vector2): number
|
||||
{
|
||||
return Math.abs(this.x - v.x) + Math.abs(this.y - v.y)
|
||||
}
|
||||
|
||||
setLength(length: number): Vector2
|
||||
{
|
||||
return this.multiplyScalar(length / this.length())
|
||||
}
|
||||
|
||||
lerp(v: Vector2, alpha: number): Vector2
|
||||
{
|
||||
this.x += (v.x - this.x) * alpha
|
||||
this.y += (v.y - this.y) * alpha
|
||||
return this
|
||||
}
|
||||
|
||||
lerpVectors(v1: Vector2, v2: Vector2, alpha: number): Vector2
|
||||
{
|
||||
return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1)
|
||||
}
|
||||
|
||||
equals(v: Vector2): boolean
|
||||
{
|
||||
return ((v.x === this.x) && (v.y === this.y))
|
||||
}
|
||||
|
||||
fromArray(array: Float32Array | number[], offset: number = 0): Vector2
|
||||
{
|
||||
this.x = array[offset]
|
||||
this.y = array[offset + 1]
|
||||
return this
|
||||
}
|
||||
|
||||
toArray(array: Float32Array | number[] = [], offset: number = 0): Float32Array | number[]
|
||||
{
|
||||
array[offset] = this.x
|
||||
array[offset + 1] = this.y
|
||||
return array
|
||||
}
|
||||
|
||||
fromAttribute(attribute: any, index: number, offset: number = 0): Vector2
|
||||
{
|
||||
index = index * attribute.itemSize + offset
|
||||
this.x = attribute.array[index]
|
||||
this.y = attribute.array[index + 1]
|
||||
return this
|
||||
}
|
||||
|
||||
rotateAround(center: Vector2, angle: number): Vector2
|
||||
{
|
||||
const c: number = Math.cos(angle); const s: number = Math.sin(angle)
|
||||
const x: number = this.x - center.x
|
||||
const y: number = this.y - center.y
|
||||
this.x = x * c - y * s + center.x
|
||||
this.y = x * s + y * c + center.y
|
||||
return this
|
||||
}
|
||||
}
|
1625
samples/handleAbility/common/base/CAD.ts
Normal file
1625
samples/handleAbility/common/base/CAD.ts
Normal file
File diff suppressed because it is too large
Load Diff
126
samples/handleAbility/common/base/StringFormat.ts
Normal file
126
samples/handleAbility/common/base/StringFormat.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
export class StringFormat
|
||||
{
|
||||
/**
|
||||
* 对Date的扩展,将 Date 转化为指定格式的String
|
||||
* 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符
|
||||
* 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
|
||||
* eg:
|
||||
* (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
|
||||
* (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
|
||||
* (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
|
||||
* (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
|
||||
* (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
|
||||
*/
|
||||
static Date(date: Date, fmt): string
|
||||
{
|
||||
let o = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'd+': date.getDate(), // 日
|
||||
'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时
|
||||
'H+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
'S': date.getMilliseconds(), // 毫秒
|
||||
}
|
||||
let week = {
|
||||
0: '/u65e5',
|
||||
1: '/u4e00',
|
||||
2: '/u4e8c',
|
||||
3: '/u4e09',
|
||||
4: '/u56db',
|
||||
5: '/u4e94',
|
||||
6: '/u516d',
|
||||
}
|
||||
if (/(y+)/.test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length))
|
||||
}
|
||||
if (/(E+)/.test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`])
|
||||
}
|
||||
for (let k in o)
|
||||
{
|
||||
if (new RegExp(`(${k})`).test(fmt))
|
||||
{
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)))
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
|
||||
/** 返回数字的固定小数点 123.00 */
|
||||
static number(value: number, bit: number): string
|
||||
{
|
||||
return value?.toFixed(bit).toString()
|
||||
}
|
||||
|
||||
static toFixed(value: number, bit: number = 3): number
|
||||
{
|
||||
let tt = 10 ** bit
|
||||
return Math.round(value * tt) / tt
|
||||
}
|
||||
|
||||
static filterIllegalChar(str: string): string
|
||||
{
|
||||
// var pattern = new RegExp("[/:*?'<>|\\]");
|
||||
// var rs = "";
|
||||
// for (var i = 0; i < str.length; i++)
|
||||
// {
|
||||
// rs = rs + str.substr(i, 1).replace(pattern, '');
|
||||
// }
|
||||
// return rs;
|
||||
|
||||
let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\</g, '').replace(/\>/g, '').replace(/\|/g, '').replace(/\'/g, '')
|
||||
return rt
|
||||
}
|
||||
|
||||
/** 文本格式化 */
|
||||
static format(str: string, ...args: any[]): string
|
||||
{
|
||||
let data = args
|
||||
let tmpl = str
|
||||
for (const item of tmpl.matchAll(/\{(.+?)\}/g))
|
||||
{
|
||||
let parts = item[1].split(',').map(i => i.trim())
|
||||
let index = Number(parts[0])
|
||||
let arg = data[index]
|
||||
|
||||
let val = (arg || '').toString() // 默认
|
||||
|
||||
if (arg instanceof Date) // 日期
|
||||
{
|
||||
let fm = 'MM-dd HH;mm'
|
||||
if (parts.length > 1)
|
||||
{
|
||||
fm = parts[1]
|
||||
}
|
||||
val = this.Date(arg, fm)
|
||||
}
|
||||
|
||||
if (parts.length > 1 && parts[1][0] === '#')
|
||||
{
|
||||
// {2,#3} -> 数字 转成3位 001,...023,
|
||||
val = val.padStart(Number(parts[1].substring(1)), '0')
|
||||
}
|
||||
tmpl = tmpl.replace(item[0], val)
|
||||
}
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
/** 实现右对齐,右边填充 (25,4,'*') => '25**' */
|
||||
static PadEnd(num: number, totalWidth: number, paddingChar: string): string
|
||||
{
|
||||
let str = num.toString()
|
||||
return str.padEnd(totalWidth, paddingChar[0])
|
||||
}
|
||||
|
||||
/** 实现右对齐,左边填充 (25,4,'0') => '0025' */
|
||||
static PadStart(num: number, totalWidth: number, paddingChar: string): string
|
||||
{
|
||||
let str = num.toString()
|
||||
return str.padStart(totalWidth, paddingChar[0])
|
||||
}
|
||||
}
|
381
samples/handleAbility/common/core/Path.ts
Normal file
381
samples/handleAbility/common/core/Path.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import { Box2 } from '../Box2.js'
|
||||
import { clipperCpp } from '../ClipperCpp.js'
|
||||
import type { NestFiler } from '../Filer.js'
|
||||
import type { Point } from '../Point.js'
|
||||
import { equaln } from '../Util.js'
|
||||
import { Vector2 } from '../Vector2.js'
|
||||
|
||||
/**
|
||||
* 轮廓路径类
|
||||
* 可以求NFP,和保存NFPCahce
|
||||
* 因为NFP结果是按照最低点移动的,所以将点旋转后,按照盒子将点移动到0点.
|
||||
*/
|
||||
export class Path
|
||||
{
|
||||
Id: number
|
||||
Points: Point[]
|
||||
OutsideNFPCache: { [key: number]: Point[][] } = {}
|
||||
InsideNFPCache: { [key: number]: Point[][] } = {}
|
||||
|
||||
constructor(public OrigionPoints?: Point[], rotation: number = 0)
|
||||
{
|
||||
if (OrigionPoints)
|
||||
this.Init(OrigionPoints, rotation)
|
||||
}
|
||||
|
||||
Origion: Path
|
||||
// 点表在旋转后的原始最小点.使用这个点将轮廓移动到0点
|
||||
OrigionMinPoint: Vector2
|
||||
Rotation: number
|
||||
|
||||
Size: Vector2// 序列化
|
||||
private Init(origionPoints: Point[], rotation: number)
|
||||
{
|
||||
this.Rotation = rotation
|
||||
if (rotation === 0)
|
||||
this.Points = origionPoints.map((p) => { return { ...p } })
|
||||
else
|
||||
{
|
||||
let c = Math.cos(rotation)
|
||||
let s = Math.sin(rotation)
|
||||
|
||||
let npts: Point[] = []
|
||||
for (let p of origionPoints)
|
||||
{
|
||||
let x = p.x
|
||||
let y = p.y
|
||||
const x1 = x * c - y * s
|
||||
const y1 = x * s + y * c
|
||||
npts.push({ x: x1, y: y1 })
|
||||
}
|
||||
this.Points = npts
|
||||
}
|
||||
|
||||
let box = new Box2()
|
||||
let v2 = new Vector2()
|
||||
for (let p of this.Points)
|
||||
{
|
||||
v2.x = p.x
|
||||
v2.y = p.y
|
||||
box.expandByPoint(v2)
|
||||
}
|
||||
|
||||
this.OrigionMinPoint = box.min
|
||||
this.Size = box.max.sub(box.min)
|
||||
|
||||
for (let p of this.Points)
|
||||
{
|
||||
p.x -= box.min.x
|
||||
p.y -= box.min.y
|
||||
}
|
||||
}
|
||||
|
||||
GetNFPs(path: Path, outside: boolean): Point[][]
|
||||
{
|
||||
// 寻找内轮廓时,面积应该比本path小,这个判断移交给使用者自己判断
|
||||
// if (!outside && this.Area < path.Area) return [];
|
||||
let nfps = clipperCpp.lib.minkowskiSumPath(this.BigIntPoints, path.MirrorPoints, true)
|
||||
|
||||
// 必须删除自交,否则将会出错
|
||||
nfps = clipperCpp.lib.simplifyPolygons(nfps)
|
||||
nfps = nfps.filter((nfp) =>
|
||||
{
|
||||
let area = Area(nfp)
|
||||
// if (area > 1) return outside;//第一个不一定是外轮廓,但是面积为正时肯定为外轮廓 (因为使用了简化多段线,所以这个代码已经不能有了)
|
||||
if (Math.abs(area) < 10)
|
||||
return false// 应该不用在移除这个了
|
||||
|
||||
let { x, y } = nfp[0]
|
||||
if (outside)
|
||||
{
|
||||
if (this.Area > path.Area)
|
||||
{
|
||||
let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }
|
||||
if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y)
|
||||
return true
|
||||
let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints)
|
||||
return dir === 0
|
||||
}
|
||||
else
|
||||
{
|
||||
let p = { x: this.InPoint.x - x, y: this.InPoint.y - y }
|
||||
if (p.x < 0 || p.y < 0 || p.x > path.BigSize.x || p.y > path.BigSize.y)
|
||||
return true
|
||||
let dir = clipperCpp.lib.pointInPolygon(p, path.BigIntPoints)
|
||||
return dir === 0
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }
|
||||
if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y)
|
||||
return false
|
||||
let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints)
|
||||
return dir === 1
|
||||
}
|
||||
})
|
||||
return nfps
|
||||
}
|
||||
|
||||
GetOutsideNFP(path: Path): Point[][]
|
||||
{
|
||||
let nfps = this.OutsideNFPCache[path.Id]
|
||||
if (nfps)
|
||||
return nfps
|
||||
|
||||
if (this.IsRect && path.IsRect)
|
||||
{
|
||||
let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]
|
||||
let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]
|
||||
nfps = [[
|
||||
{ x: -bx, y: -by },
|
||||
{ x: ax, y: -by },
|
||||
{ x: ax, y: ay },
|
||||
{ x: -bx, y: ay },
|
||||
]]
|
||||
}
|
||||
else
|
||||
nfps = this.GetNFPs(path, true)
|
||||
this.OutsideNFPCache[path.Id] = nfps
|
||||
// 虽然有这种神奇的特性,但是好像并不会提高性能。
|
||||
// path.OutsideNFPCache[this.id] = (this, nfps.map(nfp =>
|
||||
// {
|
||||
// return nfp.map(p =>
|
||||
// {
|
||||
// return { x: -p.x, y: -p.y };
|
||||
// });
|
||||
// }));
|
||||
return nfps
|
||||
}
|
||||
|
||||
GetInsideNFP(path: Path): Point[][]
|
||||
{
|
||||
if (path.Area > this.Area)
|
||||
return
|
||||
let nfp = this.InsideNFPCache[path.Id]
|
||||
if (nfp)
|
||||
return nfp
|
||||
|
||||
let nfps: Point[][]
|
||||
if (this.IsRect)
|
||||
{
|
||||
let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]
|
||||
let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]
|
||||
|
||||
let l = ax - bx
|
||||
let h = ay - by
|
||||
|
||||
const MinNumber = 200// 清理的数值是100,所以200是可以接受的, 200=0.020问题不大(过盈配合)
|
||||
if (l < -MinNumber || h < -MinNumber)
|
||||
return
|
||||
|
||||
if (l < MinNumber)
|
||||
l = MinNumber
|
||||
else
|
||||
l += MinNumber
|
||||
|
||||
if (h < MinNumber)
|
||||
h = MinNumber
|
||||
else
|
||||
h += MinNumber
|
||||
|
||||
nfps = [[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: l, y: 0 },
|
||||
{ x: l, y: h },
|
||||
{ x: 0, y: h },
|
||||
]]
|
||||
}
|
||||
else
|
||||
nfps = this.GetNFPs(path, false)
|
||||
|
||||
if (path.Id !== undefined)
|
||||
this.InsideNFPCache[path.Id] = nfps
|
||||
return nfps
|
||||
}
|
||||
|
||||
private _InPoint: Point
|
||||
|
||||
/**
|
||||
* 用这个点来检测是否在Path内部
|
||||
*/
|
||||
private get InPoint()
|
||||
{
|
||||
if (this._InPoint)
|
||||
return this._InPoint
|
||||
let mp = { x: (this.Points[0].x + this.Points[1].x) / 2, y: (this.Points[0].y + this.Points[1].y) / 2 }
|
||||
let normal = new Vector2(this.Points[1].x - this.Points[0].x, this.Points[1].y - this.Points[0].y).normalize()
|
||||
// [normal.x, normal.y] = [normal.y, -normal.x];
|
||||
mp.x -= normal.y
|
||||
mp.y += normal.x
|
||||
|
||||
mp.x *= 1e4
|
||||
mp.y *= 1e4
|
||||
this._InPoint = mp
|
||||
return mp
|
||||
}
|
||||
|
||||
protected _BigIntPoints: Point[]
|
||||
get BigIntPoints()
|
||||
{
|
||||
if (this._BigIntPoints)
|
||||
return this._BigIntPoints
|
||||
this._BigIntPoints = this.Points.map((p) =>
|
||||
{
|
||||
return {
|
||||
x: Math.round(p.x * 1e4),
|
||||
y: Math.round(p.y * 1e4),
|
||||
}
|
||||
})
|
||||
return this._BigIntPoints
|
||||
}
|
||||
|
||||
private _BigSize: Vector2
|
||||
get BigSize()
|
||||
{
|
||||
if (this._BigSize)
|
||||
return this._BigSize
|
||||
this._BigSize = new Vector2(this.Size.x * 1e4, this.Size.y * 1e4)
|
||||
return this._BigSize
|
||||
}
|
||||
|
||||
protected _MirrorPoints: Point[]
|
||||
get MirrorPoints()
|
||||
{
|
||||
if (!this._MirrorPoints)
|
||||
this._MirrorPoints = this.BigIntPoints.map((p) =>
|
||||
{
|
||||
return { x: -p.x, y: -p.y }
|
||||
})
|
||||
|
||||
return this._MirrorPoints
|
||||
}
|
||||
|
||||
protected _BoundingBox: Box2
|
||||
get BoundingBox()
|
||||
{
|
||||
if (!this._BoundingBox)
|
||||
{
|
||||
this._BoundingBox = new Box2(new Vector2(), this.Size)
|
||||
}
|
||||
return this._BoundingBox
|
||||
}
|
||||
|
||||
protected _Area: number
|
||||
get Area()
|
||||
{
|
||||
if (this._Area === undefined)
|
||||
this._Area = Area(this.Points)
|
||||
return this._Area
|
||||
}
|
||||
|
||||
set Area(a: number)
|
||||
{
|
||||
this._Area = a
|
||||
}
|
||||
|
||||
private _IsRect: boolean
|
||||
get IsRect()
|
||||
{
|
||||
if (this._IsRect === undefined)
|
||||
{
|
||||
let s = this.BoundingBox.getSize(new Vector2())
|
||||
this._IsRect = equaln(this.Area, s.x * s.y, 1)
|
||||
}
|
||||
return this._IsRect
|
||||
}
|
||||
|
||||
ReadFile(file: NestFiler): void
|
||||
{
|
||||
let ver = file.Read()
|
||||
this.Id = file.Read()
|
||||
let arr = file.Read()
|
||||
this.Points = []
|
||||
for (let i = 0; i < arr.length; i += 2)
|
||||
{
|
||||
let p = { x: arr[i], y: arr[i + 1] }
|
||||
this.Points.push(p)
|
||||
}
|
||||
|
||||
this.Size = new Vector2(file.Read(), file.Read())
|
||||
this._Area = file.Read()
|
||||
let id = file.Read()
|
||||
if (id !== -1)
|
||||
{
|
||||
this.Origion = id
|
||||
this.Rotation = file.Read()
|
||||
this.OrigionMinPoint = new Vector2(file.Read(), file.Read())
|
||||
}
|
||||
}
|
||||
|
||||
WriteFile(file: NestFiler): void
|
||||
{
|
||||
file.Write(1)// ver
|
||||
file.Write(this.Id)
|
||||
let arr: number[] = []
|
||||
for (let p of this.Points)
|
||||
arr.push(p.x, p.y)
|
||||
file.Write(arr)
|
||||
|
||||
file.Write(this.Size.x)
|
||||
file.Write(this.Size.y)
|
||||
file.Write(this._Area)
|
||||
if (this.Origion && this.Origion.Id)
|
||||
{
|
||||
// 如果有原始的id,则传递它,以便后续进行NFP复用.
|
||||
file.Write(this.Origion.Id)
|
||||
file.Write(this.Rotation)
|
||||
file.Write(this.OrigionMinPoint.x)
|
||||
file.Write(this.OrigionMinPoint.y)
|
||||
}
|
||||
else
|
||||
file.Write(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// 点表面积
|
||||
export function Area(pts: Point[]): number
|
||||
{
|
||||
let cnt = pts.length
|
||||
if (cnt < 3)
|
||||
return 0
|
||||
let a = 0
|
||||
for (let i = 0, j = cnt - 1; i < cnt; ++i)
|
||||
{
|
||||
a += (pts[j].x + pts[i].x) * (pts[j].y - pts[i].y)
|
||||
j = i
|
||||
}
|
||||
return -a * 0.5
|
||||
}
|
||||
|
||||
/**
|
||||
* 平移点表,返回新点表
|
||||
*/
|
||||
export function TranslatePath(pts: Point[], p: Point): Point[]
|
||||
{
|
||||
return pts.map((px) =>
|
||||
{
|
||||
return { x: p.x + px.x, y: p.y + px.y }
|
||||
})
|
||||
}
|
||||
|
||||
export function TranslatePath_Self(pts: Point[], mx: number, my: number): Point[]
|
||||
{
|
||||
for (let pt of pts)
|
||||
{
|
||||
pt.x += mx
|
||||
pt.y += my
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
// 缩放点表,返回原始点表
|
||||
export function PathScale(pts: Point[], scale: number): Point[]
|
||||
{
|
||||
for (let p of pts)
|
||||
{
|
||||
p.x *= scale
|
||||
p.y *= scale
|
||||
}
|
||||
return pts
|
||||
}
|
0
samples/handleAbility/common/工具类.md
Normal file
0
samples/handleAbility/common/工具类.md
Normal file
202
samples/handleAbility/knifeHelper.ts
Normal file
202
samples/handleAbility/knifeHelper.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { _knifeType, Knife, SideModel } from "../confClass";
|
||||
import { knifeData,knifeData1,knifeData2 } from "../demoKnives";
|
||||
/** 刀库 管理刀具*/
|
||||
export class KnifeHelper {
|
||||
knifeList: Knife[]
|
||||
constructor(_knifeList) {
|
||||
|
||||
this.knifeList.push(knifeData);
|
||||
|
||||
|
||||
if (Array.isArray(_knifeList) && _knifeList.length >0 ) {
|
||||
this.loadKnifeList(_knifeList)
|
||||
}
|
||||
}
|
||||
|
||||
loadKnifeList(_knifeList) {
|
||||
this.knifeList = _knifeList || []
|
||||
}
|
||||
|
||||
/** 根据刀名称获取造型刀 */
|
||||
getModelKnifeByName(name: string): Knife | undefined {
|
||||
return this.knifeList.find(t => t.knifeName.trim() == name.trim() && t.isModelingKnife());
|
||||
}
|
||||
|
||||
/** 根据半径获取造型刀 */
|
||||
getModelKnifeByRadius(radius: number, depth: number): Knife | undefined {
|
||||
return this.knifeList.find(t => t.isModelingKnife() && Math.abs(t.diameter - 2 * radius) < 0.02 && t.length >= depth - 0.01);
|
||||
}
|
||||
|
||||
/** 获取合适的开料刀作为造型刀 */
|
||||
getFittedModelKnife(radius: number, depth: number): Knife | null {
|
||||
const knives = this.knifeList.filter(t => t.isModelingKnife() && t.length > depth - 0.01 && t.diameter / 2 < radius - 0.02);
|
||||
if (knives) {
|
||||
knives.sort((a, b) => b.diameter - a.diameter); //按刀直径倒序排序
|
||||
return knives[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 根据厚度获取开料刀 */
|
||||
getKnifeByThickness(thickness: number): Knife | undefined {
|
||||
return this.knifeList.find(t => t.length >= thickness);
|
||||
}
|
||||
/** 通用 找刀具 根据查询条件 */
|
||||
getKnifeByParams(params: _knifeType) {
|
||||
let knife: Knife | null = null
|
||||
|
||||
if (params) {
|
||||
let tempKnifeList: Knife[] = [...this.knifeList] // []
|
||||
let keys = Object.keys(params)
|
||||
if (keys.length > 0) {
|
||||
keys.forEach(key => {
|
||||
if (Array.isArray(params[key]) && key == 'ability') {
|
||||
// 进来的应该是ability 是数组 判断刀的能力
|
||||
for (const arrItem of params[key]) {
|
||||
let _knifeList = this.knifeList.filter(e => e.ability.includes(arrItem))
|
||||
_knifeList.forEach(k => {
|
||||
if (!this.KnifeIsInKnifeList(k, tempKnifeList)) {
|
||||
tempKnifeList.push(k)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (['string', 'number'].includes(typeof (params[key]))) {
|
||||
if (params && params[key] && typeof (params[key]) == 'number') {
|
||||
|
||||
if (key == 'length') {
|
||||
tempKnifeList = tempKnifeList.filter(e => e[key] >= params[key])
|
||||
} else {
|
||||
tempKnifeList = tempKnifeList.filter(e => e[key] == params[key])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
if (tempKnifeList.length > 0) {
|
||||
knife = tempKnifeList[0]
|
||||
}
|
||||
} else {
|
||||
console.log('传入的查询条件 没有参数')
|
||||
}
|
||||
}
|
||||
return knife
|
||||
}
|
||||
|
||||
/** 判断 刀是否在 刀列表内
|
||||
* true 存在
|
||||
* false 不存在
|
||||
*/
|
||||
KnifeIsInKnifeList(_knife: Knife, _knifeList: Knife[]) {
|
||||
let k = _knifeList.find(e => _knife.axleId == e.axleId
|
||||
&& _knife.diameter == e.diameter
|
||||
&& _knife.knifeId == e.knifeId)
|
||||
|
||||
if (k) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
getSideModelKnife(model: SideModel): Knife | undefined | null {
|
||||
if (model.knifeName && model.knifeName.trim() != '') {
|
||||
return this.getModelKnifeByName(model.knifeName.trim());
|
||||
}
|
||||
//获取刀半径(直径)和长度与造型定义的刀半径和深度一致的刀
|
||||
const knife = this.getSideModelKnifeByRadius(model.knifeRadius, model.depth, model.direction);
|
||||
if (knife) return knife;
|
||||
//获取刀半径(直径)小于造型定义的刀半径、长度不小于造型深度的刀
|
||||
return this.getFittedSideModelKnife(model.knifeRadius, model.depth, model.direction);
|
||||
}
|
||||
/** 获取合适的开料刀作为造型刀 */
|
||||
getFittedSideModelKnife(radius: number, depth: number, direction): Knife | null {
|
||||
let processFaceArr: number[] = [];
|
||||
processFaceArr.push(5)
|
||||
switch (direction) {
|
||||
case 0:
|
||||
processFaceArr.push(4)
|
||||
|
||||
break;
|
||||
case 1:
|
||||
processFaceArr.push(2)
|
||||
|
||||
break;
|
||||
case 2:
|
||||
processFaceArr.push(3)
|
||||
|
||||
break;
|
||||
case 3:
|
||||
|
||||
processFaceArr.push(1)
|
||||
break;
|
||||
case 10:
|
||||
|
||||
|
||||
break;
|
||||
case 11:
|
||||
|
||||
break;
|
||||
case 12:
|
||||
|
||||
break;
|
||||
case 13:
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const knives = this.knifeList.filter(t => t.isModelingKnife() && t.length > depth - 0.01 && t.diameter / 2 < radius - 0.02 && processFaceArr.includes(t.processFace));
|
||||
if (knives) {
|
||||
knives.sort((a, b) => b.diameter - a.diameter); //按刀直径倒序排序
|
||||
return knives[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/** 根据半径获取造型刀 */
|
||||
// direction 0 下 1右 2上 3左 10 左下斜 11 右下斜 12右上斜 13 左上斜
|
||||
// processFace: number // 加工面 2024 11 21 : 正面 0 反面 1 左侧面 2 右侧面 3 上侧面 4 下侧面 5 任意 6
|
||||
getSideModelKnifeByRadius(radius: number, depth: number, direction): Knife | undefined {
|
||||
|
||||
let processFaceArr: number[] = [];
|
||||
processFaceArr.push(5)
|
||||
switch (direction) {
|
||||
case 0:
|
||||
processFaceArr.push(4)
|
||||
|
||||
break;
|
||||
case 1:
|
||||
processFaceArr.push(2)
|
||||
|
||||
break;
|
||||
case 2:
|
||||
processFaceArr.push(3)
|
||||
|
||||
break;
|
||||
case 3:
|
||||
|
||||
processFaceArr.push(1)
|
||||
break;
|
||||
case 10:
|
||||
|
||||
|
||||
break;
|
||||
case 11:
|
||||
|
||||
break;
|
||||
case 12:
|
||||
|
||||
break;
|
||||
case 13:
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let knife = this.knifeList.find(t => t.isModelingKnife() && Math.abs(t.diameter - 2 * radius) < 0.02 && t.length >= depth - 0.01 && processFaceArr.includes(t.processFace))
|
||||
|
||||
return knife;
|
||||
}
|
||||
|
||||
}
|
2
samples/handleAbility/基础功能目录.md
Normal file
2
samples/handleAbility/基础功能目录.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# 基础功能目录
|
||||
该目录下的功能函数尽量不包含业务逻辑
|
1752
samples/moduleManager/RectOptimizeWorker/RectOptimizeMachine.js
Normal file
1752
samples/moduleManager/RectOptimizeWorker/RectOptimizeMachine.js
Normal file
File diff suppressed because it is too large
Load Diff
1746
samples/moduleManager/RectOptimizeWorker/RectOptimizeMachine.ts
Normal file
1746
samples/moduleManager/RectOptimizeWorker/RectOptimizeMachine.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
||||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var RectOptimizeMachine_1 = require("./RectOptimizeMachine");
|
||||
var ctx = this;
|
||||
if (typeof window !== 'undefined' && 'Worker' in window) {
|
||||
ctx.addEventListener('message', function (event) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
var m, _a, blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain, info;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
m = new RectOptimizeMachine_1.RectOptimizeMachine();
|
||||
m.CallBack = function (best, fit, arg, info) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
ctx.postMessage([best, fit, arg, info]);
|
||||
return [2 /*return*/];
|
||||
});
|
||||
}); };
|
||||
if (!(event.data.type == 'start')) return [3 /*break*/, 1];
|
||||
_a = (event.data.data), blockList = _a[0], boardList = _a[1], boardCount = _a[2], optimizeTimes = _a[3], isDoubleFaceBlockFirst = _a[4], gap = _a[5], gzpb = _a[6], isDoubleFaceBlockInRemain = _a[7];
|
||||
m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain);
|
||||
return [3 /*break*/, 3];
|
||||
case 1:
|
||||
info = {
|
||||
type: 'isStop',
|
||||
};
|
||||
return [4 /*yield*/, m.Stop(info)];
|
||||
case 2:
|
||||
_b.sent();
|
||||
ctx.postMessage([[], null, null, info]);
|
||||
ctx === null || ctx === void 0 ? void 0 : ctx.terminate();
|
||||
_b.label = 3;
|
||||
case 3: return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); });
|
||||
}
|
||||
else {
|
||||
}
|
||||
exports.default = {};
|
@@ -0,0 +1,42 @@
|
||||
import type { Big_bang, xbang } from './bang'
|
||||
import { RectOptimizeMachine } from './RectOptimizeMachine'
|
||||
// import {Worker} from "worker_threads"
|
||||
import { Worker as NodeWorker } from 'worker_threads';
|
||||
const ctx: NodeWorker | Worker = self as any
|
||||
debugger
|
||||
if (typeof window !== 'undefined' && 'Worker' in window) {
|
||||
ctx.addEventListener('message', async (event) => {
|
||||
let m = new RectOptimizeMachine()
|
||||
m.CallBack = async (best, fit, arg, info) => {
|
||||
|
||||
ctx.postMessage([best, fit, arg, info])
|
||||
}
|
||||
if (event.data.type == 'start') {
|
||||
/**
|
||||
* blockList 小板列表
|
||||
* boardList 大板(N个元素,前N-1个元素表示余料板且余料板须为矩形,第N个元素表示大板)
|
||||
* boardCount 余料板数量(bigBang中前N-1个元素对应的数量,如果bigBang中只有一个元素即只有大板没有余料板,则为空数组)
|
||||
* optimizeTimes 新优化次数
|
||||
* isDoubleFaceBlockFirst 双面加工的小板是否优先排入
|
||||
* gap 排版缝隙 = 开料刀直径 + 缝隙
|
||||
* gzpb 规则排版
|
||||
* isDoubleFaceBlockInRemain 余料板是否排入双面加工的小板
|
||||
*/
|
||||
let [blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain] = (event.data.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean]
|
||||
|
||||
m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain)
|
||||
} else {
|
||||
const info = {
|
||||
type: 'isStop',
|
||||
}
|
||||
await m.Stop(info)
|
||||
ctx.postMessage([[], null, null, info])
|
||||
ctx?.terminate()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default {} as typeof Worker & (new () => Worker)
|
60
samples/moduleManager/RectOptimizeWorker/bang.js
Normal file
60
samples/moduleManager/RectOptimizeWorker/bang.js
Normal file
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WorkerItemType = exports.BlockRegion = exports.Big_bang = exports.HoleType = exports.ComposingType = exports.LineType = void 0;
|
||||
/**纹路类型 Positive=0正纹 Reverse=1反纹 CanReversal=2可翻转 */
|
||||
var LineType;
|
||||
(function (LineType) {
|
||||
/**正纹 */
|
||||
LineType[LineType["Positive"] = 0] = "Positive";
|
||||
/**反纹 */
|
||||
LineType[LineType["Reverse"] = 1] = "Reverse";
|
||||
/**可翻转 */
|
||||
LineType[LineType["CanReversal"] = 2] = "CanReversal";
|
||||
})(LineType || (exports.LineType = LineType = {}));
|
||||
/**版面类型: Positive=0正面 Reverse=1反面 Arbitrary=2任意面 */
|
||||
var ComposingType;
|
||||
(function (ComposingType) {
|
||||
/**正面 */
|
||||
ComposingType[ComposingType["Positive"] = 0] = "Positive";
|
||||
/**反面 */
|
||||
ComposingType[ComposingType["Reverse"] = 1] = "Reverse";
|
||||
/**任意面 */
|
||||
ComposingType[ComposingType["Arbitrary"] = 2] = "Arbitrary";
|
||||
})(ComposingType || (exports.ComposingType = ComposingType = {}));
|
||||
/**孔类型 None=0无 Positive=1正面 Reverse=2反面 Two=3正反 */
|
||||
var HoleType;
|
||||
(function (HoleType) {
|
||||
/**无 */
|
||||
HoleType[HoleType["None"] = 0] = "None";
|
||||
/**正面 */
|
||||
HoleType[HoleType["Positive"] = 1] = "Positive";
|
||||
/**反面 */
|
||||
HoleType[HoleType["Reverse"] = 2] = "Reverse";
|
||||
/**正反 */
|
||||
HoleType[HoleType["Two"] = 3] = "Two";
|
||||
})(HoleType || (exports.HoleType = HoleType = {}));
|
||||
/** 大板 */
|
||||
var Big_bang //待优化的板
|
||||
= /** @class */ (function () {
|
||||
function Big_bang() {
|
||||
}
|
||||
return Big_bang;
|
||||
}());
|
||||
exports.Big_bang = Big_bang;
|
||||
var BlockRegion;
|
||||
(function (BlockRegion) {
|
||||
/** 左下 = 0 */
|
||||
BlockRegion[BlockRegion["LEFT_BOTTOM"] = 0] = "LEFT_BOTTOM";
|
||||
/** 右下 = 1 */
|
||||
BlockRegion[BlockRegion["RIGHT_BOTTOM"] = 1] = "RIGHT_BOTTOM";
|
||||
/** 右上 = 2 */
|
||||
BlockRegion[BlockRegion["RIGHT_TOP"] = 2] = "RIGHT_TOP";
|
||||
/** 左上 = 3 */
|
||||
BlockRegion[BlockRegion["LEFT_TOP"] = 3] = "LEFT_TOP";
|
||||
})(BlockRegion || (exports.BlockRegion = BlockRegion = {}));
|
||||
var WorkerItemType = /** @class */ (function () {
|
||||
function WorkerItemType() {
|
||||
}
|
||||
return WorkerItemType;
|
||||
}());
|
||||
exports.WorkerItemType = WorkerItemType;
|
1605
samples/moduleManager/RectOptimizeWorker/bang.ts
Normal file
1605
samples/moduleManager/RectOptimizeWorker/bang.ts
Normal file
File diff suppressed because it is too large
Load Diff
262
samples/moduleManager/module1.ts
Normal file
262
samples/moduleManager/module1.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { ProcessorModule } from "../../src/device";
|
||||
import { confItem, PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass";
|
||||
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
|
||||
import resData from './res1.json'
|
||||
import {Worker} from 'worker_threads'
|
||||
|
||||
/** 模块 新优化
|
||||
*
|
||||
* input 入参
|
||||
*
|
||||
* ProcessorModule<any, any>这里的any 可以自定义 根据组件需求
|
||||
*/
|
||||
|
||||
|
||||
export const RectOptimizeMachineModule: ProcessorModule<any, any> = {
|
||||
moduleName: "RectOptimizeMachine",
|
||||
config: {
|
||||
workerList: [],
|
||||
placeStyle: 1,
|
||||
},
|
||||
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
before(input) {
|
||||
// console.log(`优化前要做的事情`, input);
|
||||
},
|
||||
|
||||
process(input, next, context) {
|
||||
let { blockList, materialList } = input
|
||||
let bList: any = []
|
||||
let pm = materialList[0]
|
||||
blockList.map(e => {
|
||||
if (e.goodsId == pm.goodsId) {
|
||||
bList[e.blockNo] = e
|
||||
// let detail = blockDetailList.find(x => x.blockId == e.blockId)
|
||||
// // 处理可能出现的异常 --没有板明细
|
||||
// if (!Reflect.has(bList[e.blockNo], 'blockDetail')) {
|
||||
// bList[e.blockNo].blockDetail = new PlaceBlockDetail(detail)
|
||||
// }
|
||||
// bList[e.blockNo].isTurnFaceToPlace = !this.getDoFace(bList[e.blockNo], this.processMode())
|
||||
// 是否翻面后续处理
|
||||
bList[e.blockNo].isTurnFaceToPlace = true
|
||||
if (Array.isArray(pm.blockList)) {
|
||||
|
||||
pm.blockList.push(e)
|
||||
} else {
|
||||
pm.blockList = [e]
|
||||
}
|
||||
}
|
||||
})
|
||||
pm.cutBorder = this.cutBoardBorder
|
||||
pm.cutKnifeGap = this.blockKnifeLineSpacing
|
||||
|
||||
/** 小板 */
|
||||
let bans: xbang[] = []
|
||||
|
||||
// 实际开料大板的列表
|
||||
let big_Bang: Big_bang[] = []
|
||||
let big_BangSL: number[] = []
|
||||
|
||||
|
||||
let border = this.cutBoardBorder
|
||||
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
|
||||
|
||||
// 余料板 以及实际开料大板
|
||||
for (const cuttingBoard of pm.remainBoardList) {
|
||||
big_Bang.push({ w: cuttingBoard.width - border * 2 + borderOff * 2, l: cuttingBoard.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
big_BangSL.push(cuttingBoard.count || 999)
|
||||
}
|
||||
// big_Bang = []
|
||||
// big_BangSL = []
|
||||
// 母板 兜底的
|
||||
big_Bang.push({ w: pm.width - border * 2 + borderOff * 2, l: pm.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
// 生成小板
|
||||
for (let key in bList) {
|
||||
let b = bList[key]
|
||||
|
||||
let bid = b.blockNo
|
||||
|
||||
let width = b.placeFullWidth
|
||||
let length = b.placeFullLength
|
||||
let line = toLine(b.texture)
|
||||
|
||||
bans.push({
|
||||
l: length,
|
||||
w: width,
|
||||
line,
|
||||
face: toface(b),
|
||||
id: bid,
|
||||
bno: b.blockNo,
|
||||
holeFaceCount: 3,
|
||||
isRect: !b.isUnRegular,
|
||||
hasHole: false,
|
||||
isdtwosided: true,
|
||||
})
|
||||
}
|
||||
|
||||
let bestCount = 0
|
||||
if (bans.length == 0) // 没有板了
|
||||
{
|
||||
|
||||
if (context.CallBack) {
|
||||
let best = []
|
||||
let yl: Big_bang[] = []
|
||||
let fit = 0
|
||||
let resObj = {
|
||||
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
|
||||
info: {
|
||||
times: -1,
|
||||
type: 'noBan'
|
||||
}
|
||||
}
|
||||
context.CallBack(resObj)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
let xyhcs = 50
|
||||
if (bans.length > 1000) { xyhcs = 40 }
|
||||
else if (bans.length > 1500) { xyhcs = 30 }
|
||||
else if (bans.length > 2000) { xyhcs = 25 }
|
||||
else if (bans.length > 4000) { xyhcs = 20 }
|
||||
else if (bans.length > 6000) { xyhcs = 15 }
|
||||
else if (bans.length > 10000) { xyhcs = 10 }
|
||||
else if (bans.length > 15000) { xyhcs = 5 }
|
||||
else if (bans.length > 20000) { xyhcs = 1 }
|
||||
let isDoubleFaceBlockFirst = this.isDoubleFaceBlockFirst // 双面加工排前面
|
||||
let gap = this.blockKnifeLineSpacing
|
||||
// this.bestfit = 0
|
||||
|
||||
/** 这里要做个多线程 测试环境下 这部分有问题 暂时略过 */
|
||||
for (let j = 0; j < 1; j++) {
|
||||
|
||||
// let w = new Worker(new URL('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker', import.meta.url), { type: 'module' })
|
||||
let w= new Worker('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker')
|
||||
const data = {
|
||||
type: 'start',
|
||||
data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.isRectPlace, this.isDoubleFaceBlockInRemain],
|
||||
}
|
||||
let item: WorkerItemType = {
|
||||
w: w,
|
||||
goodsId: pm.goodsId,
|
||||
pm,
|
||||
status: 'start'
|
||||
}
|
||||
if (this.config.workerList.findIndex(e => e.goodsId == item.goodsId) == -1) {
|
||||
this.config.workerList.push(item)
|
||||
}
|
||||
|
||||
|
||||
let workItem = this.config.workerList.find(e => e.goodsId == pm.goodsId)
|
||||
if (workItem && workItem != undefined) {
|
||||
workItem.w?.postMessage(data)
|
||||
|
||||
workItem.w.onmessage = async (d) => {
|
||||
let [best, yl, fit, info] = d.data as [any[], Big_bang[], number, any]
|
||||
|
||||
switch (info.type) {
|
||||
case 'loop':
|
||||
let resObj = {
|
||||
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
|
||||
info
|
||||
}
|
||||
if (context.CallBack) {
|
||||
context.CallBack(resObj)
|
||||
}
|
||||
|
||||
break;
|
||||
case 'stop':
|
||||
console.error('stop =》dataHandleBase', info, this.config.workerList)
|
||||
this.terminateWorker({ goodsId: pm.goodsId })
|
||||
break;
|
||||
case 'isStop':
|
||||
// console.error('isStop', info)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (context.CallBack) {
|
||||
context.CallBack(resData)
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
after(result) {
|
||||
console.log(`优化后要做的事情`,);
|
||||
},
|
||||
|
||||
onError(error) {
|
||||
console.log('出错了哦', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function toLine(texture): LineType {
|
||||
if (texture == 0)
|
||||
return LineType.Positive
|
||||
if (texture == 1)
|
||||
return LineType.CanReversal
|
||||
return LineType.Reverse
|
||||
}
|
||||
/** 小板加工面:Positive=0正面 Reverse=1反面 Arbitrary=2任意 */
|
||||
function toface(block: PlaceBlock): ComposingType {
|
||||
let turnF = block.isTurnFaceToPlace
|
||||
// if (this.isTurnFaceToPlace)
|
||||
if (true)
|
||||
turnF = !turnF
|
||||
if (!turnF)
|
||||
return ComposingType.Positive
|
||||
return ComposingType.Reverse
|
||||
}
|
||||
/** 配置列表 */
|
||||
export const confList: confItem[] = [
|
||||
{
|
||||
key: 'isCutProcess',
|
||||
label: '开料机(雕刻机)加工',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isCutAndCNCProcess',
|
||||
label: '开料机CNC组合',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'isCustomized',
|
||||
label: '定制加工',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'cutBoardBorder',
|
||||
label: '总修边宽度',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
key: 'blockKnifeLineSpacing',
|
||||
label: '刀路间距',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
key: 'isDoubleFaceBlockFirst',
|
||||
label: '双面开料优先排版',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isRectPlace',
|
||||
label: '新优化规则排版',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
// yuLiaoBoardDo2FaceBlock
|
||||
key: 'isDoubleFaceBlockInRemain',
|
||||
label: '余料板允许排入双面加工的小板',
|
||||
value: true
|
||||
},
|
||||
]
|
245
samples/moduleManager/module_RectOptimizeMachine.ts
Normal file
245
samples/moduleManager/module_RectOptimizeMachine.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
import { confItem, PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass";
|
||||
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
|
||||
import resData from './res1.json'
|
||||
import { UniversalWorker } from "../WorkerHelper";
|
||||
import { RectOptimizeMachine } from "./RectOptimizeWorker/RectOptimizeMachine";
|
||||
/** 模块 新优化
|
||||
*
|
||||
* input 入参
|
||||
*
|
||||
* ProcessorModule<any, any>这里的any 可以自定义 根据组件需求
|
||||
*/
|
||||
|
||||
|
||||
export const RectOptimizeMachineModule: ProcessorModule<any, any> = {
|
||||
moduleName: "RectOptimizeMachine",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
|
||||
placeStyle: 1,
|
||||
/** 总修边宽度 */
|
||||
cutBoardBorder: 3,
|
||||
cutKnifeGap: 1,
|
||||
isDoubleFaceBlockFirst: true,
|
||||
blockKnifeLineSpacing: 0,
|
||||
isRectPlace: false,
|
||||
isDoubleFaceBlockInRemain: true,
|
||||
},
|
||||
workerList: [],
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
before(input) {
|
||||
// console.log(`优化前要做的事情`, input);
|
||||
},
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
let { blockList, materialList } = input
|
||||
let bList: any = []
|
||||
let pm = materialList[0]
|
||||
blockList.map(e => {
|
||||
if (e.goodsId == pm.goodsId) {
|
||||
bList[e.blockNo] = e
|
||||
// let detail = blockDetailList.find(x => x.blockId == e.blockId)
|
||||
// // 处理可能出现的异常 --没有板明细
|
||||
// if (!Reflect.has(bList[e.blockNo], 'blockDetail')) {
|
||||
// bList[e.blockNo].blockDetail = new PlaceBlockDetail(detail)
|
||||
// }
|
||||
// bList[e.blockNo].isTurnFaceToPlace = !this.getDoFace(bList[e.blockNo], this.processMode())
|
||||
// 是否翻面后续处理
|
||||
bList[e.blockNo].isTurnFaceToPlace = true
|
||||
if (Array.isArray(pm.blockList)) {
|
||||
|
||||
pm.blockList.push(e)
|
||||
} else {
|
||||
pm.blockList = [e]
|
||||
}
|
||||
}
|
||||
})
|
||||
pm.cutBorder = this.config.cutBoardBorder
|
||||
pm.cutKnifeGap = this.config.cutKnifeGap
|
||||
|
||||
/** 小板 */
|
||||
let bans: xbang[] = []
|
||||
|
||||
// 实际开料大板的列表
|
||||
let big_Bang: Big_bang[] = []
|
||||
let big_BangSL: number[] = []
|
||||
|
||||
|
||||
let border = this.config.cutBoardBorder
|
||||
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
|
||||
|
||||
// 余料板 以及实际开料大板
|
||||
for (const cuttingBoard of pm.remainBoardList) {
|
||||
big_Bang.push({ w: cuttingBoard.width - border * 2 + borderOff * 2, l: cuttingBoard.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
big_BangSL.push(cuttingBoard.count || 999)
|
||||
}
|
||||
// big_Bang = []
|
||||
// big_BangSL = []
|
||||
// 母板 兜底的
|
||||
big_Bang.push({ w: pm.width - border * 2 + borderOff * 2, l: pm.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
|
||||
// 生成小板
|
||||
for (let key in bList) {
|
||||
let b = bList[key]
|
||||
|
||||
let bid = b.blockNo
|
||||
|
||||
let width = b.placeFullWidth
|
||||
let length = b.placeFullLength
|
||||
let line = toLine(b.texture)
|
||||
|
||||
bans.push({
|
||||
l: length,
|
||||
w: width,
|
||||
line,
|
||||
face: toface(b),
|
||||
id: bid,
|
||||
bno: b.blockNo,
|
||||
holeFaceCount: 3,
|
||||
isRect: !b.isUnRegular,
|
||||
hasHole: false,
|
||||
isdtwosided: true,
|
||||
})
|
||||
}
|
||||
|
||||
let bestCount = 0
|
||||
if (bans.length == 0) // 没有板了
|
||||
{
|
||||
|
||||
if (context.CallBack) {
|
||||
let best = []
|
||||
let yl: Big_bang[] = []
|
||||
let fit = 0
|
||||
let resObj = {
|
||||
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
|
||||
info: {
|
||||
times: -1,
|
||||
type: 'noBan'
|
||||
}
|
||||
}
|
||||
context.CallBack(resObj)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
let xyhcs = 50
|
||||
if (bans.length > 1000) { xyhcs = 40 }
|
||||
else if (bans.length > 1500) { xyhcs = 30 }
|
||||
else if (bans.length > 2000) { xyhcs = 25 }
|
||||
else if (bans.length > 4000) { xyhcs = 20 }
|
||||
else if (bans.length > 6000) { xyhcs = 15 }
|
||||
else if (bans.length > 10000) { xyhcs = 10 }
|
||||
else if (bans.length > 15000) { xyhcs = 5 }
|
||||
else if (bans.length > 20000) { xyhcs = 1 }
|
||||
let isDoubleFaceBlockFirst = this.config.isDoubleFaceBlockFirst // 双面加工排前面
|
||||
let gap = this.config.blockKnifeLineSpacing
|
||||
|
||||
/** 这里要做个多线程 测试环境下 这部分有问题 暂时略过 */
|
||||
const m = new RectOptimizeMachine()
|
||||
const self = this
|
||||
m.CallBack = async (best, fit, arg, info) => {
|
||||
|
||||
console.log('优化结果', pm, [best, fit, arg, info]);
|
||||
let res={
|
||||
moduleName:self.moduleName,
|
||||
pm,
|
||||
result: [best, fit, arg, info],
|
||||
input,
|
||||
context
|
||||
}
|
||||
self.onMessage(res)
|
||||
}
|
||||
const data = {
|
||||
type: 'start',
|
||||
data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.config.isRectPlace, this.config.isDoubleFaceBlockInRemain],
|
||||
}
|
||||
m.Start(bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.config.isRectPlace, this.config.isDoubleFaceBlockInRemain)
|
||||
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
after(result, input, context) {
|
||||
console.log(`优化后要做的事情`,);
|
||||
},
|
||||
|
||||
onError(error) {
|
||||
console.log('出错了哦', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function toLine(texture): LineType {
|
||||
if (texture == 0)
|
||||
return LineType.Positive
|
||||
if (texture == 1)
|
||||
return LineType.CanReversal
|
||||
return LineType.Reverse
|
||||
}
|
||||
/** 小板加工面:Positive=0正面 Reverse=1反面 Arbitrary=2任意 */
|
||||
function toface(block: PlaceBlock): ComposingType {
|
||||
let turnF = block.isTurnFaceToPlace
|
||||
// if (this.isTurnFaceToPlace)
|
||||
if (true)
|
||||
turnF = !turnF
|
||||
if (!turnF)
|
||||
return ComposingType.Positive
|
||||
return ComposingType.Reverse
|
||||
}
|
||||
/** 配置列表 */
|
||||
export const confList: confItem[] = [
|
||||
{
|
||||
key: 'isCutProcess',
|
||||
label: '开料机(雕刻机)加工',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isCutAndCNCProcess',
|
||||
label: '开料机CNC组合',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'isCustomized',
|
||||
label: '定制加工',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: 'cutBoardBorder',
|
||||
label: '总修边宽度',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
key: 'blockKnifeLineSpacing',
|
||||
label: '刀路间距',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
key: 'isDoubleFaceBlockFirst',
|
||||
label: '双面开料优先排版',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'isRectPlace',
|
||||
label: '新优化规则排版',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
// yuLiaoBoardDo2FaceBlock
|
||||
key: 'isDoubleFaceBlockInRemain',
|
||||
label: '余料板允许排入双面加工的小板',
|
||||
value: true
|
||||
},
|
||||
]
|
130
samples/moduleManager/module_autoCalcCutOrder.ts
Normal file
130
samples/moduleManager/module_autoCalcCutOrder.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
import { KLSC, YH_bang } from "./RectOptimizeWorker/bang";
|
||||
|
||||
/** 模块 开料顺序
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
/** 开料顺序 */
|
||||
export const AutoCalcCutOrder: ProcessorModule<any, any> = {
|
||||
moduleName: "AutoCalcCutOrder",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
boardWidth: 0,
|
||||
boardLength: 0,
|
||||
placeStyle: 1,
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { materialList } = input
|
||||
|
||||
for (const pm of materialList) {
|
||||
for (const pb of pm.boardList) {
|
||||
let selectBs = pb.blockList;
|
||||
let beginId = 0;
|
||||
const has0 = pb.blockList.filter(t => t.cutOrder == 0);
|
||||
const has1 = pb.blockList.filter(t => t.cutOrder > 0);
|
||||
const has2 = pb.blockList.filter(t => t.cutOrder < 0);
|
||||
//有手动指定开料顺序的
|
||||
if (has0.length > 0 && (has1.length + has2.length) > 0) {
|
||||
selectBs = has0;
|
||||
if (has1.length > 0) //开头的
|
||||
{
|
||||
const bs = has1.sort((a, b) => a.cutOrder - b.cutOrder);
|
||||
for (const b of bs) {
|
||||
beginId++;
|
||||
b.cutOrder = beginId;
|
||||
}
|
||||
}
|
||||
if (has2.length > 0) //结尾的
|
||||
{
|
||||
const bs = has2.sort((a, b) => a.cutOrder - b.cutOrder);
|
||||
let endId = has0.length + has1.length;
|
||||
for (const b of bs) {
|
||||
endId++;
|
||||
b.cutOrder = endId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bangs: YH_bang[] = [];
|
||||
let blocks = new Array();
|
||||
for (let i = 0; i < selectBs.length; i++) {
|
||||
let block = selectBs[i];
|
||||
let bangid = i + 1;
|
||||
let x = block.placeX;
|
||||
let y = block.placeY;
|
||||
let pbg = block.placeLength;
|
||||
let pbk = block.placeWidth;
|
||||
blocks[bangid] = block;
|
||||
|
||||
bangs.push({
|
||||
bangid,
|
||||
line: 0,
|
||||
pbg,
|
||||
pbk,
|
||||
x,
|
||||
y,
|
||||
ishb: false,
|
||||
hb: [],
|
||||
isbig: false,
|
||||
isqg: false,
|
||||
isgr: false,
|
||||
gr: [],
|
||||
grid: -1
|
||||
});
|
||||
}
|
||||
let dt = pm.diameter + pm.cutKnifeGap;
|
||||
let k = pb.width;
|
||||
let g = pb.length;
|
||||
|
||||
let xdsc: KLSC = new KLSC(bangs, k, g, dt, 0, 0, 1);
|
||||
// try {
|
||||
// xdsc
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
let rt = xdsc.SCid;
|
||||
// let rt = JSXDSC(bangs, dt, k, g);
|
||||
|
||||
if (rt.length < selectBs.length) return;
|
||||
for (let i = 0; i < rt.length; i++) {
|
||||
let bid = rt[i];
|
||||
beginId++;
|
||||
blocks[bid].cutOrder = beginId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
76
samples/moduleManager/module_checkBlocks.ts
Normal file
76
samples/moduleManager/module_checkBlocks.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
|
||||
/** 模块 检查异常板
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
export const CheckBlocks: ProcessorModule<any, any> = {
|
||||
moduleName: "CheckMaterial",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
minBlockWidth: 0,
|
||||
minBlockThickness: 0,
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { materialList, blockList } = input
|
||||
const self = this;
|
||||
let checkArr = checkBlocks(materialList, blockList)
|
||||
if (checkArr.length > 0) {
|
||||
const _errinfo: ErrorInfo = {
|
||||
moduleName: this.moduleName,
|
||||
moduleVersion: this.moduleVersion,
|
||||
info: {
|
||||
data: checkArr,
|
||||
msg: '该订单内小板有异常'
|
||||
}
|
||||
}
|
||||
this.onError(_errinfo)
|
||||
}
|
||||
function checkBlocks(materialList, blockList): any[] {
|
||||
|
||||
let allBlocks: any[] = []
|
||||
for (let pm of materialList) {
|
||||
let bList = blockList.filter(e => e.goodsId == pm.goodsId)
|
||||
for (const pb of bList) {
|
||||
if (pb.width < self.minBlockWidth) {
|
||||
allBlocks.push(pb)
|
||||
}
|
||||
if (pb.thickness < self.minBlockThickness) {
|
||||
allBlocks.push(pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allBlocks
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
77
samples/moduleManager/module_checkMaterial.ts
Normal file
77
samples/moduleManager/module_checkMaterial.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
|
||||
/** 模块 检查是否 板材尺寸大于机台尺寸
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
/** 检查是否 板材尺寸大于机台尺寸 */
|
||||
export const CheckMaterial: ProcessorModule<any, any> = {
|
||||
moduleName: "CheckMaterial",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
boardWidth: 0,
|
||||
boardLength: 0,
|
||||
placeStyle:1,
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { materialList } = input
|
||||
const self = this;
|
||||
let checkArr = checkMetrial(materialList)
|
||||
if (checkArr.length > 0) {
|
||||
const _errinfo: ErrorInfo = {
|
||||
moduleName: this.moduleName,
|
||||
moduleVersion: this.moduleVersion,
|
||||
info: {
|
||||
data: checkArr,
|
||||
msg: '该订单内大板有异常'
|
||||
}
|
||||
}
|
||||
this.onError(_errinfo)
|
||||
}
|
||||
|
||||
function checkMetrial(materialList): any[] {
|
||||
|
||||
let errPMs: any = []
|
||||
|
||||
if (self.placeStyle !== 1) {
|
||||
for (let pm of materialList) {
|
||||
if (pm.orgWidth > self.boardWidth || pm.orgLength > self.boardLength) {
|
||||
// console.log('板材尺寸大于机台尺寸', PlaceStore.sysConfig)
|
||||
errPMs.push(pm)
|
||||
}
|
||||
}
|
||||
}
|
||||
return errPMs
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
58
samples/moduleManager/module_demoCallback.ts
Normal file
58
samples/moduleManager/module_demoCallback.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
|
||||
/** 模块 开料顺序
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
/** 开料顺序 */
|
||||
export const AutoCalcCutOrder: ProcessorModule<any, any> = {
|
||||
moduleName: "CheckMaterial",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
boardWidth: 0,
|
||||
boardLength: 0,
|
||||
placeStyle:1,
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { materialList } = input
|
||||
// if (pb.isLocked) return;
|
||||
// let blocks = pb.blockList;
|
||||
|
||||
// if (blocks.length == 0) return;
|
||||
// if (blocks.length == 1) {
|
||||
// blocks[0].cutOrder = 1;
|
||||
// }
|
||||
// else {
|
||||
// this.autoSortBlockNew(pm, pb);
|
||||
// }
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
152
samples/moduleManager/module_handleMaterialPlaceResult.ts
Normal file
152
samples/moduleManager/module_handleMaterialPlaceResult.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
import { BlockPlaceResult, BoardPlaceResult, BoardPosition, MaterialPlaceResult, PlaceStyle } from "../confClass";
|
||||
|
||||
/** 模块 处理优化源数据 数据处理 将优化数据转 为 MaterialPlaceResult
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
export const HandleMaterialPlaceResult: ProcessorModule<any, any> = {
|
||||
moduleName: "HandleMaterialPlaceResult",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
cutBoardBorder:3,
|
||||
// 是否跟随大板定位
|
||||
placeOriginByBoardLocation: true,
|
||||
boardLocation: 0
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
|
||||
let {
|
||||
bList, best, yl, pm, width, length
|
||||
} = context.MaterialPlaceSource
|
||||
let blocks = bList
|
||||
let retData = new MaterialPlaceResult()
|
||||
let boardCount = 0
|
||||
let remainCount = 0
|
||||
let border = this.config.cutBoardBorder
|
||||
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
|
||||
|
||||
|
||||
// 所有大板上的小板面积
|
||||
let size_all = 0
|
||||
|
||||
for (let i = 0; i < best.length; i++) {
|
||||
let bd = best[i]
|
||||
let isRemainBoard = false
|
||||
boardCount = retData.boards.length + 1
|
||||
if (i < yl.length) // 余料板
|
||||
{
|
||||
width = yl[i].w + border * 2 - borderOff * 2
|
||||
length = yl[i].l + border * 2 - borderOff * 2
|
||||
isRemainBoard = true
|
||||
remainCount++
|
||||
}
|
||||
|
||||
let boardResult = new BoardPlaceResult()
|
||||
boardResult.boardId = boardCount
|
||||
boardResult.width = width
|
||||
boardResult.length = length
|
||||
boardResult.isRemainBoard = isRemainBoard
|
||||
|
||||
let pid = 0
|
||||
for (let b of bd) {
|
||||
pid++
|
||||
let block = blocks[b.bangid]
|
||||
let placeStyle = PlaceStyle.FRONT
|
||||
if (!block) {
|
||||
return
|
||||
}
|
||||
let faceF = block.isTurnFaceToPlace || false
|
||||
let isTurnFaceToPlace = this.config.placeOriginByBoardLocation && (
|
||||
this.config.boardLocation == BoardPosition.RIGHT_BOTTOM || this.boardLocation == BoardPosition.LEFT_TOP)
|
||||
if (isTurnFaceToPlace)
|
||||
faceF = !faceF
|
||||
if (faceF) // 翻面开料
|
||||
{
|
||||
if (block.texture == 0) {
|
||||
placeStyle = PlaceStyle.BACK
|
||||
}
|
||||
else if (block.texture == 2) {
|
||||
placeStyle = PlaceStyle.BACK_TURN_LEFT
|
||||
}
|
||||
else {
|
||||
placeStyle = (b.pbg == block.placeFullLength ? PlaceStyle.BACK : PlaceStyle.BACK_TURN_LEFT)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (block.texture == 0) {
|
||||
placeStyle = PlaceStyle.FRONT
|
||||
}
|
||||
else if (block.texture == 2) {
|
||||
placeStyle = PlaceStyle.FRONT_TURN_RIGHT
|
||||
}
|
||||
else {
|
||||
placeStyle = (b.pbg == block.placeFullLength ? PlaceStyle.FRONT : PlaceStyle.FRONT_TURN_RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
let br: any = null
|
||||
br = new BlockPlaceResult(block.blockNo, boardCount, pid, b.x, b.y, b.pbk, b.pbg, placeStyle, block.area)
|
||||
boardResult.blocks.push(br)
|
||||
boardResult.area += block.area
|
||||
}
|
||||
retData.boards.push(boardResult)
|
||||
size_all += boardResult.area
|
||||
|
||||
let val = (size_all - boardResult.area) / (retData.boards.length - 1)
|
||||
retData.avgUsageRateAll = size_all / retData.boards.length
|
||||
retData.avgUsageRateExcludeLastBoard = Number.isNaN(val) ? boardResult.area : val
|
||||
retData.usageRateLastBoard = boardResult.area
|
||||
retData.boardCount = retData.boards.length
|
||||
}
|
||||
retData.boardCount = retData.boards.length // 大板数
|
||||
let remainBoardCount = retData.boards.filter(t => t.remainNo != '').length // 异形大板数
|
||||
let remianCount = 0
|
||||
|
||||
if (Array.isArray(pm.remainBoardList)) {
|
||||
pm.remainBoardList.forEach(e => {
|
||||
if (e?.placeBoardJSON) {
|
||||
let str = e?.placeBoardJSON
|
||||
e.placeBoardList = JSON.parse(str)
|
||||
remianCount += e.placeBoardList.length
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
retData.remainBoardCount = pm?.remainBoardList ? remianCount : yl.length + remainBoardCount
|
||||
|
||||
console.log({ placeResult: retData, pm } )
|
||||
let res = { module:this.moduleName, placeResult: retData, pm }
|
||||
|
||||
Reflect.set(context,'MaterialPlaceResult',res)
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
639
samples/moduleManager/module_handlePlaceResultToPlaceMaterial.ts
Normal file
639
samples/moduleManager/module_handlePlaceResultToPlaceMaterial.ts
Normal file
@@ -0,0 +1,639 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
import { BoardPosition, PlaceBlock, PlaceBoard, PlaceMaterial, PlaceStyle } from "../confClass";
|
||||
import { ArrayExt } from "../handleAbility/common/ArrayExt";
|
||||
|
||||
/** 模块 检查是否 板材尺寸大于机台尺寸
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
class Point {
|
||||
/** 坐标x */
|
||||
x: number
|
||||
/** 坐标y */
|
||||
y: number
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查是否 板材尺寸大于机台尺寸 */
|
||||
export const handlePlaceResultToPlaceMaterial: ProcessorModule<any, any> = {
|
||||
moduleName: "handlePlaceResultToPlaceMaterial",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
boardWidth: 0,
|
||||
boardLength: 0,
|
||||
placeStyle: 1,
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
// const { placeResult,pm } = input
|
||||
|
||||
let pm: PlaceMaterial = context.MaterialPlaceResult.pm
|
||||
pm.tempBestPlaceResult = context.MaterialPlaceResult.placeResult
|
||||
// 没发现优化结果
|
||||
if (!pm.tempBestPlaceResult) {
|
||||
let errInfo: ErrorInfo = {
|
||||
moduleName: this.moduleName,
|
||||
info: '没有优化数据'
|
||||
}
|
||||
return errInfo
|
||||
}
|
||||
|
||||
let placeResult = pm.tempBestPlaceResult
|
||||
|
||||
let orgBoardList = pm.boardList
|
||||
|
||||
let dic: any = [] // 存放当前 优化的小板
|
||||
|
||||
pm.boardList = []
|
||||
let boardId = 1
|
||||
// 第一次 false
|
||||
if (pm.tempPlaceResultOnyUnlockedBoard) {
|
||||
for (let pb of orgBoardList) {
|
||||
if (pb.isLocked == false && pb.cutedType == 0) // 未锁定
|
||||
{
|
||||
pb.blockList.forEach(i => dic[i.blockNo] = i)
|
||||
}
|
||||
else // 锁定
|
||||
{
|
||||
if (pb.isAdnormal) {
|
||||
let sb = pm.remainBoardList.find(t => t.id == Number(pb.boardNo))
|
||||
if (sb)
|
||||
sb.isUsed = true
|
||||
}
|
||||
pb.boardId = boardId
|
||||
pm.boardList.push(pb)
|
||||
boardId++
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
pm.blockList.forEach(i => dic[i.blockNo] = i)
|
||||
}
|
||||
|
||||
// 0904 修复 需求改造后 导致 这里的dic 最终为空
|
||||
if (Object.keys(dic).length == 0) {
|
||||
pm.blockList.forEach(i => dic[i.blockNo] = i)
|
||||
}
|
||||
|
||||
let locator = this.boardLocation
|
||||
// 大板优化结果
|
||||
|
||||
for (let bpr of placeResult.boards) {
|
||||
boardId++
|
||||
// bpr.isRemainBoard false
|
||||
let bW = bpr.isRemainBoard ? bpr.width : pm.width
|
||||
let bL = bpr.isRemainBoard ? bpr.length : pm.length
|
||||
let pb = new PlaceBoard(boardId, bW, bL, bpr.remainId, bpr.remainNo)
|
||||
pb.isCreateRemainSpace = true
|
||||
|
||||
for (let bInfo of bpr.blocks) {
|
||||
let block: PlaceBlock = dic[bInfo.blockId]
|
||||
|
||||
if (block) {
|
||||
block.isPlaced = true
|
||||
block.boardId = boardId
|
||||
block.placeId = bInfo.placeId
|
||||
block.placeX = bInfo.placeX
|
||||
block.placeY = bInfo.placeY
|
||||
// console.log('重置 开料面 开料信息 after', block.boardId, block.blockNo, block, block.placeStyle, bInfo.placeStyle)
|
||||
|
||||
/**
|
||||
* 雕刻机(钻孔、拉槽、开料)优化排版,开料排版面(开料正面)选择顺序:排版面>>造型>>排钻>>设计正面
|
||||
1. 排版面
|
||||
小板设计排版面为正面或反面,设置对应面为开料排版面,
|
||||
小板设计排版面为随意面,则继续;
|
||||
2. 造型
|
||||
小板只有单面造型,设置对应面为开料排版面,
|
||||
小板双面无造型或双面有造型,则继续;
|
||||
3. 排钻
|
||||
小板有大孔(偏心轮锁孔),设置对应面为开料排版面,
|
||||
小板无大孔(偏心轮锁孔),则设置孔多的面为开料排版面,
|
||||
小板无孔,则继续;
|
||||
4. 设计正面
|
||||
默认设置设计正面为开料排版面。
|
||||
*/
|
||||
|
||||
block.placeStyle = bInfo.placeStyle
|
||||
|
||||
let posOff = this.getOffDis(block)
|
||||
|
||||
block.placeOffX = posOff.x
|
||||
block.placeOffY = posOff.y
|
||||
block.placeX = bInfo.placeX + posOff.x
|
||||
block.placeY = bInfo.placeY + posOff.y
|
||||
|
||||
if (bInfo.placeStyle == PlaceStyle.FRONT || bInfo.placeStyle == PlaceStyle.FRONT_TURN_BACK
|
||||
|| bInfo.placeStyle == PlaceStyle.BACK || bInfo.placeStyle == PlaceStyle.BACK_TURN_BACK) {
|
||||
block.placeWidth = block.cutWidth
|
||||
block.placeLength = block.cutLength
|
||||
}
|
||||
else {
|
||||
block.placeWidth = block.cutLength
|
||||
block.placeLength = block.cutWidth
|
||||
}
|
||||
|
||||
block.isAutoPlaced = true
|
||||
block.isOverlap = false
|
||||
block.cutOrder = 0
|
||||
pb.blockList.push(block)
|
||||
pb.blockCount++
|
||||
pb.blockArea += block.area
|
||||
delete dic[bInfo.blockId] // 移除block;
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
if (bpr.isScrap) {
|
||||
// 设置前余料板的使用状态,将不需要的释放.
|
||||
console.log('设置前余料板的使用状态,将不需要的释放.')
|
||||
let sb = pm.remainBoardList.find(t => t.id == bpr.remainId)
|
||||
if (sb) {
|
||||
sb.isUsed = true
|
||||
|
||||
if (sb.placeStyle % 2 != 0) {
|
||||
pb.width = sb.length
|
||||
pb.length = sb.width
|
||||
}
|
||||
let pl = sb.placePolyline
|
||||
pb.points = sb?.placePolyline?.LineData.map((t) => { return { x: t.pt.x, y: t.pt.y, bul: t.bul } }) || []
|
||||
// pb.StoreNo = sb.StoreHouse;
|
||||
}
|
||||
}
|
||||
pb.usageRate = Math.round(10000 * pb.blockArea / pb.area) / 100
|
||||
pm.boardList.push(pb)
|
||||
console.log('大板靠板翻转', pb.boardId, locator)
|
||||
|
||||
// 大板靠板翻转
|
||||
this.turnPlacePosition(pb, locator)
|
||||
for (let block of pb.blockList) {
|
||||
// 重置 开料面 开料信息
|
||||
// console.log('重置 开料面 开料信息', block.boardId, block.blockNo, block, block.placeStyle)
|
||||
if (block.placeStyle == null || block.placeStyle == undefined) {
|
||||
console.log('handleTempPlaceResultToPlaceMaterial error !block.placeStyle is null or undefined')
|
||||
} else {
|
||||
this.resetPlaceStyle(block, block.placeStyle)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//【功能】 大板长边两侧 width 范围内 避免出现造型 DisPoseModelInBoardBorderWidth > 0 生效
|
||||
// this.DisPoseModelInBoardBorder(
|
||||
// pm,
|
||||
// this.boardBorderModelRange,
|
||||
// this.boardBorderModelModeToFace,
|
||||
// this.boardBorderModelByMachine,
|
||||
// this.modelNearBoardBorder,
|
||||
// )
|
||||
|
||||
// for (let pb of pm.boardList) {
|
||||
// // 重设大板汇总 重设 大板开料 顺序,下刀点
|
||||
// this.resetPlaceBoard(pm, pb)
|
||||
// // 检查干涉
|
||||
// if (pb.isLocked == false && pb.cutedType == 0)
|
||||
// this.checkOverlapInBoard(pm, pb)
|
||||
// }
|
||||
|
||||
let rBoardCount = placeResult.boardCount
|
||||
let rUseSize_avg = placeResult.avgUsageRateAll
|
||||
let rUseSize_noLast = placeResult.avgUsageRateExcludeLastBoard
|
||||
let rUseSize_last = placeResult.usageRateLastBoard
|
||||
|
||||
pm.avgUsageRateAll = rUseSize_avg
|
||||
pm.avgUsageRateExcludeLastBoard = rUseSize_noLast
|
||||
pm.usageRateLastBoard = rUseSize_last
|
||||
pm.boardCount = pm.boardList.length
|
||||
pm.remainBoardCount = placeResult.remainBoardCount
|
||||
pm.minBoardId = 1
|
||||
pm.maxBoardId = pm.boardCount
|
||||
//【功能】 获取封边长度
|
||||
// pm.edgeSealLengthList = this.getMaterialSealEdge(pm.blockList)
|
||||
|
||||
pm.boardCountFlipFace = ArrayExt.count(pm.boardList, t => t.isTwoFaceProcessing)
|
||||
|
||||
pm.isOptimized = true
|
||||
pm.tempBestPlaceResult = null
|
||||
pm.tempPlaceResultError = ''
|
||||
|
||||
let mesg = ''
|
||||
let c = 0
|
||||
for (let v in dic) {
|
||||
mesg += `${v} `
|
||||
c++
|
||||
}
|
||||
|
||||
mesg = `有${c}片小板未能排入大板,有可能是尖角导致优化失败.${mesg}`
|
||||
|
||||
if (c > 0) {
|
||||
console.log(pm)
|
||||
// createMessage.error(mesg)
|
||||
|
||||
throw new Error(mesg)
|
||||
}
|
||||
|
||||
// return pm
|
||||
let res = { module:this.moduleName, pm }
|
||||
Reflect.set(context,this.moduleName,res)
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
},
|
||||
/** 获得板件偏移值 */
|
||||
getOffDis(block: PlaceBlock, placeStyle?: PlaceStyle): any {
|
||||
// console.log('获得板件偏移值')
|
||||
if (placeStyle == null || placeStyle == undefined) {
|
||||
placeStyle = block.placeStyle
|
||||
}
|
||||
|
||||
let expandSize: any = block.sizeExpand
|
||||
let posOff = { x: 0, y: 0, left: 0, right: 0, top: 0, bottom: 0 }
|
||||
if (expandSize) {
|
||||
switch (placeStyle) {
|
||||
case PlaceStyle.FRONT: // 正面
|
||||
posOff.x = expandSize.left
|
||||
posOff.y = expandSize.bottom
|
||||
posOff.left = expandSize.left
|
||||
posOff.right = expandSize.right
|
||||
posOff.bottom = expandSize.bottom
|
||||
posOff.top = expandSize.top
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转
|
||||
posOff.x = expandSize.bottom
|
||||
posOff.y = expandSize.right
|
||||
posOff.left = expandSize.bottom
|
||||
posOff.right = expandSize.top
|
||||
posOff.bottom = expandSize.right
|
||||
posOff.top = expandSize.left
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK: // 正面后转
|
||||
posOff.x = expandSize.right
|
||||
posOff.y = expandSize.top
|
||||
posOff.left = expandSize.right
|
||||
posOff.right = expandSize.left
|
||||
posOff.bottom = expandSize.top
|
||||
posOff.top = expandSize.bottom
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT: // 正面左转
|
||||
posOff.x = expandSize.top
|
||||
posOff.y = expandSize.left
|
||||
posOff.left = expandSize.top
|
||||
posOff.right = expandSize.bottom
|
||||
posOff.bottom = expandSize.left
|
||||
posOff.top = expandSize.right
|
||||
break
|
||||
case PlaceStyle.BACK: // 反面
|
||||
posOff.x = expandSize.right
|
||||
posOff.y = expandSize.bottom
|
||||
posOff.left = expandSize.right
|
||||
posOff.right = expandSize.left
|
||||
posOff.bottom = expandSize.bottom
|
||||
posOff.top = expandSize.top
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT: // 反面右转
|
||||
posOff.x = expandSize.bottom
|
||||
posOff.y = expandSize.left
|
||||
posOff.left = expandSize.bottom
|
||||
posOff.right = expandSize.top
|
||||
posOff.bottom = expandSize.left
|
||||
posOff.top = expandSize.right
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK: // 反面后转
|
||||
posOff.x = expandSize.left
|
||||
posOff.y = expandSize.top
|
||||
posOff.left = expandSize.left
|
||||
posOff.right = expandSize.right
|
||||
posOff.bottom = expandSize.top
|
||||
posOff.top = expandSize.bottom
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT: // 反面左转
|
||||
posOff.x = expandSize.top
|
||||
posOff.y = expandSize.right
|
||||
posOff.left = expandSize.bottom
|
||||
posOff.right = expandSize.bottom
|
||||
posOff.bottom = expandSize.right
|
||||
posOff.top = expandSize.left
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return posOff
|
||||
},
|
||||
/**翻转 */
|
||||
turnPlacePosition(pb: PlaceBoard, newlocator: BoardPosition) {
|
||||
if (this.placeOriginByBoardLocation == false)
|
||||
return
|
||||
if (pb.isAdnormal())
|
||||
return // 余料板是余料板,不参与翻转
|
||||
let width = pb.width
|
||||
let length = pb.length
|
||||
// RIGHT_BOTTOM, 靠板
|
||||
if (newlocator == BoardPosition.RIGHT_BOTTOM) {
|
||||
for (let block of pb.blockList) {
|
||||
let x = width - block.placeX - block.placeWidth
|
||||
let y = block.placeY
|
||||
let placeStyle = this.getPlaceStyle_zy(block)
|
||||
block.placeX = x
|
||||
block.placeY = y
|
||||
block.placeStyle = placeStyle
|
||||
}
|
||||
}
|
||||
// RIGHT_TOP, 靠板
|
||||
if (newlocator == BoardPosition.RIGHT_TOP) {
|
||||
console.log('BoardPosition=BoardPosition.RIGHT_TOP')
|
||||
for (let block of pb.blockList) {
|
||||
let x = width - block.placeX - block.placeWidth
|
||||
let y = length - block.placeLength - block.placeY
|
||||
let placeStyle = this.getPlaceStyle_dj(block)
|
||||
block.placeX = x
|
||||
block.placeY = y
|
||||
block.placeStyle = placeStyle
|
||||
}
|
||||
}
|
||||
// 左上角, 靠板
|
||||
if (newlocator == BoardPosition.LEFT_TOP) {
|
||||
console.log('BoardPosition=BoardPosition.左上角')
|
||||
for (let block of pb.blockList) {
|
||||
let x = block.placeX
|
||||
let y = length - block.placeLength - block.placeY
|
||||
let placeStyle = this.getPlaceStyle_sx(block)
|
||||
block.placeX = x
|
||||
block.placeY = y
|
||||
block.placeStyle = placeStyle
|
||||
}
|
||||
}
|
||||
},
|
||||
/** 板放置后,重置placeWidth, placeLength, 封边, 正反面, 面孔, 造型等 */
|
||||
resetPlaceStyle(_block: PlaceBlock, newStyle: PlaceStyle) {
|
||||
let block = new PlaceBlock(_block)
|
||||
block = { ..._block }
|
||||
// console.debug('resetPlaceStyle test!!!',newStyle)
|
||||
block.placeStyle = newStyle
|
||||
|
||||
// tryFix
|
||||
let _width = block.cutWidth
|
||||
let _lenth = block.cutLength
|
||||
if (block.width > block.length) {
|
||||
block.cutWidth = Math.max(_width, _lenth)
|
||||
block.cutLength = Math.min(_width, _lenth)
|
||||
} else {
|
||||
block.cutWidth = Math.min(_width, _lenth)
|
||||
block.cutLength = Math.max(_width, _lenth)
|
||||
}
|
||||
|
||||
|
||||
switch (newStyle) {
|
||||
case PlaceStyle.FRONT: // 正面
|
||||
block.placeWidth = block.cutWidth
|
||||
block.placeLength = block.cutLength
|
||||
block.placeSealLeft = block.sealLeft
|
||||
block.placeSealRight = block.sealRight
|
||||
block.placeSealTop = block.sealTop
|
||||
block.placeSealBottom = block.sealBottom
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountBottom || 0
|
||||
block.placeDirection = '→'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↓'
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转
|
||||
block.placeWidth = block.cutLength
|
||||
block.placeLength = block.cutWidth
|
||||
block.placeSealLeft = block.sealBottom
|
||||
block.placeSealRight = block.sealTop
|
||||
block.placeSealTop = block.sealLeft
|
||||
block.placeSealBottom = block.sealRight
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountBottom || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountRight || 0
|
||||
block.placeDirection = '↓'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '←'
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK: // 正面后转
|
||||
block.placeWidth = block.cutWidth
|
||||
block.placeLength = block.cutLength
|
||||
block.placeSealLeft = block.sealRight
|
||||
block.placeSealRight = block.sealLeft
|
||||
block.placeSealTop = block.sealBottom
|
||||
block.placeSealBottom = block.sealTop
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountBottom || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountTop || 0
|
||||
block.placeDirection = '←'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↑'
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT: // 正面左转
|
||||
block.placeWidth = block.cutLength
|
||||
block.placeLength = block.cutWidth
|
||||
block.placeSealLeft = block.sealTop
|
||||
block.placeSealRight = block.sealBottom
|
||||
block.placeSealTop = block.sealRight
|
||||
block.placeSealBottom = block.sealLeft
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountBottom || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountLeft || 0
|
||||
block.placeDirection = '↑'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '→'
|
||||
break
|
||||
case PlaceStyle.BACK: // 反面
|
||||
block.placeWidth = block.cutWidth
|
||||
block.placeLength = block.cutLength
|
||||
block.placeSealLeft = block.sealRight
|
||||
block.placeSealRight = block.sealLeft
|
||||
block.placeSealTop = block.sealTop
|
||||
block.placeSealBottom = block.sealBottom
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountBottom || 0
|
||||
block.placeDirection = '→'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↑'
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT: // 反面右转
|
||||
block.placeWidth = block.cutLength
|
||||
block.placeLength = block.cutWidth
|
||||
block.placeSealLeft = block.sealBottom
|
||||
block.placeSealRight = block.sealTop
|
||||
block.placeSealTop = block.sealRight
|
||||
block.placeSealBottom = block.sealLeft
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountBottom || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountLeft || 0
|
||||
block.placeDirection = '↓'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '→'
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK: // 反面后转
|
||||
block.placeWidth = block.cutWidth
|
||||
block.placeLength = block.cutLength
|
||||
block.placeSealLeft = block.sealLeft
|
||||
block.placeSealRight = block.sealRight
|
||||
block.placeSealTop = block.sealTop
|
||||
block.placeSealBottom = block.sealBottom
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountRight || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountBottom || 0
|
||||
block.placeDirection = '←'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↓'
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT: // 反面左转
|
||||
block.placeWidth = block.cutLength
|
||||
block.placeLength = block.cutWidth
|
||||
block.placeSealLeft = block.sealTop
|
||||
block.placeSealRight = block.sealBottom
|
||||
block.placeSealTop = block.sealLeft
|
||||
block.placeSealBottom = block.sealRight
|
||||
block.holeCountSideLeft = block?.blockDetail?.holeCountTop || 0
|
||||
block.holeCountSideRight = block?.blockDetail?.holeCountBottom || 0
|
||||
block.holeCountSideTop = block?.blockDetail?.holeCountLeft || 0
|
||||
block.holeCountSideBottom = block?.blockDetail?.holeCountRight || 0
|
||||
block.placeDirection = '↑'
|
||||
block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '←'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
this.resetDoFace_HoleModel(block)
|
||||
},
|
||||
resetDoFace_HoleModel(block: PlaceBlock) {
|
||||
|
||||
let isTurnOver = block.isTurnOver
|
||||
if (block.blockDetail == null) {
|
||||
console.error('resetDoFace_HoleModel error,with out blockDetail');
|
||||
return
|
||||
}
|
||||
let orgMA = block.blockDetail.modelListFaceA
|
||||
let orgMB = block.blockDetail.modelListFaceB
|
||||
let orgMT = block.blockDetail.modelListThrough
|
||||
let orgHA = block.blockDetail.holeListFaceA
|
||||
let orgHB = block.blockDetail.holeListFaceB
|
||||
let orgHT = block.blockDetail.holeListThrough
|
||||
|
||||
if (isTurnOver) {
|
||||
block.modelListFaceA = orgMB.concat(orgMT);
|
||||
block.modelListFaceB = orgMA;
|
||||
block.holeListFaceA = orgHB.concat(orgHT);
|
||||
block.holeListFaceB = orgHA;
|
||||
}
|
||||
else {
|
||||
block.modelListFaceA = orgMA.concat(orgMT);
|
||||
block.modelListFaceB = orgMB;
|
||||
block.holeListFaceA = orgHA.concat(orgHT);;
|
||||
block.holeListFaceB = orgHB;
|
||||
}
|
||||
|
||||
|
||||
//获取贴标位置
|
||||
let p = this.getPlaceXYInBlock(block, block.blockDetail.labelPosX, block.blockDetail.labelPosY, false, false);
|
||||
block.labelPosX = p.x;
|
||||
block.labelPosY = p.y;
|
||||
},
|
||||
getPlaceXYInBlock(block: PlaceBlock, x: number, y: number, isSealed: boolean, isFaceB = false, preCutValueOff = null): Point {
|
||||
let bwidth = block.cutWidth
|
||||
let blength = block.cutLength
|
||||
let x0: any = x
|
||||
let y0: any = y
|
||||
if (isSealed) // 已封边
|
||||
{
|
||||
bwidth = block.width
|
||||
blength = block.length
|
||||
x0 += block.offsetX
|
||||
y0 += block.offsetY
|
||||
}
|
||||
if (!isSealed && preCutValueOff) // 没封边,且有封边
|
||||
{
|
||||
bwidth += preCutValueOff.w
|
||||
blength += preCutValueOff.l
|
||||
x0 += preCutValueOff.x
|
||||
y0 += preCutValueOff.y
|
||||
}
|
||||
|
||||
let _point: any = this.getPlacedPostionInBlock(block.placeStyle, bwidth, blength, x0, y0, isFaceB)
|
||||
|
||||
return _point
|
||||
},
|
||||
/** 获得翻转后的新坐标 */
|
||||
getPlacedPostionInBlock(placeStyle: PlaceStyle, bwidth: number, blength: number, x0: number, y0: number, isFaceB: boolean): Point {
|
||||
let posX = x0
|
||||
let posY = y0
|
||||
let placeWidth = bwidth
|
||||
switch (placeStyle) {
|
||||
case PlaceStyle.FRONT:
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT:
|
||||
posX = y0
|
||||
posY = bwidth - x0
|
||||
placeWidth = blength
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK:
|
||||
posX = bwidth - x0
|
||||
posY = blength - y0
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT:
|
||||
posX = blength - y0
|
||||
posY = x0
|
||||
placeWidth = blength
|
||||
break
|
||||
case PlaceStyle.BACK:
|
||||
posX = bwidth - x0
|
||||
posY = y0
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT:
|
||||
posX = y0
|
||||
posY = x0
|
||||
placeWidth = blength
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK:
|
||||
posX = x0
|
||||
posY = blength - y0
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT:
|
||||
posX = blength - y0
|
||||
posY = bwidth - x0
|
||||
placeWidth = blength
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (isFaceB) {
|
||||
posX = placeWidth - posX
|
||||
}
|
||||
return new Point(posX, posY)
|
||||
}
|
||||
}
|
||||
|
83
samples/moduleManager/module_init2VModel.ts
Normal file
83
samples/moduleManager/module_init2VModel.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
import { FaceType, PlaceBlockDetail } from "../confClass";
|
||||
// import { PolylineHelper } from "../handleAbility/common/LayoutEngine/PolylineHelper";
|
||||
// import {PolylineHelper} from "../handleAbility/common/LayoutEngine/PolylineHelper.js"
|
||||
/** 模块 造型轮廓(含封边),扣除封边, 变成开料坐标
|
||||
* !!!!!! 有异常 要调整
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
export const Init2VModel: ProcessorModule<any, any> = {
|
||||
moduleName: "Init2VModel",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { blockDetailList } = input
|
||||
for (const bd of blockDetailList) {
|
||||
init2VModel(bd);
|
||||
}
|
||||
|
||||
function init2VModel(blockDetail: PlaceBlockDetail, isCNC = false) {
|
||||
for (let model of blockDetail.models) {
|
||||
if (!model.isVKnifeModel)
|
||||
continue
|
||||
let vModels: any = []
|
||||
model.VLines = vModels
|
||||
let isFaceB = model.face == FaceType.BACK
|
||||
if (model.pointList.length < 1)
|
||||
continue
|
||||
let ps = model.pointList.map((t) => { return { x: t.pointX, y: t.pointY, bul: t.curve } })
|
||||
let pl = PolylineHelper.create(ps)
|
||||
if (model.VLines?.length > 0)
|
||||
return // 已经分析了
|
||||
model.VLines = []
|
||||
for (let os of model.offsetList) {
|
||||
let knife1 = isCNC ? null : this.getModelKnifeByName(os.name)
|
||||
let knifeR = os.radius
|
||||
let knifeId = knife1 ? knife1.knifeId : -1
|
||||
try {
|
||||
let vps_1 = PolylineHelper.getVModelPoints_offset(pl, os.offset, os.depth, os.angle)
|
||||
let vLine = { isFaceB, name: os.name, value: os.offset, knife: knife1, knifeId, knifeRadius: knifeR, depth: os.depth, points: vps_1, offset: os }
|
||||
vModels.push(vLine) // 偏移路径
|
||||
model.VLines.push(vLine)
|
||||
}
|
||||
catch (err) {
|
||||
console.log('v型刀走刀路径算法出错。' + err)
|
||||
}
|
||||
}
|
||||
model.VLines = vModels
|
||||
}
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
66
samples/moduleManager/module_resetModelContour.ts
Normal file
66
samples/moduleManager/module_resetModelContour.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
|
||||
import { ErrorInfo } from "../../src/device";
|
||||
import { PlaceBlockDetail } from "../confClass";
|
||||
|
||||
/** 模块 造型轮廓(含封边),扣除封边, 变成开料坐标
|
||||
* !!!!!!!!! 有异常 要调整
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
export const ResetModelContour: ProcessorModule<any, any> = {
|
||||
moduleName: "ResetModelContour",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
const { blockDetailList } = input
|
||||
for (const bd of blockDetailList) {
|
||||
let blockDetail = new PlaceBlockDetail(bd);
|
||||
resetModelContour(blockDetail);
|
||||
}
|
||||
/** 造型轮廓(含封边),扣除封边, 变成开料坐标 */
|
||||
function resetModelContour(bd: PlaceBlockDetail) {
|
||||
let ox = bd.offsetX
|
||||
let oy = bd.offsetY
|
||||
for (let m of bd.models) {
|
||||
if (m.hasContour()) {
|
||||
let ptsArr = m.originModeling.outline.map(e => e.pts)
|
||||
for (let pt of ptsArr) {
|
||||
// 23.8.5 发现矩形的挖穿轮廓坐标是不含封边的
|
||||
pt.x -= ox
|
||||
pt.y -= oy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
|
||||
console.error('出错了哦', error);
|
||||
|
||||
}
|
||||
};
|
||||
|
93
samples/moduleManager/module_tools.ts
Normal file
93
samples/moduleManager/module_tools.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Processor, ProcessorModule } from "../../src/device";
|
||||
import { confItem, Knife, PlaceBlock, PlaceBlockDetail, PlaceMaterial, _knifeType } from "../confClass";
|
||||
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
|
||||
|
||||
/** 模块 刀库
|
||||
*
|
||||
* input 入参
|
||||
|
||||
*/
|
||||
|
||||
/** 优化前的 刀库 */
|
||||
export const ToolsModule: ProcessorModule<any, any> = {
|
||||
moduleName: "RectOptimizeMachine",
|
||||
moduleVersion: '20250714',
|
||||
config: {
|
||||
knifeList: [],
|
||||
},
|
||||
setConfig(config) {
|
||||
this.config = { ...this.config, ...config };
|
||||
},
|
||||
|
||||
// 会在处理器自动执行
|
||||
/**
|
||||
*
|
||||
* @param input 输入数据
|
||||
* @param next 下一个流程的函数
|
||||
* @param context 上下文
|
||||
* @returns
|
||||
*/
|
||||
|
||||
process(input, next, context) {
|
||||
|
||||
|
||||
// 将刀库添加到上下文
|
||||
Reflect.set(context, 'knifeList', this.config.knifeList)
|
||||
// 将刀具查询方法加到上下文
|
||||
Reflect.set(context, 'getKnifeByParams', getKnifeByParams)
|
||||
/** 通用 找刀具 根据查询条件 */
|
||||
function getKnifeByParams(params: _knifeType, knifeList: Knife[]) {
|
||||
let knife: Knife | null = null
|
||||
|
||||
if (params) {
|
||||
let tempKnifeList: Knife[] = [...knifeList] // []
|
||||
let keys = Object.keys(params)
|
||||
if (keys.length > 0) {
|
||||
keys.forEach(key => {
|
||||
if (Array.isArray(params[key]) && key == 'ability') {
|
||||
// 进来的应该是ability 是数组 判断刀的能力
|
||||
for (const arrItem of params[key]) {
|
||||
let _knifeList = knifeList.filter(e => e.ability.includes(arrItem))
|
||||
_knifeList.forEach(k => {
|
||||
if (!this.KnifeIsInKnifeList(k, tempKnifeList)) {
|
||||
tempKnifeList.push(k)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (['string', 'number'].includes(typeof (params[key]))) {
|
||||
if (params && params[key] && typeof (params[key]) == 'number') {
|
||||
|
||||
if (key == 'length') {
|
||||
tempKnifeList = tempKnifeList.filter(e => e[key] >= params[key])
|
||||
} else {
|
||||
tempKnifeList = tempKnifeList.filter(e => e[key] == params[key])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
if (tempKnifeList.length > 0) {
|
||||
knife = tempKnifeList[0]
|
||||
}
|
||||
} else {
|
||||
console.log('传入的查询条件 没有参数')
|
||||
}
|
||||
}
|
||||
return knife
|
||||
}
|
||||
|
||||
return next ? next(input) : input;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onError(error) {
|
||||
console.log('出错了哦', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
29607
samples/moduleManager/res1.json
Normal file
29607
samples/moduleManager/res1.json
Normal file
File diff suppressed because it is too large
Load Diff
38
src/base.ts
38
src/base.ts
@@ -1,15 +1,29 @@
|
||||
export class ConfigBase {
|
||||
name: string = '';
|
||||
version:string = '1.0.0';
|
||||
enable:boolean = true;
|
||||
[key: string]: any;
|
||||
import { ConfigBase } from "./models/config";
|
||||
|
||||
|
||||
/**
|
||||
* 加工处理器上下文
|
||||
*/
|
||||
export abstract class ProcessorContext<TInput,TOutput,TConfig extends ConfigBase>{
|
||||
/**
|
||||
* 输入数据
|
||||
*/
|
||||
public input?:TInput;
|
||||
/**
|
||||
* 合并配置文件与临时输入参
|
||||
*/
|
||||
public params?:TConfig;
|
||||
/**
|
||||
* 输出数据
|
||||
*/
|
||||
public output?:TOutput;
|
||||
}
|
||||
|
||||
export interface FileOptions {
|
||||
encode?: string;
|
||||
addBOM?: boolean;
|
||||
}
|
||||
export interface FileInfo extends FileOptions {
|
||||
name: string,
|
||||
content: string | Blob | Uint8Array,
|
||||
/**
|
||||
* 处理器基类
|
||||
*/
|
||||
export abstract class ProcessorBase<TInput,TOutput,TConfig extends ConfigBase> {
|
||||
public abstract get name():string;
|
||||
public abstract get version(): string;
|
||||
public abstract exec(context:ProcessorContext<TInput,TOutput,TConfig>):Promise<void>|void
|
||||
}
|
353
src/device.ts
Normal file
353
src/device.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
import { error } from "console";
|
||||
|
||||
// 回调函数类型定义
|
||||
type ProcessCallback<T, R> = (
|
||||
input: T,
|
||||
next?: (input: T) => R | Promise<R>,
|
||||
context?: any
|
||||
) => R | Promise<R>;
|
||||
|
||||
|
||||
// 模块配置类型
|
||||
type ModuleConfig = Record<string, any>;
|
||||
|
||||
// 回调函数类型
|
||||
type ModuleCallback<T, R> = (result: R, input?: T,context?:any) => void | Promise<void>;
|
||||
|
||||
export interface ErrorInfo {
|
||||
moduleName?: string
|
||||
moduleVersion?: string
|
||||
info?: any
|
||||
}
|
||||
|
||||
export class ProcessorManager<T, R> implements ProcessorCollection<T, R> {
|
||||
private processors = new Map<string, Processor<T, R>>();
|
||||
private currentProcessor?: Processor<T, R>;
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
/** 注册模块流程 */
|
||||
registerProcessor(name: string, processor: Processor<T, R>): this {
|
||||
this.processors.set(name, processor);
|
||||
return this;
|
||||
}
|
||||
/** 使用处理器 */
|
||||
useProcessor(name: string): Processor<T, R> {
|
||||
const processor = this.processors.get(name);
|
||||
if (!processor) {
|
||||
throw new Error(`Processor ${name} not found`);
|
||||
}
|
||||
this.currentProcessor = processor;
|
||||
return processor;
|
||||
}
|
||||
/** 获取处理器 */
|
||||
getProcessor(name: string): Processor<T, R> | undefined {
|
||||
return this.processors.get(name);
|
||||
}
|
||||
/** 获取正在使用的处理器 */
|
||||
getCurrentProcessor(): Processor<T, R> | undefined {
|
||||
return this.currentProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展后的功能模块接口
|
||||
export interface ProcessorModuleBase<T, R> {
|
||||
// 主处理函数(可回调)
|
||||
process?: ProcessCallback<T, R>;
|
||||
|
||||
// // 直接是处理函数(无回调)
|
||||
// handle?: (input: T, next?: (input: T) => R | Promise<R>, context?: Record<string, any>) => R | Promise<R>;
|
||||
|
||||
// 模块名称(用于标识和排序)
|
||||
moduleName?: string;
|
||||
/** 模块版本号 */
|
||||
moduleVersion?: string
|
||||
// 模块配置
|
||||
config?: ModuleConfig;
|
||||
// // 多线程列表
|
||||
// workerList?: any[]
|
||||
// 获取配置列表
|
||||
getConfigList?: () => any[]
|
||||
// 设置配置的方法
|
||||
setConfig?: (config: ModuleConfig) => void;
|
||||
|
||||
// // 前置回调(在模块处理前执行)
|
||||
// before?: ModuleCallback<T, R>;
|
||||
|
||||
// // 后置回调(在模块处理后执行)
|
||||
// after?: ModuleCallback<T, R>;
|
||||
|
||||
// 错误处理回调
|
||||
onError?: (error: unknown, input?: T) => void | Promise<void>;
|
||||
|
||||
// 消息传递 模块将数据传给处理器
|
||||
onMessage?: Function//(data:unknown) => void | Promise<void>;
|
||||
[key: string]:any
|
||||
}
|
||||
|
||||
/** 支持自定义函数 */
|
||||
export interface ProcessorModuleExtensions {
|
||||
/** 允许在模块内添加自定义函数 和属性 */
|
||||
[key: string]: ((...args: any[]) => any) | undefined;
|
||||
}
|
||||
|
||||
export interface strictModuleExtensions {
|
||||
|
||||
}
|
||||
|
||||
export type ProcessorModule<T, R> = ProcessorModuleBase<T, R> // & ProcessorModuleExtensions;
|
||||
|
||||
export interface Processor<T, R> {
|
||||
// 注册模块
|
||||
use(module: ProcessorModule<T, R> | ProcessorModule<T, R>[]): this;
|
||||
|
||||
// 调整模块顺序
|
||||
reorderModules(moduleNames: string[]): this;
|
||||
|
||||
// 执行处理流程
|
||||
process(input: T): Promise<R>;
|
||||
|
||||
// 获取当前模块列表
|
||||
getModules(): ProcessorModule<T, R>[];
|
||||
|
||||
// 新增方法:更新模块配置
|
||||
updateModuleConfig(config: ModuleConfig): this;
|
||||
|
||||
/**
|
||||
获取模块的配置项
|
||||
*/
|
||||
getModuleConfig(): ModuleConfig
|
||||
/**
|
||||
* 获取模块配置列表
|
||||
*/
|
||||
getModuleConfigList(): any[]
|
||||
//
|
||||
//
|
||||
}
|
||||
|
||||
// 处理器集合接口
|
||||
export interface ProcessorCollection<T, R> {
|
||||
// 注册处理器
|
||||
registerProcessor(name: string, processor: Processor<T, R>): this;
|
||||
|
||||
// 切换当前处理器
|
||||
useProcessor(name: string): Processor<T, R>;
|
||||
|
||||
// 获取处理器
|
||||
getProcessor(name: string): Processor<T, R> | undefined;
|
||||
}
|
||||
|
||||
/** 流程管理器 处理器内组件的执行流程管理器
|
||||
*
|
||||
* 负责管理 组件执行顺序 和执行模块
|
||||
*/
|
||||
|
||||
export class StepControllerProcessor<T, R> implements Processor<T, R> {
|
||||
private modules: ProcessorModule<T, R>[] = [];
|
||||
private modulesMap = new Map<string, ProcessorModule<T, R>>();
|
||||
private onMessageFunction: Function = () => { }
|
||||
use(module: ProcessorModule<T, R> | ProcessorModule<T, R>[]): this {
|
||||
const modules = Array.isArray(module) ? module : [module];
|
||||
|
||||
modules.forEach(m => {
|
||||
if (m.moduleName) {
|
||||
this.modulesMap.set(m.moduleName, m);
|
||||
m.onMessage = this.onMessageFunction
|
||||
}
|
||||
this.modules.push(m);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
reorderModules(moduleNames: string[]): this {
|
||||
const orderedModules = moduleNames
|
||||
.map(name => this.modulesMap.get(name))
|
||||
.filter(Boolean) as ProcessorModule<T, R>[];
|
||||
|
||||
const remainingModules = this.modules.filter(
|
||||
m => !m.moduleName || !moduleNames.includes(m.moduleName)
|
||||
);
|
||||
|
||||
this.modules = [...orderedModules, ...remainingModules];
|
||||
return this;
|
||||
}
|
||||
/** 更新处理器 内的模块的配置 */
|
||||
updateModuleConfig(config: ModuleConfig): this {
|
||||
// const module = this.modulesMap.get(moduleName)
|
||||
let log = []
|
||||
for (const m of this.modules) {
|
||||
if (m.config != undefined) {
|
||||
let conArr = Object.keys(m.config)
|
||||
let m_config = {}
|
||||
for (const keyStr of conArr) {
|
||||
if (Reflect.has(config, keyStr)) {
|
||||
// m.config[keyStr] = config[keyStr]
|
||||
Reflect.set(m_config, keyStr, config[keyStr])
|
||||
} else {
|
||||
let noConfigInfo = {
|
||||
moduleName: m.moduleName,
|
||||
configKey: keyStr,
|
||||
info: '没有配置参数!'
|
||||
}
|
||||
log.push(noConfigInfo)
|
||||
}
|
||||
}
|
||||
if (m?.setConfig) {
|
||||
m?.setConfig(m_config)
|
||||
} else {
|
||||
let noConfigInfo = {
|
||||
moduleName: m.moduleName,
|
||||
configKey: '',
|
||||
info: '模块设置了【config】,却没有设置【setConfig】;'
|
||||
}
|
||||
log.push(noConfigInfo)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (log.length > 0) {
|
||||
let str = log.map(e => {
|
||||
let s = '模块:' + e.moduleName
|
||||
if (e.configKey) {
|
||||
s = s + '-' + e.configKey
|
||||
}
|
||||
s = s + ',' + e.info + ';'
|
||||
return s
|
||||
})
|
||||
console.warn('加载配置异常:', str)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
getModuleConfig(): ModuleConfig {
|
||||
let config:ModuleConfig = {}
|
||||
for (const m of this.modules) {
|
||||
config = {...config,...m.config}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
getModuleConfigList(){
|
||||
//#region 方式1
|
||||
|
||||
let configList:any[] = []
|
||||
let config:ModuleConfig = {}
|
||||
for (const m of this.modules) {
|
||||
config = {...config,...m.config}
|
||||
// let keys = Object.keys(m.config)
|
||||
}
|
||||
let keys = Object.keys(config)
|
||||
for (const key of keys) {
|
||||
let temp= {
|
||||
key: key,
|
||||
value: config[key]
|
||||
}
|
||||
configList.push(temp)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 方式二
|
||||
let configList1:any[] = []
|
||||
for (const m of this.modules) {
|
||||
if(Reflect.has(m,'getConfigList')){
|
||||
let m_configList = m.getConfigList() || []
|
||||
configList1 = [...configList1,...m_configList]
|
||||
}
|
||||
|
||||
}
|
||||
//#endregion
|
||||
return configList
|
||||
}
|
||||
|
||||
private async executeModule(
|
||||
module: ProcessorModule<T, R>,
|
||||
input: T,
|
||||
next: (input: T) => Promise<R>,
|
||||
context: Record<string, any>
|
||||
): Promise<R> {
|
||||
try {
|
||||
// 执行前置回调
|
||||
if (module.before) {
|
||||
await module.before(input, input);
|
||||
}
|
||||
|
||||
// 执行主处理逻辑(支持两种风格)
|
||||
let result: R;
|
||||
|
||||
if (module.process) {
|
||||
// 回调风格
|
||||
const processResult = module.process(input, next, context);
|
||||
result = processResult instanceof Promise ? await processResult : processResult;
|
||||
} else if (module.handle) {
|
||||
// 传统风格
|
||||
const handleResult = module.handle(input, next, context);
|
||||
result = handleResult instanceof Promise ? await handleResult : handleResult;
|
||||
} else {
|
||||
// 默认直接调用 next
|
||||
result = await next(input);
|
||||
}
|
||||
|
||||
// 执行后置回调
|
||||
if (module.after) {
|
||||
await module.after(result, input,context);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 执行错误处理
|
||||
if (module.onError) {
|
||||
await module.onError(error, input);
|
||||
// 即使出错也继续流程(除非抛出)
|
||||
return await next(input);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async executeModuleByModuleName(moduleName:string,input){
|
||||
let module =this.modulesMap.get(moduleName)
|
||||
|
||||
return module.process(input)
|
||||
}
|
||||
|
||||
async process(input: T,_context?:Record<string, any>): Promise<R> {
|
||||
if (this.modules.length === 0) {
|
||||
throw new Error("No modules registered");
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
const modules = this.modules;
|
||||
const context: Record<string, any> = {..._context};
|
||||
|
||||
const executeNext = async (currentInput: T): Promise<R> => {
|
||||
const currentModule = modules[currentIndex++];
|
||||
if (!currentModule) {
|
||||
console.log('执行结束,上下文',context);
|
||||
return currentInput as unknown as R;
|
||||
}
|
||||
|
||||
// 创建 next 函数
|
||||
const next = async (nextInput: T): Promise<R> => {
|
||||
return executeNext(nextInput);
|
||||
};
|
||||
|
||||
return this.executeModule(currentModule, currentInput, next, context);
|
||||
};
|
||||
|
||||
return executeNext(input);
|
||||
}
|
||||
|
||||
|
||||
|
||||
getModules(): ProcessorModule<T, R>[] {
|
||||
return [...this.modules];
|
||||
}
|
||||
|
||||
setOnMessageFunc(func: Function) {
|
||||
this.onMessageFunction = func
|
||||
}
|
||||
|
||||
}
|
4
src/index.ts
Normal file
4
src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './base';
|
||||
export * from './parsers';
|
||||
export * from './models/config';
|
||||
export * from './models/file';
|
28
src/models/config.ts
Normal file
28
src/models/config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 配置基类,下划线开头的变量不会被序列化
|
||||
*/
|
||||
export class ConfigBase {
|
||||
name: string = '';
|
||||
version:string = '1.0.0';
|
||||
[key: string]: any;
|
||||
|
||||
/**
|
||||
* 加载反序列化数据
|
||||
* @param data
|
||||
*/
|
||||
load(data:Record<string,unknown>){
|
||||
for (const key of Object.getOwnPropertyNames(this).filter(i=>i[0]!=='_')) {
|
||||
if(data[key]!=undefined){
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化json方法
|
||||
* @returns
|
||||
*/
|
||||
toJson(){
|
||||
return JSON.stringify(this,(k,v)=>k[0]=='_'?undefined:v);
|
||||
}
|
||||
}
|
8
src/models/file.ts
Normal file
8
src/models/file.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface FileOptions {
|
||||
encode?: string;
|
||||
addBOM?: boolean;
|
||||
}
|
||||
export interface FileInfo extends FileOptions {
|
||||
name: string,
|
||||
content: string | Blob | Uint8Array,
|
||||
}
|
221
src/paser.test.ts
Normal file
221
src/paser.test.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { describe, it, expect, test } from 'vitest';
|
||||
import { DemoParser } from '../samples/demoParser';
|
||||
// import { ProcessorModule, StepControllerProcessor } from '../src/device';
|
||||
import { ProcessorManager, StepControllerProcessor } from '../src/device';
|
||||
import { demoHandleGroupCutting } from '../samples/demoDatahandle/demoDataHandle1';
|
||||
import testJson from "./test.json"
|
||||
import { RectOptimizeMachineModule } from '../samples/moduleManager/module_RectOptimizeMachine';
|
||||
// import { Worker, parentPort } from 'worker_threads';
|
||||
// import { UniversalWorker } from '../samples/WorkerHelper';
|
||||
|
||||
|
||||
// 在测试文件中直接使用 console.log 或 debugger 语句
|
||||
test('demoParser', () => {
|
||||
// expect(1 + 1).toBe(2);
|
||||
const text = `FSTART
|
||||
TD 5
|
||||
G0 X100 Y100 Z10 F8000
|
||||
g0 x100 y100 z18 f8000
|
||||
g2 x120 y120 z18 R20 f8000
|
||||
g3 x100 y100 z18 i20 j0 f8000
|
||||
TN T2
|
||||
FEND
|
||||
`
|
||||
const demoParser = new DemoParser();
|
||||
const result = demoParser.execTest(text);
|
||||
console.log(result);
|
||||
});
|
||||
|
||||
test('dataHandle', async () => {
|
||||
const sysConfig = {
|
||||
placeStyle: 1,
|
||||
boardWidth: 2440,
|
||||
boardLength: 1220,
|
||||
cutBoardBorder:3,
|
||||
cutKnifeGap:1,
|
||||
isDoubleFaceBlockFirst:true,
|
||||
// 测试刀库数据
|
||||
knifeList: [knifeData, knifeData1, knifeData2]
|
||||
|
||||
}
|
||||
const json = testJson
|
||||
// 创建处理器集合
|
||||
const processorManager = new ProcessorManager<any, any>();
|
||||
// 这里省略了 选择处理器的环节 --这里选了demo开料机
|
||||
const cuttingHandle = new demoHandleGroupCutting()
|
||||
// 注册处理器
|
||||
processorManager.registerProcessor(cuttingHandle.processorName, cuttingHandle.processor)
|
||||
// 使用XX处理器 会返回激活【正在使用】的 处理器
|
||||
let processor = processorManager.useProcessor(cuttingHandle.processorName)
|
||||
// 或者 这样 获取 正在使用的处理器 注:若未执行useProcessor 将会返回 undefind
|
||||
// processor = processorManager.getCurrentProcessor()
|
||||
/** 处理器配置加载 */
|
||||
processor.updateModuleConfig(sysConfig)
|
||||
// 方式1
|
||||
const res = await processor.process(json)
|
||||
console.log('处理器所有流程都结束了', res);
|
||||
})
|
||||
test('Module Test',async()=>{
|
||||
//测试 模块
|
||||
// 配置项
|
||||
const sysConfig = {
|
||||
placeStyle: 1,
|
||||
boardWidth: 2440,
|
||||
boardLength: 1220,
|
||||
// 测试刀库数据
|
||||
knifeList: [knifeData, knifeData1, knifeData2]
|
||||
|
||||
}
|
||||
// 测试数据
|
||||
const json = testJson
|
||||
// 处理器集合 的管理器
|
||||
const processorManager = new ProcessorManager<any, any>();
|
||||
// 处理器的管理器
|
||||
const stepManager = new StepControllerProcessor<any, any>();
|
||||
// 处理器 要调用的模块 可设置多个
|
||||
stepManager.use([RectOptimizeMachineModule])
|
||||
stepManager.setOnMessageFunc(callBack)
|
||||
function callBack(data){
|
||||
console.log('数据回调,这里测试优化',)
|
||||
}
|
||||
//
|
||||
let processName = 'testRectOptimize'
|
||||
processorManager.registerProcessor(processName,stepManager)
|
||||
processorManager.useProcessor(processName,)
|
||||
})
|
||||
|
||||
/** demo 刀具数据 */
|
||||
export const knifeData = {
|
||||
"isEnabled": true,
|
||||
"axleId": 2,
|
||||
"knifeId": 2,
|
||||
"processFace": "",
|
||||
"knifeName": "T1",
|
||||
"knifeType": 3,
|
||||
"ability": [
|
||||
5
|
||||
],
|
||||
"diameter": 5,
|
||||
"length": 20,
|
||||
"sawThiness": 7,
|
||||
"sawDirection": 2,
|
||||
"processDirection": 4,
|
||||
"speed": 0,
|
||||
"stepDepth": 0,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"offsetZ": 0,
|
||||
"baseX": 0,
|
||||
"baseY": 0,
|
||||
"isModularDrill": false,
|
||||
"isPreStartEnabled": false,
|
||||
"preStartAheadActionCount": 5,
|
||||
"isPreStartToolChangeDelay": false,
|
||||
"preStartToolChangeDelayCode": "",
|
||||
"isAxisStartCodePostpost": false,
|
||||
"isAxisStopCodePrepose": false,
|
||||
"drillGroupCode": "",
|
||||
"axisStartCode": "M03 S18000\n",
|
||||
"knifeStartCode": `M06 T1\nG43 H1\n`,
|
||||
"drillGroupStartCode": "T1",
|
||||
"drillGroupEndCode": "",
|
||||
"knifeStopCode": "",
|
||||
"axisStopCode": "M05\n",
|
||||
"preStartActionDeferCode": "",
|
||||
"useHolesGroupKnife": false,
|
||||
"preStartActionStepsLimit": "",
|
||||
"knifeNo": "",
|
||||
"editable": true,
|
||||
"isDefaultCutKnife": false,
|
||||
"isPreStartChangeKnifeDefer": false
|
||||
}
|
||||
export const knifeData1 = {
|
||||
"isEnabled": true,
|
||||
"axleId": 2,
|
||||
"knifeId": 2,
|
||||
"processFace": "",
|
||||
"knifeName": "T2",
|
||||
"knifeType": 3,
|
||||
"ability": [
|
||||
5
|
||||
],
|
||||
"diameter": 6,
|
||||
"length": 20,
|
||||
"sawThiness": 7,
|
||||
"sawDirection": 2,
|
||||
"processDirection": 4,
|
||||
"speed": 0,
|
||||
"stepDepth": 0,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"offsetZ": 0,
|
||||
"baseX": 0,
|
||||
"baseY": 0,
|
||||
"isModularDrill": false,
|
||||
"isPreStartEnabled": false,
|
||||
"preStartAheadActionCount": 5,
|
||||
"isPreStartToolChangeDelay": false,
|
||||
"preStartToolChangeDelayCode": "",
|
||||
"isAxisStartCodePostpost": false,
|
||||
"isAxisStopCodePrepose": false,
|
||||
"drillGroupCode": "",
|
||||
"axisStartCode": "M03 S18000\n",
|
||||
"knifeStartCode": `M06 T2\nG43 H2\n`,
|
||||
"drillGroupStartCode": "T2",
|
||||
"drillGroupEndCode": "",
|
||||
"knifeStopCode": "",
|
||||
"axisStopCode": "M05\n",
|
||||
"preStartActionDeferCode": "",
|
||||
"useHolesGroupKnife": false,
|
||||
"preStartActionStepsLimit": "",
|
||||
"knifeNo": "",
|
||||
"editable": true,
|
||||
"isDefaultCutKnife": false,
|
||||
"isPreStartChangeKnifeDefer": false
|
||||
}
|
||||
export const knifeData2 = {
|
||||
"isEnabled": true,
|
||||
"axleId": 2,
|
||||
"knifeId": 2,
|
||||
"processFace": "",
|
||||
"knifeName": "T3",
|
||||
"knifeType": 3,
|
||||
"ability": [
|
||||
5
|
||||
],
|
||||
"diameter": 6,
|
||||
"length": 20,
|
||||
"sawThiness": 7,
|
||||
"sawDirection": 2,
|
||||
"processDirection": 4,
|
||||
"speed": 0,
|
||||
"stepDepth": 0,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"offsetZ": 0,
|
||||
"baseX": 0,
|
||||
"baseY": 0,
|
||||
"isModularDrill": false,
|
||||
"isPreStartEnabled": false,
|
||||
"preStartAheadActionCount": 5,
|
||||
"isPreStartToolChangeDelay": false,
|
||||
"preStartToolChangeDelayCode": "",
|
||||
"isAxisStartCodePostpost": false,
|
||||
"isAxisStopCodePrepose": false,
|
||||
"drillGroupCode": "",
|
||||
|
||||
"axisStartCode": "M03 S18000\n",
|
||||
"knifeStartCode": `M06 T2\nG43 H2\n`,
|
||||
"drillGroupStartCode": "T3",
|
||||
"drillGroupEndCode": "",
|
||||
"knifeStopCode": "",
|
||||
"axisStopCode": "M05\n",
|
||||
|
||||
"preStartActionDeferCode": "",
|
||||
"useHolesGroupKnife": false,
|
||||
"preStartActionStepsLimit": "",
|
||||
"knifeNo": "",
|
||||
"editable": true,
|
||||
"isDefaultCutKnife": false,
|
||||
"isPreStartChangeKnifeDefer": false
|
||||
}
|
@@ -1,7 +1,48 @@
|
||||
export abstract class ProcessorBase {
|
||||
public readonly name: string = '';
|
||||
public readonly version: string = '1.0.0';
|
||||
public abstract exec(...args: any[]): any
|
||||
import { ProcessorBase } from "./base";
|
||||
import { ConfigBase } from "./models/config";
|
||||
import { FileInfo } from "./models/file";
|
||||
|
||||
// todo: 类型参数待补完
|
||||
|
||||
export class BlockInfo{
|
||||
|
||||
}
|
||||
|
||||
export class BlockProcessorConfig extends ConfigBase{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户板材数据过滤与转换
|
||||
*/
|
||||
export abstract class BlockProcessor extends ProcessorBase<BlockInfo[],BlockInfo[],BlockProcessorConfig>{
|
||||
|
||||
}
|
||||
|
||||
export class LayoutInput {
|
||||
|
||||
}
|
||||
export class LayoutOutput{
|
||||
|
||||
}
|
||||
|
||||
export class LyaoutProcessorConfig extends ConfigBase{
|
||||
|
||||
}
|
||||
|
||||
export abstract class LayoutProcessor extends ProcessorBase<LayoutInput,LayoutOutput,LyaoutProcessorConfig>{
|
||||
|
||||
}
|
||||
|
||||
export class ExporterInput{
|
||||
|
||||
}
|
||||
|
||||
export class ExporterProcessorConfig extends ConfigBase{
|
||||
|
||||
}
|
||||
|
||||
export abstract class ExporterProcessor extends ProcessorBase<ExporterInput,FileInfo,ExporterProcessorConfig>{
|
||||
|
||||
}
|
29451
src/test.json
Normal file
29451
src/test.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,13 @@
|
||||
import { test } from '@jest/globals'
|
||||
import { test, it, expect } from 'vitest';
|
||||
import { DemoParser } from '../samples/demoParser';
|
||||
// import { ProcessorModule, StepControllerProcessor } from '../src/device';
|
||||
// import { ProcessorManager } from '../src/device';
|
||||
// import { demoHandleGroupCutting } from '../samples/demoDatahandle/demoDataHandle1';
|
||||
// import testJson from "./test.json"
|
||||
// import { Worker, parentPort } from 'worker_threads';
|
||||
// import { UniversalWorker } from '../samples/WorkerHelper';
|
||||
test('demoParser', () => {
|
||||
const text = `FSTART
|
||||
const text = `FSTART
|
||||
TD 5
|
||||
G0 X100 Y100 Z10 F8000
|
||||
g0 x100 y100 z18 f8000
|
||||
@@ -10,7 +16,170 @@ g3 x100 y100 z18 i20 j0 f8000
|
||||
TN T2
|
||||
FEND
|
||||
`
|
||||
const demoParser = new DemoParser();
|
||||
const result = demoParser.execTest(text);
|
||||
console.log(result);
|
||||
})
|
||||
const demoParser = new DemoParser();
|
||||
const result = demoParser.execTest(text);
|
||||
console.log(result);
|
||||
|
||||
|
||||
})
|
||||
|
||||
// describe('data', async () => {
|
||||
// const sysConfig = {
|
||||
// placeStyle: 1,
|
||||
// boardWidth: 2440,
|
||||
// boardLength: 1220,
|
||||
// // 测试刀库数据
|
||||
// knifeList: [knifeData, knifeData1, knifeData2]
|
||||
// }
|
||||
// const json = testJson
|
||||
// // 创建处理器集合
|
||||
// const processorManager = new ProcessorManager<any, any>();
|
||||
// // 这里省略了 选择处理器的环节 --这里选了demo开料机
|
||||
// const cuttingHandle = new demoHandleGroupCutting()
|
||||
// // 注册处理器
|
||||
// processorManager.registerProcessor(cuttingHandle.processorName, cuttingHandle.processor)
|
||||
// // 使用XX处理器 会返回激活【正在使用】的 处理器
|
||||
// let processor = processorManager.useProcessor(cuttingHandle.processorName)
|
||||
// // 或者 这样 获取 正在使用的处理器 注:若未执行useProcessor 将会返回 undefind
|
||||
// // processor = processorManager.getCurrentProcessor()
|
||||
// /** 处理器配置加载 */
|
||||
// processor.updateModuleConfig(sysConfig)
|
||||
// const res = await processor.process(json)
|
||||
// // console.log('处理器所有流程都结束了', res);
|
||||
// })
|
||||
//
|
||||
// /** demo 刀具数据 */
|
||||
// export const knifeData = {
|
||||
// "isEnabled": true,
|
||||
// "axleId": 2,
|
||||
// "knifeId": 2,
|
||||
// "processFace": "",
|
||||
// "knifeName": "T1",
|
||||
// "knifeType": 3,
|
||||
// "ability": [
|
||||
// 5
|
||||
// ],
|
||||
// "diameter": 5,
|
||||
// "length": 20,
|
||||
// "sawThiness": 7,
|
||||
// "sawDirection": 2,
|
||||
// "processDirection": 4,
|
||||
// "speed": 0,
|
||||
// "stepDepth": 0,
|
||||
// "offsetX": 0,
|
||||
// "offsetY": 0,
|
||||
// "offsetZ": 0,
|
||||
// "baseX": 0,
|
||||
// "baseY": 0,
|
||||
// "isModularDrill": false,
|
||||
// "isPreStartEnabled": false,
|
||||
// "preStartAheadActionCount": 5,
|
||||
// "isPreStartToolChangeDelay": false,
|
||||
// "preStartToolChangeDelayCode": "",
|
||||
// "isAxisStartCodePostpost": false,
|
||||
// "isAxisStopCodePrepose": false,
|
||||
// "drillGroupCode": "",
|
||||
// "axisStartCode": "M03 S18000\n",
|
||||
// "knifeStartCode": `M06 T1\nG43 H1\n`,
|
||||
// "drillGroupStartCode": "T1",
|
||||
// "drillGroupEndCode": "",
|
||||
// "knifeStopCode": "",
|
||||
// "axisStopCode": "M05\n",
|
||||
// "preStartActionDeferCode": "",
|
||||
// "useHolesGroupKnife": false,
|
||||
// "preStartActionStepsLimit": "",
|
||||
// "knifeNo": "",
|
||||
// "editable": true,
|
||||
// "isDefaultCutKnife": false,
|
||||
// "isPreStartChangeKnifeDefer": false
|
||||
// }
|
||||
// export const knifeData1 = {
|
||||
// "isEnabled": true,
|
||||
// "axleId": 2,
|
||||
// "knifeId": 2,
|
||||
// "processFace": "",
|
||||
// "knifeName": "T2",
|
||||
// "knifeType": 3,
|
||||
// "ability": [
|
||||
// 5
|
||||
// ],
|
||||
// "diameter": 6,
|
||||
// "length": 20,
|
||||
// "sawThiness": 7,
|
||||
// "sawDirection": 2,
|
||||
// "processDirection": 4,
|
||||
// "speed": 0,
|
||||
// "stepDepth": 0,
|
||||
// "offsetX": 0,
|
||||
// "offsetY": 0,
|
||||
// "offsetZ": 0,
|
||||
// "baseX": 0,
|
||||
// "baseY": 0,
|
||||
// "isModularDrill": false,
|
||||
// "isPreStartEnabled": false,
|
||||
// "preStartAheadActionCount": 5,
|
||||
// "isPreStartToolChangeDelay": false,
|
||||
// "preStartToolChangeDelayCode": "",
|
||||
// "isAxisStartCodePostpost": false,
|
||||
// "isAxisStopCodePrepose": false,
|
||||
// "drillGroupCode": "",
|
||||
// "axisStartCode": "M03 S18000\n",
|
||||
// "knifeStartCode": `M06 T2\nG43 H2\n`,
|
||||
// "drillGroupStartCode": "T2",
|
||||
// "drillGroupEndCode": "",
|
||||
// "knifeStopCode": "",
|
||||
// "axisStopCode": "M05\n",
|
||||
// "preStartActionDeferCode": "",
|
||||
// "useHolesGroupKnife": false,
|
||||
// "preStartActionStepsLimit": "",
|
||||
// "knifeNo": "",
|
||||
// "editable": true,
|
||||
// "isDefaultCutKnife": false,
|
||||
// "isPreStartChangeKnifeDefer": false
|
||||
// }
|
||||
// export const knifeData2 = {
|
||||
// "isEnabled": true,
|
||||
// "axleId": 2,
|
||||
// "knifeId": 2,
|
||||
// "processFace": "",
|
||||
// "knifeName": "T3",
|
||||
// "knifeType": 3,
|
||||
// "ability": [
|
||||
// 5
|
||||
// ],
|
||||
// "diameter": 6,
|
||||
// "length": 20,
|
||||
// "sawThiness": 7,
|
||||
// "sawDirection": 2,
|
||||
// "processDirection": 4,
|
||||
// "speed": 0,
|
||||
// "stepDepth": 0,
|
||||
// "offsetX": 0,
|
||||
// "offsetY": 0,
|
||||
// "offsetZ": 0,
|
||||
// "baseX": 0,
|
||||
// "baseY": 0,
|
||||
// "isModularDrill": false,
|
||||
// "isPreStartEnabled": false,
|
||||
// "preStartAheadActionCount": 5,
|
||||
// "isPreStartToolChangeDelay": false,
|
||||
// "preStartToolChangeDelayCode": "",
|
||||
// "isAxisStartCodePostpost": false,
|
||||
// "isAxisStopCodePrepose": false,
|
||||
// "drillGroupCode": "",
|
||||
//
|
||||
// "axisStartCode": "M03 S18000\n",
|
||||
// "knifeStartCode": `M06 T2\nG43 H2\n`,
|
||||
// "drillGroupStartCode": "T3",
|
||||
// "drillGroupEndCode": "",
|
||||
// "knifeStopCode": "",
|
||||
// "axisStopCode": "M05\n",
|
||||
//
|
||||
// "preStartActionDeferCode": "",
|
||||
// "useHolesGroupKnife": false,
|
||||
// "preStartActionStepsLimit": "",
|
||||
// "knifeNo": "",
|
||||
// "editable": true,
|
||||
// "isDefaultCutKnife": false,
|
||||
// "isPreStartChangeKnifeDefer": false
|
||||
// }
|
||||
|
29439
tests/test.json
Normal file
29439
tests/test.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,29 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"exclude": ["samples","tests"],
|
||||
// "compilerOptions": {
|
||||
// "target": "ES6",
|
||||
"module": "ESNext", // 必须用 ESNext
|
||||
// "esModuleInterop": true,
|
||||
// "moduleResolution": "node",
|
||||
// "skipLibCheck": true,
|
||||
// "sourceMap": true,
|
||||
// "outDir": "./dist",
|
||||
// "rootDir": ".", // 改为 "." 包含所有文件
|
||||
// "strict": true,
|
||||
// "baseUrl": ".", // 确保路径解析正确
|
||||
// "paths": {
|
||||
// // 可选:自定义路径映射(如 "@/*": ["src/*"])
|
||||
// }
|
||||
// },
|
||||
// "include": ["src/**/*", "tests/**/*"], // 包含测试文件
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
"types": ["@vitest/browser/providers/playwright"],
|
||||
"resolveJsonModule": true, // 允许导入 .json 文件
|
||||
"esModuleInterop": true, // 确保 CommonJS/ESM 兼容
|
||||
"module": "ESNext", // 推荐使用 ESM
|
||||
"moduleResolution": "Node" // 确保 Node.js 模块解析
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
11
vitest-example/HelloWorld.test.ts
Normal file
11
vitest-example/HelloWorld.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { getByText } from '@testing-library/dom'
|
||||
import HelloWorld from './HelloWorld.js'
|
||||
|
||||
test('renders name', () => {
|
||||
const parent = HelloWorld({ name: 'Vitest' })
|
||||
document.body.appendChild(parent)
|
||||
|
||||
const element = getByText(parent, 'Hello Vitest!')
|
||||
expect(element).toBeInTheDocument()
|
||||
})
|
9
vitest-example/HelloWorld.ts
Normal file
9
vitest-example/HelloWorld.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function HelloWorld({ name }: { name: string }): HTMLDivElement {
|
||||
const parent = document.createElement('div')
|
||||
|
||||
const h1 = document.createElement('h1')
|
||||
h1.textContent = 'Hello ' + name + '!'
|
||||
parent.appendChild(h1)
|
||||
|
||||
return parent
|
||||
}
|
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
// https://vitest.dev/guide/browser/playwright
|
||||
instances: [
|
||||
{ browser: 'chromium' },
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
Reference in New Issue
Block a user