feat:提交
This commit is contained in:
commit
8f6e7368e0
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
.npmrc
|
||||
.cache
|
||||
tests/server/static
|
||||
tests/server/static/upload
|
||||
|
||||
*.local
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.eslintcache
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
.vscode/**
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.lock
|
||||
|
||||
package-lock.json
|
||||
|
||||
.history
|
1476
demoPage.md
Normal file
1476
demoPage.md
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "processParser",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^24.0.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"fflate": "^0.7.4",
|
||||
"file-saver": "^2.0.2",
|
||||
"processparser": "file:"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "ESNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
21
readme.md
Normal file
21
readme.md
Normal file
@ -0,0 +1,21 @@
|
||||
## 解析器接收数据 生成加工文件
|
||||
|
||||
·数据说明
|
||||
1、刀路描述
|
||||
2、调用的指令
|
||||
3、加工项附带的信息(加工项所在板件信息和所在订单信息等等业务信息,可根据需求添加和开发)
|
||||
4、刀具信息
|
||||
|
||||
## 测试流程
|
||||
|
||||
1.npm install
|
||||
|
||||
2 编译 TypeScript 代码 npx tsc
|
||||
|
||||
3.运行编译后的 JavaScript 文件
|
||||
|
||||
# 参考
|
||||
内网测试地址:https://192.168.1.205/
|
||||
测试账户密码:admin admin123 左侧菜单demo栏目下
|
||||
|
||||
demo页面vue代码 demoPage.md
|
364
src/index.ts
Normal file
364
src/index.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import { ParserMain } from "./processParser/parseMain"
|
||||
import { device1 } from "./processParser/processConfig/device1/parser1"
|
||||
import { device2 } from "./processParser/processConfig/device2/parser2"
|
||||
/** 范例 */
|
||||
|
||||
const boardLocation = '3'
|
||||
const widthSideAxis = '0'
|
||||
const lengthSideAxis = '2'
|
||||
const parserMain = new ParserMain()
|
||||
/** 测试数据 */
|
||||
const jsonData = `
|
||||
//数据说明
|
||||
//(1)加工前先调6厘的刀,
|
||||
//(2)做孔加工的时候找【T1】的刀加工
|
||||
//(3)做造型的加工找【T2】的刀
|
||||
//(4)开料时 找5厘的刀
|
||||
{
|
||||
// 加工项信息
|
||||
"processList": [
|
||||
{
|
||||
// 加工指令数据集
|
||||
"processItemInfo": {
|
||||
// 代码
|
||||
"code": "MARK",
|
||||
// 指令
|
||||
"order": "Mark",
|
||||
// 指令参数
|
||||
"codeParams": "// start fileHead"
|
||||
},
|
||||
// 加工刀路信息
|
||||
"list": []
|
||||
},
|
||||
{
|
||||
"processItemInfo": {
|
||||
"code": "FSTART",
|
||||
"order": "F1"
|
||||
},
|
||||
"list": []
|
||||
},
|
||||
{
|
||||
"processItemInfo": {
|
||||
"code": "T",
|
||||
"order": "TD",
|
||||
"codeParams": {
|
||||
"diameter": 6
|
||||
}
|
||||
},
|
||||
"list": []
|
||||
},
|
||||
{
|
||||
// 一个加工项 模拟一个孔的加工
|
||||
"processItemInfo": {
|
||||
"knife":{
|
||||
"knifeName": "T1"
|
||||
},
|
||||
"code": "C",
|
||||
"order": "KnifeMove",
|
||||
"block":{
|
||||
"cutWidth":400,
|
||||
"cutLength":600
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
// 移动到安全点
|
||||
{ "x": 100.01, "y": 100.01, "z": 30.01, "dir": 0, "f": 10000 },
|
||||
// 下刀
|
||||
{ "x": 100.01, "y": 100.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 抬刀
|
||||
{ "x": 100.01, "y": 100.01, "z": 30.01, "dir": 0, "f": 10000 }
|
||||
]
|
||||
},
|
||||
{
|
||||
// 一个加工项 模拟一个造型的加工
|
||||
"processItemInfo": {
|
||||
"knife":{
|
||||
"knifeName": "T2"
|
||||
},
|
||||
"code": "C",
|
||||
"order": "KnifeMove",
|
||||
"block":{
|
||||
"cutWidth":400,
|
||||
"cutLength":600
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
// 移动到安全点
|
||||
{ "x": 150.01, "y": 150.01, "z": 30.01, "dir": 0, "f": 10000 },
|
||||
// 下刀
|
||||
{ "x": 150.01, "y": 150.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 345.01, "y": 150.01, "z": 0.01, "r": 5, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 350.01, "y": 155.01, "z": 0.01, "dir": 2, "r": 5.01, "f": 8000 },
|
||||
// // 走刀
|
||||
// { "x": 350.01, "y": 150.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 350.01, "y": 160.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 150.01, "y": 160.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 150.01, "y": 150.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 抬刀
|
||||
{ "x": 150.01, "y": 150.01, "z": 30.01, "dir": 0, "f": 10000 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"processItemInfo": {
|
||||
"code": "MARK",
|
||||
"order": "Mark",
|
||||
"codeParams": "// cut block"
|
||||
},
|
||||
"list": []
|
||||
},
|
||||
{
|
||||
"processItemInfo": {
|
||||
"knife":{
|
||||
"diameter": 5
|
||||
},
|
||||
"code": "C",
|
||||
"order": "KnifeMove",
|
||||
"block":{
|
||||
"cutWidth":400,
|
||||
"cutLength":600
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
// 移动到安全点
|
||||
{ "x": 0.01, "y": 0.01, "z": 30.01, "dir": 0, "f": 10000 },
|
||||
// 下刀 到板面
|
||||
{ "x": 0.01, "y": 0.01, "z": 18.01, "dir": 0, "f": 10000 },
|
||||
//切入
|
||||
{ "x": 0.01, "y": 0.01, "z": 6.01, "dir": 1, "f": 10000 },
|
||||
// 斜线下刀
|
||||
{ "x": 20.01, "y": 0.01, "z": 0.01, "dir": 1, "f": 5000 },
|
||||
// 走刀
|
||||
{ "x": 380.01, "y": 0.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 弧线走刀 G2
|
||||
{ "x": 400.01, "y": 30.01, "z": 0.01, "r": 20.01, "dir": 2, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 400.01, "y": 600.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 50.01, "y": 600.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 弧线走刀 G3
|
||||
{ "x": 0.01, "y": 560.01, "z": 0.01, "r": 40.01, "dir": 3, "f": 8000 },
|
||||
// 走刀
|
||||
{ "x": 0.01, "y": 0.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 斜线下刀没走的 走掉
|
||||
{ "x": 20.01, "y": 0.01, "z": 0.01, "dir": 1, "f": 8000 },
|
||||
// 抬刀
|
||||
{ "x": 20.01, "y": 0.01, "z": 30.01, "dir": 0, "f": 10000 }
|
||||
]
|
||||
},
|
||||
{
|
||||
// 程序结束 ----停刀 + 停 轴
|
||||
"processItemInfo": {
|
||||
"code": "CE",
|
||||
"order": "CE1"
|
||||
},
|
||||
"list": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
// 文件尾
|
||||
"processItemInfo": {
|
||||
"code": "FEND",
|
||||
"order": "FEND1"
|
||||
},
|
||||
"list": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
/** 测试数据 */
|
||||
const txtData = `FSTART\nTD 5\nG0 X100 Y100 Z10 F8000\ng0 x100 y100 z18 f8000\ng2 x120 y120 z18 R20 f8000\ng3 x100 y100 z18 i20 j0 k0 f8000\nTN T2\nFEND\n`
|
||||
|
||||
function demo(){
|
||||
getDeviceList()
|
||||
|
||||
setDevice()
|
||||
setDeviceByName()
|
||||
setDeviceByKey()
|
||||
}
|
||||
|
||||
/**处理JSON格式的数据 */
|
||||
async function handleJsonData() {
|
||||
let parserMain = new ParserMain();
|
||||
parserMain.setDeviceByName('解析器1');
|
||||
let output = ''
|
||||
let str = jsonData
|
||||
let strArr = str.split('\n')
|
||||
let arr = strArr.filter(e => !e.includes('//') && !e.includes('/**') || e.includes('"//'))
|
||||
arr.forEach(s => {
|
||||
s = s.trim()
|
||||
})
|
||||
let realStr = arr.join('\n')
|
||||
let dd: any = ''
|
||||
try {
|
||||
dd = JSON.parse(realStr)
|
||||
} catch (error) {
|
||||
console.error('输入的Json有误')
|
||||
}
|
||||
let tt = await parserMain?.getCode(dd) || ''
|
||||
output += tt
|
||||
console.log('handleJsonData', output)
|
||||
}
|
||||
|
||||
handleJsonData()
|
||||
|
||||
/** 处理指令txt文本数据 */
|
||||
function handleTxtData() {
|
||||
let parserMain = new ParserMain();
|
||||
parserMain.setDeviceByName('解析器1');
|
||||
let output = ''
|
||||
let str = txtData
|
||||
let strArr = str.split('\n')
|
||||
|
||||
let code = ''
|
||||
|
||||
let orderList = parserMain._device.getCodeListByOrderKeys(['FSTART', 'T', 'C', 'FEND', 'MARK'])
|
||||
|
||||
const reg_G = /^[Gg](\d+\.?\d*)$/
|
||||
const reg_X = /^[Xx](\d+\.?\d*)$/
|
||||
const reg_Y = /^[Yy](\d+\.?\d*)$/
|
||||
const reg_Z = /^[Zz](\d+\.?\d*)$/
|
||||
const reg_F = /^[Ff](\d+\.?\d*)$/
|
||||
const reg_R = /^[Rr](\d+\.?\d*)$/
|
||||
const reg_I = /^[Ii](\d+\.?\d*)$/
|
||||
const reg_J = /^[Jj](\d+\.?\d*)$/
|
||||
const reg_K = /^[Kk](\d+\.?\d*)$/
|
||||
|
||||
strArr.forEach(str => {
|
||||
let codeLineData = str.split(' ')
|
||||
if (Object.keys(codeLineData) && codeLineData.length > 0) {
|
||||
let orderKey = codeLineData[0]
|
||||
let paramsValue = codeLineData[1]
|
||||
let orderItem = orderList.find(e => e.order == orderKey)
|
||||
|
||||
if (orderItem) {
|
||||
/** */
|
||||
// console.log('orderKey', orderKey);
|
||||
if (orderItem.order == 'KnifeMove') {
|
||||
// let codeParams: CodeParams = {
|
||||
|
||||
// }
|
||||
|
||||
} else {
|
||||
code += parserMain.handleCode(orderItem.code, orderItem.order, paramsValue)
|
||||
}
|
||||
} else {
|
||||
let tempCodeParams = {}
|
||||
codeLineData.forEach(e => {
|
||||
if (['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03',
|
||||
'g0', 'g00', 'g1', 'g01', 'g2', 'g02', 'g3', 'g03'
|
||||
].includes(e)) {
|
||||
let match = e.match(reg_G)
|
||||
if (match != null) {
|
||||
if (match[1] == '0' || match[1] == '00') {
|
||||
tempCodeParams.dir = 0
|
||||
} else if (match[1] == '1' || match[1] == '01') {
|
||||
tempCodeParams.dir = 1
|
||||
} else if (match[1] == '2' || match[1] == '02') {
|
||||
tempCodeParams.dir = 2
|
||||
} else if (match[1] == '3' || match[1] == '03') {
|
||||
tempCodeParams.dir = 3
|
||||
}
|
||||
|
||||
}
|
||||
} else if (e.includes('x') || e.includes('X')) {
|
||||
let match = e.match(reg_X)
|
||||
if (match != null) {
|
||||
tempCodeParams.x = match[1]
|
||||
}
|
||||
} else if (e.includes('y') || e.includes('Y')) {
|
||||
let match = e.match(reg_Y)
|
||||
if (match != null) {
|
||||
tempCodeParams.y = match[1]
|
||||
}
|
||||
} else if (e.includes('z') || e.includes('Z')) {
|
||||
let match = e.match(reg_Z)
|
||||
if (match != null) {
|
||||
tempCodeParams.z = match[1]
|
||||
}
|
||||
} else if (e.includes('f') || e.includes('F')) {
|
||||
let match = e.match(reg_F)
|
||||
if (match != null) {
|
||||
tempCodeParams.f = match[1]
|
||||
}
|
||||
} else if (e.includes('r') || e.includes('R')) {
|
||||
let match = e.match(reg_R)
|
||||
if (match != null) {
|
||||
tempCodeParams.r = match[1]
|
||||
}
|
||||
} else if (e.includes('i') || e.includes('I')) {
|
||||
let match = e.match(reg_I)
|
||||
if (match != null) {
|
||||
tempCodeParams.i = match[1]
|
||||
}
|
||||
} else if (e.includes('j') || e.includes('J')) {
|
||||
let match = e.match(reg_J)
|
||||
if (match != null) {
|
||||
tempCodeParams.j = match[1]
|
||||
}
|
||||
} else if (e.includes('k') || e.includes('K')) {
|
||||
let match = e.match(reg_K)
|
||||
if (match != null) {
|
||||
tempCodeParams.k = match[1]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// console.log('tempCodeParams.dir', tempCodeParams.dir, str);
|
||||
if (tempCodeParams.dir != undefined) {
|
||||
let tempProcessItem = {
|
||||
list: [tempCodeParams]
|
||||
}
|
||||
|
||||
code += parserMain.handleCode('C', 'KnifeMove', tempProcessItem)
|
||||
} else {
|
||||
code += str + '\n'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (parserMain._device.isUseSimpleCode == true) {
|
||||
code = parserMain._device.handleSimpleCode(code)
|
||||
}
|
||||
output += code
|
||||
console.log('handleTxtData',output)
|
||||
}
|
||||
|
||||
// handleTxtData()
|
||||
//#region 获取解析器列表
|
||||
function getDeviceList(){
|
||||
let list = parserMain.getDeviceList()
|
||||
// console.log(list)
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region 设置要使用的解析器
|
||||
|
||||
function setDevice(){
|
||||
parserMain.setDevice(device1)
|
||||
// console.log(parserMain._device)
|
||||
|
||||
}
|
||||
|
||||
function setDeviceByName(){
|
||||
parserMain.setDeviceByName('解析器2')
|
||||
// console.log(parserMain._device)
|
||||
}
|
||||
|
||||
function setDeviceByKey(){
|
||||
parserMain.setDeviceByKey('device1')
|
||||
// console.log(parserMain._device)
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 设置
|
187
src/processParser/common/ArrayExt.ts
Normal file
187
src/processParser/common/ArrayExt.ts
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 删除数组中指定的元素,返回数组本身
|
||||
* @param {Array<any>} arr 需要操作的数组
|
||||
* @param {*} el 需要移除的元素
|
||||
*/
|
||||
export function arrayRemove<T>(arr: Array<T>, el: T): Array<T>
|
||||
{
|
||||
let j = 0
|
||||
for (let i = 0, l = arr.length; i < l; i++)
|
||||
{
|
||||
if (arr[i] !== el)
|
||||
{
|
||||
arr[j++] = arr[i]
|
||||
}
|
||||
}
|
||||
arr.length = j
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
export function arrayRemoveOnce<T>(arr: Array<T>, el: T): Array<T>
|
||||
{
|
||||
let index = arr.indexOf(el)
|
||||
if (index !== -1)
|
||||
arr.splice(index, 1)
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除通过函数校验的元素
|
||||
* @param {(e: T) => boolean} checkFuntion 校验函数
|
||||
*/
|
||||
export function arrayRemoveIf<T>(arr: Array<T>, checkFuntion: (e: T) => boolean): Array<T>
|
||||
{
|
||||
let j = 0
|
||||
for (let i = 0, l = arr.length; i < l; i++)
|
||||
{
|
||||
if (!checkFuntion(arr[i]))
|
||||
{
|
||||
arr[j++] = arr[i]
|
||||
}
|
||||
}
|
||||
arr.length = j
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
/** 获取数据第一个元素 */
|
||||
export function arrayFirst<T>(arr: Array<T>): T
|
||||
{
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
/** 获取数组最后一个元素 */
|
||||
export function arrayLast<T>(arr: { [key: number]: T; length: number }): T
|
||||
{
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数值从小到大排序数组
|
||||
* @param {Array<T>} arr
|
||||
* @returns {Array<T>} 返回自身
|
||||
*/
|
||||
export function arraySortByNumber<T>(arr: Array<T>): Array<T>
|
||||
{
|
||||
arr.sort(sortNumberCompart)
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* 对排序好的数组进行去重操作
|
||||
* @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
|
||||
}
|
||||
|
||||
/** 原地更新数组,注意这个函数并不会比map快 */
|
||||
export function arrayMap<T>(arr: Array<T>, mapFunc: (v: T) => T): Array<T>
|
||||
{
|
||||
for (let i = 0, count = arr.length; i < count; i++)
|
||||
arr[i] = mapFunc(arr[i])
|
||||
return arr
|
||||
}
|
||||
|
||||
/** 排序数比较 */
|
||||
function sortNumberCompart(e1: any, e2: any)
|
||||
{
|
||||
return e1 - e2
|
||||
}
|
||||
|
||||
/** 判断两个数是否相等 */
|
||||
function checkEqual(e1: any, e2: any): boolean
|
||||
{
|
||||
return e1 === e2
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变数组的值顺序
|
||||
* @param arr 需要改变初始值位置的数组
|
||||
* @param index //将index位置以后的值放到起始位置
|
||||
*/
|
||||
export function changeArrayStartIndex<T>(arr: T[], index: number): T[]
|
||||
{
|
||||
arr.unshift(...arr.splice(index))
|
||||
return arr
|
||||
}
|
||||
|
||||
/** 判断两个数组是否相等 */
|
||||
export function equalArray<T>(a: T[], b: T[], checkF = checkEqual)
|
||||
{
|
||||
if (a === b)
|
||||
return true
|
||||
if (a.length !== b.length)
|
||||
return false
|
||||
for (let i = 0; i < a.length; ++i)
|
||||
if (!checkF(a[i], b[i]))
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** 数组克隆 */
|
||||
export function arrayClone<T>(arr: T[]): T[]
|
||||
{
|
||||
return arr.slice()
|
||||
}
|
||||
|
||||
/** 数组2元素合并到数组1末尾 */
|
||||
// https://jsperf.com/merge-array-implementations/30
|
||||
export function arrayPushArray<T>(arr1: T[], arr2: T[]): T[]
|
||||
{
|
||||
let arr1Length = arr1.length
|
||||
let arr2Length = arr2.length
|
||||
arr1.length = arr1Length + arr2Length
|
||||
for (let i = 0; i < arr2Length; i++)
|
||||
arr1[arr1Length + i] = arr2[i]
|
||||
|
||||
return arr1
|
||||
}
|
||||
|
||||
/** 数组元素求合 */
|
||||
export function arraySum(arr: number[])
|
||||
{
|
||||
let sum = 0
|
||||
for (let n of arr) sum += n
|
||||
return sum
|
||||
}
|
||||
|
||||
/** 条件过滤集合 */
|
||||
export function FilterSet<T>(s: Set<T>, fn: (el: T) => boolean): Set<T>
|
||||
{
|
||||
let ns = new Set<T>()
|
||||
for (let el of s)
|
||||
{
|
||||
if (fn(el))
|
||||
ns.add(el)
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
/** 查找数组中最大的元素 */
|
||||
export function arrayMax<T>(arr: T[], f: (item: T) => number = a => (a as unknown as number)): [T, number]
|
||||
{
|
||||
let max = Number.NEGATIVE_INFINITY
|
||||
let maxIndex = -1
|
||||
for (let i = 0; i < arr.length; i++)
|
||||
{
|
||||
let item = arr[i]
|
||||
let v = f(item)
|
||||
if (v > max)
|
||||
{
|
||||
maxIndex = i
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return [arr[maxIndex], maxIndex]
|
||||
}
|
183
src/processParser/common/Box2.ts
Normal file
183
src/processParser/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
src/processParser/common/ClipperCpp.ts
Normal file
25
src/processParser/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('载入成功!')
|
||||
})
|
||||
})
|
||||
}
|
62
src/processParser/common/ComparePoint.ts
Normal file
62
src/processParser/common/ComparePoint.ts
Normal file
@ -0,0 +1,62 @@
|
||||
interface Vec2 { x: number; y: number }
|
||||
|
||||
export type CompareVectorFn = (v1: Vec2, v2: Vec2) => number
|
||||
|
||||
const comparePointCache: Map<string, CompareVectorFn> = new Map()
|
||||
|
||||
const ALLKEY = ['x', 'X', 'y', 'Y', 'z', 'Z']
|
||||
const KEY = ['x', 'y', 'z']
|
||||
|
||||
/**
|
||||
* 构建返回一个用来排序的函数.根据key创建排序规则.
|
||||
*
|
||||
* 当key = "xyz" 时,点集按 x从小到大,y从小到大 z从小到大
|
||||
* key = "X" 时,点集按 x从大到小
|
||||
* 以此类推.
|
||||
*
|
||||
* 例子:
|
||||
* let pts:Vector3[] =...;
|
||||
* pts.sort(comparePoint("x")); //x从小到大排序
|
||||
* pts.sort(comparePoint("zX")); //z从小到大 x从大到小
|
||||
*
|
||||
* @export
|
||||
* @param {string} sortKey
|
||||
*/
|
||||
export function ComparePoint(sortKey: string): CompareVectorFn
|
||||
{
|
||||
if (comparePointCache.has(sortKey))
|
||||
return comparePointCache.get(sortKey)
|
||||
|
||||
let sortIndex = []
|
||||
|
||||
for (let char of sortKey)
|
||||
{
|
||||
let index = ALLKEY.indexOf(char)
|
||||
|
||||
let i2 = index / 2
|
||||
let ci = Math.floor(i2)
|
||||
sortIndex.push([KEY[ci], i2 > ci ? 1 : -1])
|
||||
}
|
||||
|
||||
let compareFunction = (v1: Vec2, v2: Vec2): number =>
|
||||
{
|
||||
if (!v1)
|
||||
return -1
|
||||
if (!v2)
|
||||
return 1
|
||||
for (let s of sortIndex)
|
||||
{
|
||||
let vv1 = v1[s[0]]
|
||||
let vv2 = v2[s[0]]
|
||||
if (vv1 === vv2)
|
||||
continue
|
||||
if (vv2 > vv1)
|
||||
return s[1]
|
||||
else return -s[1]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
comparePointCache.set(sortKey, compareFunction)
|
||||
return compareFunction
|
||||
}
|
9
src/processParser/common/ConvexHull2D.ts
Normal file
9
src/processParser/common/ConvexHull2D.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import convexHull from 'monotone-convex-hull-2d'
|
||||
import type { Point } from './Point'
|
||||
|
||||
export function ConvexHull2D(points: Point[]): Point[]
|
||||
{
|
||||
let pts = points.map(p => [p.x, p.y])
|
||||
let indexs: number[] = convexHull(pts)
|
||||
return indexs.map(i => points[i])
|
||||
}
|
31
src/processParser/common/Filer.ts
Normal file
31
src/processParser/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++]
|
||||
}
|
||||
}
|
1265
src/processParser/common/GraphAvoidance.ts
Normal file
1265
src/processParser/common/GraphAvoidance.ts
Normal file
File diff suppressed because it is too large
Load Diff
255
src/processParser/common/LayoutEngine/Curves2Parts.ts
Normal file
255
src/processParser/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
|
||||
}
|
655
src/processParser/common/LayoutEngine/LayoutEngine.ts
Normal file
655
src/processParser/common/LayoutEngine/LayoutEngine.ts
Normal file
File diff suppressed because one or more lines are too long
379
src/processParser/common/LayoutEngine/OddmentsHelper.ts
Normal file
379
src/processParser/common/LayoutEngine/OddmentsHelper.ts
Normal file
@ -0,0 +1,379 @@
|
||||
/** 自动生成余料板空间 */
|
||||
|
||||
import { Vector3 } from 'three'
|
||||
import { Polyline2Points } from 'cadapi'
|
||||
import type { Polyline } from 'cadapi'
|
||||
import { BlockSizePlus } from '../../Pack/BlockSizePlus'
|
||||
import { BlockHelper } from '../BlockHelper'
|
||||
import { arrayRemoveDuplicateBySort } from '../Nest/Common/ArrayExt'
|
||||
import { InitClipperCpp } from '../Nest/Common/ClipperCpp'
|
||||
import type { Vector2 } from '../Nest/Common/Vector2'
|
||||
import { Container } from '../Nest/Core/Container'
|
||||
import { NestCache } from '../Nest/Core/NestCache'
|
||||
import { ParseOddments } from '../Nest/Core/ParseOddments'
|
||||
import { Part } from '../Nest/Core/Part'
|
||||
import { Path } from '../Nest/Core/Path'
|
||||
import { PathGeneratorSingle } from '../Nest/Core/PathGenerator'
|
||||
import { PlaceStore } from '../Store'
|
||||
import { CurveWrap, Points2Polyline } from './Curves2Parts'
|
||||
import { PolylineHelper } from './PolylineHelper'
|
||||
import { BlockPlus } from '@/Source/Pack/BlockPlus'
|
||||
import { ScrapBlock } from '@/Source/Models/ScrapBorad'
|
||||
import type { PlaceBlock, PlaceBoard, PlaceMetrial, SaleBlockModel } from '@/Source/Models/Orders'
|
||||
import { Arc2d } from '@/Source/Base/CAD'
|
||||
import { ArrayExt } from '@/Source/Base/ArrayExt'
|
||||
|
||||
export class OddmentsHelper {
|
||||
squarePath: Path
|
||||
canPutPaths: Path[]
|
||||
offset = 3.5
|
||||
|
||||
initSquarePath() {
|
||||
const seqW = PlaceStore.sysConfig.scrapBlockSquare || 200
|
||||
const wmin = PlaceStore.sysConfig.srcapBlockWidthMin || 100
|
||||
const wmax = PlaceStore.sysConfig.scrapBlockWidthMax || 600
|
||||
this.squarePath = NestCache.CreatePath(60, 60, 0)
|
||||
this.canPutPaths = [
|
||||
NestCache.CreatePath(seqW, seqW, 0),
|
||||
NestCache.CreatePath(wmin, wmax, 0),
|
||||
NestCache.CreatePath(wmax, wmin, 0),
|
||||
]
|
||||
}
|
||||
|
||||
async analyzeAllScrapSpace(pm1: PlaceMetrial, pb1: PlaceBoard) {
|
||||
this.initSquarePath()
|
||||
if (pm1 && pb1) // 当前板 先做
|
||||
{
|
||||
this.offset = (pm1.CutDia) / 2
|
||||
await this.analyzeScrapSpace(pm1, pb1)
|
||||
}
|
||||
const order = PlaceStore.order
|
||||
for (const pm of order.MetrialList) {
|
||||
this.offset = (pm.CutDia) / 2
|
||||
for (const pb of pm.BoardList)
|
||||
await this.analyzeScrapSpace(pm, pb)
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeScrapSpace(pm: PlaceMetrial, board: PlaceBoard) {
|
||||
console.log('OddmentsHelper analyzeScrapSpace', pm, board)
|
||||
if (board.needToScrapSpace == false)
|
||||
return
|
||||
await InitClipperCpp()
|
||||
|
||||
const border = 0// PlaceStore.sysConfig.BoardBorder_A;
|
||||
let borderB = PlaceStore.sysConfig.BoardBorder_B
|
||||
if (board.IsDo2Face == false)
|
||||
borderB = 0
|
||||
let binPath: Path
|
||||
if (board.Points && board.Points.length > 0) // 非矩形板
|
||||
|
||||
binPath = new Path(board.Points)
|
||||
|
||||
else // 矩形板
|
||||
|
||||
binPath = new Path([{ x: border, y: border }, { x: board.Width - borderB, y: border }, { x: board.Width - borderB, y: board.Length - borderB }, { x: border, y: board.Length - borderB }])
|
||||
|
||||
binPath.Id = undefined
|
||||
PathGeneratorSingle.RegisterId(binPath)
|
||||
// 容器
|
||||
const container = new Container(binPath)
|
||||
|
||||
for (let i = 0; i < board.blockList.length; i++) {
|
||||
const block = board.blockList[i]
|
||||
const part = this.toPart(block, binPath, this.offset * 2, i)
|
||||
// 设置位置
|
||||
part[0].PlacePosition = { x: (part[1].x + block.PlaceX - border) * 1e4, y: (part[1].y + block.PlaceY - border) * 1e4 }
|
||||
container.PlacedParts.push(part[0])
|
||||
}
|
||||
// for (let i = 0; i < board.blockList.length; i++)
|
||||
// {
|
||||
// let block = board.blockList[i];
|
||||
// let part = this.toPart(block, binPath, 0, board.blockList.length + i);
|
||||
// //设置位置
|
||||
// part[0].PlacePosition = { x: (part[1].x + block.PlaceX - border) * 1e4, y: (part[1].y + block.PlaceY - border) * 1e4 };
|
||||
// container.PlacedParts.push(part[0]);
|
||||
// }
|
||||
|
||||
// let f = new NestFiler();
|
||||
// f.Write(container.PlacedParts.length);
|
||||
// for (let part of container.PlacedParts)
|
||||
// f.Write(part.State.Contour.BigIntPoints);
|
||||
// container.WriteFile(f);
|
||||
// console.log(JSON.stringify(f._datas));
|
||||
|
||||
// FileZip.WriteFile(`${board.BoardID}.container`, JSON.stringify(f._datas));
|
||||
|
||||
board.ScrapBlockList = [] // 清空
|
||||
try {
|
||||
const spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
|
||||
for (const space of spaces) {
|
||||
const sb = this.toScrapBlock(space)
|
||||
sb.PlaceX = sb.PlaceX + border
|
||||
sb.PlaceY = sb.PlaceY + border
|
||||
board.ScrapBlockList.push(sb)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// throw new Error(`计算余料空间异常:${pm.FullName} 第${board.BoardID}片`);
|
||||
console.log(`计算余料空间异常:${pm.FullName} 第${board.BoardID}片`)
|
||||
throw new Error(`计算余料空间异常:${pm.FullName} 第${board.BoardID}片;请保存优化联系售后工程师分析`)
|
||||
}
|
||||
|
||||
// 分析小板内 造型孔洞 余料空间
|
||||
for (const block of board.blockList) {
|
||||
let childBlocks: PlaceBlock[]
|
||||
// 造型孔
|
||||
for (const m of block.Models) {
|
||||
if (m.Depth < block.Thickness - 0.05)
|
||||
continue
|
||||
if (m.HasContour == false)
|
||||
continue
|
||||
if (m.IsDo == false)
|
||||
continue
|
||||
if (!childBlocks) {
|
||||
childBlocks = board.blockList.filter(t => t.PlaceX > block.PlaceX
|
||||
&& t.PlaceX + t.PlaceWidth < block.PlaceX + block.PlaceWidth
|
||||
&& t.PlaceY > block.PlaceY
|
||||
&& t.PlaceY + t.PlaceLength < block.PlaceY + block.PlaceLength)
|
||||
}
|
||||
const spaces = await this.getModelSpace(block, m, childBlocks)
|
||||
for (const space of spaces) {
|
||||
const sb = this.toScrapBlock(space)
|
||||
// sb.PlaceX += border;
|
||||
// sb.PlaceY += border;
|
||||
sb.PlaceX += block.PlaceX
|
||||
sb.PlaceY += block.PlaceY
|
||||
board.ScrapBlockList.push(sb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board.needToScrapSpace = false
|
||||
// console.log(`board id=${board.BoardID} find scrap block count = ${board.ScrapBlockList.length}`);
|
||||
// console.log(`${board.BoardID} scraping ok`)
|
||||
}
|
||||
|
||||
async getModelSpace(block: PlaceBlock, m: SaleBlockModel, blocks: PlaceBlock[]): Promise<any[]> {
|
||||
if (m.OriginModeling == null)
|
||||
return []
|
||||
const pts = m.OriginModeling.outline.pts
|
||||
const buls = m.OriginModeling.outline.buls
|
||||
// blocks = [];
|
||||
// 起点
|
||||
let pts_n = []
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
const p0 = BlockHelper.GetPlaceXYInBlock(block, pts[i].x, pts[i].y, false, false)
|
||||
pts_n.push({ x: p0.X, y: p0.Y, bul: buls[i] })
|
||||
}
|
||||
// 如果反面,翻转方向,重设坐标
|
||||
if (block.IsTurnOver)
|
||||
pts_n = this.reversePoint(pts_n, block.CuttingWidth)
|
||||
|
||||
// pts_n.forEach(t => { t.x += block.PlaceX; t.y += block.PlaceY; });
|
||||
|
||||
let polyLine
|
||||
polyLine = Points2Polyline(pts_n)
|
||||
const cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
|
||||
if (cicle)
|
||||
polyLine = cicle
|
||||
const cureW2 = new CurveWrap(polyLine, this.offset, false)
|
||||
const pts2 = cureW2.GetInsidePoints2(this.offset)
|
||||
if (!pts2)
|
||||
return []
|
||||
let posx = 10000
|
||||
let posy = 10000
|
||||
for (const p of pts2) {
|
||||
if (p.x < posx)
|
||||
posx = p.x
|
||||
if (p.y < posy)
|
||||
posy = p.y
|
||||
}
|
||||
for (const p of pts2) {
|
||||
p.x -= posx
|
||||
p.y -= posy
|
||||
}
|
||||
const binPath = new Path(pts2)
|
||||
if (binPath.Area < 15000)
|
||||
return []
|
||||
|
||||
await InitClipperCpp()
|
||||
binPath.Id = undefined
|
||||
PathGeneratorSingle.RegisterId(binPath)
|
||||
// 容器
|
||||
const container = new Container(binPath)
|
||||
let i = 0
|
||||
for (const b of blocks) {
|
||||
const part = this.toPart(b, binPath, this.offset, i)
|
||||
if (part[0].State.Contour == null)
|
||||
continue
|
||||
part[0].Id = i++
|
||||
// let isin = this.blockInBlock(block, b, polyLine);
|
||||
// if (isin == false) continue;
|
||||
// let part = this.toPart(b, binPath, this.offset, i++);
|
||||
|
||||
// 设置位置
|
||||
part[0].PlacePosition = { x: (b.PlaceX - block.PlaceX - posx) * 1e4, y: (b.PlaceY - block.PlaceY - posy) * 1e4 }
|
||||
container.PlacedParts.push(part[0])
|
||||
}
|
||||
try {
|
||||
// let f = new NestFiler();
|
||||
// f.Write(container.PlacedParts.length);
|
||||
// for (let part of container.PlacedParts)
|
||||
// f.Write(part.State.Contour.BigIntPoints);
|
||||
// container.WriteFile(f);
|
||||
// console.log(JSON.stringify(f._datas));
|
||||
// FileZip.WriteFile(`${board.BoardID}.container`, JSON.stringify(f._datas));
|
||||
|
||||
const spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
|
||||
for (const space of spaces)
|
||||
space.Points.forEach((t) => { t.x += posx; t.y += posy })
|
||||
|
||||
return spaces
|
||||
}
|
||||
catch (err) {
|
||||
console.log(`板${block.BlockNo}造型孔分析余料空间失败.`)
|
||||
throw new Error(`板${block.BlockNo}造型孔分析余料空间失败.请保存优化联系售后工程师分析`)
|
||||
}
|
||||
// let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths);
|
||||
// for (let space of spaces)
|
||||
// {
|
||||
// space.Points.forEach(t => { t.x += posx; t.y += posy; });
|
||||
// }
|
||||
// return spaces;
|
||||
}
|
||||
|
||||
toPart(block: PlaceBlock, binPath: Path, offset: number, id: number): [Part, Vector2] {
|
||||
const w = 10000
|
||||
const binPath2 = new Path([{ x: -w, y: -w }, { x: w, y: -w }, { x: w, y: w }, { x: -w, y: w }])
|
||||
const part = new Part()
|
||||
part.Id = id
|
||||
|
||||
part.UserData = { bno: block.BlockNo, size: block.Size, isYX: block.IsUnRegular, width: block.CuttingWidth, length: block.CuttingLength }
|
||||
|
||||
let path: Path
|
||||
let bPoint: Vector2
|
||||
|
||||
const sizeOff = BlockSizePlus.getOffDis(block) // 外扩偏移
|
||||
const hasSizeOff = (sizeOff.left + sizeOff.right + sizeOff.top + sizeOff.bottom) > (block.PlaceMetrial.CutDia * 2 + block.PlaceMetrial.CutGap * 2 + 0.01)
|
||||
|
||||
// 矩形 且 带板外不偏移
|
||||
if (block.IsUnRegular == false && hasSizeOff == false) {
|
||||
path = NestCache.CreatePath(block.PlaceWidth + sizeOff.left + sizeOff.right, block.PlaceLength + sizeOff.top + sizeOff.bottom, offset)
|
||||
bPoint = path.OrigionMinPoint
|
||||
// 轮廓
|
||||
part.Init2(path, binPath2, [0])
|
||||
}
|
||||
else // 异形
|
||||
{
|
||||
const pts = []
|
||||
|
||||
const cured = BlockPlus.getBorder(block)
|
||||
for (const l of cured) {
|
||||
if (l instanceof Arc2d)
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
|
||||
|
||||
else
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
|
||||
}
|
||||
|
||||
let polyLine
|
||||
polyLine = Points2Polyline(pts)
|
||||
polyLine.CloseMark = true
|
||||
|
||||
const cureW = new CurveWrap(polyLine, offset, true)
|
||||
const pts2 = cureW.GetOutsidePoints()
|
||||
arrayRemoveDuplicateBySort(pts, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
path = new Path(pts2)
|
||||
|
||||
const cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
|
||||
if (!cicle) {
|
||||
const area = path.BoundingBox.area - path.Area
|
||||
if (area < 15000 && pts.length > 6)
|
||||
path = NestCache.CreatePath(block.PlaceWidth, block.PlaceLength, offset)
|
||||
}
|
||||
part.Init2(path, binPath2, [0])
|
||||
|
||||
// 不能放下,那么尝试不简化路径
|
||||
if (!path.IsRect && !cicle && part.RotatedStates.length == 0) {
|
||||
const pts2 = Polyline2Points(polyLine, true, offset)[1]
|
||||
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
path = new Path(pts2)
|
||||
part.Init2(path, binPath, [0])
|
||||
}
|
||||
// 轮廓
|
||||
bPoint = path.OrigionMinPoint
|
||||
}
|
||||
|
||||
if (hasSizeOff) {
|
||||
bPoint.x = bPoint.x - sizeOff.left
|
||||
bPoint.y = bPoint.y - sizeOff.bottom
|
||||
}
|
||||
|
||||
return [part, bPoint]
|
||||
}
|
||||
|
||||
toScrapBlock(path: Path): ScrapBlock {
|
||||
const bx = path.OrigionMinPoint.x
|
||||
const by = path.OrigionMinPoint.y
|
||||
const points = path.Points
|
||||
const ptsX = points.map((t) => { return t.x })
|
||||
const ptsY = points.map((t) => { return t.y })
|
||||
|
||||
const x0 = ArrayExt.min(ptsX, t => t)
|
||||
const y0 = ArrayExt.min(ptsY, t => t)
|
||||
const x1 = ArrayExt.max(ptsX, t => t)
|
||||
const y1 = ArrayExt.max(ptsY, t => t)
|
||||
|
||||
const sp = new ScrapBlock(x0, y0, x1 - x0, y1 - y0)
|
||||
|
||||
const pts = points.map((t) => { return { x: t.x - x0, y: t.y - y0 } })
|
||||
|
||||
sp.PlaceX = bx + x0
|
||||
sp.PlaceY = by + y0
|
||||
|
||||
sp.setPoints(pts)
|
||||
return sp
|
||||
}
|
||||
|
||||
// 判断板是否在造型洞里头
|
||||
blockInBlock(block: PlaceBlock, b: PlaceBlock, modlPl: Polyline, posX = 0, posY = 0): boolean {
|
||||
const cured = BlockPlus.getBorder(b)
|
||||
const pts = []
|
||||
for (const l of cured) {
|
||||
if (l instanceof Arc2d)
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
|
||||
|
||||
else
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
|
||||
}
|
||||
const polyLine = Points2Polyline(pts)
|
||||
const cureW2 = new CurveWrap(polyLine, this.offset, false)
|
||||
const pts2 = cureW2.GetInsidePoints2(this.offset)
|
||||
|
||||
// pts2 = pts2.map(t => { return { x: t.x + b.PlaceX - block.PlaceX - posX, y: t.y + b.PlaceY - block.PlaceY - posY }; });
|
||||
|
||||
for (const t of pts2) {
|
||||
const x = t.x + b.PlaceX - block.PlaceX - posX
|
||||
const y = t.y + b.PlaceY - block.PlaceY - posY
|
||||
if (modlPl.PtInCurve(new Vector3(x, y, 0)) == false)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private reversePoint(pts, w): any[] {
|
||||
const newPts = []
|
||||
for (let i = pts.length - 1; i >= 0; i--) {
|
||||
const p = pts[i]
|
||||
const x = p.x
|
||||
const y = p.y
|
||||
const j = i == 0 ? pts.length - 1 : i - 1
|
||||
|
||||
const bul = pts[j].bul
|
||||
newPts.push({ x, y, bul })
|
||||
}
|
||||
return newPts
|
||||
}
|
||||
}
|
369
src/processParser/common/LayoutEngine/PolylineHelper.ts
Normal file
369
src/processParser/common/LayoutEngine/PolylineHelper.ts
Normal file
@ -0,0 +1,369 @@
|
||||
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 '../../common/base/CAD'
|
||||
import { Arc2d, Point2d, copyTextToClipboard } from '../../common/base/CAD'
|
||||
import { CurveWrap } from './Curves2Parts'
|
||||
|
||||
// import type { Curve2d } from '../../common/base/CAD'
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
408
src/processParser/common/LayoutEngine/RemainHelper.ts
Normal file
408
src/processParser/common/LayoutEngine/RemainHelper.ts
Normal file
@ -0,0 +1,408 @@
|
||||
import type { Polyline } from 'cadapi'
|
||||
import { Polyline2Points } from 'cadapi'
|
||||
import { Vector3 } from 'three'
|
||||
import { ArrayExt } from '../base/ArrayExt.js'
|
||||
import { Arc2d } from '../base/CAD.js'
|
||||
import { arrayRemoveDuplicateBySort } from '../ArrayExt.js'
|
||||
import { InitClipperCpp } from '../ClipperCpp.js'
|
||||
import type { Vector2 } from '../Vector2.js'
|
||||
import { Container } from '../core/Container.js'
|
||||
import { NestCache } from '../core/NestCache.js'
|
||||
import { ParseOddments } from '../core/ParseOddments.js'
|
||||
import { Part } from '../core/Part.js'
|
||||
import { Path } from '../core/Path.js'
|
||||
import { PathGeneratorSingle } from '../core/PathGenerator.js'
|
||||
import type { PlaceBlock } from '../../vo/order/PlaceBlock.js'
|
||||
import type { PlaceBoard } from '../../vo/order/PlaceBoard.js'
|
||||
import type { PlaceMaterial } from '../../vo/order/PlaceMaterial.js'
|
||||
import type { BlockModel } from '../../vo/model/BlockModel.js'
|
||||
import { RemainBlock } from '../../vo/remain/RemainBlock.js'
|
||||
import { BlockPlus } from '../../vo/order/BlockPlus.js'
|
||||
import { BlockSizePlus } from '../../vo/order/BlockSizePlus.js'
|
||||
import { BlockHelper } from '../../vo/order/BlockHelper.js'
|
||||
import { PlaceStore } from '../../vo/order/PlaceStore.js'
|
||||
import { PolylineHelper } from './PolylineHelper.js'
|
||||
import { CurveWrap, Points2Polyline } from './Curves2Parts.js'
|
||||
|
||||
/** 自动生成余料板空间 */
|
||||
export class RemainHelper {
|
||||
squarePath: Path
|
||||
canPutPaths: Path[]
|
||||
offset = 3.5
|
||||
|
||||
initSquarePath() {
|
||||
// let squareWidth = PlaceStore.sysConfig.scrapBlockSquare || 200;
|
||||
// let widthMin = PlaceStore.sysConfig.srcapBlockWidthMin || 100;
|
||||
// let widthMax = PlaceStore.sysConfig.scrapBlockWidthMax || 600;
|
||||
let squareWidth = 200
|
||||
let widthMin = 100
|
||||
let widthMax = 600
|
||||
this.squarePath = NestCache.CreatePath(60, 60, 0)
|
||||
this.canPutPaths = [
|
||||
NestCache.CreatePath(squareWidth, squareWidth, 0),
|
||||
NestCache.CreatePath(widthMin, widthMax, 0),
|
||||
NestCache.CreatePath(widthMax, widthMin, 0),
|
||||
]
|
||||
}
|
||||
|
||||
/** 分析所有余料空间 */
|
||||
async analyzeAllRemainSpace(pm1: PlaceMaterial, pb1: PlaceBoard) {
|
||||
this.initSquarePath()
|
||||
console.log('分析所有余料空间');
|
||||
if (pm1 && pb1) // 当前板 先做
|
||||
{
|
||||
|
||||
this.offset = (pm1.diameter + pm1.cutKnifeGap) / 2 + PlaceStore.order.sysConfig.preMillingSize
|
||||
pb1.isCreateRemainSpace = true
|
||||
await this.analyzeScrapSpace(pm1, pb1)
|
||||
}
|
||||
let order = PlaceStore.order
|
||||
for (let pm of order.materialList) {
|
||||
this.offset = (pm.diameter + pm.cutKnifeGap ) / 2 + PlaceStore.order.sysConfig.preMillingSize
|
||||
for (let pb of pm.boardList) {
|
||||
await this.analyzeScrapSpace(pm, pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 分析所有余料空间 */
|
||||
async analyzeScrapSpace(pm: PlaceMaterial, board: PlaceBoard) {
|
||||
// console.log('RemainHelper analyzeScrapSpace', pm, board)
|
||||
if (board.isCreateRemainSpace == false)
|
||||
return
|
||||
await InitClipperCpp()
|
||||
|
||||
let cutBoardBorder = PlaceStore.sysConfig.cutBoardBorder;
|
||||
let cutBoardBorderB = PlaceStore.sysConfig.cutBoardBorderB
|
||||
if (board.isTwoFaceProcessing == false)
|
||||
cutBoardBorderB = 0
|
||||
let binPath: Path
|
||||
if (board.points && board.points.length > 0) // 非矩形板
|
||||
{
|
||||
binPath = new Path(board.points)
|
||||
}
|
||||
else // 矩形板
|
||||
{
|
||||
binPath = new Path([{ x: cutBoardBorder, y: cutBoardBorder },
|
||||
{ x: board.width - cutBoardBorderB, y: cutBoardBorder },
|
||||
{ x: board.width - cutBoardBorderB, y: board.length - cutBoardBorderB },
|
||||
{ x: cutBoardBorder, y: board.length - cutBoardBorderB },
|
||||
])
|
||||
}
|
||||
binPath.Id = undefined
|
||||
PathGeneratorSingle.RegisterId(binPath)
|
||||
// 容器
|
||||
let container = new Container(binPath)
|
||||
|
||||
for (let i = 0; i < board.blockList.length; i++) {
|
||||
let block = board.blockList[i]
|
||||
let part = this.toPart(block, binPath, this.offset * 2, i)
|
||||
// 设置位置
|
||||
part[0].PlacePosition = { x: (part[1].x + block.placeX + block.placeOffX - cutBoardBorder) * 1e4, y: (part[1].y + block.placeY + block.placeOffY - cutBoardBorder) * 1e4 }
|
||||
container.PlacedParts.push(part[0])
|
||||
}
|
||||
// for (let i = 0; i < board.BlockList.length; i++)
|
||||
// {
|
||||
// let block = board.BlockList[i];
|
||||
// let part = this.toPart(block, binPath, 0, board.BlockList.length + i);
|
||||
// //设置位置
|
||||
// part[0].PlacePosition = { x: (part[1].x + block.placeX - border) * 1e4, y: (part[1].y + block.placeY - border) * 1e4 };
|
||||
// container.PlacedParts.push(part[0]);
|
||||
// }
|
||||
|
||||
// let f = new NestFiler();
|
||||
// f.Write(container.PlacedParts.length);
|
||||
// for (let part of container.PlacedParts)
|
||||
// f.Write(part.State.Contour.BigIntPoints);
|
||||
// container.WriteFile(f);
|
||||
// console.log(JSON.stringify(f._datas));
|
||||
|
||||
// FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas));
|
||||
|
||||
board.remainBlockList = [] // 清空
|
||||
try {
|
||||
let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
|
||||
for (let space of spaces) {
|
||||
let sb = this.toRemainBlock(space)
|
||||
sb.placeX = sb.placeX + cutBoardBorder
|
||||
sb.placeY = sb.placeY + cutBoardBorder
|
||||
board.remainBlockList.push(sb)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// throw new Error(`计算余料空间异常:${pm.FullName} 第${board.boardId}片`);
|
||||
console.log(`计算余料空间异常:${pm.fullName} 第${board.boardId}片`, err)
|
||||
throw new Error(`计算余料空间异常:${pm.fullName} 第${board.boardId}片;请保存优化联系售后工程师分析`)
|
||||
}
|
||||
|
||||
// 分析小板内 造型孔洞 余料空间
|
||||
for (let block of board.blockList) {
|
||||
let childBlocks: PlaceBlock[]
|
||||
// 造型孔
|
||||
for (let m of block.models) {
|
||||
if (m.depth < block.thickness - 0.05)
|
||||
continue
|
||||
if (m.hasContour == false)
|
||||
continue
|
||||
if (m.isCutting == false)
|
||||
continue
|
||||
if (!childBlocks) {
|
||||
childBlocks = board.blockList.filter(t => t.placeX > block.placeX
|
||||
&& t.placeX + t.placeWidth < block.placeX + block.placeWidth
|
||||
&& t.placeY > block.placeY
|
||||
&& t.placeY + t.placeLength < block.placeY + block.placeLength)
|
||||
}
|
||||
let spaces = await this.getModelSpace(block, m, childBlocks)
|
||||
for (let space of spaces) {
|
||||
let sb = this.toRemainBlock(space)
|
||||
// sb.placeX += border;
|
||||
// sb.placeY += border;
|
||||
sb.placeX += block.placeX
|
||||
sb.placeY += block.placeY
|
||||
board.remainBlockList.push(sb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board.isCreateRemainSpace = false
|
||||
// console.log(`board id=${board.boardId} find scrap block count = ${board.remainBlockList.length}`);
|
||||
// console.log(`${board.boardId} scraping ok`)
|
||||
}
|
||||
|
||||
/** 获取造型空间 */
|
||||
async getModelSpace(block: PlaceBlock, m: BlockModel, blocks: PlaceBlock[]): Promise<any[]> {
|
||||
if (m.originModeling == null)
|
||||
return []
|
||||
let pts = m.originModeling.outline.pts
|
||||
let buls = m.originModeling.outline.buls
|
||||
|
||||
if (pts == undefined) {
|
||||
pts = m.originModeling.outline.map(e => e.pts)
|
||||
}
|
||||
|
||||
if (buls == undefined) {
|
||||
buls = m.originModeling.outline.map(e => e.buls)
|
||||
}
|
||||
// blocks = [];
|
||||
// 起点
|
||||
let pts_n = []
|
||||
for (let i = 0; i < pts.length; i++) {
|
||||
let p0 = BlockHelper.getPlaceXYInBlock(block, pts[i].x, pts[i].y, false, false)
|
||||
pts_n.push({ x: p0.x, y: p0.y, bul: buls[i] })
|
||||
}
|
||||
// 如果反面,翻转方向,重设坐标
|
||||
if (block.isTurnOver)
|
||||
pts_n = this.reversePoint(pts_n, block.cutWidth)
|
||||
|
||||
// pts_n.forEach(t => { t.x += block.placeX; t.y += block.placeY; });
|
||||
|
||||
let polyLine
|
||||
polyLine = Points2Polyline(pts_n)
|
||||
let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
|
||||
if (cicle)
|
||||
polyLine = cicle
|
||||
let cureW2 = new CurveWrap(polyLine, this.offset, false)
|
||||
let pts2 = cureW2.GetInsidePoints2(this.offset)
|
||||
if (!pts2)
|
||||
return []
|
||||
let posx = 10000
|
||||
let posy = 10000
|
||||
for (let p of pts2) {
|
||||
if (p.x < posx)
|
||||
posx = p.x
|
||||
if (p.y < posy)
|
||||
posy = p.y
|
||||
}
|
||||
for (let p of pts2) {
|
||||
p.x -= posx
|
||||
p.y -= posy
|
||||
}
|
||||
let binPath = new Path(pts2)
|
||||
if (binPath.Area < 15000)
|
||||
return []
|
||||
|
||||
await InitClipperCpp()
|
||||
binPath.Id = undefined
|
||||
PathGeneratorSingle.RegisterId(binPath)
|
||||
// 容器
|
||||
let container = new Container(binPath)
|
||||
let i = 0
|
||||
for (let b of blocks) {
|
||||
let part = this.toPart(b, binPath, this.offset, i)
|
||||
if (part[0].State.Contour == null)
|
||||
continue
|
||||
part[0].Id = i++
|
||||
// let isin = this.blockInBlock(block, b, polyLine);
|
||||
// if (isin == false) continue;
|
||||
// let part = this.toPart(b, binPath, this.offset, i++);
|
||||
|
||||
// 设置位置
|
||||
part[0].PlacePosition = { x: (b.placeX - block.placeX - posx) * 1e4, y: (b.placeY - block.placeY - posy) * 1e4 }
|
||||
container.PlacedParts.push(part[0])
|
||||
}
|
||||
try {
|
||||
// let f = new NestFiler();
|
||||
// f.Write(container.PlacedParts.length);
|
||||
// for (let part of container.PlacedParts)
|
||||
// f.Write(part.State.Contour.BigIntPoints);
|
||||
// container.WriteFile(f);
|
||||
// console.log(JSON.stringify(f._datas));
|
||||
// FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas));
|
||||
|
||||
let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
|
||||
for (let space of spaces) {
|
||||
space.Points.forEach((t) => { t.x += posx; t.y += posy })
|
||||
}
|
||||
return spaces
|
||||
}
|
||||
catch (err) {
|
||||
console.log(`板${block.blockNo}造型孔分析余料空间失败.`)
|
||||
throw new Error(`板${block.blockNo}造型孔分析余料空间失败.请保存优化联系售后工程师分析`)
|
||||
}
|
||||
// let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths);
|
||||
// for (let space of spaces)
|
||||
// {
|
||||
// space.Points.forEach(t => { t.x += posx; t.y += posy; });
|
||||
// }
|
||||
// return spaces;
|
||||
}
|
||||
|
||||
/** 转配件 */
|
||||
toPart(block: PlaceBlock, binPath: Path, offset: number, id: number): [Part, Vector2] {
|
||||
let w = 10000
|
||||
let binPath2 = new Path([{ x: -w, y: -w }, { x: w, y: -w }, { x: w, y: w }, { x: -w, y: w }])
|
||||
let part = new Part()
|
||||
part.Id = id
|
||||
|
||||
part.UserData = { bno: block.blockNo, area: block.area, isUnRegular: block.isUnRegular, width: block.cutWidth, length: block.cutLength }
|
||||
|
||||
let path: Path
|
||||
let bPoint: Vector2
|
||||
|
||||
let sizeOff = BlockSizePlus.getOffDis(block) // 外扩偏移
|
||||
let hasSizeOff = (sizeOff.left + sizeOff.right + sizeOff.top + sizeOff.bottom) > (block.placeMaterial.diameter * 2 + block.placeMaterial.cutKnifeGap * 2 + 0.01)
|
||||
|
||||
// 矩形且带板外不偏移
|
||||
if (block.isUnRegular == false && hasSizeOff == false) {
|
||||
path = NestCache.CreatePath(block.placeWidth + sizeOff.left + sizeOff.right, block.placeLength + sizeOff.top + sizeOff.bottom, offset)
|
||||
bPoint = path.OrigionMinPoint
|
||||
// 轮廓
|
||||
part.Init2(path, binPath2, [0])
|
||||
}
|
||||
else // 异形
|
||||
{
|
||||
let pts = []
|
||||
|
||||
let cured = BlockPlus.getBorder_moving(block)
|
||||
for (let l of cured) {
|
||||
if (l instanceof Arc2d) {
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
|
||||
}
|
||||
else {
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
let polyLine
|
||||
polyLine = Points2Polyline(pts)
|
||||
polyLine.CloseMark = true
|
||||
|
||||
let cureW = new CurveWrap(polyLine, offset, true)
|
||||
let pts2 = cureW.GetOutsidePoints()
|
||||
arrayRemoveDuplicateBySort(pts, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
path = new Path(pts2)
|
||||
|
||||
let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
|
||||
if (!cicle) {
|
||||
let area = path.BoundingBox.area - path.Area
|
||||
if (area < 15000 && pts.length > 6)
|
||||
path = NestCache.CreatePath(block.placeWidth, block.placeLength, offset)
|
||||
}
|
||||
part.Init2(path, binPath2, [0])
|
||||
|
||||
// 不能放下,那么尝试不简化路径
|
||||
if (!path.IsRect && !cicle && part.RotatedStates.length == 0) {
|
||||
let pts2 = Polyline2Points(polyLine, true, offset)[1]
|
||||
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
|
||||
path = new Path(pts2)
|
||||
part.Init2(path, binPath, [0])
|
||||
}
|
||||
// 轮廓
|
||||
bPoint = path.OrigionMinPoint
|
||||
}
|
||||
|
||||
if (hasSizeOff) {
|
||||
bPoint.x = bPoint.x - sizeOff.left
|
||||
bPoint.y = bPoint.y - sizeOff.bottom
|
||||
}
|
||||
|
||||
return [part, bPoint]
|
||||
}
|
||||
|
||||
/** 余料板空间 */
|
||||
toRemainBlock(path: Path): RemainBlock {
|
||||
let bx = path.OrigionMinPoint.x
|
||||
let by = path.OrigionMinPoint.y
|
||||
let points = path.Points
|
||||
let ptsX = points.map((t) => { return t.x })
|
||||
let ptsY = points.map((t) => { return t.y })
|
||||
|
||||
let x0 = ArrayExt.min(ptsX, t => t)
|
||||
let y0 = ArrayExt.min(ptsY, t => t)
|
||||
let x1 = ArrayExt.max(ptsX, t => t)
|
||||
let y1 = ArrayExt.max(ptsY, t => t)
|
||||
|
||||
let sp = new RemainBlock(x0, y0, x1 - x0, y1 - y0)
|
||||
|
||||
let pts = points.map((t) => { return { x: t.x - x0, y: t.y - y0 } })
|
||||
|
||||
sp.placeX = bx + x0
|
||||
sp.placeY = by + y0
|
||||
|
||||
sp.setPoints(pts)
|
||||
return sp
|
||||
}
|
||||
|
||||
/** 判断板是否在造型洞里头 */
|
||||
blockInModelOfBlock(block: PlaceBlock, b: PlaceBlock, modlPl: Polyline, posX = 0, posY = 0): boolean {
|
||||
let cured = BlockPlus.getBorder(b)
|
||||
let pts = []
|
||||
for (let l of cured) {
|
||||
if (l instanceof Arc2d) {
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
|
||||
}
|
||||
else {
|
||||
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
|
||||
}
|
||||
}
|
||||
let polyLine = Points2Polyline(pts)
|
||||
let cureW2 = new CurveWrap(polyLine, this.offset, false)
|
||||
let pts2 = cureW2.GetInsidePoints2(this.offset)
|
||||
|
||||
// pts2 = pts2.map(t => { return { x: t.x + b.placeX - block.placeX - posX, y: t.y + b.placeY - block.placeY - posY }; });
|
||||
|
||||
for (let t of pts2) {
|
||||
let x = t.x + b.placeX - block.placeX - posX
|
||||
let y = t.y + b.placeY - block.placeY - posY
|
||||
if (modlPl.PtInCurve(new Vector3(x, y, 0)) == false)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** 反向点 */
|
||||
private reversePoint(pts, w): any[] {
|
||||
let newPts = []
|
||||
for (let i = pts.length - 1; i >= 0; i--) {
|
||||
let p = pts[i]
|
||||
let x = p.x
|
||||
let y = p.y
|
||||
let j = i == 0 ? pts.length - 1 : i - 1
|
||||
|
||||
let bul = pts[j].bul
|
||||
newPts.push({ x, y, bul })
|
||||
}
|
||||
return newPts
|
||||
}
|
||||
}
|
85
src/processParser/common/LayoutEngine/Simplify2.ts
Normal file
85
src/processParser/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]
|
||||
}
|
104
src/processParser/common/LayoutEngine/writeP.ts
Normal file
104
src/processParser/common/LayoutEngine/writeP.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import type { PolylineProps } from 'cadapi'
|
||||
import { CADFiler, Polyline } from 'cadapi'
|
||||
import { Vector2 } from 'three'
|
||||
import { copyTextToClipboard } from '../../Base/CAD'
|
||||
|
||||
export class ClipboardTest {
|
||||
public static writePolyline1(pl: Polyline, pts1) {
|
||||
// pl 原图
|
||||
// pts :偏移后的点
|
||||
|
||||
const lined: PolylineProps[] = []
|
||||
const count = pts1.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p0 = pts1[i]
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
|
||||
}
|
||||
const pl1 = new Polyline(lined)
|
||||
pl1.CloseMark = true
|
||||
|
||||
const f = new CADFiler()
|
||||
f.Clear()
|
||||
f.Write(2)
|
||||
f.WriteObject(pl)
|
||||
f.WriteObject(pl1)
|
||||
|
||||
const test = JSON.stringify(f.Data)
|
||||
|
||||
// for (let pl of lined)
|
||||
// f.WriteObject(pl)
|
||||
copyTextToClipboard(test)
|
||||
}
|
||||
|
||||
public static writePolyline2(pl: Polyline, pts1, pts2) {
|
||||
// pl 原图
|
||||
// pts :偏移后的点
|
||||
|
||||
const lined: PolylineProps[] = []
|
||||
const count = pts1.length
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p0 = pts1[i]
|
||||
lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
|
||||
}
|
||||
const pl1 = new Polyline(lined)
|
||||
|
||||
const lined2: PolylineProps[] = []
|
||||
const count2 = pts2.length
|
||||
for (let i = 0; i < count2; i++) {
|
||||
const p0 = pts2[i]
|
||||
lined2.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
|
||||
}
|
||||
const pl2 = new Polyline(lined2)
|
||||
|
||||
const f = new CADFiler()
|
||||
f.Clear()
|
||||
f.Write(3)
|
||||
f.WriteObject(pl)
|
||||
f.WriteObject(pl1)
|
||||
f.WriteObject(pl2)
|
||||
const test = JSON.stringify(f.Data)
|
||||
|
||||
// for (let pl of lined)
|
||||
// f.WriteObject(pl)
|
||||
copyTextToClipboard(test)
|
||||
}
|
||||
|
||||
public static writeClipboard(pts) {
|
||||
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: 0 })
|
||||
}
|
||||
|
||||
const pls = new Polyline(lined)
|
||||
const f = new CADFiler()
|
||||
f.Clear()
|
||||
f.Write(1)
|
||||
f.WriteObject(pls)
|
||||
const test = JSON.stringify(f.Data)
|
||||
|
||||
// for (let pl of lined)
|
||||
// f.WriteObject(pl)
|
||||
copyTextToClipboard(test)
|
||||
}
|
||||
|
||||
public static writePolyLine(pls) {
|
||||
const f = new CADFiler()
|
||||
f.Clear()
|
||||
f.Write(1)
|
||||
f.WriteObject(pls)
|
||||
const test = JSON.stringify(f.Data)
|
||||
copyTextToClipboard(test)
|
||||
}
|
||||
|
||||
public static write2PolyLine(pls, pls2) {
|
||||
const f = new CADFiler()
|
||||
f.Clear()
|
||||
f.Write(2)
|
||||
f.WriteObject(pls)
|
||||
f.WriteObject(pls2)
|
||||
const test = JSON.stringify(f.Data)
|
||||
copyTextToClipboard(test)
|
||||
}
|
||||
}
|
5
src/processParser/common/Point.ts
Normal file
5
src/processParser/common/Point.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Point
|
||||
{
|
||||
x: number
|
||||
y: number
|
||||
}
|
11
src/processParser/common/Random.ts
Normal file
11
src/processParser/common/Random.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FixIndex } from './Util'
|
||||
|
||||
export function RandomIndex(count: number, exclude?: number): number
|
||||
{
|
||||
let index = Math.floor(Math.random() * count)
|
||||
if (index === count)
|
||||
index = 0
|
||||
if (index === exclude)
|
||||
index = FixIndex(index + 1, count)
|
||||
return index
|
||||
}
|
7
src/processParser/common/Sleep.ts
Normal file
7
src/processParser/common/Sleep.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export async function Sleep(time: number)
|
||||
{
|
||||
return new Promise((res) =>
|
||||
{
|
||||
setTimeout(res, time)
|
||||
})
|
||||
}
|
38
src/processParser/common/Util.ts
Normal file
38
src/processParser/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
src/processParser/common/Vector2.ts
Normal file
335
src/processParser/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
|
||||
}
|
||||
}
|
375
src/processParser/common/base/ArrayExt.ts
Normal file
375
src/processParser/common/base/ArrayExt.ts
Normal file
@ -0,0 +1,375 @@
|
||||
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)
|
||||
}
|
||||
}
|
1392
src/processParser/common/base/CAD.ts
Normal file
1392
src/processParser/common/base/CAD.ts
Normal file
File diff suppressed because it is too large
Load Diff
490
src/processParser/common/base/Dxf.ts
Normal file
490
src/processParser/common/base/Dxf.ts
Normal file
@ -0,0 +1,490 @@
|
||||
export class DxfWritor
|
||||
{
|
||||
layers: WeakSet<Layer>
|
||||
activeLayer: Layer
|
||||
lineTypes: WeakSet<LineType>
|
||||
offX = 0
|
||||
offY = 0 // 基点
|
||||
constructor()
|
||||
{
|
||||
this.layers = new WeakSet()
|
||||
this.activeLayer = null
|
||||
this.lineTypes = new WeakSet()
|
||||
|
||||
for (let i = 0; i < DrawingType.LINE_TYPES.length; ++i)
|
||||
{
|
||||
this.addLineType(
|
||||
DrawingType.LINE_TYPES[i].name,
|
||||
DrawingType.LINE_TYPES[i].description,
|
||||
DrawingType.LINE_TYPES[i].elements,
|
||||
)
|
||||
}
|
||||
|
||||
for (let i = 0; i < DrawingType.LAYERS.length; ++i)
|
||||
{
|
||||
this.addLayer(
|
||||
DrawingType.LAYERS[i].name,
|
||||
DrawingType.LAYERS[i].colorNumber,
|
||||
DrawingType.LAYERS[i].lineTypeName,
|
||||
)
|
||||
}
|
||||
|
||||
this.setActiveLayer('0')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} description
|
||||
* @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a
|
||||
*/
|
||||
addLineType(name, description, elements)
|
||||
{
|
||||
this.lineTypes[name] = new LineType(name, description, elements)
|
||||
return this
|
||||
}
|
||||
|
||||
addLayer(name, colorNumber, lineTypeName)
|
||||
{
|
||||
this.layers[name] = new Layer(name, colorNumber, lineTypeName)
|
||||
return this
|
||||
}
|
||||
|
||||
/** 设置当前图层 白=0, 红=1, 黄=2, 绿=3,CYAN=4,BLUE=5,MAGENTA=6 */
|
||||
setActiveLayer(name)
|
||||
{
|
||||
this.activeLayer = this.layers[name]
|
||||
return this
|
||||
}
|
||||
|
||||
setOffset(x1, y1)
|
||||
{
|
||||
this.offX = x1
|
||||
this.offY = y1
|
||||
}
|
||||
|
||||
clear()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
drawLine(x1, y1, x2, y2)
|
||||
{
|
||||
this.activeLayer.addShape(new Line(x1 + this.offX, y1 + this.offY, x2 + this.offX, y2 + this.offY))
|
||||
return this
|
||||
}
|
||||
|
||||
drawRect(x1, y1, w, h)
|
||||
{
|
||||
x1 = x1 + this.offX
|
||||
y1 = y1 + this.offY
|
||||
this.activeLayer.addShape(new Line(x1, y1, x1 + w, y1))
|
||||
this.activeLayer.addShape(new Line(x1 + w, y1, x1 + w, y1 + h))
|
||||
this.activeLayer.addShape(new Line(x1 + w, y1 + h, x1, y1 + h))
|
||||
this.activeLayer.addShape(new Line(x1, y1 + h, x1, y1))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x1 - Center x
|
||||
* @param {number} y1 - Center y
|
||||
* @param {number} r - radius
|
||||
* @param {number} startAngle - degree
|
||||
* @param {number} endAngle - degree
|
||||
*/
|
||||
drawArc(x1, y1, r, startAngle, endAngle)
|
||||
{
|
||||
this.activeLayer.addShape(new Arc(x1 + this.offX, y1 + this.offY, r, startAngle, endAngle))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x1 - Center x
|
||||
* @param {number} y1 - Center y
|
||||
* @param {number} r - radius
|
||||
*/
|
||||
drawCircle(x1, y1, r)
|
||||
{
|
||||
this.activeLayer.addShape(new Circle(x1 + this.offX, y1 + this.offY, r))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x1 - x
|
||||
* @param {number} y1 - y
|
||||
* @param {number} height - Text height
|
||||
* @param {number} rotation - Text rotation
|
||||
* @param {string} value - the string itself
|
||||
*/
|
||||
drawText(x1, y1, height, rotation, value)
|
||||
{
|
||||
this.activeLayer.addShape(new Text(x1 + this.offX, y1 + this.offY, height, rotation, value))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ]
|
||||
*/
|
||||
drawPolyline(points, isClose = false)
|
||||
{
|
||||
for (const p of points)
|
||||
{
|
||||
p.x += this.offX
|
||||
p.y += this.offY
|
||||
}
|
||||
this.activeLayer.addShape(new Polyline(points, isClose))
|
||||
return this
|
||||
}
|
||||
|
||||
_getDxfLtypeTable()
|
||||
{
|
||||
let s = '0\nTABLE\n' // start table
|
||||
s += '2\nLTYPE\n' // name table as LTYPE table
|
||||
|
||||
for (let lineTypeName in this.lineTypes)
|
||||
{
|
||||
s += this.lineTypes[lineTypeName].toDxfString()
|
||||
}
|
||||
|
||||
s += '0\nENDTAB\n' // end table
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
_getDxfLayerTable()
|
||||
{
|
||||
let s = '0\nTABLE\n' // start table
|
||||
s += '2\nLAYER\n' // name table as LAYER table
|
||||
|
||||
for (let layerName in this.layers)
|
||||
{
|
||||
s += this.layers[layerName].toDxfString()
|
||||
}
|
||||
|
||||
s += '0\nENDTAB\n'
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
let s = ''
|
||||
|
||||
// start section
|
||||
s += '0\nSECTION\n'
|
||||
// name section as TABLES section
|
||||
s += '2\nTABLES\n'
|
||||
|
||||
s += this._getDxfLtypeTable()
|
||||
s += this._getDxfLayerTable()
|
||||
|
||||
// end section
|
||||
s += '0\nENDSEC\n'
|
||||
|
||||
// ENTITES section
|
||||
s += '0\nSECTION\n'
|
||||
s += '2\nENTITIES\n'
|
||||
|
||||
for (let layerName in this.layers)
|
||||
{
|
||||
let layer = this.layers[layerName]
|
||||
s += layer.shapesToDxf()
|
||||
// let shapes = layer.getShapes();
|
||||
}
|
||||
|
||||
s += '0\nENDSEC\n'
|
||||
|
||||
// close file
|
||||
s += '0\nEOF'
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
namespace DrawingType
|
||||
{
|
||||
// AutoCAD Color Index (ACI)
|
||||
// http://sub-atomic.com/~moses/acadcolors.html
|
||||
export const ACI = {
|
||||
LAYER: 0,
|
||||
RED: 1,
|
||||
YELLOW: 2,
|
||||
GREEN: 3,
|
||||
CYAN: 4,
|
||||
BLUE: 5,
|
||||
MAGENTA: 6,
|
||||
WHITE: 7,
|
||||
}
|
||||
export const LINE_TYPES = [
|
||||
{ name: 'CONTINUOUS', description: '______', elements: [] },
|
||||
{ name: 'DASHED', description: '_ _ _ ', elements: [5.0, -5.0] },
|
||||
{ name: 'DOTTED', description: '. . . ', elements: [0.0, -5.0] },
|
||||
]
|
||||
export const LAYERS = [
|
||||
{ name: '0', colorNumber: ACI.WHITE, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '1', colorNumber: ACI.RED, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '2', colorNumber: ACI.YELLOW, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '3', colorNumber: ACI.GREEN, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '4', colorNumber: ACI.CYAN, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '5', colorNumber: ACI.BLUE, lineTypeName: 'CONTINUOUS' },
|
||||
{ name: '6', colorNumber: ACI.MAGENTA, lineTypeName: 'CONTINUOUS' },
|
||||
]
|
||||
}
|
||||
|
||||
export class LineType
|
||||
{
|
||||
name: string
|
||||
description: string
|
||||
elements: any[]
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} description
|
||||
* @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a
|
||||
*/
|
||||
constructor(name, description, elements)
|
||||
{
|
||||
this.name = name
|
||||
this.description = description
|
||||
this.elements = elements
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://www.autodesk.com/techpubs/autocad/acadr14/dxf/ltype_al_u05_c.htm
|
||||
*/
|
||||
toDxfString()
|
||||
{
|
||||
let s = '0\nLTYPE\n'
|
||||
s += '72\n65\n'
|
||||
s += '70\n64\n'
|
||||
s += `2\n${this.name}\n`
|
||||
s += `3\n${this.description}\n`
|
||||
s += `73\n${this.elements.length}\n`
|
||||
s += `40\n${this.getElementsSum()}\n`
|
||||
|
||||
for (let i = 0; i < this.elements.length; ++i)
|
||||
{
|
||||
s += `49\n${this.elements[i]}\n`
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
getElementsSum()
|
||||
{
|
||||
let sum = 0
|
||||
for (let i = 0; i < this.elements.length; ++i)
|
||||
{
|
||||
sum += Math.abs(this.elements[i])
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
export class Layer
|
||||
{
|
||||
name: string
|
||||
colorNumber: string
|
||||
lineTypeName: string
|
||||
shapes: any[]
|
||||
constructor(name, colorNumber, lineTypeName)
|
||||
{
|
||||
this.name = name
|
||||
this.colorNumber = colorNumber
|
||||
this.lineTypeName = lineTypeName
|
||||
this.shapes = []
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
let s = '0\nLAYER\n'
|
||||
s += '70\n64\n'
|
||||
s += `2\n${this.name}\n`
|
||||
s += `62\n${this.colorNumber}\n`
|
||||
s += `6\n${this.lineTypeName}\n`
|
||||
return s
|
||||
}
|
||||
|
||||
addShape(shape)
|
||||
{
|
||||
this.shapes.push(shape)
|
||||
shape.layer = this
|
||||
}
|
||||
|
||||
getShapes()
|
||||
{
|
||||
return this.shapes
|
||||
}
|
||||
|
||||
shapesToDxf()
|
||||
{
|
||||
let s = ''
|
||||
for (let i = 0; i < this.shapes.length; ++i)
|
||||
{
|
||||
s += this.shapes[i].toDxfString()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
export class Arc
|
||||
{
|
||||
x1: number
|
||||
y1: number
|
||||
r: number
|
||||
startAngle: number
|
||||
endAngle: number
|
||||
layer: Layer
|
||||
/**
|
||||
* @param {number} x1 - Center x
|
||||
* @param {number} y1 - Center y
|
||||
* @param {number} r - radius
|
||||
* @param {number} startAngle - degree
|
||||
* @param {number} endAngle - degree
|
||||
*/
|
||||
constructor(x1, y1, r, startAngle, endAngle)
|
||||
{
|
||||
this.x1 = x1
|
||||
this.y1 = y1
|
||||
this.r = r
|
||||
this.startAngle = startAngle
|
||||
this.endAngle = endAngle
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
// https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm
|
||||
let s = `0\nARC\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
|
||||
s += `40\n${this.r}\n50\n${this.startAngle}\n51\n${this.endAngle}\n`
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
export class Circle
|
||||
{
|
||||
x1: number
|
||||
y1: number
|
||||
r: number
|
||||
layer: Layer
|
||||
/**
|
||||
* @param {number} x1 - Center x
|
||||
* @param {number} y1 - Center y
|
||||
* @param {number} r - radius
|
||||
*/
|
||||
constructor(x1, y1, r)
|
||||
{
|
||||
this.x1 = x1
|
||||
this.y1 = y1
|
||||
this.r = r
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
// https://www.autodesk.com/techpubs/autocad/acadr14/dxf/circle_al_u05_c.htm
|
||||
let s = `0\nCIRCLE\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
|
||||
s += `40\n${this.r}\n`
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
export class Line
|
||||
{
|
||||
x1: number
|
||||
y1: number
|
||||
x2: number
|
||||
y2: number
|
||||
layer: Layer
|
||||
constructor(x1: number, y1: number, x2: number, y2: number)
|
||||
{
|
||||
this.x1 = x1
|
||||
this.y1 = y1
|
||||
this.x2 = x2
|
||||
this.y2 = y2
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
// https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm
|
||||
let s = `0\nLINE\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
|
||||
s += `11\n${this.x2}\n21\n${this.y2}\n31\n0\n`
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
export class Polyline
|
||||
{
|
||||
points: any[]
|
||||
isClose = false
|
||||
layer: Layer
|
||||
/**
|
||||
* @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ]
|
||||
*/
|
||||
constructor(points, isClose)
|
||||
{
|
||||
this.points = points
|
||||
this.isClose = isClose
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
// https://www.autodesk.com/techpubs/autocad/acad2000/dxf/polyline_dxf_06.htm
|
||||
// https://www.autodesk.com/techpubs/autocad/acad2000/dxf/vertex_dxf_06.htm
|
||||
let s = `0\nPOLYLINE\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `66\n1\n70\n${this.isClose ? 1 : 0}\n`
|
||||
|
||||
for (let i = 0; i < this.points.length; ++i)
|
||||
{
|
||||
s += `0\nVERTEX\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `70\n0\n`
|
||||
s += `10\n${this.points[i].x}\n20\n${this.points[i].y}\n42\n${this.points[i].r}\n`
|
||||
}
|
||||
|
||||
s += `0\nSEQEND\n`
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
export class Text
|
||||
{
|
||||
x1: number
|
||||
y1: number
|
||||
height: number
|
||||
rotation: number
|
||||
value: string
|
||||
layer: Layer
|
||||
/**
|
||||
* @param {number} x1 - x
|
||||
* @param {number} y1 - y
|
||||
* @param {number} height - Text height
|
||||
* @param {number} rotation - Text rotation
|
||||
* @param {string} value - the string itself
|
||||
*/
|
||||
constructor(x1, y1, height, rotation, value)
|
||||
{
|
||||
this.x1 = x1
|
||||
this.y1 = y1
|
||||
this.height = height
|
||||
this.rotation = rotation
|
||||
this.value = value
|
||||
}
|
||||
|
||||
toDxfString()
|
||||
{
|
||||
// https://www.autodesk.com/techpubs/autocad/acadr14/dxf/text_al_u05_c.htm
|
||||
let s = `0\nTEXT\n`
|
||||
s += `8\n${this.layer.name}\n`
|
||||
s += `1\n${this.value}\n`
|
||||
s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
|
||||
s += `40\n${this.height}\n50\n${this.rotation}\n`
|
||||
return s
|
||||
}
|
||||
}
|
122
src/processParser/common/base/File.ts
Normal file
122
src/processParser/common/base/File.ts
Normal file
@ -0,0 +1,122 @@
|
||||
|
||||
|
||||
export class textFile
|
||||
{
|
||||
/**保存单文件 text类型的 */
|
||||
static saveFile(filename: string, content: string)
|
||||
{
|
||||
let blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||
this.saveAs(filename, blob);
|
||||
}
|
||||
|
||||
/**读取文件 选择文件组件,读取文档处理函数 */
|
||||
static readFile(eleFile, fn_doText, fileType = "", fn_msg = null)
|
||||
{
|
||||
let noEleFile = !(eleFile);
|
||||
if (noEleFile)
|
||||
{
|
||||
eleFile = document.createElement('input');
|
||||
eleFile.type = 'file';
|
||||
eleFile.accept = 'text/*';
|
||||
eleFile.hidden = true;
|
||||
document.body.appendChild(eleFile);
|
||||
}
|
||||
|
||||
|
||||
if (fileType && fileType != "") eleFile.accept = fileType;
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (event)
|
||||
{
|
||||
let text = event.target["result"];
|
||||
if (fn_doText) fn_doText(text);
|
||||
};
|
||||
// 选择文件
|
||||
eleFile.onchange = function (event)
|
||||
{
|
||||
let file = event.target.files[0];
|
||||
if (file)
|
||||
{
|
||||
reader.readAsText(file);
|
||||
}
|
||||
if (fn_msg != null)
|
||||
{
|
||||
fn_msg();
|
||||
}
|
||||
};
|
||||
eleFile.click();
|
||||
|
||||
if (noEleFile)
|
||||
{
|
||||
document.body.removeChild(eleFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static getStringFromFile(eleFile, fileType = "", fn_msg = null): string
|
||||
{
|
||||
let noEleFile = !(eleFile);
|
||||
if (noEleFile)
|
||||
{
|
||||
eleFile = document.createElement('input');
|
||||
eleFile.type = 'file';
|
||||
eleFile.accept = 'text/*';
|
||||
eleFile.hidden = true;
|
||||
document.body.appendChild(eleFile);
|
||||
}
|
||||
|
||||
|
||||
if (fileType && fileType != "") eleFile.accept = fileType;
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (event)
|
||||
{
|
||||
let text = event.target["result"];
|
||||
return text;
|
||||
};
|
||||
// 选择文件
|
||||
eleFile.onchange = function (event)
|
||||
{
|
||||
let file = event.target.files[0];
|
||||
if (file)
|
||||
{
|
||||
reader.readAsText(file);
|
||||
}
|
||||
if (fn_msg != null)
|
||||
{
|
||||
fn_msg();
|
||||
}
|
||||
};
|
||||
eleFile.click();
|
||||
|
||||
if (noEleFile)
|
||||
{
|
||||
document.body.removeChild(eleFile);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**保存文件 */
|
||||
static saveAs(fileName: string, data: Blob)
|
||||
{
|
||||
// 创建隐藏的可下载链接
|
||||
let eleLink = document.createElement('a');
|
||||
eleLink.download = fileName;
|
||||
eleLink.style.display = 'none';
|
||||
eleLink.href = URL.createObjectURL(data);
|
||||
// 触发点击
|
||||
document.body.appendChild(eleLink);
|
||||
eleLink.click();
|
||||
// 然后移除
|
||||
document.body.removeChild(eleLink);
|
||||
}
|
||||
|
||||
static readFile2()
|
||||
{
|
||||
let rf = document.createElement('input');
|
||||
rf.type = 'file';
|
||||
rf.accept = 'text/*';
|
||||
rf.hidden = true;
|
||||
|
||||
|
||||
document.body.removeChild(rf);
|
||||
}
|
||||
|
||||
}
|
118
src/processParser/common/base/MaskedNumberRange.ts
Normal file
118
src/processParser/common/base/MaskedNumberRange.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/** 文本表示的数字取值范围,比如 : "2,3,5-8" 表示
|
||||
* () => 大于等于 小于等于
|
||||
* [] => 大于 小于
|
||||
*/
|
||||
export class MaskedNumberRange {
|
||||
maskedNumbers: any[]
|
||||
constructor(strRs: string) {
|
||||
let rs = []
|
||||
let strs = strRs.split(',')
|
||||
for (let str of strs) {
|
||||
if (str.trim() == '')
|
||||
continue
|
||||
let n = Number(str)
|
||||
|
||||
let s_flag = 'in'
|
||||
let e_flag = 'in'
|
||||
if (!Number.isNaN(n)) {
|
||||
rs.push({ s: n, e: n, s_flag, e_flag })
|
||||
}
|
||||
else {
|
||||
let zys = str.split('-')
|
||||
if (zys.length != 2)
|
||||
continue
|
||||
if (zys[0].trim() == '' || zys[1].trim() == '')
|
||||
continue
|
||||
|
||||
|
||||
|
||||
let start :any = zys[0]
|
||||
let end :any = zys[1]
|
||||
if (zys[0].trim().includes('(')) {
|
||||
s_flag = 'notIn'
|
||||
start = start.replaceAll('(', '')
|
||||
} else if (zys[0].trim().includes('[')) {
|
||||
s_flag = 'in'
|
||||
start = start.replaceAll('[', '')
|
||||
}
|
||||
|
||||
if (start == 'x') {
|
||||
s_flag = 'infinite'
|
||||
}else{
|
||||
start = Number(start)
|
||||
}
|
||||
|
||||
if (zys[1].trim().includes(')')) {
|
||||
e_flag = 'notIn'
|
||||
end = end.replaceAll(')', '')
|
||||
} else if (zys[1].trim().includes(']')) {
|
||||
e_flag = 'in'
|
||||
end = end.replaceAll(']', '')
|
||||
}
|
||||
|
||||
if (end == 'x') {
|
||||
e_flag = 'infinite'
|
||||
}else{
|
||||
end = Number(end)
|
||||
}
|
||||
|
||||
|
||||
|
||||
let s = start // Number(zys[0])
|
||||
let e = end //Number(zys[1])
|
||||
|
||||
|
||||
|
||||
rs.push({ s: s, e: e, s_flag, e_flag })
|
||||
}
|
||||
}
|
||||
this.maskedNumbers = rs
|
||||
}
|
||||
|
||||
/** 参数是否在给定范围内 */
|
||||
isInRange(r: number): boolean {
|
||||
let res = false
|
||||
|
||||
for (let se of this.maskedNumbers) {
|
||||
|
||||
if (se.s_flag == 'infinite') {
|
||||
// 无穷 不判断
|
||||
res = true
|
||||
} else if (se.s_flag == 'notIn') {
|
||||
if (r > se.s) {
|
||||
res = true
|
||||
}
|
||||
} else if (se.s_flag == 'in') {
|
||||
if (r >= se.s) {
|
||||
res = true
|
||||
}
|
||||
}
|
||||
|
||||
if (res == true) {
|
||||
let res1 = false
|
||||
|
||||
if (se.e_flag == 'infinite') {
|
||||
res1 = true
|
||||
} else if (se.e_flag == 'notIn') {
|
||||
if (r < se.e) {
|
||||
res1 = true
|
||||
}
|
||||
} else if (se.e_flag == 'in') {
|
||||
if (r <= se.e) {
|
||||
res1 = true
|
||||
}
|
||||
}
|
||||
|
||||
res = res && res1
|
||||
}
|
||||
|
||||
if(res == true){
|
||||
return true
|
||||
}
|
||||
|
||||
// if (r > se.s && r < se.e)
|
||||
// return true
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
128
src/processParser/common/base/MathComm.ts
Normal file
128
src/processParser/common/base/MathComm.ts
Normal file
@ -0,0 +1,128 @@
|
||||
/** 相等, 相差小于diff */
|
||||
export function equal(a: number, b: number, diff = 0.001): boolean
|
||||
{
|
||||
return Math.abs(a - b) < diff
|
||||
}
|
||||
|
||||
/** ab相差dis */
|
||||
export function isDiffer(a: number, b: number, dis: number, diff = 0.001): boolean
|
||||
{
|
||||
let rdis = Math.abs(a - b)
|
||||
return rdis > dis - diff && rdis < dis + diff
|
||||
}
|
||||
|
||||
/** 两点间距离 */
|
||||
export function getDis2(p1, p2): number
|
||||
{
|
||||
let x1 = p1.pointX || p1.X || p1.x || 0
|
||||
let y1 = p1.pointY || p1.Y || p1.y || 0
|
||||
let x2 = p2.pointX || p2.X || p2.x || 0
|
||||
let y2 = p2.pointY || p2.Y || p2.y || 0
|
||||
return getDis(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
/** 两点间距离 */
|
||||
export function getDis(x1: number, y1: number, x2: number, y2: number): number
|
||||
{
|
||||
let xt = x1 - x2
|
||||
let yt = y1 - y2
|
||||
return Math.sqrt(xt * xt + yt * yt)
|
||||
}
|
||||
|
||||
/** 两点间弧度半径 */
|
||||
export function getR(x1: number, y1: number, x2: number, y2: number, bul: number): number
|
||||
{
|
||||
if (bul == 0)
|
||||
return 0
|
||||
let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2
|
||||
return 0.5 * d * (1 + bul ** 2) / bul
|
||||
}
|
||||
|
||||
/** 获取两点间的半径 */
|
||||
export function getR2(p1, p2): number
|
||||
{
|
||||
let bul = p1.Curve || p1.bul || 0
|
||||
if (bul == 0)
|
||||
return 0
|
||||
|
||||
let x1 = p1.pointX || p1.X || p1.x
|
||||
let y1 = p1.pointY || p1.Y || p1.y
|
||||
let x2 = p2.pointX || p2.X || p2.x
|
||||
let y2 = p2.pointY || p2.Y || p2.y
|
||||
|
||||
return getR(x1, y1, x2, y2, bul)
|
||||
}
|
||||
|
||||
/** 截取小数点 */
|
||||
export function round(n: number, bit = 3): number
|
||||
{
|
||||
if (bit < 1)
|
||||
return n
|
||||
let tt = 10 ** bit
|
||||
return Math.round(n * tt) / tt
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得四点p{x,y}形成的矩形{x,y,w,l};
|
||||
* 不是矩形 返回null
|
||||
*/
|
||||
export function getRect(p0, p1, p2, p3): any
|
||||
{
|
||||
if (equal(p0.x, p1.x) == false && equal(p0.y, p1.y) == false)
|
||||
return null
|
||||
|
||||
// 中心点
|
||||
let xc = (p0.x + p1.x + p2.x + p3.x) / 4
|
||||
let yc = (p0.y + p1.y + p2.y + p3.y) / 4
|
||||
|
||||
let dis0 = getDis(p0.x, p0.y, xc, yc)
|
||||
let dis1 = getDis(p1.x, p1.y, xc, yc)
|
||||
let dis2 = getDis(p2.x, p2.y, xc, yc)
|
||||
let dis3 = getDis(p3.x, p3.y, xc, yc)
|
||||
|
||||
if (equal(dis0, dis1) && equal(dis0, dis2) && equal(dis0, dis3))
|
||||
{
|
||||
let x = Math.min(p0.x, p1.x, p2.x, p3.x)
|
||||
let y = Math.min(p0.y, p1.y, p2.y, p3.y)
|
||||
|
||||
let w = Math.max(p0.x, p1.x, p2.x, p3.x) - x
|
||||
let l = Math.max(p0.y, p1.y, p2.y, p3.y) - y
|
||||
if (w < 0.01 || l < 0.01)
|
||||
return null
|
||||
return { x, y, w, l }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 获取两点的直线的一般式方程AX+BY+C=0 ABC */
|
||||
export function getLineABC(x1: number, y1: number, x2: number, y2: number)
|
||||
{
|
||||
let A = y2 - y1
|
||||
let B = x1 - x2
|
||||
let C = x2 * y1 - x1 * y2
|
||||
return { A, B, C }
|
||||
}
|
||||
|
||||
/** 获取点 p(x,y) 到两点的线的距离 */
|
||||
export function getDis_PointLine(x1: number, y1: number, x2: number, y2: number, x: number, y: number)
|
||||
{
|
||||
let abc = getLineABC(x1, y1, x2, y2)
|
||||
let aabb = Math.sqrt(abc.A * abc.A + abc.B * abc.B)
|
||||
if (aabb == 0)
|
||||
return 0
|
||||
|
||||
return Math.abs(abc.A * x + abc.B * y + abc.C) / aabb
|
||||
}
|
||||
|
||||
/** 删除重复点 */
|
||||
export function removeRepeatPoint(pts, fuzz = 0.01)
|
||||
{
|
||||
for (let n = pts.length - 1; n >= 0; n--) {
|
||||
let p = n - 1
|
||||
if (p < 0)
|
||||
p = pts.length - 1
|
||||
|
||||
if (getDis2(pts[p], pts[n]) < fuzz)
|
||||
pts.splice(n, 1)
|
||||
}
|
||||
}
|
169
src/processParser/common/base/PlaceStyleHelper.ts
Normal file
169
src/processParser/common/base/PlaceStyleHelper.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { PlaceStyle, EdgeType} from '../../confClass.js'
|
||||
|
||||
/**
|
||||
* 排版方式,返回排版后的[下右上左]所对应原始边 rt = [,,,] 下0 右1 上2 左3
|
||||
* 比如 rt[0] = 3 表示现在的下边是原始的左边
|
||||
* rt[2] = 0 表示现在的上边是原始的下边
|
||||
* @param placeStyle 放置方式
|
||||
*/
|
||||
export function getOriginalSides(placeStyle: PlaceStyle): number[]
|
||||
{
|
||||
// let orgSides = [0, 1, 2, 3];
|
||||
let orgSides = [EdgeType.BOTTOM, EdgeType.RIGHT, EdgeType.TOP, EdgeType.LEFT]
|
||||
switch (placeStyle)
|
||||
{
|
||||
case PlaceStyle.FRONT: // 正面
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.TOP
|
||||
orgSides[EdgeType.TOP] = EdgeType.LEFT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.BOTTOM
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK: // 正面后转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.TOP
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.LEFT
|
||||
orgSides[EdgeType.TOP] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.LEFT] = EdgeType.RIGHT
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT: // 正面左转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.LEFT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.TOP] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.TOP
|
||||
break
|
||||
case PlaceStyle.BACK: // 反面
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.LEFT
|
||||
orgSides[EdgeType.TOP] = EdgeType.TOP
|
||||
orgSides[EdgeType.LEFT] = EdgeType.RIGHT
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT: // 反面右转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.LEFT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.TOP
|
||||
orgSides[EdgeType.TOP] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.BOTTOM
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK: // 反面后转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.TOP
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.TOP] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.LEFT] = EdgeType.LEFT
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT: // 反面左转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.TOP] = EdgeType.LEFT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.TOP
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return orgSides
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回原始边[下右上左] 放置后位置 rt = [,,,,]
|
||||
* 例如 rt[0] = 3 表示原始下边,现在放置后的新位置是左边
|
||||
* rt[2] = 0 表示原始上边,现在放置后的新位置是下边
|
||||
* @param placeStyle 放置方式
|
||||
*/
|
||||
export function getPlacedSides(placeStyle: PlaceStyle): number[]
|
||||
{
|
||||
// let orgSides = [0, 1, 2, 3];
|
||||
let orgSides = [EdgeType.BOTTOM, EdgeType.RIGHT, EdgeType.TOP, EdgeType.LEFT]
|
||||
switch (placeStyle)
|
||||
{
|
||||
case PlaceStyle.FRONT: // 正面
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.LEFT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.TOP] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.TOP
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK: // 正面后转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.TOP
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.LEFT
|
||||
orgSides[EdgeType.TOP] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.LEFT] = EdgeType.RIGHT
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT: // 正面左转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.TOP
|
||||
orgSides[EdgeType.TOP] = EdgeType.LEFT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.BOTTOM
|
||||
break
|
||||
case PlaceStyle.BACK: // 反面
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.LEFT
|
||||
orgSides[EdgeType.TOP] = EdgeType.TOP
|
||||
orgSides[EdgeType.LEFT] = EdgeType.RIGHT
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT: // 反面右转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.LEFT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.TOP
|
||||
orgSides[EdgeType.TOP] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.BOTTOM
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK: // 反面后转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.TOP
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.TOP] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.LEFT] = EdgeType.LEFT
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT: // 反面左转
|
||||
orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT
|
||||
orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM
|
||||
orgSides[EdgeType.TOP] = EdgeType.LEFT
|
||||
orgSides[EdgeType.LEFT] = EdgeType.TOP
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return orgSides
|
||||
}
|
||||
|
||||
/** 获取排版位置 */
|
||||
export function getPlacePosition(x: number, y: number, width: number, length: number, placeStyle: PlaceStyle): any
|
||||
{
|
||||
let posX = x
|
||||
let posY = y
|
||||
switch (placeStyle)
|
||||
{
|
||||
case PlaceStyle.FRONT: // 正面
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转
|
||||
posX = y
|
||||
posY = width - x
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_BACK: // 正面后转
|
||||
posX = width - x
|
||||
posY = length - y
|
||||
break
|
||||
case PlaceStyle.FRONT_TURN_LEFT: // 正面左转
|
||||
posX = length - y
|
||||
posY = x
|
||||
break
|
||||
case PlaceStyle.BACK: // 反面
|
||||
posX = width - x
|
||||
posY = y
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_RIGHT: // 反面右转
|
||||
posX = y
|
||||
posY = x
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_BACK: // 反面后转
|
||||
posX = x
|
||||
posY = length - y
|
||||
break
|
||||
case PlaceStyle.BACK_TURN_LEFT: // 反面左转
|
||||
posX = length - y
|
||||
posY = width - x
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return { x: posX, y: posY }
|
||||
}
|
152
src/processParser/common/base/StringBase64.ts
Normal file
152
src/processParser/common/base/StringBase64.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import gb2312 from './gb2312.json'
|
||||
|
||||
export class StringBase64 {
|
||||
static ToBase64_gb2312(str: string): string {
|
||||
let bin = this.toGB2312Bytes(str || '')
|
||||
let str64 = this.encode(bin)
|
||||
return str64
|
||||
}
|
||||
|
||||
static _table = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/']
|
||||
|
||||
/** 将二进制转换成base64 */
|
||||
static encode(bin: number[]): string {
|
||||
let codes = []
|
||||
let un = 0
|
||||
un = bin.length % 3
|
||||
if (un == 1)
|
||||
bin.push(0, 0)
|
||||
else if (un == 2)
|
||||
bin.push(0)
|
||||
for (let i = 2; i < bin.length; i += 3) {
|
||||
let c = bin[i - 2] << 16
|
||||
c |= bin[i - 1] << 8
|
||||
c |= bin[i]
|
||||
codes.push(this._table[c >> 18 & 0x3F])
|
||||
codes.push(this._table[c >> 12 & 0x3F])
|
||||
codes.push(this._table[c >> 6 & 0x3F])
|
||||
codes.push(this._table[c & 0x3F])
|
||||
}
|
||||
if (un >= 1) {
|
||||
codes[codes.length - 1] = '='
|
||||
bin.pop()
|
||||
}
|
||||
if (un == 1) {
|
||||
codes[codes.length - 2] = '='
|
||||
bin.pop()
|
||||
}
|
||||
return codes.join('')
|
||||
}
|
||||
|
||||
/** 将utf8 转成gb2312字符串 */
|
||||
static toGb2312String(str1: string): string {
|
||||
let substr = ''
|
||||
let a = ''
|
||||
let b = ''
|
||||
let c = ''
|
||||
let i = -1
|
||||
i = str1.indexOf('%')
|
||||
if (i == -1) {
|
||||
return str1
|
||||
}
|
||||
while (i != -1) {
|
||||
if (i < 3) {
|
||||
substr = substr + str1.substr(0, i - 1)
|
||||
str1 = str1.substr(i + 1, str1.length - i)
|
||||
a = str1.substr(0, 2)
|
||||
str1 = str1.substr(2, str1.length - 2)
|
||||
if ((Number.parseInt(`0x${a}`) & 0x80) == 0) {
|
||||
substr = substr + String.fromCharCode(Number.parseInt(`0x${a}`))
|
||||
}
|
||||
else if ((Number.parseInt(`0x${a}`) & 0xE0) == 0xC0) { // two byte
|
||||
b = str1.substr(1, 2)
|
||||
str1 = str1.substr(3, str1.length - 3)
|
||||
let widechar = (Number.parseInt(`0x${a}`) & 0x1F) << 6
|
||||
widechar = widechar | (Number.parseInt(`0x${b}`) & 0x3F)
|
||||
substr = substr + String.fromCharCode(widechar)
|
||||
}
|
||||
else {
|
||||
b = str1.substr(1, 2)
|
||||
str1 = str1.substr(3, str1.length - 3)
|
||||
c = str1.substr(1, 2)
|
||||
str1 = str1.substr(3, str1.length - 3)
|
||||
let widechar = (Number.parseInt(`0x${a}`) & 0x0F) << 12
|
||||
widechar = widechar | ((Number.parseInt(`0x${b}`) & 0x3F) << 6)
|
||||
widechar = widechar | (Number.parseInt(`0x${c}`) & 0x3F)
|
||||
substr = substr + String.fromCharCode(widechar)
|
||||
}
|
||||
}
|
||||
else {
|
||||
substr = substr + str1.substring(0, i)
|
||||
str1 = str1.substring(i)
|
||||
}
|
||||
i = str1.indexOf('%')
|
||||
}
|
||||
|
||||
return substr + str1
|
||||
}
|
||||
|
||||
private static _unicode2gb
|
||||
static getUnicode2gb() {
|
||||
if (this._unicode2gb == null) {
|
||||
this._unicode2gb = gb2312
|
||||
}
|
||||
return this._unicode2gb
|
||||
}
|
||||
|
||||
static toGB2312Bytes(str: string): number[] {
|
||||
let unicode2gb = this.getUnicode2gb()
|
||||
let res = []; let len = str.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
let code = str.charCodeAt(i)
|
||||
if (code <= 0x007F) {
|
||||
res.push(code)
|
||||
}
|
||||
else {
|
||||
let hex = unicode2gb[`0x${code.toString(16).toUpperCase()}`]
|
||||
let gb = Number(hex)
|
||||
if (Number.isNaN(gb))
|
||||
gb = Number('0xA1F5')
|
||||
|
||||
let arr = []
|
||||
while (gb > 0) {
|
||||
arr.push(gb & 0xFF)
|
||||
gb >>= 8
|
||||
}
|
||||
while (arr.length > 0) res.push(arr.pop())
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
static fromGB2312Bytes(gb2312Bytes: number[]): string {
|
||||
let unicode2gb = this.getUnicode2gb()
|
||||
let res = []
|
||||
// var i = 0
|
||||
for (let i = 0; i < gb2312Bytes.length; i++) {
|
||||
let code = gb2312Bytes[i]
|
||||
if (code < 0xA1 || code > 0xFE || i + 1 == gb2312Bytes.length) {
|
||||
res.push(String.fromCharCode(code))
|
||||
continue
|
||||
}
|
||||
let c2 = gb2312Bytes[i + 1]
|
||||
if (code < 0xA1 || code > 0xFE) {
|
||||
res.push(String.fromCharCode(code))
|
||||
continue
|
||||
}
|
||||
let g = c2 | code << 8
|
||||
|
||||
c2 = Number(unicode2gb[`0x${g.toString(16).toUpperCase()}`])
|
||||
if (typeof c2 == 'undefined') {
|
||||
res.push(String.fromCharCode(code))
|
||||
continue
|
||||
}
|
||||
res.push(String.fromCharCode(c2))
|
||||
i++
|
||||
}
|
||||
return res.join('')
|
||||
}
|
||||
}
|
82
src/processParser/common/base/StringBuidler.ts
Normal file
82
src/processParser/common/base/StringBuidler.ts
Normal file
@ -0,0 +1,82 @@
|
||||
export class StringBuider
|
||||
{
|
||||
strings: string[]
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.strings = []
|
||||
}
|
||||
|
||||
/** 插入 */
|
||||
Append(obj: any)
|
||||
{
|
||||
this.strings.push(obj)
|
||||
}
|
||||
|
||||
/** 插入文本 */
|
||||
AppendString(str: string) { this.strings.push(str) }
|
||||
|
||||
/** 格式化 插入 */
|
||||
AppendFormat(str: string, ...arg: any[]): string
|
||||
{
|
||||
for (let i = 0; i < arg.length; i++)
|
||||
{
|
||||
let parent = `\\{${i}\\}`
|
||||
let reg = new RegExp(parent, 'g')
|
||||
str = str.replace(reg, arg[i])
|
||||
}
|
||||
this.strings.push(str)
|
||||
return str
|
||||
}
|
||||
|
||||
/** 添加一行(回车) */
|
||||
AppendLine(str = '')
|
||||
{
|
||||
if (str != null)
|
||||
{
|
||||
this.AppendString(str)
|
||||
}
|
||||
}
|
||||
|
||||
/** 插入 */
|
||||
Insert(index: number, ...strs)
|
||||
{
|
||||
if (strs.length == 0)
|
||||
return
|
||||
if (index > this.strings.length)
|
||||
index = this.strings.length
|
||||
if (index < 0)
|
||||
index = 0
|
||||
this.strings.splice(index, 0, ...strs)
|
||||
}
|
||||
|
||||
/** 删除片段 */
|
||||
Remove(startIndex: number, length: number): string[]
|
||||
{
|
||||
return this.strings.splice(startIndex, length)
|
||||
}
|
||||
|
||||
/** 所有文本 */
|
||||
ToString(): string
|
||||
{
|
||||
return this.strings.join('\r\n')
|
||||
}
|
||||
|
||||
/** 清空 */
|
||||
Clear() { this.strings = [] }
|
||||
|
||||
/** 容量 */
|
||||
get size() { return this.strings.length }
|
||||
/** 文本数 */
|
||||
get Count() { return this.strings.length }
|
||||
|
||||
GetStringLength(): number
|
||||
{
|
||||
let length = 0
|
||||
for (const str of this.strings)
|
||||
{
|
||||
length += str.length
|
||||
}
|
||||
return length
|
||||
}
|
||||
}
|
126
src/processParser/common/base/StringFormat.ts
Normal file
126
src/processParser/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])
|
||||
}
|
||||
}
|
119
src/processParser/common/base/TextFile.ts
Normal file
119
src/processParser/common/base/TextFile.ts
Normal file
@ -0,0 +1,119 @@
|
||||
export class textFile
|
||||
{
|
||||
/** 保存单文件 text类型的 */
|
||||
static saveFile(filename: string, content: string)
|
||||
{
|
||||
let blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
|
||||
this.saveAs(filename, blob)
|
||||
}
|
||||
|
||||
/** 读取文件 选择文件组件,读取文档处理函数 */
|
||||
static readFile(eleFile, fn_doText, fileType = '', fn_msg = null)
|
||||
{
|
||||
let noEleFile = !(eleFile)
|
||||
if (noEleFile)
|
||||
{
|
||||
eleFile = document.createElement('input')
|
||||
eleFile.type = 'file'
|
||||
eleFile.accept = 'text/*'
|
||||
eleFile.hidden = true
|
||||
document.body.appendChild(eleFile)
|
||||
}
|
||||
|
||||
if (fileType && fileType != '')
|
||||
eleFile.accept = fileType
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (event)
|
||||
{
|
||||
let text = event.target.result
|
||||
if (fn_doText)
|
||||
fn_doText(text)
|
||||
}
|
||||
// 选择文件
|
||||
eleFile.onchange = function (event)
|
||||
{
|
||||
let file = event.target.files[0]
|
||||
if (file)
|
||||
{
|
||||
reader.readAsText(file)
|
||||
}
|
||||
if (fn_msg != null)
|
||||
{
|
||||
fn_msg()
|
||||
}
|
||||
}
|
||||
eleFile.click()
|
||||
|
||||
if (noEleFile)
|
||||
{
|
||||
document.body.removeChild(eleFile)
|
||||
}
|
||||
}
|
||||
|
||||
static getStringFromFile(eleFile, fileType = '', fn_msg = null): string
|
||||
{
|
||||
let noEleFile = !(eleFile)
|
||||
if (noEleFile)
|
||||
{
|
||||
eleFile = document.createElement('input')
|
||||
eleFile.type = 'file'
|
||||
eleFile.accept = 'text/*'
|
||||
eleFile.hidden = true
|
||||
document.body.appendChild(eleFile)
|
||||
}
|
||||
|
||||
if (fileType && fileType != '')
|
||||
eleFile.accept = fileType
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (event)
|
||||
{
|
||||
let text = event.target.result
|
||||
return text
|
||||
}
|
||||
// 选择文件
|
||||
eleFile.onchange = function (event)
|
||||
{
|
||||
let file = event.target.files[0]
|
||||
if (file)
|
||||
{
|
||||
reader.readAsText(file)
|
||||
}
|
||||
if (fn_msg != null)
|
||||
{
|
||||
fn_msg()
|
||||
}
|
||||
}
|
||||
eleFile.click()
|
||||
|
||||
if (noEleFile)
|
||||
{
|
||||
document.body.removeChild(eleFile)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 保存文件 */
|
||||
static saveAs(fileName: string, data: Blob)
|
||||
{
|
||||
// 创建隐藏的可下载链接
|
||||
let eleLink = document.createElement('a')
|
||||
eleLink.download = fileName
|
||||
eleLink.style.display = 'none'
|
||||
eleLink.href = URL.createObjectURL(data)
|
||||
// 触发点击
|
||||
document.body.appendChild(eleLink)
|
||||
eleLink.click()
|
||||
// 然后移除
|
||||
document.body.removeChild(eleLink)
|
||||
}
|
||||
|
||||
static readFile2()
|
||||
{
|
||||
let rf = document.createElement('input')
|
||||
rf.type = 'file'
|
||||
rf.accept = 'text/*'
|
||||
rf.hidden = true
|
||||
|
||||
document.body.removeChild(rf)
|
||||
}
|
||||
}
|
136
src/processParser/common/base/ZipFile.ts
Normal file
136
src/processParser/common/base/ZipFile.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { saveAs } from 'file-saver'
|
||||
import type { ZipProvider } from '../zip.js'
|
||||
import { DefaultZipProvider, FileInfo } from '../zip.js'
|
||||
import { StringBuider } from './StringBuidler.js'
|
||||
import { StringBase64 } from './StringBase64.js'
|
||||
|
||||
export class FileZip
|
||||
{
|
||||
zipName: string
|
||||
// zip对象
|
||||
private zip: ZipProvider = DefaultZipProvider()
|
||||
// 文件数据
|
||||
private stringBuider: StringBuider
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.stringBuider = new StringBuider()
|
||||
this.SetZipFileName('')
|
||||
}
|
||||
|
||||
/** 设置zip文件名 */
|
||||
SetZipFileName(_name: string)
|
||||
{
|
||||
this.zipName = _name
|
||||
}
|
||||
|
||||
getZipFileName()
|
||||
{
|
||||
return this.zipName
|
||||
}
|
||||
|
||||
PushBlobFile(fileName: string, content)
|
||||
{
|
||||
let newFile = new FileInfo(fileName, content, false)
|
||||
newFile.binary = true
|
||||
this.zip.addFile(newFile)
|
||||
}
|
||||
|
||||
// /**添加文件 */
|
||||
// PushFile(fileName: string, fileText: string, isBase64 = false, isgb2312 = false, isUtf8Bom=false)
|
||||
// {
|
||||
// if (isgb2312)
|
||||
// {
|
||||
// isBase64 = true;
|
||||
// fileText = StringBase64.ToBase64_gb2312(fileText);
|
||||
// }
|
||||
// if(isUtf8Bom)
|
||||
// {
|
||||
// fileText = String.fromCharCode(parseInt("0xFEFF")) + fileText;
|
||||
// }
|
||||
// let newFile = new FileInfo(fileName, fileText, isBase64);
|
||||
// this.zip.addFile(newFile);
|
||||
// }
|
||||
|
||||
/** 添加文件 fileName文件名 fileText文件内容 isBase64是否base64 encoding编码格式(gb2312, utf8bom) */
|
||||
PushFile(fileName: string, fileText: string, isBase64 = false, encoding: string = 'gb2312')
|
||||
{
|
||||
if (encoding == 'gb2312')
|
||||
{
|
||||
isBase64 = true
|
||||
fileText = StringBase64.ToBase64_gb2312(fileText)
|
||||
}
|
||||
if (encoding == 'utf8bom')
|
||||
{
|
||||
fileText = String.fromCharCode(Number.parseInt('0xFEFF')) + fileText
|
||||
}
|
||||
let newFile = new FileInfo(fileName, fileText, isBase64)
|
||||
this.zip.addFile(newFile)
|
||||
}
|
||||
|
||||
pushFile_withBom(fileName: string, fileText: string)
|
||||
{
|
||||
this.PushFile(fileName, fileText, false, 'utf8bom')
|
||||
}
|
||||
|
||||
PushFile_GB2312(fileName: string, fileText: string)
|
||||
{
|
||||
this.PushFile(fileName, fileText, false, 'gb2312')
|
||||
}
|
||||
|
||||
/** 新建文件 */
|
||||
NewFile()
|
||||
{
|
||||
this.stringBuider.Clear()
|
||||
}
|
||||
|
||||
/** 推送文本到缓存区 */
|
||||
AppentText(comment: string)
|
||||
{
|
||||
this.stringBuider.Append(comment)
|
||||
}
|
||||
|
||||
// //将缓存区的文本 保存起来
|
||||
// SaveFile(fileName: string, isgb2312: boolean = false, isutf8bom = false)
|
||||
// {
|
||||
// let fileText = this.stringBuider.ToString();
|
||||
// this.stringBuider.Clear();
|
||||
// this.PushFile(fileName, fileText, false, isgb2312, isutf8bom);
|
||||
// }
|
||||
|
||||
// 将缓存区的文本 保存起来
|
||||
SaveFile(fileName: string, encoding: string = 'gb2312')
|
||||
{
|
||||
let fileText = this.stringBuider.ToString()
|
||||
this.stringBuider.Clear()
|
||||
this.PushFile(fileName, fileText, false, encoding)
|
||||
}
|
||||
|
||||
// 下载zip文件
|
||||
async Download(zipName = '')
|
||||
{
|
||||
let content = await this.zip.saveAsync()
|
||||
// textFile.saveAs(FileZip.zipName,content);
|
||||
let name = zipName || this.zipName
|
||||
saveAs(content, name)
|
||||
}
|
||||
|
||||
static WriteFile(fname: string, data: BlobPart)
|
||||
{
|
||||
let blob = new Blob([data], { type: 'octet/stream' })
|
||||
|
||||
let download = document.createElement('a')
|
||||
download.download = fname
|
||||
download.href = window.URL.createObjectURL(blob)
|
||||
download.style.display = 'none'
|
||||
download.onclick = function ()
|
||||
{
|
||||
document.body.removeChild(download)
|
||||
}
|
||||
document.body.appendChild(download)
|
||||
|
||||
download.click()
|
||||
};
|
||||
}
|
||||
|
||||
export { FileInfo }
|
6965
src/processParser/common/base/gb2312.json
Normal file
6965
src/processParser/common/base/gb2312.json
Normal file
File diff suppressed because it is too large
Load Diff
176
src/processParser/common/bmp.ts
Normal file
176
src/processParser/common/bmp.ts
Normal file
@ -0,0 +1,176 @@
|
||||
const decoder = new TextDecoder()
|
||||
export class BmpDecoder {
|
||||
pos: number
|
||||
buffer: ArrayBuffer
|
||||
is_with_alpha: boolean
|
||||
bottom_up: boolean
|
||||
flag: string
|
||||
constructor(buffer: ArrayBuffer, is_with_alpha: boolean) {
|
||||
this.pos = 0
|
||||
this.buffer = buffer
|
||||
this.is_with_alpha = !!is_with_alpha
|
||||
this.bottom_up = true
|
||||
|
||||
this.flag = decoder.decode(this.buffer.slice(0, (this.pos += 2)))
|
||||
|
||||
if (this.flag != 'BM')
|
||||
throw new Error('Invalid BMP File')
|
||||
// this.parseHeader();
|
||||
// this.parseRGBA();
|
||||
}
|
||||
|
||||
parseHeader() {}
|
||||
parseRGBA() {}
|
||||
}
|
||||
|
||||
export function createBmpFile(
|
||||
imgData: ImageData,
|
||||
paramDic: { [key: string]: string } = {},
|
||||
) {
|
||||
let dpi = 300
|
||||
let bitPP = paramDic.bpp ? Number(paramDic.bpp) : 24
|
||||
let printResolution = Math.round(dpi * 39.3701)
|
||||
|
||||
if ([1, 24, 32].includes(bitPP) == false) {
|
||||
throw new Error(`不支持的${bitPP}bpp BMP`)
|
||||
}
|
||||
|
||||
let data = imgData.data
|
||||
|
||||
let width = imgData.width
|
||||
let height = imgData.height
|
||||
let extraBytes = width % 4
|
||||
let rgbSize = data.length
|
||||
switch (bitPP) {
|
||||
case 1:
|
||||
{
|
||||
let rowSize = width / 8 + (width % 8 > 0 ? 1 : 0)
|
||||
extraBytes = rowSize % 4
|
||||
rgbSize = height * (rowSize + extraBytes)
|
||||
}
|
||||
break
|
||||
case 24:
|
||||
{
|
||||
let rowSize = 3 * width
|
||||
extraBytes = rowSize % 4
|
||||
rgbSize = height * (rowSize + extraBytes)
|
||||
}
|
||||
break
|
||||
}
|
||||
let headerInfoSize = 40
|
||||
|
||||
/** ****************header */
|
||||
let flag = 'BM'
|
||||
let reserved = 0
|
||||
let offset = 54
|
||||
let fileSize = rgbSize + offset
|
||||
let planes = 1
|
||||
|
||||
let compress = 0
|
||||
let hr = printResolution
|
||||
let vr = printResolution
|
||||
let colors = 0
|
||||
let importantColors = 0
|
||||
let colorPaletteSize = 0
|
||||
if (bitPP == 1) {
|
||||
colorPaletteSize = 2 * 4
|
||||
}
|
||||
let buffer = new ArrayBuffer(offset + colorPaletteSize + rgbSize)
|
||||
let tempBuffer = new DataView(buffer)
|
||||
let pos = 0
|
||||
|
||||
// BMP Header
|
||||
// ID
|
||||
tempBuffer.setUint8(pos, flag.charCodeAt(0))
|
||||
pos += 1
|
||||
tempBuffer.setUint8(pos, flag.charCodeAt(1))
|
||||
pos += 1
|
||||
// size
|
||||
tempBuffer.setUint32(pos, fileSize, true)
|
||||
pos += 4
|
||||
|
||||
// Application specific
|
||||
tempBuffer.setUint32(pos, reserved, true)
|
||||
pos += 4
|
||||
// Offset
|
||||
tempBuffer.setUint32(pos, offset, true)
|
||||
pos += 4
|
||||
|
||||
// DIB Header
|
||||
// Number of bytes in the DIB header (from this point)
|
||||
tempBuffer.setUint32(pos, headerInfoSize, true)
|
||||
pos += 4
|
||||
|
||||
tempBuffer.setUint32(pos, width, true)
|
||||
pos += 4
|
||||
tempBuffer.setUint32(pos, height, true)
|
||||
pos += 4
|
||||
|
||||
tempBuffer.setUint16(pos, planes, true)
|
||||
pos += 2
|
||||
tempBuffer.setUint16(pos, bitPP, true)
|
||||
pos += 2
|
||||
|
||||
tempBuffer.setUint16(pos, compress, true)
|
||||
pos += 4
|
||||
|
||||
tempBuffer.setUint16(pos, rgbSize, true)
|
||||
pos += 4
|
||||
|
||||
tempBuffer.setUint16(pos, hr, true)
|
||||
pos += 4
|
||||
tempBuffer.setUint16(pos, vr, true)
|
||||
pos += 4
|
||||
|
||||
tempBuffer.setUint16(pos, colors, true)
|
||||
pos += 4
|
||||
tempBuffer.setUint16(pos, importantColors, true)
|
||||
pos += 4
|
||||
|
||||
if (bitPP == 1) {
|
||||
tempBuffer.setUint8(pos++, 0) // b
|
||||
tempBuffer.setUint8(pos++, 0) // g
|
||||
tempBuffer.setUint8(pos++, 0) // r
|
||||
tempBuffer.setUint8(pos++, 0) // a
|
||||
|
||||
tempBuffer.setUint8(pos++, 255) // b
|
||||
tempBuffer.setUint8(pos++, 255) // g
|
||||
tempBuffer.setUint8(pos++, 255) // r
|
||||
tempBuffer.setUint8(pos++, 0) // a
|
||||
}
|
||||
|
||||
let pixData = ''
|
||||
let writeBinData = () => {
|
||||
tempBuffer.setUint8(pos++, Number.parseInt(pixData.padEnd(8, '0'), 2))
|
||||
pixData = ''
|
||||
}
|
||||
for (let y = height - 1; y >= 0; y--) {
|
||||
let rowIndex = y * width * 4
|
||||
for (let x = 0; x < width; x++) {
|
||||
let p = rowIndex + x * 4
|
||||
|
||||
if (bitPP == 1) {
|
||||
if (pixData.length == 8) {
|
||||
writeBinData()
|
||||
}
|
||||
let rgb = [data[p + 0], data[p + 1], data[p + 2]]
|
||||
pixData += rgb.filter((v, i) => v > 170).length == 3 ? '1' : '0' // 170 以下小板缩略图条会断
|
||||
} else {
|
||||
tempBuffer.setUint8(pos++, data[p + 2]) // b
|
||||
tempBuffer.setUint8(pos++, data[p + 1]) // g
|
||||
tempBuffer.setUint8(pos++, data[p + 0]) // r
|
||||
if (bitPP == 32) {
|
||||
tempBuffer.setUint8(pos++, data[p + 3]) // a
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitPP == 1 && pixData.length > 0) {
|
||||
writeBinData()
|
||||
}
|
||||
for (let index = 0; index < extraBytes; index++) {
|
||||
tempBuffer.setUint8(pos++, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return tempBuffer.buffer
|
||||
}
|
399
src/processParser/common/core/Container.ts
Normal file
399
src/processParser/common/core/Container.ts
Normal file
@ -0,0 +1,399 @@
|
||||
import { ClipType, PolyFillType } from 'js-angusj-clipper/web'
|
||||
import type { Paths } from 'js-angusj-clipper/web'
|
||||
import type { SubjectInput } from 'js-angusj-clipper/web/clipFunctions'
|
||||
import { Box2 } from '../Box2'
|
||||
import { clipperCpp } from '../ClipperCpp'
|
||||
import type { CompareVectorFn } from '../ComparePoint'
|
||||
import { ComparePoint } from '../ComparePoint'
|
||||
import { ConvexHull2D } from '../ConvexHull2D'
|
||||
import type { NestFiler } from '../Filer'
|
||||
import type { Point } from '../Point'
|
||||
import { equaln } from '../Util'
|
||||
import { Vector2 } from '../Vector2'
|
||||
import { PlaceType } from '../../vo/enums/PlaceType'
|
||||
import { NestCache } from './NestCache'
|
||||
import type { Part } from './Part'
|
||||
import { PartGroup } from './Part'
|
||||
import type { Path } from './Path'
|
||||
import { Area, TranslatePath } from './Path'
|
||||
|
||||
/**
|
||||
* 当零件尝试放置后容器的状态,用于尝试放置,并且尝试贪心
|
||||
*/
|
||||
interface PartPlacedContainerState
|
||||
{
|
||||
p: Point
|
||||
area?: number
|
||||
hull?: Point[]
|
||||
box?: Box2
|
||||
}
|
||||
|
||||
/**
|
||||
* 排料零件的容器,用来放置零件
|
||||
* 它是一块大板
|
||||
* 也可以是一个网洞
|
||||
* 也可以是余料
|
||||
*/
|
||||
export class Container
|
||||
{
|
||||
ParentId: number = -1// 容器bin来源 -1表示默认的bin,大于等于0表示来自板件网洞
|
||||
ChildrenIndex: number = 0// 网洞的索引位置
|
||||
ParentM: Point// 在旋转网洞时,和非旋转网洞时表现不一样. (网洞相对于父零件的位置,如果是使用旋转网洞则表示网洞的最下角位置)
|
||||
|
||||
PlacedParts: Part[] = []
|
||||
// 放置状态
|
||||
PlacedArea = 0
|
||||
PlacedBox: Box2
|
||||
PlacedHull: Point[]
|
||||
|
||||
StatusKey: string
|
||||
|
||||
CompartePoint: CompareVectorFn
|
||||
constructor(protected BinPath?: Path, private _PlaceType = PlaceType.Box, compare = 'xy')
|
||||
{
|
||||
if (BinPath)
|
||||
this.StatusKey = `${this.BinPath.Id.toString()},${this._PlaceType}`
|
||||
|
||||
this.CompartePoint = ComparePoint(compare)
|
||||
}
|
||||
|
||||
get UseRatio(): number
|
||||
{
|
||||
return this.PlacedBox.area / this.BinPath.Area
|
||||
}
|
||||
|
||||
private _NotPuts: Set<number>[] = []// 已经无法放置的pathId
|
||||
|
||||
private PrePut(part: Part): PartPlacedContainerState
|
||||
{
|
||||
// 无法容纳
|
||||
if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
|
||||
return
|
||||
|
||||
let cacheKey = `${this.StatusKey},${part.State.Contour.Id}`
|
||||
let cacheP = NestCache.PositionCache[cacheKey]
|
||||
// 读取缓存位置
|
||||
if (cacheP)
|
||||
{
|
||||
NestCache.count1++
|
||||
return this.Calc(part, cacheP)
|
||||
}
|
||||
|
||||
let binNfp = this.BinPath.GetInsideNFP(part.State.Contour)
|
||||
if (!binNfp || binNfp.length === 0)
|
||||
return
|
||||
|
||||
// 首个
|
||||
if (this.PlacedParts.length === 0)
|
||||
{
|
||||
let p = this.GetFarLeftP(binNfp)
|
||||
NestCache.PositionCache[cacheKey] = p
|
||||
return this.Calc(part, p)
|
||||
}
|
||||
|
||||
// 快速退出
|
||||
let noSet = NestCache.NoPutCache[this.StatusKey]
|
||||
if (noSet)
|
||||
this._NotPuts.push(noSet)
|
||||
for (let set of this._NotPuts)
|
||||
{
|
||||
if (set.has(part.State.Contour.Id))
|
||||
{
|
||||
NestCache.count2++
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let finalNfp = this.GetNFPs(part, binNfp)
|
||||
if (!finalNfp || finalNfp.length === 0)
|
||||
{
|
||||
if (noSet)
|
||||
noSet.add(part.State.Contour.Id)
|
||||
else
|
||||
{
|
||||
noSet = new Set([part.State.Contour.Id])
|
||||
NestCache.NoPutCache[this.StatusKey] = noSet
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 选择合适的放置点
|
||||
let minArea: number = Number.POSITIVE_INFINITY
|
||||
let translate: Point
|
||||
let bestBox: Box2
|
||||
let bestHull: Point[]
|
||||
|
||||
let tempVec = new Vector2()
|
||||
for (let nfp of finalNfp)
|
||||
{
|
||||
for (let p of nfp)
|
||||
{
|
||||
tempVec.set(p.x * 1e-4, p.y * 1e-4)
|
||||
switch (this._PlaceType)
|
||||
{
|
||||
case PlaceType.Box:
|
||||
{
|
||||
let box2 = part.State.Contour.BoundingBox.clone()
|
||||
box2.translate(tempVec)
|
||||
let rectBox = this.PlacedBox.clone().union(box2)
|
||||
let size = rectBox.getSize()
|
||||
let area = size.x * size.y
|
||||
if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
|
||||
{
|
||||
translate = p
|
||||
minArea = area
|
||||
bestBox = rectBox
|
||||
}
|
||||
break
|
||||
}
|
||||
case PlaceType.Hull:
|
||||
{
|
||||
let pts = TranslatePath(part.State.Contour.Points, tempVec)
|
||||
let nhull = ConvexHull2D([...pts, ...this.PlacedHull])
|
||||
let area = Math.abs(Area(nhull))
|
||||
if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
|
||||
{
|
||||
translate = p
|
||||
minArea = area
|
||||
bestHull = nhull
|
||||
}
|
||||
break
|
||||
}
|
||||
case PlaceType.Gravity:
|
||||
{
|
||||
if (!translate || this.CompartePoint(p, translate) === -1)
|
||||
translate = p
|
||||
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (translate)
|
||||
{
|
||||
NestCache.PositionCache[cacheKey] = translate
|
||||
if (!bestBox)
|
||||
bestBox = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate({ x: translate.x * 1e-4, y: translate.y * 1e-4 }))
|
||||
return { p: translate, area: minArea, box: bestBox, hull: bestHull }
|
||||
}
|
||||
}
|
||||
|
||||
private Calc(part: Part, p: Point): PartPlacedContainerState
|
||||
{
|
||||
let d: PartPlacedContainerState = { p }
|
||||
|
||||
let m: Point = { x: p.x * 1e-4, y: p.y * 1e-4 }
|
||||
|
||||
if (this.PlacedBox)
|
||||
d.box = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate(m))
|
||||
else
|
||||
d.box = part.State.Contour.BoundingBox.clone().translate(m)
|
||||
|
||||
// 凸包
|
||||
if (this._PlaceType === PlaceType.Hull)
|
||||
{
|
||||
if (!this.PlacedHull)
|
||||
{
|
||||
d.hull = TranslatePath(part.State.Contour.Points, m)
|
||||
d.area = part.State.Contour.Area
|
||||
}
|
||||
else
|
||||
{
|
||||
d.hull = ConvexHull2D([...this.PlacedHull, ...TranslatePath(part.State.Contour.Points, m)])
|
||||
d.area = Area(d.hull)
|
||||
}
|
||||
|
||||
d.area = Math.abs(d.area)
|
||||
}
|
||||
else
|
||||
{
|
||||
d.area = d.box.area
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
PutPart(p: Part, greedy = false): boolean
|
||||
{
|
||||
let bestD: PartPlacedContainerState
|
||||
if (greedy)
|
||||
{
|
||||
let bestI: number
|
||||
for (let i = 0; i < p.RotatedStates.length; i++)
|
||||
{
|
||||
p.StateIndex = i
|
||||
let d = this.PrePut(p)
|
||||
if (d && (!bestD || bestD.area > d.area
|
||||
|| (
|
||||
this._PlaceType === PlaceType.Hull
|
||||
&& equaln(bestD.area, d.area, 0.1)
|
||||
&& d.box.area < bestD.box.area
|
||||
)
|
||||
))
|
||||
{
|
||||
bestD = d
|
||||
bestI = i
|
||||
}
|
||||
}
|
||||
if (bestD)
|
||||
p.StateIndex = bestI
|
||||
}
|
||||
else
|
||||
bestD = this.PrePut(p)
|
||||
|
||||
if (bestD)
|
||||
{
|
||||
p.PlacePosition = bestD.p
|
||||
this.PlacedBox = bestD.box ?? this.PlacedBox
|
||||
this.PlacedHull = bestD.hull
|
||||
this.AppendPart(p, false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected GetNFPs(part: Part, binNfp: Point[][]): Paths
|
||||
{
|
||||
// 合并(零件和所有已经放置零件的NFP)
|
||||
let nfps: SubjectInput[] = []
|
||||
for (let placedPart of this.PlacedParts)
|
||||
{
|
||||
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour)
|
||||
if (!nfp)
|
||||
return
|
||||
for (let n of nfp)
|
||||
{
|
||||
let nnfp = TranslatePath(n, placedPart.PlacePosition)
|
||||
nfps.push({ data: nnfp, closed: true })
|
||||
}
|
||||
}
|
||||
// 合并nfp
|
||||
let combinedNfp = clipperCpp.lib.clipToPaths({
|
||||
subjectInputs: nfps,
|
||||
clipType: ClipType.Union,
|
||||
subjectFillType: PolyFillType.NonZero,
|
||||
})
|
||||
|
||||
combinedNfp = clipperCpp.lib.cleanPolygons(combinedNfp, 100)
|
||||
|
||||
if (combinedNfp.length === 0)
|
||||
return
|
||||
|
||||
// 减去nfp
|
||||
let finalNfp = clipperCpp.lib.clipToPaths({
|
||||
subjectInputs: [{ data: binNfp, closed: true }],
|
||||
clipInputs: [{ data: combinedNfp }],
|
||||
clipType: ClipType.Difference,
|
||||
subjectFillType: PolyFillType.NonZero,
|
||||
})
|
||||
finalNfp = clipperCpp.lib.cleanPolygons(finalNfp, 100)
|
||||
return finalNfp
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Part添加的Placed列表
|
||||
* @param part 零件,已经计算了放置状态
|
||||
* @param [calc] 是否计算当前的容器状态?
|
||||
*/
|
||||
private AppendPart(part: Part, calc = true): void
|
||||
{
|
||||
this.StatusKey += `,${part.State.Contour.Id}`
|
||||
this.PlacedParts.push(part)
|
||||
this.PlacedArea += part.State.Contour.Area
|
||||
let m = { x: part.PlacePosition.x * 1e-4, y: part.PlacePosition.y * 1e-4 }
|
||||
if (calc)
|
||||
{
|
||||
// 凸包
|
||||
if (this._PlaceType === PlaceType.Hull)
|
||||
{
|
||||
if (!this.PlacedHull)
|
||||
this.PlacedHull = TranslatePath(part.State.Contour.Points, m)
|
||||
else
|
||||
this.PlacedHull = ConvexHull2D(this.PlacedHull.concat(TranslatePath(part.State.Contour.Points, m)))
|
||||
}
|
||||
}
|
||||
if (calc || this._PlaceType !== PlaceType.Box)
|
||||
{
|
||||
if (this.PlacedBox)
|
||||
this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(m))
|
||||
else
|
||||
this.PlacedBox = part.State.Contour.BoundingBox.clone().translate(m)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到最左边的点
|
||||
*/
|
||||
protected GetFarLeftP(nfp: Point[][]): Point
|
||||
{
|
||||
let leftP: Point
|
||||
for (let path of nfp)
|
||||
{
|
||||
for (let p of path)
|
||||
{
|
||||
if (!leftP || this.CompartePoint(p, leftP) === -1)
|
||||
leftP = p
|
||||
}
|
||||
}
|
||||
return leftP
|
||||
}
|
||||
|
||||
// #region -------------------------File-------------------------
|
||||
// 对象从文件中读取数据,初始化自身
|
||||
ReadFile(file: NestFiler, parts: Part[])
|
||||
{
|
||||
this.ParentId = file.Read()
|
||||
this.ChildrenIndex = file.Read()
|
||||
this.ParentM = file.Read()
|
||||
let count = file.Read() as number
|
||||
this.PlacedParts = []
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let index = file.Read() as number
|
||||
let part = parts[index]
|
||||
part.StateIndex = file.Read()
|
||||
part.PlacePosition = file.Read()
|
||||
this.PlacedParts.push(part)
|
||||
}
|
||||
|
||||
if (!this.PlacedBox)
|
||||
this.PlacedBox = new Box2()
|
||||
this.PlacedBox.min.fromArray(file.Read())
|
||||
this.PlacedBox.max.fromArray(file.Read())
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// 对象将自身数据写入到文件.
|
||||
WriteFile(file: NestFiler)
|
||||
{
|
||||
file.Write(this.ParentId)
|
||||
file.Write(this.ChildrenIndex)
|
||||
file.Write(this.ParentM)
|
||||
|
||||
let parts: Part[] = []
|
||||
for (let p of this.PlacedParts)
|
||||
{
|
||||
if (p instanceof PartGroup)
|
||||
parts.push(...p.Export())
|
||||
else
|
||||
parts.push(p)
|
||||
}
|
||||
|
||||
file.Write(parts.length)
|
||||
for (let p of parts)
|
||||
{
|
||||
file.Write(p.Id)
|
||||
file.Write(p.StateIndex)
|
||||
file.Write(p.PlacePosition)
|
||||
}
|
||||
|
||||
if (!this.PlacedBox)
|
||||
this.PlacedBox = new Box2()
|
||||
file.Write(this.PlacedBox.min.toArray())
|
||||
file.Write(this.PlacedBox.max.toArray())
|
||||
}
|
||||
// #endregion
|
||||
}
|
5
src/processParser/common/core/GNestConfig.ts
Normal file
5
src/processParser/common/core/GNestConfig.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const GNestConfig = {
|
||||
RotateHole: true, // 在旋转零件的时候旋转网洞
|
||||
UsePartGroup: false, // 如果开启这个特性,将在第一次放置零件时,尝试计算完全对插的板件,并且使用它.(基因注册,模范夫妻)
|
||||
UseOffsetSimplify: true,
|
||||
}
|
274
src/processParser/common/core/Individual.ts
Normal file
274
src/processParser/common/core/Individual.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import { arrayLast, arrayRemoveOnce } from '../ArrayExt'
|
||||
import type { NestFiler } from '../Filer'
|
||||
import { RandomIndex } from '../Random'
|
||||
import type { PlaceType } from '../../vo/enums/PlaceType'
|
||||
import { Container } from './Container'
|
||||
import { GNestConfig } from './GNestConfig'
|
||||
import { DefaultComparePointKeys } from './NestDatabase'
|
||||
import type { Part } from './Part'
|
||||
import type { Path } from './Path'
|
||||
|
||||
/**
|
||||
* 个体(表示某一次优化的结果,或者还没开始优化的状态)
|
||||
* 个体是由一堆零件组成的,零件可以有不同的状态。
|
||||
*
|
||||
* 个体单独变异
|
||||
* 可以是某个零件的旋转状态发生改变
|
||||
* 可以是零件的顺序发生改变
|
||||
*
|
||||
* 个体交配(感觉暂时不需要)
|
||||
* 个体和其他个体进行基因交换
|
||||
*/
|
||||
export class Individual {
|
||||
constructor(public Parts?: Part[], public mutationRate = 0.5, private bin?: Path, private OddmentsBins: Path[] = [], private ComparePointKeys: string[] = DefaultComparePointKeys) { }
|
||||
|
||||
Fitness: number // (评估健康) 大板个数-1 + 最后一块板的利用率
|
||||
Containers: Container[] // 大板列表(已排
|
||||
HoleContainers: Container[]// 网洞列表
|
||||
OddmentsContainers: Container[]// 余料列表
|
||||
/**
|
||||
* 评估健康程度
|
||||
*/
|
||||
Evaluate(bestCount: number, greedy = false, type?: PlaceType) {
|
||||
if (GNestConfig.RotateHole)
|
||||
return this.EvaluateOfUseRotateHole(bestCount, greedy, type)
|
||||
|
||||
this.Containers = []
|
||||
this.HoleContainers = []
|
||||
// 初始化余料列表
|
||||
this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
|
||||
this.InitHoleContainers()
|
||||
|
||||
// 余料优先
|
||||
let parts = this.Parts.filter(p => !this.OddmentsContainers.some(odd => odd.PutPart(p)))
|
||||
|
||||
// 网洞优先
|
||||
parts = parts.filter(p => !this.HoleContainers.some(hole => hole.PutPart(p)))
|
||||
|
||||
while (parts.length > 0) {
|
||||
if (this.Containers.length > bestCount) {
|
||||
this.Fitness = Math.ceil(bestCount) + 1
|
||||
return
|
||||
}
|
||||
const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
|
||||
if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
|
||||
{
|
||||
const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
|
||||
|
||||
const PutGroupF = (): boolean => {
|
||||
// 基因注册
|
||||
const nextPartIndex = parts.findIndex(p => p !== maxP)
|
||||
if (nextPartIndex !== -1) {
|
||||
const nextPart = parts[nextPartIndex]
|
||||
const groups = maxP.ParseGroup(nextPart, this.bin)
|
||||
for (const group of groups) {
|
||||
if (container.PutPart(group)) {
|
||||
parts.splice(nextPartIndex, 1)
|
||||
arrayRemoveOnce(parts, maxP)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (GNestConfig.UsePartGroup && PutGroupF()) { }
|
||||
else if (container.PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
|
||||
}
|
||||
parts = parts.filter(p => !container.PutPart(p, greedy))
|
||||
if (!greedy)// 如果没有贪心,排完后在贪心一下
|
||||
parts = parts.filter(p => !container.PutPart(p, true))
|
||||
this.Containers.push(container)
|
||||
}
|
||||
this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers)?.UseRatio ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 在网洞利用时,保持纹路正确
|
||||
*
|
||||
* @param bestCount
|
||||
* @param greedy
|
||||
* @param type
|
||||
*/
|
||||
private EvaluateOfUseRotateHole(bestCount: number, greedy = false, type?: PlaceType) {
|
||||
// 初始化余料列表
|
||||
this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
|
||||
|
||||
this.Containers = []
|
||||
this.HoleContainers = []
|
||||
let parts = this.Parts.concat()
|
||||
while (parts.length > 0) {
|
||||
if (this.Containers.length > bestCount)// 提前结束,已经超过最佳用板量
|
||||
{
|
||||
this.Fitness = Math.ceil(bestCount) + 1
|
||||
return
|
||||
}
|
||||
|
||||
const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
|
||||
const holes: Container[] = []
|
||||
const PutPart = (part: Part, greedy: boolean): boolean => {
|
||||
// 先放置在网洞内
|
||||
const isOk
|
||||
= this.OddmentsContainers.some(oc => oc.PutPart(part, greedy)) // 余料
|
||||
|| holes.some((h) => {
|
||||
const isOk = h.PutPart(part, greedy)
|
||||
return isOk
|
||||
}) // 网洞
|
||||
|| container.PutPart(part, greedy) // 大板
|
||||
|
||||
if (isOk)
|
||||
this.AppendHoles(part, holes)
|
||||
|
||||
return isOk
|
||||
}
|
||||
|
||||
if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
|
||||
{
|
||||
const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
|
||||
|
||||
const PutGroupF = (): boolean => {
|
||||
// 基因注册
|
||||
const nextPartIndex = parts.findIndex(p => p !== maxP)
|
||||
if (nextPartIndex !== -1) {
|
||||
const nextPart = parts[nextPartIndex]
|
||||
const groups = maxP.ParseGroup(nextPart, this.bin)
|
||||
for (const group of groups) {
|
||||
if (PutPart(group, greedy)) {
|
||||
parts.splice(nextPartIndex, 1)
|
||||
arrayRemoveOnce(parts, maxP)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (GNestConfig.UsePartGroup && PutGroupF()) { }
|
||||
else if (PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
|
||||
}
|
||||
|
||||
parts = parts.filter(p => !PutPart(p, greedy))
|
||||
if (!greedy)// 如果没有贪心,排完后在贪心一下
|
||||
parts = parts.filter(p => !PutPart(p, true))
|
||||
|
||||
if (container.PlacedParts.length)
|
||||
this.Containers.push(container)
|
||||
}
|
||||
this.Fitness = this.Containers.length - 1 + (arrayLast(this.Containers)?.UseRatio ?? 0)
|
||||
}
|
||||
|
||||
private AppendHoles(part: Part<any, any>, holes: Container[]) {
|
||||
for (let i = 0; i < part.Holes.length; i++) {
|
||||
const hole = part.Holes[i]
|
||||
const container = new Container(hole.Contour)
|
||||
container.ParentId = part.Id
|
||||
container.ChildrenIndex = i
|
||||
container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
|
||||
this.HoleContainers.push(container)
|
||||
holes.push(container)
|
||||
}
|
||||
}
|
||||
|
||||
private InitHoleContainers() {
|
||||
if (GNestConfig.RotateHole)
|
||||
return
|
||||
for (const part of this.Parts) {
|
||||
for (let i = 0; i < part.Holes.length; i++) {
|
||||
const hole = part.Holes[i]
|
||||
const container = new Container(hole.Contour)
|
||||
container.ParentId = part.Id
|
||||
container.ChildrenIndex = i
|
||||
container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
|
||||
this.HoleContainers.push(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Clone() {
|
||||
const p = new Individual(this.Parts.map(p => p.Clone()), this.mutationRate, this.bin, this.OddmentsBins, this.ComparePointKeys)
|
||||
p.Mutate()
|
||||
return p
|
||||
}
|
||||
|
||||
/**
|
||||
* 突变
|
||||
*/
|
||||
Mutate() {
|
||||
if (this.mutationRate > 0.5) {
|
||||
for (let i = 0; i < this.Parts.length; i++) {
|
||||
const rand = Math.random()
|
||||
if (rand < this.mutationRate * 0.5) {
|
||||
// 和下一个调换顺序
|
||||
const j = i + 1
|
||||
if (j < this.Parts.length)
|
||||
[this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]]
|
||||
}
|
||||
if (rand < this.mutationRate)
|
||||
this.Parts[i].Mutate()
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 洗牌
|
||||
const rand = Math.random()
|
||||
if (rand < 0.5) {
|
||||
const index = RandomIndex(this.Parts.length - 2)
|
||||
const count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2) || 1
|
||||
const parts = this.Parts.splice(index, count)
|
||||
this.Parts.push(...parts)
|
||||
this.Parts[index].Mutate()
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < this.Parts.length; i++) {
|
||||
const rand = Math.random()
|
||||
if (rand < this.mutationRate) {
|
||||
this.Parts[i].Mutate()
|
||||
i += 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.mutationRate > 0.2)
|
||||
this.mutationRate -= 0.004
|
||||
return this
|
||||
}
|
||||
|
||||
// #region -------------------------File-------------------------
|
||||
|
||||
// 对象从文件中读取数据,初始化自身
|
||||
ReadFile(file: NestFiler) {
|
||||
const ver = file.Read()// ver
|
||||
|
||||
this.Fitness = file.Read()
|
||||
let count = file.Read() as number
|
||||
this.Containers = []
|
||||
for (let i = 0; i < count; i++)
|
||||
this.Containers.push(new Container().ReadFile(file, this.Parts))
|
||||
|
||||
count = file.Read()
|
||||
this.HoleContainers = []
|
||||
for (let i = 0; i < count; i++)
|
||||
this.HoleContainers.push(new Container().ReadFile(file, this.Parts))
|
||||
|
||||
count = file.Read()
|
||||
this.OddmentsContainers = []
|
||||
for (let i = 0; i < count; i++)
|
||||
this.OddmentsContainers.push(new Container().ReadFile(file, this.Parts))
|
||||
}
|
||||
|
||||
// 对象将自身数据写入到文件.
|
||||
WriteFile(f: NestFiler) {
|
||||
f.Write(1)// ver
|
||||
f.Write(this.Fitness)
|
||||
f.Write(this.Containers.length)
|
||||
for (const c of this.Containers)
|
||||
c.WriteFile(f)
|
||||
|
||||
f.Write(this.HoleContainers.length)
|
||||
for (const c of this.HoleContainers)
|
||||
c.WriteFile(f)
|
||||
|
||||
f.Write(this.OddmentsContainers.length)
|
||||
for (const c of this.OddmentsContainers)
|
||||
c.WriteFile(f)
|
||||
}
|
||||
// #endregion
|
||||
}
|
37
src/processParser/common/core/NestCache.ts
Normal file
37
src/processParser/common/core/NestCache.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { Point } from '../Point'
|
||||
import { Path } from './Path'
|
||||
|
||||
export class NestCache
|
||||
{
|
||||
static count1 = 0
|
||||
static count2 = 0// noset
|
||||
|
||||
static PositionCache: { [key: string]: Point } = {}
|
||||
static NoPutCache: { [key: string]: Set<number> } = {}
|
||||
private static CacheRect = new Map<string, Path>()
|
||||
|
||||
/**
|
||||
* 用于创建原点在0点的矩形路径
|
||||
*/
|
||||
static CreatePath(x: number, y: number, knifRadius = 3.5): Path
|
||||
{
|
||||
let minX = -knifRadius
|
||||
let maxX = x + knifRadius
|
||||
let minY = -knifRadius
|
||||
let maxY = y + knifRadius
|
||||
return new Path([
|
||||
{ x: minX, y: minY },
|
||||
{ x: maxX, y: minY },
|
||||
{ x: maxX, y: maxY },
|
||||
{ x: minX, y: maxY },
|
||||
])
|
||||
}
|
||||
|
||||
static Clear()
|
||||
{
|
||||
this.count1 = 0
|
||||
this.count2 = 0
|
||||
this.CacheRect.clear()
|
||||
this.PositionCache = {}
|
||||
}
|
||||
}
|
82
src/processParser/common/core/NestDatabase.ts
Normal file
82
src/processParser/common/core/NestDatabase.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { NestFiler } from '../Filer'
|
||||
import { Part } from './Part'
|
||||
import { Path } from './Path'
|
||||
import { PathGeneratorSingle } from './PathGenerator'
|
||||
|
||||
export const DefaultComparePointKeys = ['xy', 'yx']
|
||||
|
||||
/**
|
||||
* 排料数据库,用这个类来序列化需要排料的数据
|
||||
* 用于在Work间传输
|
||||
*/
|
||||
export class NestDatabase {
|
||||
Bin: Path // 默认的容器
|
||||
OddmentsBins: Path[]// 余料容器列表
|
||||
Paths: Path[] // 所有的Path都在这里
|
||||
Parts: Part[] // 所有的零件
|
||||
ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式
|
||||
|
||||
// #region -------------------------File-------------------------
|
||||
// 对象从文件中读取数据,初始化自身
|
||||
ReadFile(file: NestFiler) {
|
||||
const ver = file.Read()
|
||||
let count = file.Read() as number
|
||||
this.Paths = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
const path = new Path()
|
||||
path.ReadFile(file)
|
||||
this.Paths.push(path)
|
||||
}
|
||||
this.Bin = this.Paths[file.Read()]
|
||||
PathGeneratorSingle.paths = this.Paths
|
||||
count = file.Read()
|
||||
this.Parts = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
const part = new Part()
|
||||
part.ReadFile(file)
|
||||
this.Parts.push(part)
|
||||
}
|
||||
|
||||
count = file.Read()
|
||||
this.OddmentsBins = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
const path = new Path()
|
||||
path.ReadFile(file)
|
||||
this.OddmentsBins.push(path)
|
||||
}
|
||||
|
||||
if (ver > 1)
|
||||
this.ComparePointKeys = file.Read()
|
||||
return this
|
||||
}
|
||||
|
||||
// 对象将自身数据写入到文件.
|
||||
WriteFile(file: NestFiler) {
|
||||
file.Write(2)
|
||||
file.Write(this.Paths.length)
|
||||
for (const path of this.Paths)
|
||||
path.WriteFile(file)
|
||||
|
||||
file.Write(this.Bin.Id)
|
||||
file.Write(this.Parts.length)
|
||||
for (const part of this.Parts)
|
||||
part.WriteFile(file)
|
||||
|
||||
if (!this.OddmentsBins)
|
||||
this.OddmentsBins = []
|
||||
file.Write(this.OddmentsBins.length)
|
||||
for (const path of this.OddmentsBins)
|
||||
path.WriteFile(file)
|
||||
|
||||
file.Write(this.ComparePointKeys)
|
||||
|
||||
return this
|
||||
}
|
||||
// #endregion
|
||||
|
||||
get File() {
|
||||
const f = new NestFiler()
|
||||
this.WriteFile(f)
|
||||
return f
|
||||
}
|
||||
}
|
163
src/processParser/common/core/OptimizeMachine.ts
Normal file
163
src/processParser/common/core/OptimizeMachine.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { arrayRemoveIf } from '../ArrayExt'
|
||||
import { clipperCpp } from '../ClipperCpp'
|
||||
import { Sleep } from '../Sleep'
|
||||
import { Individual } from './Individual'
|
||||
import { NestCache } from './NestCache'
|
||||
import { DefaultComparePointKeys } from './NestDatabase'
|
||||
import type { Part } from './Part'
|
||||
import type { Path } from './Path'
|
||||
|
||||
/**
|
||||
* 优化器
|
||||
* 配置优化器
|
||||
* 放入零件
|
||||
* 按下启动
|
||||
* 按下暂停
|
||||
* 清理机台
|
||||
*/
|
||||
export class OptimizeMachine {
|
||||
// 配置
|
||||
Config: {
|
||||
PopulationCount: number// 种群个数
|
||||
}
|
||||
|
||||
Bin: Path // 默认的容器
|
||||
OddmentsBins: Path[]// 余料容器列表
|
||||
Parts: Part[] // 所有的零件
|
||||
ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式
|
||||
|
||||
// //计算重复的零件 TODO:需要对相同的零件提取出来
|
||||
// PartCount: number[] = [];
|
||||
|
||||
private _IsSuspend = false
|
||||
|
||||
protected _Individuals: Individual[]// 个体列表
|
||||
|
||||
constructor() {
|
||||
this.Config = { PopulationCount: 50 }
|
||||
}
|
||||
|
||||
// 放入零件
|
||||
PutParts(parts: Part[]) {
|
||||
if (globalThis.document)
|
||||
parts = parts.slice()
|
||||
arrayRemoveIf(parts, p => p.RotatedStates.length === 0)
|
||||
this.Parts = parts
|
||||
|
||||
// //计算重复的零件(暂时不用)
|
||||
// for (let part of parts)
|
||||
// {
|
||||
// let count = this.PartCount[part.Id];
|
||||
// this.PartCount[part.Id] = count === undefined ? 1 : (count + 1);
|
||||
// }
|
||||
}
|
||||
|
||||
callBack: (i: Individual) => Promise<void>
|
||||
|
||||
// 启动
|
||||
async Start() {
|
||||
if (this.Parts.length === 0)
|
||||
return
|
||||
console.log(this.Parts.length)
|
||||
this._IsSuspend = false
|
||||
NestCache.Clear()
|
||||
this.Parts.sort((p1, p2) => p2.State.Contour.Area - p1.State.Contour.Area)
|
||||
this._Individuals = [new Individual(this.Parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys)]
|
||||
for (let i = 1; i < this.Config.PopulationCount; i++) {
|
||||
const parts = this.Parts.map(p => p.Clone())
|
||||
if (i < 3) {
|
||||
for (let i = parts.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[parts[i], parts[j]] = [parts[j], parts[i]]
|
||||
parts[i].Mutate()
|
||||
}
|
||||
}
|
||||
this._Individuals.push(new Individual(parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys))
|
||||
}
|
||||
// 2.执行
|
||||
await this.Run()
|
||||
}
|
||||
|
||||
calcCount = 0
|
||||
best = Number.POSITIVE_INFINITY
|
||||
bestP: Individual
|
||||
bestCount = 0
|
||||
private async Run() {
|
||||
console.time('1')
|
||||
if (this.Parts.length === 0)
|
||||
return
|
||||
// 开始自然选择
|
||||
while (!this._IsSuspend) // 实验停止信号
|
||||
{
|
||||
const goBack = this.calcCount - this.bestCount > 8000
|
||||
// 1.适应环境(放置零件)
|
||||
for (let i = 0; i < this._Individuals.length; i++) {
|
||||
if (globalThis.document || !clipperCpp.lib)
|
||||
await Sleep(0)
|
||||
const p = this._Individuals[i]
|
||||
if (this.calcCount < 1000 || this.calcCount % 1000 === 0)
|
||||
p.Evaluate(this.best, true, i % 3)
|
||||
else
|
||||
p.Evaluate(this.best)
|
||||
|
||||
if (!this.bestP || p.Fitness < this.bestP.Fitness) {
|
||||
this.bestP = p
|
||||
this.best = p.Fitness
|
||||
await this.callBack(p)
|
||||
}
|
||||
if (goBack)
|
||||
p.mutationRate = 0.5
|
||||
}
|
||||
|
||||
this.calcCount += this._Individuals.length
|
||||
if (this.calcCount % 100 === 0) {
|
||||
await Sleep(0)
|
||||
}
|
||||
|
||||
this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness)
|
||||
const bestP = this._Individuals[0]
|
||||
// //回调最好的
|
||||
// if (bestP.Fitness < this.best)
|
||||
// {
|
||||
// this.best = bestP.Fitness;
|
||||
// this.bestP = bestP;
|
||||
// // console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2);
|
||||
// if (this.callBack)
|
||||
// await this.callBack(bestP);
|
||||
// }
|
||||
|
||||
// 自然选择
|
||||
this._Individuals.splice(-10)// 杀死它
|
||||
for (let i = 0; i < 4; i++)
|
||||
this._Individuals.push(bestP.Clone())
|
||||
this._Individuals.push(this.bestP.Clone())
|
||||
for (let i = 0; i < 3; i++)
|
||||
this._Individuals.push(this._Individuals[1].Clone())
|
||||
for (let i = 0; i < 2; i++)
|
||||
this._Individuals.push(this._Individuals[2].Clone())
|
||||
// 全部突变
|
||||
for (const p of this._Individuals) {
|
||||
if (p.Fitness !== undefined)
|
||||
p.Mutate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停
|
||||
Suspend() {
|
||||
console.timeEnd('1')
|
||||
console.log(this.best, this.calcCount, NestCache.count1)
|
||||
this._IsSuspend = true
|
||||
console.log('暂停')
|
||||
}
|
||||
|
||||
// 继续
|
||||
Continue() {
|
||||
this._IsSuspend = false
|
||||
this.Run()
|
||||
}
|
||||
|
||||
// 清理机台
|
||||
Clear() {
|
||||
}
|
||||
}
|
238
src/processParser/common/core/ParseOddments.ts
Normal file
238
src/processParser/common/core/ParseOddments.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import { ClipType, EndType, JoinType, PolyFillType } from 'js-angusj-clipper/web'
|
||||
import type { ClipInput } from 'js-angusj-clipper/web'
|
||||
import { Box2 } from '..//Box2'
|
||||
import { clipperCpp } from '../ClipperCpp'
|
||||
import type { Container } from './Container'
|
||||
import { NestCache } from './NestCache'
|
||||
import { Path, PathScale, TranslatePath, TranslatePath_Self } from './Path'
|
||||
|
||||
const SquarePath = NestCache.CreatePath(60, 60, 0)
|
||||
const CanPutPaths = [
|
||||
NestCache.CreatePath(200, 200, 0),
|
||||
NestCache.CreatePath(600, 100, 0),
|
||||
NestCache.CreatePath(100, 600, 0),
|
||||
]
|
||||
|
||||
/**
|
||||
* 分析排料结果的余料
|
||||
* @param container 排料结果的容器
|
||||
* @param binPath 容器的bin
|
||||
* @param [knifeRadius] 刀半径(以便我们再次偏移)
|
||||
* @param squarePath 使用一个正方形路径来简化余料轮廓
|
||||
* @param canPutPaths 使用可以放置的路径列表来测试余料是否可用,如果可用,则保留
|
||||
* @returns Path[] 轮廓的位置存储在OrigionMinPoint中
|
||||
*/
|
||||
export function ParseOddments(container: Container, binPath: Path, knifeRadius: number = 3.5, squarePath: Path = SquarePath, canPutPaths: Path[] = CanPutPaths): Path[]
|
||||
{
|
||||
// 构建轮廓数据
|
||||
let partPaths: ClipInput[] = container.PlacedParts.map((part) =>
|
||||
{
|
||||
// 直接在这里偏移,而不缓存,应该没有性能问题
|
||||
let newPts = clipperCpp.lib.offsetToPaths({
|
||||
delta: knifeRadius * 1e4,
|
||||
offsetInputs: [{ data: part.State.Contour.BigIntPoints, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
|
||||
let path = TranslatePath(newPts, { x: part.PlacePosition.x - 5e3, y: part.PlacePosition.y - 5e3 })// 因为移动了0.5,0.5,所以这里也要移动0.5
|
||||
return { data: path }
|
||||
})
|
||||
// console.log('构建轮廓数据', partPaths)
|
||||
// 所有的余料(使用布尔差集)
|
||||
let oddmentsPolygon = clipperCpp.lib.clipToPolyTree({
|
||||
subjectInputs: [{ data: binPath.BigIntPoints, closed: true }],
|
||||
clipInputs: partPaths,
|
||||
clipType: ClipType.Difference,
|
||||
subjectFillType: PolyFillType.NonZero,
|
||||
})
|
||||
|
||||
// 现在我们用树状结构,应该不会自交了?(文档写了,返回的结果不可能重叠或者自交)
|
||||
// 简化结果,避免自交
|
||||
// oddmentsPolygon = clipperCpp.lib.simplifyPolygons(oddmentsPolygon);
|
||||
|
||||
function CreatePolygon(minx: number, miny: number, maxx: number, maxy: number)
|
||||
{
|
||||
return [
|
||||
{ x: minx, y: miny },
|
||||
{ x: maxx, y: miny },
|
||||
{ x: maxx, y: maxy },
|
||||
{ x: minx, y: maxy },
|
||||
]
|
||||
}
|
||||
|
||||
let clipedPaths: Path[] = []// 已经减去网洞投影的余料轮廓列表
|
||||
|
||||
// 由于手动排版可能造成余料网洞,我们将网洞的盒子投影,然后裁剪余料,避免余料有网洞
|
||||
for (let node of oddmentsPolygon.childs)
|
||||
{
|
||||
let nodePolygon = node.contour
|
||||
// 减去网洞
|
||||
if (node.childs.length)
|
||||
{
|
||||
let box = new Box2().setFromPoints(nodePolygon)
|
||||
|
||||
let childBoxPolygon = node.childs.map((cnode) =>
|
||||
{
|
||||
let cbox = new Box2().setFromPoints(cnode.contour)
|
||||
let type = 0// 0左1右2上3下
|
||||
let minDist = Number.POSITIVE_INFINITY
|
||||
|
||||
let letftDist = cbox.min.x - box.min.x
|
||||
let rightDist = box.max.x - cbox.max.x
|
||||
let topDist = box.max.y - cbox.max.y
|
||||
let downDist = cbox.min.y - box.min.y
|
||||
|
||||
if (rightDist < letftDist)
|
||||
{
|
||||
type = 1
|
||||
minDist = rightDist
|
||||
}
|
||||
|
||||
if (topDist < minDist)
|
||||
{
|
||||
type = 2
|
||||
minDist = topDist
|
||||
}
|
||||
|
||||
if (downDist < minDist)
|
||||
type = 3
|
||||
|
||||
if (type === 0)
|
||||
return CreatePolygon(box.min.x, cbox.min.y, cbox.max.x, cbox.max.y)
|
||||
if (type === 1)
|
||||
return CreatePolygon(cbox.min.x, cbox.min.y, box.max.x, cbox.max.y)
|
||||
if (type === 2)
|
||||
return CreatePolygon(cbox.min.x, cbox.min.y, cbox.max.x, box.max.y)
|
||||
if (type === 3)
|
||||
return CreatePolygon(cbox.min.x, box.min.y, cbox.max.x, cbox.max.y)
|
||||
})
|
||||
|
||||
let splits = clipperCpp.lib.clipToPaths({
|
||||
subjectInputs: [{ data: nodePolygon, closed: true }],
|
||||
clipInputs: childBoxPolygon.map((polygon) => { return { data: polygon } }),
|
||||
clipType: ClipType.Difference,
|
||||
subjectFillType: PolyFillType.NonZero,
|
||||
})
|
||||
|
||||
for (let p of splits)
|
||||
clipedPaths.push(new Path(PathScale(p, 1e-4)))
|
||||
}
|
||||
else
|
||||
clipedPaths.push(new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } })))
|
||||
}
|
||||
|
||||
let OddmentsPaths: Path[] = []
|
||||
for (let polygonPath of clipedPaths)
|
||||
{
|
||||
// 先获取内部的nfp
|
||||
let insideNFPS = polygonPath.GetInsideNFP(squarePath)
|
||||
|
||||
if (!insideNFPS)
|
||||
continue
|
||||
|
||||
let beferPolygons: ClipInput[] = []
|
||||
|
||||
for (let nfp of insideNFPS)
|
||||
{
|
||||
let nfpPath = new Path(PathScale(nfp, 1e-4))
|
||||
// 通过内部nfp还原实际轮廓
|
||||
let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, squarePath.BigIntPoints, true)
|
||||
sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons)
|
||||
|
||||
for (let poly of sumPolygons)
|
||||
{
|
||||
if (clipperCpp.lib.area(poly) < 0)
|
||||
continue// 移除内部的,无意义的
|
||||
|
||||
let tempPath = new Path(poly.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))// 这里new一个新的,下面就复用这个
|
||||
if (canPutPaths.some(p => tempPath.GetInsideNFP(p)?.length))// 能塞的下指定的轮廓才会被留下
|
||||
{
|
||||
if (beferPolygons.length)
|
||||
{
|
||||
// 移动到实际位置
|
||||
TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4)
|
||||
|
||||
// 在这里裁剪之前的余料轮廓
|
||||
let tree = clipperCpp.lib.clipToPolyTree({
|
||||
subjectInputs: [{ data: poly, closed: true }],
|
||||
clipInputs: beferPolygons,
|
||||
clipType: ClipType.Difference,
|
||||
subjectFillType: PolyFillType.NonZero,
|
||||
})
|
||||
|
||||
for (let node of tree.childs)
|
||||
{
|
||||
if (node.childs.length)
|
||||
continue
|
||||
|
||||
tempPath = new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))
|
||||
|
||||
// 继续简化
|
||||
tempPath = SimplifyPathOfSqPath(tempPath, squarePath)
|
||||
|
||||
if (!tempPath)
|
||||
continue
|
||||
|
||||
OddmentsPaths.push(tempPath)
|
||||
|
||||
// 偏移2把刀
|
||||
let offsetedPolygon = clipperCpp.lib.offsetToPaths({
|
||||
delta: knifeRadius * 2e4,
|
||||
offsetInputs: [{ data: node.contour, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 设置轮廓的位置
|
||||
tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x
|
||||
tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y
|
||||
OddmentsPaths.push(tempPath)
|
||||
|
||||
// 将余料轮廓加入到裁剪轮廓中,用于裁剪后续的余料
|
||||
if (insideNFPS.length)
|
||||
{
|
||||
// 移动到实际位置
|
||||
TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4)
|
||||
// 偏移2把刀
|
||||
let offsetedPolygon = clipperCpp.lib.offsetToPaths({
|
||||
delta: knifeRadius * 2e4,
|
||||
offsetInputs: [{ data: poly, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
|
||||
})[0]
|
||||
beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log('ParseOddments end', OddmentsPaths)
|
||||
return OddmentsPaths
|
||||
}
|
||||
|
||||
// 使用矩形轮廓来简化余料轮廓(通常一进一出)
|
||||
function SimplifyPathOfSqPath(polygonPath: Path, sqPath: Path): Path | undefined
|
||||
{
|
||||
// 先获取内部的nfp
|
||||
let insideNFPS = polygonPath.GetInsideNFP(sqPath)
|
||||
if (insideNFPS.length > 0)// 目前一般只有1个,不知道会不会有多个
|
||||
{
|
||||
let nfp = insideNFPS[0]
|
||||
let nfpPath = new Path(PathScale(nfp, 1e-4))
|
||||
// 通过内部nfp还原实际轮廓
|
||||
let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, sqPath.BigIntPoints, true)
|
||||
sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons)
|
||||
|
||||
for (let poly of sumPolygons)// 通常是一个内部的+一个外部的
|
||||
{
|
||||
if (clipperCpp.lib.area(poly) < 0)
|
||||
continue// 移除内部的,无意义的
|
||||
|
||||
let tempPath = new Path(PathScale(poly, 1e-4))
|
||||
|
||||
tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x
|
||||
tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y
|
||||
return tempPath
|
||||
}
|
||||
}
|
||||
}
|
394
src/processParser/common/core/Part.ts
Normal file
394
src/processParser/common/core/Part.ts
Normal file
@ -0,0 +1,394 @@
|
||||
import type { Box2 } from '../Box2'
|
||||
import type { NestFiler } from '../Filer'
|
||||
import type { Point } from '../Point'
|
||||
import { RandomIndex } from '../Random'
|
||||
import { FixIndex } from '../Util'
|
||||
import { Vector2 } from '../Vector2'
|
||||
import { GNestConfig } from './GNestConfig'
|
||||
import { NestCache } from './NestCache'
|
||||
import { PartState } from './PartState'
|
||||
import { Path } from './Path'
|
||||
import { PathGeneratorSingle } from './PathGenerator'
|
||||
|
||||
const EmptyArray = []
|
||||
|
||||
/**
|
||||
* 零件类
|
||||
* 零件类可以绑定数据,也存在位置和旋转状态的信息
|
||||
*
|
||||
* 初始化零件:
|
||||
* 传入零件的轮廓,刀半径,包围容器(或者为空?)
|
||||
* 初始化用于放置的轮廓。将轮廓首点移动到0点,记录移动的点P。
|
||||
*
|
||||
* 零件放置位置:
|
||||
* 表示零件轮廓首点的位置。
|
||||
*
|
||||
* 零件的旋转:
|
||||
* 表示零件轮廓按照首点(0)旋转。
|
||||
*
|
||||
* 还原零件的放置状态:
|
||||
* 同样将零件移动到0点
|
||||
* 同样将零件旋转
|
||||
* 同样将零件移动到指定的位置
|
||||
* 零件可能处于容器中,变换到容器坐标系
|
||||
*
|
||||
*/
|
||||
export class Part<T = any, Matrix = any>
|
||||
{
|
||||
Id: number// 用于确定Part的唯一性,并且有助于在其他Work中还原
|
||||
private _Holes: PartState[] = []
|
||||
_RotateHoles: PartState[][] = []
|
||||
// 零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态
|
||||
StateIndex = 0 // 旋转状态
|
||||
RotatedStates: PartState[] = []// 可能的旋转状态列表
|
||||
PlacePosition: Point // 放置位置(相对于容器的位置)
|
||||
|
||||
HolePosition: Point//
|
||||
|
||||
// #region 临时数据(不会被序列化到优化线程)
|
||||
UserData: T// 只用于还原零件的显示状态(或者关联到实际数据)
|
||||
Parent: Part// 如果这个零件放置在了网洞中,这个Parent表示这个网洞所属的零件,我们可以得到这个零件的放置信息,并且可以从Container中的ParentM得到网洞相对于零件的位置
|
||||
PlaceCS: Matrix// 放置矩阵 Matrix4
|
||||
PlaceIndex: number// 放置的大板索引
|
||||
// #endregion
|
||||
|
||||
GroupMap: { [key: number]: Part[] } = {}
|
||||
get State(): PartState // 零件当前的状态
|
||||
{
|
||||
return this.RotatedStates[this.StateIndex]
|
||||
}
|
||||
|
||||
get Holes(): PartState[]
|
||||
{
|
||||
if (GNestConfig.RotateHole)
|
||||
return this._RotateHoles[this.StateIndex] || EmptyArray
|
||||
return this._Holes
|
||||
}
|
||||
|
||||
// 初始化零件的各个状态,按360度旋转个数
|
||||
Init(path: Path, bin: Path, rotateCount = 4): this
|
||||
{
|
||||
let rotations: number[] = []
|
||||
let a = 2 * Math.PI / rotateCount
|
||||
for (let i = 0; i < rotateCount; i++)
|
||||
{
|
||||
rotations.push(a * i)
|
||||
}
|
||||
this.Init2(path, bin, rotations)
|
||||
return this
|
||||
}
|
||||
|
||||
// 初始化零件的各个状态,按旋转角度表
|
||||
Init2(path: Path, bin: Path, rotations: number[] = []): this
|
||||
{
|
||||
let pathP = path.OrigionMinPoint
|
||||
let path_0 = PathGeneratorSingle.Allocate(path)
|
||||
let pathSet = new Set<Path>()
|
||||
|
||||
// 初始化零件的状态集合
|
||||
for (let pa of rotations)
|
||||
{
|
||||
let partState = new PartState()
|
||||
partState.Rotation = pa
|
||||
if (pa === 0)
|
||||
{
|
||||
partState.Contour = path_0
|
||||
partState.OrigionMinPoint = pathP
|
||||
partState.MinPoint = path.OrigionMinPoint
|
||||
}
|
||||
else
|
||||
{
|
||||
let path_r = new Path(path.Points, pa)
|
||||
partState.Contour = PathGeneratorSingle.Allocate(path_r)
|
||||
partState.Contour.Area = path_0.Area
|
||||
// 避免重复的Path进入State
|
||||
if (pathSet.has(partState.Contour))
|
||||
continue
|
||||
let p0 = path_r.OrigionMinPoint
|
||||
let c = Math.cos(-pa)
|
||||
let s = Math.sin(-pa)
|
||||
let x1 = p0.x * c - p0.y * s
|
||||
let y1 = p0.x * s + p0.y * c
|
||||
partState.OrigionMinPoint = new Vector2(pathP.x + x1, pathP.y + y1)
|
||||
|
||||
// 计算正确的最小点
|
||||
let tempPath = new Path(path.OrigionPoints, pa)
|
||||
partState.MinPoint = tempPath.OrigionMinPoint
|
||||
}
|
||||
// 记录已有Path
|
||||
pathSet.add(partState.Contour)
|
||||
// 必须能放置
|
||||
if (bin.GetInsideNFP(partState.Contour))
|
||||
{
|
||||
this.RotatedStates.push(partState)
|
||||
PathGeneratorSingle.RegisterId(partState.Contour)
|
||||
}
|
||||
}
|
||||
|
||||
// 为了复用NFP,不管第0个Path是否可用,都注册它.
|
||||
if (this.RotatedStates.length > 4)
|
||||
PathGeneratorSingle.RegisterId(path_0)
|
||||
return this
|
||||
}
|
||||
|
||||
ParseGroup(partOther: Part, bin: Path): Part[]
|
||||
{
|
||||
let arr = this.GroupMap[partOther.Id]
|
||||
if (arr)
|
||||
return arr
|
||||
|
||||
arr = []
|
||||
if (this.Holes.length || partOther.Holes.length)
|
||||
return arr
|
||||
this.GroupMap[partOther.Id] = arr
|
||||
if (this.State.Contour.IsRect || partOther.State.Contour.IsRect)
|
||||
return arr
|
||||
if (this.State.Contour.Area > this.State.Contour.BoundingBox.area * 0.9)
|
||||
return arr
|
||||
if (partOther.State.Contour.Area > partOther.State.Contour.BoundingBox.area * 0.9)
|
||||
return arr
|
||||
|
||||
for (let i = 0; i < this.RotatedStates.length; i++)
|
||||
{
|
||||
let s1 = this.RotatedStates[i]
|
||||
for (let j = 1; j < partOther.RotatedStates.length; j++)
|
||||
{
|
||||
let s2 = partOther.RotatedStates[j]
|
||||
let nfps = s1.Contour.GetOutsideNFP(s2.Contour)
|
||||
for (let nfp of nfps)
|
||||
{
|
||||
for (let k = 0; k < nfp.length * 2; k++)
|
||||
{
|
||||
let p: Point
|
||||
if (k % 2 === 0)
|
||||
{
|
||||
p = { ...nfp[k / 2] }
|
||||
}
|
||||
else
|
||||
{
|
||||
let p1 = nfp[FixIndex(k / 2 - 0.5, nfp)]
|
||||
let p2 = nfp[FixIndex(k / 2 + 0.5, nfp)]
|
||||
p = { x: p1.x + p2.x, y: p1.y + p2.y }
|
||||
p.x *= 0.5
|
||||
p.y *= 0.5
|
||||
}
|
||||
p.x *= 1e-4
|
||||
p.y *= 1e-4
|
||||
|
||||
let newBox = s2.Contour.BoundingBox.clone().translate(p)
|
||||
newBox.union(s1.Contour.BoundingBox)
|
||||
|
||||
if (newBox.area < (s1.Contour.Area + s2.Contour.Area) * 1.3)
|
||||
{
|
||||
let partGroup = new PartGroup(this, partOther, i, j, p, newBox, bin)
|
||||
if (partGroup.RotatedStates.length > 0
|
||||
&& !arr.some(p => p.State.Contour === partGroup.State.Contour)// 类似的
|
||||
)
|
||||
{
|
||||
arr.push(partGroup)
|
||||
return arr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// 添加网洞
|
||||
AppendHole(path: Path)
|
||||
{
|
||||
let hole = new PartState()
|
||||
hole.Contour = PathGeneratorSingle.Allocate(path)
|
||||
PathGeneratorSingle.RegisterId(hole.Contour)
|
||||
hole.OrigionMinPoint = path.OrigionMinPoint
|
||||
hole.Rotation = 0
|
||||
this._Holes.push(hole)
|
||||
|
||||
if (GNestConfig.RotateHole)
|
||||
for (let i = 0; i < this.RotatedStates.length; i++)
|
||||
{
|
||||
let r = this.RotatedStates[i].Rotation
|
||||
let arr = this._RotateHoles[i]
|
||||
if (!arr)
|
||||
{
|
||||
arr = []
|
||||
this._RotateHoles[i] = arr
|
||||
}
|
||||
|
||||
if (r === 0)
|
||||
{
|
||||
hole.MinPoint = path.OrigionMinPoint
|
||||
arr.push(hole)
|
||||
}
|
||||
else
|
||||
{
|
||||
let newPath = new Path(path.Points, r)
|
||||
let newHole = new PartState()
|
||||
newHole.Rotation = r
|
||||
newHole.Contour = PathGeneratorSingle.Allocate(newPath)
|
||||
PathGeneratorSingle.RegisterId(newHole.Contour)
|
||||
newHole.OrigionMinPoint = newPath.OrigionMinPoint
|
||||
|
||||
// 计算正确的最小点
|
||||
let tempPath = new Path(path.OrigionPoints, r)
|
||||
newHole.MinPoint = tempPath.OrigionMinPoint
|
||||
|
||||
arr.push(newHole)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:因为现在实现的是左右翻转,所以会出现角度匹配不完全的问题(缺失上下翻转)
|
||||
Mirror(doubleFace: boolean)
|
||||
{
|
||||
let states = this.RotatedStates
|
||||
let holes = this._Holes
|
||||
let roholes = this._RotateHoles
|
||||
if (!doubleFace)
|
||||
{
|
||||
this.RotatedStates = []
|
||||
this._Holes = []
|
||||
this._RotateHoles = []
|
||||
}
|
||||
let count = states.length
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let s = states[i]
|
||||
let ns = s.Mirror()
|
||||
if (ns && !this.RotatedStates.some(state => state.Contour === s.Contour))
|
||||
{
|
||||
this.RotatedStates.push(ns)
|
||||
|
||||
if (this._Holes.length > 0)
|
||||
this._Holes.push(holes[i].Mirror())
|
||||
|
||||
if (this._RotateHoles.length > 0)
|
||||
this._RotateHoles.push(roholes[i].map(s => s.Mirror()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 浅克隆
|
||||
Clone()
|
||||
{
|
||||
let part = new Part()
|
||||
part.Id = this.Id
|
||||
part.UserData = this.UserData
|
||||
part.RotatedStates = this.RotatedStates
|
||||
part.StateIndex = this.StateIndex
|
||||
part._Holes = this._Holes
|
||||
part._RotateHoles = this._RotateHoles
|
||||
return part
|
||||
}
|
||||
|
||||
// 旋转起来,改变自身旋转状态(变异)
|
||||
Mutate(): this
|
||||
{
|
||||
this.StateIndex = RandomIndex(this.RotatedStates.length, this.StateIndex)
|
||||
return this
|
||||
}
|
||||
|
||||
// #region -------------------------File-------------------------
|
||||
ReadFile(file: NestFiler)
|
||||
{
|
||||
this.Id = file.Read()
|
||||
let count = file.Read() as number
|
||||
this.RotatedStates = []
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let state = new PartState()
|
||||
state.ReadFile(file)
|
||||
this.RotatedStates.push(state)
|
||||
}
|
||||
|
||||
// 无旋转网洞
|
||||
count = file.Read()
|
||||
this._Holes = []
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let state = new PartState()
|
||||
state.ReadFile(file)
|
||||
this._Holes.push(state)
|
||||
}
|
||||
|
||||
// 旋转网洞
|
||||
count = file.Read()
|
||||
this._RotateHoles = []
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
let count2 = file.Read() as number
|
||||
|
||||
let holes: PartState[] = []
|
||||
for (let j = 0; j < count2; j++)
|
||||
{
|
||||
let state = new PartState()
|
||||
state.ReadFile(file)
|
||||
holes.push(state)
|
||||
}
|
||||
this._RotateHoles.push(holes)
|
||||
}
|
||||
}
|
||||
|
||||
WriteFile(file: NestFiler)
|
||||
{
|
||||
file.Write(this.Id)
|
||||
file.Write(this.RotatedStates.length)
|
||||
for (let state of this.RotatedStates)
|
||||
state.WriteFile(file)
|
||||
|
||||
// 非旋转网洞
|
||||
file.Write(this._Holes.length)
|
||||
for (let hole of this._Holes)
|
||||
hole.WriteFile(file)
|
||||
|
||||
// 写入旋转网洞
|
||||
file.Write(this._RotateHoles.length)
|
||||
for (let holes of this._RotateHoles)
|
||||
{
|
||||
file.Write(holes.length)
|
||||
for (let hole of holes)
|
||||
{
|
||||
hole.WriteFile(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
|
||||
// 零件组合
|
||||
export class PartGroup extends Part
|
||||
{
|
||||
constructor(public part1: Part,
|
||||
public part2: Part,
|
||||
public index1: number,
|
||||
public index2: number,
|
||||
public p: Point,
|
||||
public box: Box2,
|
||||
bin: Path,
|
||||
)
|
||||
{
|
||||
super()
|
||||
let size = box.getSize(new Vector2())
|
||||
this.Init2(NestCache.CreatePath(size.x, size.y, 0), bin, [0])
|
||||
}
|
||||
|
||||
Export(): Part[]
|
||||
{
|
||||
this.part1.StateIndex = this.index1
|
||||
this.part2.StateIndex = this.index2
|
||||
|
||||
this.part1.PlacePosition = {
|
||||
x: this.PlacePosition.x - this.box.min.x * 1e4,
|
||||
y: this.PlacePosition.y - this.box.min.y * 1e4,
|
||||
}
|
||||
|
||||
this.part2.PlacePosition = {
|
||||
x: this.PlacePosition.x - this.box.min.x * 1e4 + this.p.x * 1e4,
|
||||
y: this.PlacePosition.y - this.box.min.y * 1e4 + this.p.y * 1e4,
|
||||
}
|
||||
|
||||
return [this.part1, this.part2]
|
||||
}
|
||||
}
|
61
src/processParser/common/core/PartState.ts
Normal file
61
src/processParser/common/core/PartState.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import type { Point } from '../Point'
|
||||
import type { NestFiler } from '../Filer'
|
||||
import { Path } from './Path'
|
||||
import { PathGeneratorSingle } from './PathGenerator'
|
||||
|
||||
/**
|
||||
* 用于存放零件旋转后的状态
|
||||
* 记录了用于放置时的轮廓。该轮廓总是首点等于0,便于放置时的计算。
|
||||
*/
|
||||
export class PartState
|
||||
{
|
||||
Rotation: number
|
||||
OrigionMinPoint: Point// 使用 Rotation(O - OrigionMinPoint) 将零件变换到和排料矩形状态相同的状态,然后使用PlacePoint(O)将零件设置到正确的状态
|
||||
|
||||
MinPoint: Point// 这个状态下的最小点
|
||||
Contour: Path// 轮廓
|
||||
|
||||
IsMirror: boolean = false
|
||||
MirrorOriginMinPoint: Point
|
||||
|
||||
Mirror(): PartState
|
||||
{
|
||||
if (this.Contour.IsRect)
|
||||
return
|
||||
|
||||
let mpts = this.Contour.Points.map((p) => { return { x: -p.x, y: p.y } }).reverse()
|
||||
let path = new Path(mpts, 0)
|
||||
let partState = new PartState()
|
||||
partState.Contour = PathGeneratorSingle.Allocate(path)
|
||||
PathGeneratorSingle.RegisterId(partState.Contour)
|
||||
partState.Rotation = this.Rotation
|
||||
partState.OrigionMinPoint = this.OrigionMinPoint
|
||||
partState.MinPoint = this.MinPoint
|
||||
partState.IsMirror = true
|
||||
partState.MirrorOriginMinPoint = path.OrigionMinPoint
|
||||
return partState
|
||||
}
|
||||
|
||||
// #region -------------------------File-------------------------
|
||||
ReadFile(file: NestFiler)
|
||||
{
|
||||
this.Rotation = file.Read()
|
||||
this.OrigionMinPoint = file.Read()
|
||||
this.MinPoint = file.Read()
|
||||
|
||||
let index = file.Read() as number
|
||||
this.Contour = PathGeneratorSingle.paths[index]
|
||||
|
||||
if (!this.Contour)
|
||||
console.error('无法得到PartState的轮廓!')
|
||||
}
|
||||
|
||||
WriteFile(file: NestFiler)
|
||||
{
|
||||
file.Write(this.Rotation)
|
||||
file.Write(this.OrigionMinPoint)
|
||||
file.Write(this.MinPoint)
|
||||
file.Write(this.Contour.Id)
|
||||
}
|
||||
// #endregion
|
||||
}
|
381
src/processParser/common/core/Path.ts
Normal file
381
src/processParser/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
|
||||
}
|
87
src/processParser/common/core/PathGenerator.ts
Normal file
87
src/processParser/common/core/PathGenerator.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { FixIndex, equaln } from '../Util'
|
||||
import type { Path } from './Path'
|
||||
|
||||
/**
|
||||
* 轮廓路径构造器
|
||||
* 传递一组简化后的点表过来,如果已经有同样的点表时,返回已经生产的Path,避免重复产生Path。
|
||||
* 使用相同的PATH有复用路径缓存。
|
||||
*
|
||||
* 每次进行优化时,必须清理构造器,保证Path生成是对本次优化唯一。
|
||||
*/
|
||||
class PathGenerator
|
||||
{
|
||||
paths: Path[] = []
|
||||
pathAreaMap: { [key: string]: Path[] } = {}
|
||||
|
||||
// 缓存命中次数
|
||||
cacheCount = 0
|
||||
|
||||
/**
|
||||
* 如果存在同样的轮廓,则返回已经构造的轮廓,
|
||||
* 如果没有,则返回自身,并且注册它。
|
||||
* 如果id没有被注册,那么证明它无法放置在bin中
|
||||
*/
|
||||
Allocate(path: Path): Path
|
||||
{
|
||||
let area = path.Area.toFixed(0)
|
||||
let paths = this.pathAreaMap[area]
|
||||
if (paths)
|
||||
{
|
||||
for (let ps of paths)
|
||||
{
|
||||
if (EqualPath(ps, path))
|
||||
{
|
||||
this.cacheCount++
|
||||
return ps
|
||||
}
|
||||
}
|
||||
paths.push(path)
|
||||
}
|
||||
else
|
||||
this.pathAreaMap[area] = [path]
|
||||
return path
|
||||
}
|
||||
|
||||
RegisterId(path: Path)
|
||||
{
|
||||
if (path.Id === undefined)
|
||||
{
|
||||
path.Id = this.paths.length
|
||||
this.paths.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
Clear()
|
||||
{
|
||||
this.paths = []
|
||||
this.pathAreaMap = {}
|
||||
this.cacheCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 两路径相等,点表个数相等且每个点都相似
|
||||
*/
|
||||
function EqualPath(path1: Path, path2: Path): boolean
|
||||
{
|
||||
if (path1.Points.length !== path2.Points.length)
|
||||
return false
|
||||
|
||||
let p0 = path1.Points[0]
|
||||
let p2Index = path2.Points.findIndex((p) =>
|
||||
{
|
||||
return equaln(p.x, p0.x, 1e-3) && equaln(p.y, p0.y, 1e-3)
|
||||
})
|
||||
|
||||
for (let i = 0; i < path1.Points.length; i++)
|
||||
{
|
||||
let p1 = path1.Points[i]
|
||||
let p2 = path2.Points[FixIndex(p2Index + i, path2.Points)]
|
||||
|
||||
if (!equaln(p1.x, p2.x, 1e-4) || !equaln(p1.y, p2.y, 1e-4))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const PathGeneratorSingle = new PathGenerator()
|
11
src/processParser/common/core/PlaceType.ts
Normal file
11
src/processParser/common/core/PlaceType.ts
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
/**排牌类型:Hull=0凸包模式 (凸包面积) Box=1盒子模式 (长x宽) Gravity=2重力模式(重力) */
|
||||
export enum PlaceType
|
||||
{
|
||||
/**凸包模式 (凸包面积) */
|
||||
Hull = 0,
|
||||
/**盒子模式 (长乘以宽) */
|
||||
Box = 1,
|
||||
/**重力模式(重力) */
|
||||
Gravity = 2
|
||||
}
|
31
src/processParser/common/decorators/json.ts
Normal file
31
src/processParser/common/decorators/json.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
export const jsonOptionMataDataKey = Symbol('JsonOptions')
|
||||
|
||||
export enum JsonOptionType
|
||||
{
|
||||
Ignore = 0,
|
||||
Property = 1,
|
||||
}
|
||||
|
||||
export class JsonOption
|
||||
{
|
||||
constructor(type: JsonOptionType, data: any = null)
|
||||
{
|
||||
this.type = type
|
||||
this.data = data
|
||||
}
|
||||
|
||||
type: JsonOptionType
|
||||
data: any
|
||||
}
|
||||
|
||||
export function jsonProperty(key: string | number)
|
||||
{
|
||||
return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Property, { key }))
|
||||
}
|
||||
|
||||
export function jsonIgnore()
|
||||
{
|
||||
return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Ignore))
|
||||
}
|
39
src/processParser/common/decorators/model.ts
Normal file
39
src/processParser/common/decorators/model.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
const displayMetadataKey = Symbol('display')
|
||||
|
||||
export function display(
|
||||
name: string,
|
||||
group: string = '',
|
||||
type: string = 'color',
|
||||
)
|
||||
{
|
||||
return Reflect.metadata(displayMetadataKey, {
|
||||
name,
|
||||
group,
|
||||
type,
|
||||
})
|
||||
}
|
||||
|
||||
export function getDisplayInfo(target: any)
|
||||
{
|
||||
let keys = Object.getOwnPropertyNames(target)
|
||||
let list = []
|
||||
keys.forEach((key) =>
|
||||
{
|
||||
let info = <{ name: string; group: string; type: number }>(
|
||||
Reflect.getMetadata(displayMetadataKey, target, key)
|
||||
)
|
||||
if (info)
|
||||
{
|
||||
info
|
||||
list.push({
|
||||
prop: key,
|
||||
name: info.name,
|
||||
group: info.group,
|
||||
type: info.type,
|
||||
})
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
98
src/processParser/common/drawing/base.ts
Normal file
98
src/processParser/common/drawing/base.ts
Normal file
@ -0,0 +1,98 @@
|
||||
export abstract class Drawing
|
||||
{
|
||||
width: number
|
||||
height: number
|
||||
|
||||
scale: number = 1
|
||||
abstract drawPath(path: DrawPathContext, option: DrawLineOption)
|
||||
|
||||
abstract drawLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
option: DrawLineOption,
|
||||
)
|
||||
abstract drawRect(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawRectOption,
|
||||
)
|
||||
abstract drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption)
|
||||
abstract drawText(text: string, x: number, y: number, option: DrawTextOption)
|
||||
abstract drawForeignObjectText(
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
option: DrawTextOption,
|
||||
)
|
||||
abstract drawImage(
|
||||
source: CanvasImageSource,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawImageOption,
|
||||
border: number,
|
||||
): Promise<any>
|
||||
|
||||
abstract clearAll()
|
||||
|
||||
abstract toHTML(): string
|
||||
}
|
||||
|
||||
export class BaseDrawOption
|
||||
{
|
||||
lineWidth: number = 1
|
||||
strokeStyle: string
|
||||
}
|
||||
|
||||
interface IDrawPath
|
||||
{
|
||||
method: string
|
||||
args: number[]
|
||||
}
|
||||
export class DrawPathContext
|
||||
{
|
||||
data: Array<IDrawPath> = []
|
||||
|
||||
moveTo(x: number, y: number)
|
||||
{
|
||||
this.data.push({ method: 'M', args: [x, y] })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
lineTo(x: number, y: number)
|
||||
{
|
||||
this.data.push({ method: 'L', args: [x, y] })
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawLineOption extends BaseDrawOption { }
|
||||
export class DrawRectOption extends BaseDrawOption
|
||||
{
|
||||
fillStyle: string
|
||||
}
|
||||
export class DrawImageOption extends BaseDrawOption { }
|
||||
export class DrawLinePolygonOption extends BaseDrawOption
|
||||
{
|
||||
isFill: boolean = true
|
||||
fillStyle: string = 'rgb(0,0,0)'
|
||||
}
|
||||
|
||||
export type DrawTextBaseLnie = 'alphabetic' | 'bottom' | 'hanging' | 'ideographic' | 'middle' | 'top'
|
||||
export type DrawTextAlign = 'start' | 'center' | 'right'
|
||||
export class DrawTextOption
|
||||
{
|
||||
maxWidth?: number
|
||||
textAlign: DrawTextAlign = 'center'
|
||||
textBaseline: DrawTextBaseLnie = 'middle'
|
||||
fillStyle: string = 'rgb(0,0,0)'
|
||||
fontSize: string = '12'
|
||||
fontWeight: string = '400'
|
||||
fontFamily: string = '宋体'
|
||||
}
|
197
src/processParser/common/drawing/canvasDrawing.ts
Normal file
197
src/processParser/common/drawing/canvasDrawing.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { createBmpFile } from '../bmp.js'
|
||||
import { getFileExt } from '../file.js'
|
||||
import type {
|
||||
DrawImageOption,
|
||||
DrawLineOption,
|
||||
DrawLinePolygonOption,
|
||||
DrawPathContext,
|
||||
DrawRectOption,
|
||||
DrawTextOption } from './base.js'
|
||||
import
|
||||
{
|
||||
Drawing,
|
||||
} from './base.js'
|
||||
|
||||
export class CanvasDrawing extends Drawing {
|
||||
canvas: HTMLCanvasElement
|
||||
ctx: CanvasRenderingContext2D
|
||||
// isCaching: boolean;
|
||||
outputType: string = 'png'
|
||||
orgWidth: number
|
||||
orgHeight: number
|
||||
toHTML(): string {
|
||||
let img = new Image(this.canvas.width, this.canvas.height)
|
||||
img.src = this.canvas.toDataURL(`image/${this.outputType}`)
|
||||
return img.outerHTML
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
canvas: HTMLCanvasElement,
|
||||
// isCaching: boolean = false,
|
||||
) {
|
||||
super()
|
||||
this.canvas = canvas
|
||||
this.width = this.orgWidth = canvas.width = canvas.width
|
||||
this.height = this.orgHeight = canvas.height = canvas.height
|
||||
// this.isCaching = isCaching;
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
}
|
||||
|
||||
drawPath(path: DrawPathContext, option: DrawLineOption) {
|
||||
this.ctx.beginPath()
|
||||
for (const item of path.data) {
|
||||
switch (item.method) {
|
||||
case 'M':
|
||||
this.ctx.moveTo(item.args[0], item.args[1])
|
||||
break
|
||||
case 'L':
|
||||
this.ctx.lineTo(item.args[0], item.args[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
this.ctx.lineWidth = option.lineWidth
|
||||
this.ctx.strokeStyle = option.strokeStyle
|
||||
this.ctx.stroke()
|
||||
}
|
||||
|
||||
drawLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
option: DrawLineOption,
|
||||
) {
|
||||
this.ctx.lineWidth = option.lineWidth
|
||||
this.ctx.strokeStyle = option.strokeStyle
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(x0, y0)
|
||||
this.ctx.lineTo(x1, y1)
|
||||
this.ctx.stroke()
|
||||
}
|
||||
|
||||
drawRect(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawRectOption,
|
||||
) {
|
||||
this.ctx.lineWidth = option.lineWidth
|
||||
this.ctx.strokeStyle = option.strokeStyle
|
||||
this.ctx.fillStyle = option.fillStyle
|
||||
if (option.fillStyle) {
|
||||
this.ctx.fillRect(x, y, width, height)
|
||||
} else {
|
||||
this.ctx.strokeRect(x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
drawText(text: string, x: number, y: number, option: DrawTextOption) {
|
||||
this.ctx.textAlign = option.textAlign as CanvasTextAlign
|
||||
this.ctx.textBaseline = option.textBaseline as CanvasTextBaseline
|
||||
this.ctx.fillStyle = option.fillStyle
|
||||
this.ctx.font
|
||||
= `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}`
|
||||
this.ctx.fillText(text, x, y, option.maxWidth)
|
||||
}
|
||||
|
||||
drawForeignObjectText(
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
option: DrawTextOption,
|
||||
) {}
|
||||
|
||||
imageCache: Map<string, HTMLImageElement> = new Map()
|
||||
drawImage(
|
||||
source: CanvasImageSource,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawImageOption,
|
||||
border: number,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ctx.drawImage(source, x + border, y + border, width, height)
|
||||
resolve(null)
|
||||
})
|
||||
}
|
||||
|
||||
drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption) {
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(points[0].x, points[0].y)
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
this.ctx.lineTo(points[i].x, points[i].y)
|
||||
}
|
||||
this.ctx.closePath()
|
||||
|
||||
this.ctx.stroke()
|
||||
if (option.isFill)
|
||||
{
|
||||
this.ctx.fillStyle = option.fillStyle
|
||||
this.ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
setScale(value: number) {
|
||||
this.scale = value
|
||||
this.canvas.width = this.width = this.orgWidth * value
|
||||
this.canvas.height = this.height = this.orgHeight * value
|
||||
this.ctx.scale(value, value)
|
||||
}
|
||||
// resize(width: number, height: number)
|
||||
// {
|
||||
// let oldWidth = this.width;
|
||||
// let oldHeight = this.height;
|
||||
// if (oldWidth != width || oldHeight != height)
|
||||
// {
|
||||
// let newCanvas = document.createElement('canvas');
|
||||
// this.width = newCanvas.width = width;
|
||||
// this.height = newCanvas.height = height;
|
||||
// let newCtx = newCanvas.getContext('2d');
|
||||
// newCtx.drawImage(this.canvas, 0, 0, oldWidth, oldHeight, 0, 0, width, height);
|
||||
// this.canvas = newCanvas;
|
||||
// this.ctx = newCtx;
|
||||
// }
|
||||
// }
|
||||
|
||||
exportFile(nameOrType: string, param: string): Promise<Blob | Uint8Array> {
|
||||
let mimeType = 'image/png'
|
||||
let ext = getFileExt(nameOrType)
|
||||
if (ext === null) {
|
||||
ext = nameOrType
|
||||
}
|
||||
let paramDic: { [key: string]: string } = {}
|
||||
if (param != null) {
|
||||
for (const item of param.split('&')) {
|
||||
let kv = item.split('=')
|
||||
paramDic[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
switch (ext) {
|
||||
case 'bmp':
|
||||
let data = createBmpFile(
|
||||
this.ctx.getImageData(0, 0, this.width, this.height),
|
||||
paramDic,
|
||||
)
|
||||
return Promise.resolve(new Uint8Array(data))
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
mimeType = 'image/jpeg'
|
||||
break
|
||||
}
|
||||
return new Promise<Blob>((resolve) => {
|
||||
this.canvas.toBlob((data: Blob) => {
|
||||
resolve(data)
|
||||
}, mimeType)
|
||||
})
|
||||
}
|
||||
}
|
39
src/processParser/common/drawing/canvasUtil.ts
Normal file
39
src/processParser/common/drawing/canvasUtil.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import type { DrawTextOption } from './base'
|
||||
|
||||
// const canvas = new OffscreenCanvas(0, 0);
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvasCtx = canvas.getContext('2d')
|
||||
|
||||
export function blobToDataURL(data: Blob): Promise<string>
|
||||
{
|
||||
return new Promise((resolve) =>
|
||||
{
|
||||
let reader = new FileReader()
|
||||
reader.onload = (response) =>
|
||||
{
|
||||
resolve(response.target.result as string)
|
||||
}
|
||||
reader.readAsDataURL(data)
|
||||
})
|
||||
}
|
||||
|
||||
export function getStringWidth(
|
||||
text: string,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawTextOption,
|
||||
): number
|
||||
{
|
||||
canvasCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
canvasCtx.textAlign = option.textAlign as CanvasTextAlign
|
||||
canvasCtx.textBaseline = option.textBaseline as CanvasTextBaseline
|
||||
canvasCtx.fillStyle = option.fillStyle
|
||||
canvasCtx.font
|
||||
= `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}`
|
||||
return canvasCtx.measureText(text).width
|
||||
}
|
33
src/processParser/common/drawing/imageCode.ts
Normal file
33
src/processParser/common/drawing/imageCode.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import jsbarcode from 'jsbarcode'
|
||||
import type { QRCodeRenderersOptions } from 'qrcode'
|
||||
import { toCanvas } from 'qrcode'
|
||||
|
||||
// const canvas = new OffscreenCanvas(0, 0);
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvasCtx = canvas.getContext('2d')
|
||||
|
||||
export async function genBarcode(text: string, options: jsbarcode.Options)
|
||||
{
|
||||
canvas.width = options.width
|
||||
canvas.height = options.height
|
||||
canvasCtx.clearRect(0, 0, options.width, options.height)
|
||||
try
|
||||
{
|
||||
jsbarcode(canvas, text, options)
|
||||
// return await canvas.transferToImageBitmap();
|
||||
return await createImageBitmap(canvas)
|
||||
} catch
|
||||
{
|
||||
return null
|
||||
}
|
||||
}
|
||||
export async function genQrcode(text: string, options: QRCodeRenderersOptions)
|
||||
{
|
||||
canvas.width = options.width
|
||||
canvas.height = options.width
|
||||
canvasCtx.clearRect(0, 0, options.width, options.width)
|
||||
await toCanvas(canvas, text, options)
|
||||
|
||||
// return await canvas.transferToImageBitmap();
|
||||
return await createImageBitmap(canvas)
|
||||
}
|
12
src/processParser/common/drawing/imageUtil.ts
Normal file
12
src/processParser/common/drawing/imageUtil.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export function UrlToBitmap(src: string)
|
||||
{
|
||||
return new Promise<HTMLImageElement>((resolve) =>
|
||||
{
|
||||
let img = new Image()
|
||||
img.src = src
|
||||
img.onload = () =>
|
||||
{
|
||||
resolve(img)
|
||||
}
|
||||
})
|
||||
}
|
3
src/processParser/common/drawing/index.ts
Normal file
3
src/processParser/common/drawing/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './base'
|
||||
export * from './canvasDrawing'
|
||||
export * from './svgDrawing'
|
251
src/processParser/common/drawing/svgDrawing.ts
Normal file
251
src/processParser/common/drawing/svgDrawing.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import type {
|
||||
DrawImageOption, DrawLineOption, DrawLinePolygonOption, DrawPathContext, DrawRectOption,
|
||||
DrawTextOption,
|
||||
} from './base.js'
|
||||
import
|
||||
{ Drawing,
|
||||
} from './base.js'
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvasCtx = canvas.getContext('2d')
|
||||
export class SvgDrawing extends Drawing
|
||||
{
|
||||
svg: SVGElement
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
width: number | string,
|
||||
height: number | string,
|
||||
scale: number = 0.8,
|
||||
)
|
||||
{
|
||||
super()
|
||||
this.scale = scale
|
||||
this.width = Number(width)
|
||||
this.height = Number(height)
|
||||
this.svg = this.createElement('svg')
|
||||
// this.svg.setAttribute('width', this.width.toString());//(Number(width) * this.scale)
|
||||
// this.svg.setAttribute('height', this.height.toString());//(Number(height) * this.scale)
|
||||
// this.svg.style.transform = 'scale(' + this.scale + ')';
|
||||
// this.svg.style.transformOrigin = 'left top';
|
||||
this.svg.setAttribute('viewBox', `0 0 ${width} ${height}`)
|
||||
}
|
||||
|
||||
private createElement(name)
|
||||
{
|
||||
return document.createElementNS('http://www.w3.org/2000/svg', name)
|
||||
}
|
||||
|
||||
toHTML(): string
|
||||
{
|
||||
return this.svg.outerHTML
|
||||
}
|
||||
|
||||
clearAll()
|
||||
{
|
||||
this.svg.innerHTML = ''
|
||||
}
|
||||
|
||||
drawPath(path: DrawPathContext, option: DrawLineOption)
|
||||
{
|
||||
let el = this.createElement('path')
|
||||
|
||||
let pathStr = ''
|
||||
for (const item of path.data)
|
||||
{
|
||||
switch (item.method)
|
||||
{
|
||||
case 'M':
|
||||
pathStr += `M ${item.args.join(' ')} `
|
||||
break
|
||||
case 'L':
|
||||
pathStr += `L ${item.args.join(' ')} `
|
||||
break
|
||||
}
|
||||
}
|
||||
el.setAttribute('d', pathStr)
|
||||
el.setAttribute('stroke-width', option.lineWidth.toString())
|
||||
el.setAttribute('stroke', option.strokeStyle)
|
||||
this.svg.appendChild(el)
|
||||
}
|
||||
|
||||
drawLine(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
option: DrawLineOption,
|
||||
)
|
||||
{
|
||||
let el = this.createElement('line')
|
||||
el.setAttribute('x1', x0.toString()) // (Number(x0) * this.scale)
|
||||
el.setAttribute('y1', y0.toString()) // (Number(y0) * this.scale)
|
||||
el.setAttribute('x2', x1.toString()) // (Number(x1) * this.scale)
|
||||
el.setAttribute('y2', y1.toString()) // (Number(y1) * this.scale)
|
||||
el.setAttribute('stroke', option.strokeStyle)
|
||||
el.setAttribute('fill', 'rgba(0,0,0,0)')
|
||||
el.setAttribute('stroke-width', option.lineWidth.toString())
|
||||
this.svg.appendChild(el)
|
||||
}
|
||||
|
||||
drawRect(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawRectOption,
|
||||
)
|
||||
{
|
||||
let el = this.createElement('rect')
|
||||
el.setAttribute('x', x.toString()) // ((x + option.baseLine / 2) * this.scale)
|
||||
el.setAttribute('y', y.toString()) // ((y + option.baseLine) * this.scale)
|
||||
el.setAttribute('width', width.toString()) // (Number(width) * this.scale)
|
||||
el.setAttribute('height', height.toString()) // (Number(height) * this.scale)
|
||||
if (option.fillStyle)
|
||||
{
|
||||
el.setAttribute('fill', option.fillStyle)
|
||||
el.setAttribute('stroke', option.strokeStyle)
|
||||
} else
|
||||
{
|
||||
el.setAttribute('fill', 'rgba(1,1,1,0)')
|
||||
el.setAttribute('stroke', option.strokeStyle)
|
||||
el.setAttribute(
|
||||
'stroke-width',
|
||||
(Math.floor(option.lineWidth) + 0.5).toString(),
|
||||
)
|
||||
}
|
||||
this.svg.appendChild(el)
|
||||
}
|
||||
|
||||
drawText(text: string, x: number, y: number, option: DrawTextOption)
|
||||
{
|
||||
let el = this.createElement('text')
|
||||
el.setAttribute('x', x.toString()) // (Number(x) * this.scale) option['CncDict'] ? (x - Number(option.fontSize) / 6).toString() :
|
||||
el.setAttribute('y', y.toString()) // (Number(y) * this.scale)
|
||||
el.style.dominantBaseline = 'text-before-edge'
|
||||
if (option.resetFont)
|
||||
{
|
||||
el.style.dominantBaseline = 'middle'
|
||||
el.setAttribute('text-anchor', 'middle')
|
||||
}
|
||||
el.setAttribute('fill', option.fillStyle)
|
||||
el.setAttribute('font-size', `${option.fontSize}px`)
|
||||
el.setAttribute('font-weight', option.fontWeight)
|
||||
el.setAttribute('font-family', option.fontFamily)
|
||||
if (option.overflowText)
|
||||
{
|
||||
el.setAttribute('textLength', option.maxWidth.toString())
|
||||
el.setAttribute('lengthAdjust', 'spacingAndGlyphs')
|
||||
}
|
||||
el.setAttribute('alignment-baseline', option.textBaseline)
|
||||
switch (option.textAlign)
|
||||
{
|
||||
case 'start':
|
||||
el.setAttribute('text-anchor', 'left')
|
||||
break
|
||||
case 'center':
|
||||
el.setAttribute('text-anchor', 'middle')
|
||||
break
|
||||
case 'right':
|
||||
el.setAttribute('text-anchor', 'end')
|
||||
break
|
||||
}
|
||||
|
||||
if (typeof text === 'string')
|
||||
{
|
||||
el.innerHTML = text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')
|
||||
} else
|
||||
{
|
||||
el.innerHTML = text
|
||||
}
|
||||
|
||||
this.svg.appendChild(el)
|
||||
}
|
||||
|
||||
drawForeignObjectText(
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
option: DrawTextOption,
|
||||
)
|
||||
{
|
||||
// let el = this.createElement('foreignObject');
|
||||
// el.setAttribute('x',x.toString());
|
||||
// el.setAttribute('y',y.toString());
|
||||
// el.setAttribute('width',option.maxWidth.toString());
|
||||
// el.setAttribute('height',option['maxHeight'].toString());
|
||||
// let span = this.createElement('span');
|
||||
// span.style.dominantBaseline = 'text-before-edge';
|
||||
// if(option['resetFont']){
|
||||
// span.style.dominantBaseline = 'middle';
|
||||
// span.style.textAnchor = 'middle';
|
||||
// }
|
||||
// span.style.fontSize = option.fontSize + 'px';
|
||||
// span.style.fontWeight = option.fontWeight;
|
||||
// span.style.fontFamily = option.fontFamily;
|
||||
// span.style.display = 'inline-block';
|
||||
// span.style.width = option.maxWidth + 'px';
|
||||
// span.style.height = option['maxHeight'] + 'px';
|
||||
// span.style.overflow = 'hidden';
|
||||
// span.innerHTML = text;
|
||||
// el.appendChild(span);
|
||||
// this.svg.appendChild(el);
|
||||
}
|
||||
|
||||
async drawImage(
|
||||
source: CanvasImageSource,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
option: DrawImageOption,
|
||||
)
|
||||
{
|
||||
let src = ''
|
||||
if (source instanceof ImageBitmap)
|
||||
{
|
||||
canvas.width = source.width
|
||||
canvas.height = source.height
|
||||
canvasCtx.clearRect(0, 0, source.width, source.height)
|
||||
canvasCtx.drawImage(source, 0, 0, source.width, source.height)
|
||||
src = canvas.toDataURL()
|
||||
} else if (source instanceof HTMLImageElement)
|
||||
{
|
||||
src = source.src
|
||||
}
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
let el = this.createElement('image')
|
||||
el.setAttribute('x', x.toString()) // (Number(x) * this.scale)
|
||||
el.setAttribute('y', y.toString()) // (Number(y) * this.scale)
|
||||
el.setAttribute('width', width.toString()) // (Number(width) * this.scale)
|
||||
el.setAttribute('height', height.toString()) // (Number(height) * this.scale)
|
||||
el.setAttribute('xlink:href', src)
|
||||
el.setAttribute('preserveAspectRatio', 'none meet')
|
||||
this.svg.appendChild(el)
|
||||
resolve(null)
|
||||
})
|
||||
}
|
||||
|
||||
drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption)
|
||||
{
|
||||
let el = this.createElement('polygon')
|
||||
let pointsStr = ''
|
||||
for (let i = 0; i < points.length; i++)
|
||||
{
|
||||
if (i == points.length - 1)
|
||||
{
|
||||
pointsStr += `${points[i].x} ${points[i].y}`
|
||||
} else
|
||||
{
|
||||
pointsStr += `${points[i].x} ${points[i].y},`
|
||||
}
|
||||
}
|
||||
el.setAttribute('points', pointsStr)
|
||||
el.setAttribute('fill', option.isFill ? option.fillStyle : 'none')
|
||||
el.setAttribute('stroke', option.strokeStyle)
|
||||
el.setAttribute('stroke-width', option.lineWidth.toString())// 添加多段线的线宽--xyh
|
||||
this.svg.appendChild(el)
|
||||
}
|
||||
}
|
22
src/processParser/common/file.ts
Normal file
22
src/processParser/common/file.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export async function readText(file: Blob, encoding?: string): Promise<string>
|
||||
{
|
||||
return new Promise((resolve) =>
|
||||
{
|
||||
let fileReader = new FileReader()
|
||||
fileReader.onload = function (e)
|
||||
{
|
||||
resolve(<string> this.result)
|
||||
}
|
||||
fileReader.readAsText(file, encoding)
|
||||
})
|
||||
}
|
||||
|
||||
export function getFileExt(name: string)
|
||||
{
|
||||
let index = name.lastIndexOf('.')
|
||||
if (index != -1)
|
||||
{
|
||||
return name.substring(index + 1, name.length)
|
||||
}
|
||||
return null
|
||||
}
|
33
src/processParser/common/log.ts
Normal file
33
src/processParser/common/log.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export class Logger
|
||||
{
|
||||
name: string
|
||||
static interceptors: LogInterceptor[] = []
|
||||
constructor(name: string)
|
||||
{
|
||||
this.name = name
|
||||
}
|
||||
|
||||
static AddInterceptor(interceptor: LogInterceptor)
|
||||
{
|
||||
Logger.interceptors.push(interceptor)
|
||||
}
|
||||
|
||||
error(error: Error, payload: any = null)
|
||||
{
|
||||
console.error(error, payload)
|
||||
Logger.interceptors.forEach((i) =>
|
||||
{
|
||||
i.call(this, error, payload)
|
||||
})
|
||||
}
|
||||
|
||||
info(message: string, payload: any = null)
|
||||
{
|
||||
console.info(message, payload)
|
||||
Logger.interceptors.forEach((i) =>
|
||||
{
|
||||
i.call(this, message, payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
export type LogInterceptor = (this: Logger, info: string | Error, payload: any) => void
|
23634
src/processParser/common/placeDataHelper.ts
Normal file
23634
src/processParser/common/placeDataHelper.ts
Normal file
File diff suppressed because it is too large
Load Diff
1519
src/processParser/common/transform.ts
Normal file
1519
src/processParser/common/transform.ts
Normal file
File diff suppressed because it is too large
Load Diff
151
src/processParser/common/zip.ts
Normal file
151
src/processParser/common/zip.ts
Normal file
@ -0,0 +1,151 @@
|
||||
// import JsZip from 'jszip';
|
||||
import type { DeflateOptions } from 'fflate'
|
||||
import { Zip, ZipDeflate, strToU8 } from 'fflate'
|
||||
import { getFileExt } from './file'
|
||||
|
||||
export class FileInfo
|
||||
{
|
||||
name: string
|
||||
content: string | Blob | Uint8Array
|
||||
encode: string
|
||||
isBase64 = false
|
||||
binary = false
|
||||
constructor(name, text, isBase64 = false)
|
||||
{
|
||||
this.name = name
|
||||
this.content = text
|
||||
this.isBase64 = isBase64
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ZipProvider
|
||||
{
|
||||
// abstract loadAsync(file: File): Promise<void>;
|
||||
protected files: FileInfo[] = []
|
||||
|
||||
addFile(file: FileInfo)
|
||||
{
|
||||
this.files.push(file)
|
||||
}
|
||||
|
||||
abstract saveAsync(): Promise<Blob>
|
||||
}
|
||||
|
||||
// export class JsZipProvider extends ZipProvider
|
||||
// {
|
||||
|
||||
// private ctx = new JsZip();
|
||||
|
||||
// // async loadAsync(file: File): Promise<void>
|
||||
// // {
|
||||
// // await this.ctx.loadAsync(file);
|
||||
// // }
|
||||
|
||||
// async saveAsync(): Promise<Blob>
|
||||
// {
|
||||
// for (const file of this.files)
|
||||
// {
|
||||
// this.ctx.file(file.name, file.content, {
|
||||
// createFolders: true,
|
||||
// base64: file.isBase64,
|
||||
// binary: file.binary,
|
||||
// compression: "DEFLATE",
|
||||
// });
|
||||
// }
|
||||
// return await this.ctx.generateAsync({ type: 'blob' });
|
||||
// }
|
||||
// }
|
||||
|
||||
export class FflateZipProvider extends ZipProvider
|
||||
{
|
||||
private ctx = new Zip()
|
||||
|
||||
private task: Promise<Blob>
|
||||
|
||||
constructor()
|
||||
{
|
||||
super()
|
||||
this.task = new Promise<Blob>((resolve, reject) =>
|
||||
{
|
||||
let result = []
|
||||
this.ctx.ondata = (err, data, final) =>
|
||||
{
|
||||
if (!err)
|
||||
{
|
||||
result.push(data)
|
||||
if (final)
|
||||
{
|
||||
resolve(new Blob(result, { type: 'application/zip' }))
|
||||
}
|
||||
} else
|
||||
{
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async saveAsync(): Promise<Blob>
|
||||
{
|
||||
for (const file of this.files)
|
||||
{
|
||||
let data: Uint8Array
|
||||
if (file.content instanceof Blob)
|
||||
{
|
||||
// console.log(file.name);
|
||||
let buffer = await file.content.arrayBuffer()
|
||||
// console.log('buffer', buffer);
|
||||
data = new Uint8Array(buffer)
|
||||
} else if (file.content instanceof Uint8Array)
|
||||
{
|
||||
data = file.content
|
||||
}
|
||||
else if (file.isBase64)
|
||||
{
|
||||
data = new Uint8Array(atob(file.content).split('').map((c) =>
|
||||
{
|
||||
return c.charCodeAt(0)
|
||||
}))
|
||||
} else
|
||||
{
|
||||
data = strToU8(file.content)
|
||||
}
|
||||
|
||||
let zipInput = new ZipDeflate(file.name, this.getOptionByExt(file.name))
|
||||
this.ctx.add(zipInput)
|
||||
zipInput.push(data, true)
|
||||
}
|
||||
this.ctx.end()
|
||||
return this.task
|
||||
}
|
||||
|
||||
getOptionByExt(fileName: string)
|
||||
{
|
||||
let option: DeflateOptions = {
|
||||
level: 6,
|
||||
// mem: 12
|
||||
}
|
||||
let ext = getFileExt(fileName)
|
||||
if (ext !== null)
|
||||
{
|
||||
switch (ext)
|
||||
{
|
||||
case 'bmp':
|
||||
option.level = 1
|
||||
option.mem = 0
|
||||
break
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
option.level = 1
|
||||
break
|
||||
case 'png':
|
||||
option.level = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return option
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultZipProvider = () => new FflateZipProvider()
|
1348
src/processParser/confClass.ts
Normal file
1348
src/processParser/confClass.ts
Normal file
File diff suppressed because it is too large
Load Diff
462
src/processParser/parseMain.ts
Normal file
462
src/processParser/parseMain.ts
Normal file
@ -0,0 +1,462 @@
|
||||
import { Knife } from "./confClass"
|
||||
import { FileZip } from "./common/base/ZipFile"
|
||||
import { _knifeType, _type, CodeParamsList } from "./confClass"
|
||||
import { device1 } from "./processConfig/device1/parser1"
|
||||
import { device2 } from "./processConfig/device2/parser2"
|
||||
|
||||
export enum CodeType {
|
||||
文件头 = 'FSTART',
|
||||
调刀 = 'T',
|
||||
主程序 = 'C',
|
||||
主程序结束 = 'CE',
|
||||
文件尾 = 'FEND',
|
||||
备注 = 'MARK',
|
||||
FileStart = 'FSTART',
|
||||
UseKnife = 'T',
|
||||
MainCode = 'C',
|
||||
MainCodeEnd = 'CE',
|
||||
FileEnd = 'FEND',
|
||||
REMARK = 'MARK',
|
||||
}
|
||||
|
||||
export class ParserMain {
|
||||
/**解析器 解析器容器 */
|
||||
_device
|
||||
deviceList = [
|
||||
{
|
||||
label: '解析器1',
|
||||
value: 'device1',
|
||||
groupLabel: '通用',
|
||||
groupKey: 'parser1',
|
||||
device: device1
|
||||
},
|
||||
{
|
||||
label: '解析器2',
|
||||
value: 'device2',
|
||||
groupKey: 'parser1',
|
||||
groupLabel: '通用',
|
||||
device: device2
|
||||
}
|
||||
]
|
||||
zipFile: FileZip
|
||||
processListData?: CodeParamsList
|
||||
|
||||
constructor(device?) {
|
||||
if (device) {
|
||||
this._device = device
|
||||
}
|
||||
this.zipFile = new FileZip()
|
||||
}
|
||||
//** 文件头 */
|
||||
FSTART(val) {
|
||||
let str = this.handleCode('FSTART', val)
|
||||
return str
|
||||
}
|
||||
/** 换刀 */
|
||||
T(val, params?) {
|
||||
let str = this.handleCode('T', val, params)
|
||||
return str
|
||||
}
|
||||
/** 主程序 加工数据 */
|
||||
C(val, params) {
|
||||
let str = this.handleCode('C', val, params)
|
||||
return str
|
||||
}
|
||||
/** 主程序结束 */
|
||||
CE(val) {
|
||||
let str = this.handleCode('CE', val)
|
||||
return str
|
||||
}
|
||||
/** 文件结束 */
|
||||
FEND(val) {
|
||||
let str = this.handleCode('FEND', val)
|
||||
return str
|
||||
}
|
||||
/** 备注 */
|
||||
MARK(val, params) {
|
||||
let str = this.handleCode('MARK', val, params)
|
||||
return str
|
||||
}
|
||||
// {
|
||||
// "value": "1",
|
||||
// "label": "铣刀",
|
||||
// },
|
||||
// {
|
||||
// "value": "2",
|
||||
// "label": "成型刀",
|
||||
// },
|
||||
// {
|
||||
// "value": "3",
|
||||
// "label": "钻头"
|
||||
// },
|
||||
// {
|
||||
// "dictType": "work_area_knif_type",
|
||||
// "value": "4",
|
||||
// "label": "锯片",
|
||||
// }
|
||||
getAbilityOptions(i) {
|
||||
let knife = this._device.knifeList[i]
|
||||
const knifeTypeOptions = this.getKnifeTypeOptions() || []
|
||||
let label = knifeTypeOptions.find(e => e.value == knife.knifeType).label || ''
|
||||
const allowedAbilities = knifeTypeAbilityMap[label] || []
|
||||
const filteredAbilities = orginAbilitys.filter(ability => allowedAbilities.includes(ability.label)) || []
|
||||
return filteredAbilities
|
||||
}
|
||||
setDefaultKnife(i) {
|
||||
console.log('setDefaultKnife', i);
|
||||
this._device.knifeList.forEach((knife, index) => {
|
||||
if (i == index) {
|
||||
knife.isDefaultCutKnife = true
|
||||
} else {
|
||||
knife.isDefaultCutKnife = false
|
||||
}
|
||||
});
|
||||
}
|
||||
getKnifeTypeOptions() {
|
||||
return knifeTypeOptions
|
||||
}
|
||||
/** 设置使用的解析器 */
|
||||
setDevice(device) {
|
||||
this._device = device
|
||||
}
|
||||
/** 根据解析器名称 设置解析器 */
|
||||
setDeviceByName(deviceName) {
|
||||
let deviceOption = this.deviceList.find(e => e.label == deviceName)
|
||||
if (deviceOption) {
|
||||
let device = deviceOption.device
|
||||
this.setDevice(new device())
|
||||
}
|
||||
}
|
||||
/** 根据解析器key 设置解析器 */
|
||||
setDeviceByKey(deviceKey) {
|
||||
let deviceOption = this.deviceList.find(e => e.value == deviceKey)
|
||||
if (deviceOption) {
|
||||
let device = deviceOption.device
|
||||
this.setDevice(new device())
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理指令代码 */
|
||||
handleCode(codeType, codeKey, params?) {
|
||||
let str = ''
|
||||
if (typeof (this._device[codeType][codeKey]) == 'function') {
|
||||
str += this._device[codeType][codeKey](params)
|
||||
} else {
|
||||
str += this._device[codeType][codeKey]
|
||||
}
|
||||
if (!codeType) {
|
||||
str = '// 未输入代码,请重新输入\n'
|
||||
} else if (!codeKey) {
|
||||
str = '// 未输入指令,请重新输入\n'
|
||||
let k = Object.keys(this._device[codeType])
|
||||
str += `// 代码【${codeType}】支持的指令为:${k.join(',')}`
|
||||
} else if (str == 'undefined') {
|
||||
str = `// 代码【${codeType}】指令【${codeKey}】解析失败,请检查代码指令配置\n`
|
||||
}
|
||||
// console.log('handleCode parserMain', str)
|
||||
/** 空行处理 */
|
||||
if (this._device.isFilterEmptyLine == true) {
|
||||
str = this._device.handleEmptyLine(str)
|
||||
}
|
||||
|
||||
// 空行前插入
|
||||
if (this._device.isNcLinePrefixEnabled == true) {
|
||||
str = this._device.handleEmptyLinePreFix(str)
|
||||
}
|
||||
// 空行后插入
|
||||
if (this._device.isNcLineSuffixEnabled == true) {
|
||||
str = this._device.handleEmptyLineSuffix(str)
|
||||
}
|
||||
|
||||
if (codeType == 'FEND') {
|
||||
this._device.lastCodeParams = undefined
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 解析器数据
|
||||
*
|
||||
*/
|
||||
loadProcessListData(data: CodeParamsList) {
|
||||
this.processListData = data
|
||||
}
|
||||
|
||||
checkProcessDataPosition(data: CodeParamsList): CodeParamsList {
|
||||
let newData = { ...data }
|
||||
|
||||
// 根据解析器的放置方式 转换数据
|
||||
for (let processList of newData.processList) {
|
||||
if (Array.isArray(processList.list) && processList.list.length > 0) {
|
||||
let newList = this._device.getRealPoints(processList.list)
|
||||
processList.list = newList
|
||||
}
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
/**根据 解析器收到的数据 生成 加工代码 */
|
||||
getCode(data: CodeParamsList) {
|
||||
let str = '';
|
||||
if (!data || !Array.isArray(data?.processList)) {
|
||||
console.error('无效数据: data.processList 必须是数组');
|
||||
return str;
|
||||
}
|
||||
|
||||
for (const item of data.processList) {
|
||||
if (!item?.processItemInfo) continue;
|
||||
const { code, order, codeParams } = item.processItemInfo;
|
||||
str += this.handleCode(code, order, code === 'C' ? item : codeParams);
|
||||
}
|
||||
|
||||
if (this._device?.isUseSimpleCode) {
|
||||
str = this._device.handleSimpleCode(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/** 导出文件 */
|
||||
async exportFile(data: CodeParamsList) {
|
||||
let zipFileName = data.processListInfo?.zipFileName || 'test.zip'
|
||||
this.zipFile.SetZipFileName(zipFileName)
|
||||
|
||||
let fileNameInfo = data.processListInfo?.fileName
|
||||
let fileName = ''
|
||||
if (typeof (fileNameInfo) == 'string') {
|
||||
fileName = fileNameInfo
|
||||
} else {
|
||||
fileName = `${fileNameInfo?.name || 'empty'}.${fileNameInfo?.Suffix || '.txt'}`
|
||||
}
|
||||
let str = '';
|
||||
for (const item of data.processList) {
|
||||
if (!item.processItemInfo) {
|
||||
continue
|
||||
}
|
||||
let code = item.processItemInfo.code
|
||||
let order = item.processItemInfo.order
|
||||
let params = item.processItemInfo.codeParams
|
||||
|
||||
if (code == 'C') {
|
||||
params = item
|
||||
}
|
||||
str += await this.handleCode(code, order, params)
|
||||
}
|
||||
|
||||
await this.zipFile.PushFile(fileName, str)
|
||||
|
||||
|
||||
await this.zipFile.Download()
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
setZipFileName(fileName: string) {
|
||||
this.zipFile.SetZipFileName(fileName)
|
||||
}
|
||||
/** 设置刀库 */
|
||||
setKnifeList(knifeList: Array<Knife | _knifeType>) {
|
||||
this._device.knifeList = knifeList
|
||||
}
|
||||
/** 获取解析器列表 */
|
||||
getDeviceList() {
|
||||
|
||||
return this.deviceList
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 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
|
||||
}
|
||||
|
||||
export const knifeTypeOptions = [
|
||||
{
|
||||
"value": "1",
|
||||
"label": "铣刀",
|
||||
},
|
||||
{
|
||||
"value": "2",
|
||||
"label": "成型刀",
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "钻头",
|
||||
},
|
||||
{
|
||||
"value": "4",
|
||||
"label": "锯片",
|
||||
},
|
||||
]
|
||||
|
||||
export const orginAbilitys = [
|
||||
{
|
||||
"value": "1",
|
||||
"label": "切割",
|
||||
},
|
||||
{
|
||||
"value": "2",
|
||||
"label": "拉槽",
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "铣型",
|
||||
},
|
||||
{
|
||||
"value": "4",
|
||||
"label": "铣孔",
|
||||
},
|
||||
{
|
||||
"value": "5",
|
||||
"label": "钻孔",
|
||||
},
|
||||
{
|
||||
"value": "6",
|
||||
"label": "拉米诺",
|
||||
},
|
||||
{
|
||||
"value": "7",
|
||||
"label": "乐扣/U形件",
|
||||
},
|
||||
{
|
||||
"value": "8",
|
||||
"label": "T形/燕尾槽刀",
|
||||
},
|
||||
]
|
||||
|
||||
export const knifeTypeAbilityMap = {
|
||||
钻头: ['钻孔'], // 3
|
||||
铣刀: ['切割', '拉槽', '铣型', '铣孔', '钻孔'], // 1
|
||||
成型刀: ['铣型', '拉米诺', '乐扣/U形件', 'T形/燕尾槽刀'], // 2
|
||||
锯片: ['拉槽', '拉米诺'],//4 '切割',下个版本支持
|
||||
}
|
905
src/processParser/processConfig/device1/parser1.ts
Normal file
905
src/processParser/processConfig/device1/parser1.ts
Normal file
@ -0,0 +1,905 @@
|
||||
/** 解析器1 */
|
||||
import { AxisType } from "../../confClass";
|
||||
import { _knifeType, _type, CodeParams, CodeParamsList, CodeParamsObj, configItem, GCodeAction, ProcessInfo,OriginZPosition } from "../../confClass";
|
||||
import { BoardPosition } from "../../confClass";
|
||||
import { FileZip } from "../../common/base/ZipFile";
|
||||
import { confList } from "./parser1Data";
|
||||
import { knifeData, knifeData1, knifeData2 } from "../../parseMain";
|
||||
|
||||
/**
|
||||
* 备注:
|
||||
* 文件导出模式(大板模式 || 小板模式)---作用于 加工项数据 和加工项的点阵数据
|
||||
* 应该在数据处理 生效
|
||||
* 解析器 只根据 板件的放置方式进行转换
|
||||
*/
|
||||
export class device1 {
|
||||
[key: string]: any;
|
||||
|
||||
/**G代码基类 */
|
||||
_Action = new GCodeAction()
|
||||
/** 刀库 */
|
||||
knifeList?: Array<_knifeType>
|
||||
/** 当前使用的刀具 */
|
||||
usedKnife?: _knifeType
|
||||
|
||||
/** 暴露给外部的参数 */
|
||||
config: configItem[] = confList
|
||||
/** 板厚 -- 读数据处理的数据 */
|
||||
thickness: number = 18
|
||||
/** 文件导出工具类 */
|
||||
zipFile?: FileZip
|
||||
|
||||
|
||||
/**是否执行换刀后的第一行精简指令 */
|
||||
doSimpleFirstCode: boolean = false
|
||||
|
||||
lastCodeParams?: CodeParams
|
||||
|
||||
/** 为了开发 方便 把一些配置的字段写在这里 */
|
||||
|
||||
/** ZIP压缩包文件名 */
|
||||
exportOrderPathName: string = '{2}.zip'
|
||||
/** NC文件编码 */
|
||||
ncFileEncoding: string = 'UTF-8'
|
||||
/** 是否添加注释信息 */
|
||||
isNcFileComment: boolean = true
|
||||
/** 是否空行插入前缀 */
|
||||
isNcLinePrefixEnabled: boolean = false
|
||||
/** 空行插入前缀 前缀内容*/
|
||||
ncLinePrefix: string = ''
|
||||
/** 文件名 */
|
||||
blockFileName: string = '{9}.ban'
|
||||
|
||||
/** 使用精简指令 */
|
||||
isUseSimpleCode: boolean = false
|
||||
/** 精简换刀后第一行指令 */
|
||||
isSimpleFirstCode: boolean = false
|
||||
/** 反转圆弧指令 */
|
||||
reverseArcCode: boolean = false
|
||||
/** 空程移动指令 */
|
||||
NcCodeFreeMove: string = 'G0'
|
||||
/** 直线插补标识 */
|
||||
NcCodeLineInterpolation: string = 'G1'
|
||||
/** 顺时针圆弧插补标识 */
|
||||
NcCodeClockwiseArcInterpolation: string = 'G2'
|
||||
/** 逆时针圆弧插补标识 */
|
||||
NcCodeAnticlockwiseArcInterpolation: string = 'G3'
|
||||
/** 水平坐标横轴标识 */
|
||||
NcCodeAxisX: string = 'Y'
|
||||
/** 水平坐标纵轴标识 */
|
||||
NcCodeAxisY: string = 'X'
|
||||
/** 垂直坐标轴标识 */
|
||||
NcCodeAxisZ: string = 'Z'
|
||||
/** 速度标识 */
|
||||
NcCodeSpeed: string = 'F'
|
||||
/** 水平坐标横轴增量标识 */
|
||||
NcCodeIncrementAxisX: string = 'I'
|
||||
/** 水平坐标纵轴增量标识 */
|
||||
NcCodeIncrementAxisY: string = 'J'
|
||||
/** 垂直坐标轴增量标识 */
|
||||
NcCodeIncrementAxisZ: string = 'K'
|
||||
/** 注释标识符 */
|
||||
leaderChar: string = '//'
|
||||
/** 解析器(输出机台)的坐标系基准 x */
|
||||
boardLength: number = 2440
|
||||
/** 解析器(输出机台)的坐标系基准 y */
|
||||
boardWidth: number = 1220
|
||||
/** 解析器(输出机台)的坐标系基准 z */
|
||||
boardHeight: number = 50
|
||||
|
||||
/** 水平基准 */
|
||||
originPointPosition: BoardPosition = BoardPosition.LEFT_TOP
|
||||
/** 垂直基准 */
|
||||
originZ0Position: OriginZPosition = OriginZPosition.BoardFace
|
||||
/** 水平纵轴坐标轴向 */
|
||||
widthSideAxis: AxisType = AxisType.X_POS
|
||||
/** 水平横轴坐标轴向 */
|
||||
lengthSideAxis: AxisType = AxisType.Y_POS
|
||||
/** 垂直轴坐标轴向 */
|
||||
heightAxis: AxisType = AxisType.Z_POS
|
||||
/** 大板定位 */
|
||||
boardLocation: BoardPosition = BoardPosition.LEFT_BOTTOM
|
||||
/** 大板长轴偏置 */
|
||||
boardOffsetY: number = 0
|
||||
/** 大板短轴偏置 */
|
||||
boardOffsetX: number = 0
|
||||
/** 导出文件 保留小数点位数 */
|
||||
decimalPointPrecision: number = 3
|
||||
/** 末尾补零 */
|
||||
fixFloatNumberEndZero: boolean = true
|
||||
/** 整数值末尾加小数点 */
|
||||
intNumberAddDecimalPoint: boolean = true
|
||||
/** 辅助开料指令 */
|
||||
helpCutKnifeCode: string = ''
|
||||
/** 辅助开料指令2 */
|
||||
helpCutKnifeCode2: string = ''
|
||||
|
||||
constructor() {
|
||||
this.knifeList = []
|
||||
this.knifeList.push(knifeData)
|
||||
this.knifeList.push(knifeData1)
|
||||
this.knifeList.push(knifeData2)
|
||||
}
|
||||
/**文件头 */
|
||||
FSTART: _type = {
|
||||
FSTART: `G54 G90\nM03\n`,
|
||||
// F1: `G54 G90\n`,
|
||||
// F2: `G53 G90\nM03\n`,
|
||||
}
|
||||
/** 换刀 */
|
||||
T: _type = {
|
||||
// T6: `T6 \nG43 H6\n`,
|
||||
// Ts6: `T6\n`,
|
||||
// T7: `T7\nG43 H7\n`,
|
||||
// ** 默认都走这个 调刀指令 若无需调刀则返回 '' 要调刀 则返回刀具的调刀代码 */
|
||||
// 20250508 将TD 定义为
|
||||
/**根据刀直径(半径)找刀 */
|
||||
|
||||
TD: (diameter, params?: any) => {
|
||||
let code = ''
|
||||
let _knife: _knifeType | undefined = undefined
|
||||
|
||||
if (diameter) {
|
||||
_knife = this.knifeList?.find(e => e.diameter == diameter)
|
||||
}
|
||||
|
||||
|
||||
if (_knife != undefined) {
|
||||
code += this.checkChangeKnifeEndCode(_knife)
|
||||
this.usedKnife = _knife
|
||||
// code += _knife.axisStartCode
|
||||
code += _knife.knifeStartCode
|
||||
}
|
||||
if (this.isSimpleFirstCode == true && this.lastCodeParams) {
|
||||
this.doSimpleFirstCode = true
|
||||
}
|
||||
return code
|
||||
},
|
||||
/**根据刀名称找刀 换刀 */
|
||||
TN: (knifeName, params?: any) => {
|
||||
let code = ''
|
||||
let _knife: any = null
|
||||
if (knifeName) {
|
||||
_knife = this.knifeList?.find(e => e.knifeName == knifeName)
|
||||
}
|
||||
|
||||
if (_knife != undefined) {
|
||||
code += this.checkChangeKnifeEndCode(_knife)
|
||||
this.usedKnife = _knife
|
||||
// code += _knife.axisStartCode
|
||||
code += _knife.knifeStartCode
|
||||
}
|
||||
if (this.isSimpleFirstCode == true && this.lastCodeParams) {
|
||||
this.doSimpleFirstCode = true
|
||||
}
|
||||
return code
|
||||
},
|
||||
// 停刀 + 停轴
|
||||
TE: () => {
|
||||
let code = ''
|
||||
if (this.usedKnife) {
|
||||
code += this.usedKnife.knifeStopCode + '\n'
|
||||
// code += this.usedKnife.axisStopCode + '\n'
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
}
|
||||
/**主程序 G0 - G3 辅助开料 */
|
||||
C: _type = {
|
||||
KnifeMove: (processItem: CodeParamsObj) => {
|
||||
let code = ''
|
||||
/** 获取调刀代码 */
|
||||
if (processItem.processItemInfo?.knife) {
|
||||
let changeKnifeCode = ''
|
||||
if (processItem.processItemInfo?.knife.diameter) {
|
||||
changeKnifeCode = this.T?.TD(processItem.processItemInfo?.knife.diameter)
|
||||
} else if (processItem.processItemInfo?.knife.knifeName) {
|
||||
changeKnifeCode = this.T?.TN(processItem.processItemInfo?.knife.knifeName)
|
||||
}
|
||||
|
||||
code += changeKnifeCode
|
||||
}
|
||||
|
||||
if (processItem.list.length > 0) {
|
||||
for (const item of processItem.list) {
|
||||
let point = item
|
||||
if (processItem.processItemInfo) {
|
||||
point = this.getXYZ(item, processItem.processItemInfo);
|
||||
}
|
||||
if (this.reverseArcCode == true) {
|
||||
if (point.dir == 2) {
|
||||
point.dir = 3
|
||||
} else if (point.dir == 3) {
|
||||
point.dir = 2
|
||||
}
|
||||
}
|
||||
code += `${this.KnifeMove_Arc(point)} \n`
|
||||
}
|
||||
} else {
|
||||
this.logError('doProcessItem fail,no processItem Data')
|
||||
}
|
||||
return code
|
||||
},
|
||||
HelpCutCode1: this.helpCutKnifeCode,
|
||||
HelpCutCode2: this.helpCutKnifeCode2
|
||||
}
|
||||
/**主程序结束 */
|
||||
CE: _type = {
|
||||
CE1: `M05\n`
|
||||
}
|
||||
/**文件尾 */
|
||||
FEND: _type = {
|
||||
FEND: `M30\n`,
|
||||
// FEND1: `M30\n`
|
||||
}
|
||||
/** 备注 */
|
||||
MARK: _type = {
|
||||
Mark: (str) => {
|
||||
let markContent = str + '\n'
|
||||
let isShowMark = this.isNcFileComment || false
|
||||
if (isShowMark) {
|
||||
let leaderChar = this.leaderChar || ''
|
||||
markContent = `${leaderChar} ${markContent}`
|
||||
} else {
|
||||
markContent = ''
|
||||
}
|
||||
return markContent + '\n'
|
||||
},
|
||||
BLOCKREMARK1: `//Cut Block`,
|
||||
}
|
||||
/** 文件操作指令 */
|
||||
FILE: _type = {
|
||||
START: (zipName) => {
|
||||
this.initZipFile()
|
||||
this.setZipFileName(zipName)
|
||||
},
|
||||
ADDFILE: (data) => {
|
||||
this.setFile(data)
|
||||
},
|
||||
END: () => {
|
||||
this.fileDownLoad()
|
||||
}
|
||||
}
|
||||
/** 根据分组key 返回配置项 */
|
||||
getConfigListByGroupKey(groupKey: string, groupCompment?: string[][]) {
|
||||
let list = this.config.filter(e => e.groupBy == groupKey)
|
||||
if (groupCompment) {
|
||||
for (const groupItem of groupCompment) {
|
||||
if (groupItem.length > 1) {
|
||||
let itemFirstOne = groupItem[0]
|
||||
let concatItem: configItem[] = [];
|
||||
let itemWithOutFirst = groupItem.filter((e, x) => x != 0)
|
||||
itemWithOutFirst.forEach(keyStr => {
|
||||
concatItem = list.filter(e => e.key == keyStr)
|
||||
list = list.filter(e => e.key != keyStr)
|
||||
})
|
||||
|
||||
let i = list.findIndex(e => e.key == itemFirstOne)
|
||||
|
||||
list[i].componentList = concatItem
|
||||
} else {
|
||||
// 分组的 配置项 如果少于2个 就不需要分组
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('getConfigListByGroupKey',groupKey,groupCompment,list);
|
||||
return list
|
||||
}
|
||||
/** 根据代码列表 获取指令列表 */
|
||||
getCodeListByOrderKeys(codeKeys: string[]) {
|
||||
let list = []
|
||||
codeKeys.forEach(code => {
|
||||
let orderList = this.getCodeByOrderKeys(code)
|
||||
list = list.concat(orderList)
|
||||
})
|
||||
return list
|
||||
}
|
||||
/** 根据代码 获取指令列表 */
|
||||
getCodeByOrderKeys(codeKey: string) {
|
||||
let list: any = []
|
||||
|
||||
let arr = Object.keys(this[codeKey])
|
||||
arr.forEach(str => {
|
||||
let temp = {
|
||||
code: codeKey,
|
||||
order: str
|
||||
}
|
||||
list.push(temp)
|
||||
})
|
||||
return list
|
||||
}
|
||||
//** 使用G0代码 移动刀具 */
|
||||
KnifeMove(params: CodeParams) {
|
||||
let res = this._Action.G0(params)
|
||||
return res + '\n'
|
||||
}
|
||||
/**
|
||||
* 根据传入的数据 生成 G0 || G1 || G2 || G3 代码
|
||||
*/
|
||||
KnifeMove_Arc(params: CodeParams) {
|
||||
let res: any = ''
|
||||
|
||||
let dir = params.dir || 0
|
||||
if (typeof (dir) == 'string') {
|
||||
dir = parseInt(dir)
|
||||
}
|
||||
|
||||
let usedParams: CodeParams = { ...params }
|
||||
if (this.doSimpleFirstCode == true && this.lastCodeParams) {
|
||||
for (const key in usedParams) {
|
||||
if (['x', 'y', 'z', 'f'].includes(key)) {
|
||||
if (usedParams[key] == this.lastCodeParams[key]) {
|
||||
Reflect.deleteProperty(usedParams, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.doSimpleFirstCode = false
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case 0:
|
||||
|
||||
if (typeof (this.NcCodeFreeMove) == 'string' && this.NcCodeFreeMove != '') {
|
||||
usedParams.codeKey = this.NcCodeFreeMove
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
usedParams.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
usedParams.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
usedParams.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
usedParams.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
res = this._Action.G0(usedParams)
|
||||
break;
|
||||
case 1:
|
||||
if (typeof (this.NcCodeLineInterpolation) == 'string' && this.NcCodeLineInterpolation != '') {
|
||||
usedParams.codeKey = this.NcCodeLineInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
usedParams.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
usedParams.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
usedParams.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
usedParams.fKey = this.NcCodeSpeed
|
||||
}
|
||||
res = this._Action.G1(usedParams)
|
||||
break;
|
||||
case 2:
|
||||
if (typeof (this.NcCodeClockwiseArcInterpolation) == 'string' && this.NcCodeClockwiseArcInterpolation != '') {
|
||||
usedParams.codeKey = this.NcCodeClockwiseArcInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
usedParams.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
usedParams.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
usedParams.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
usedParams.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
if (typeof (this.NcCodeIncrementAxisX) == 'string' && this.NcCodeIncrementAxisX != '') {
|
||||
usedParams.iKey = this.NcCodeIncrementAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisY) == 'string' && this.NcCodeIncrementAxisY != '') {
|
||||
usedParams.jKey = this.NcCodeIncrementAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisZ) == 'string' && this.NcCodeIncrementAxisZ != '') {
|
||||
usedParams.kKey = this.NcCodeIncrementAxisZ
|
||||
}
|
||||
res = this._Action.G2(usedParams)
|
||||
break;
|
||||
case 3:
|
||||
if (typeof (this.NcCodeAnticlockwiseArcInterpolation) == 'string' && this.NcCodeAnticlockwiseArcInterpolation != '') {
|
||||
usedParams.codeKey = this.NcCodeAnticlockwiseArcInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
usedParams.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
usedParams.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
usedParams.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
usedParams.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
if (typeof (this.NcCodeIncrementAxisX) == 'string' && this.NcCodeIncrementAxisX != '') {
|
||||
usedParams.iKey = this.NcCodeIncrementAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisY) == 'string' && this.NcCodeIncrementAxisY != '') {
|
||||
usedParams.jKey = this.NcCodeIncrementAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisZ) == 'string' && this.NcCodeIncrementAxisZ != '') {
|
||||
usedParams.kKey = this.NcCodeIncrementAxisZ
|
||||
}
|
||||
res = this._Action.G3(usedParams)
|
||||
break;
|
||||
default:
|
||||
res = ''
|
||||
break;
|
||||
}
|
||||
this.lastCodeParams = params
|
||||
return res
|
||||
}
|
||||
/** 换刀指令 --
|
||||
*
|
||||
* 因为设计的问题
|
||||
* 现在配置界面会直接配置换刀代码
|
||||
*
|
||||
*
|
||||
* 配置数据读取
|
||||
* @param str: 刀具名
|
||||
*/
|
||||
KnifeChange(str: String) {
|
||||
let res: any = null
|
||||
if (str != '') {
|
||||
res = this._Action.M06(str)
|
||||
}
|
||||
return res
|
||||
}
|
||||
/** 开始指令
|
||||
* 因为设计的问题
|
||||
* 相关配置从传入配置中读取
|
||||
*/
|
||||
startCode() {
|
||||
let res: any = null
|
||||
/** 文件头 */
|
||||
res = 'codeName\n'
|
||||
/** 轴启动 */
|
||||
res += this._Action.M03(18000)
|
||||
|
||||
/** 刀启动 */
|
||||
res = this._Action.M06('T1')
|
||||
|
||||
/** */
|
||||
return res
|
||||
}
|
||||
/** 结束指令
|
||||
* 因为设计的问题
|
||||
* 相关配置从传入配置中读取
|
||||
*/
|
||||
endCode() {
|
||||
let res: any = null
|
||||
|
||||
/**刀停止 */
|
||||
|
||||
/** 轴停止 */
|
||||
res = this._Action.M05()
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/** 处理空行过滤 */
|
||||
handleEmptyLine(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.filter(e => e != '')
|
||||
newArr = newArr.map(e => {
|
||||
return e + '\n'
|
||||
})
|
||||
|
||||
let newCode = newArr.join('')
|
||||
return newCode
|
||||
}
|
||||
/** 空行插入前缀 */
|
||||
handleEmptyLinePreFix(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.map(e => {
|
||||
if (e != '') {
|
||||
return e
|
||||
} else {
|
||||
return `${this.ncLinePrefix} ${e}\n`
|
||||
}
|
||||
})
|
||||
|
||||
let newCode = newArr.join('\n')
|
||||
return newCode
|
||||
}
|
||||
/** 空行插入后缀 */
|
||||
handleEmptyLineSuffix(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.map(e => {
|
||||
if (e != '') {
|
||||
return e
|
||||
} else {
|
||||
return `${e} ${this.ncLineSuffix}\n`
|
||||
}
|
||||
})
|
||||
|
||||
let newCode = newArr.join('\n')
|
||||
return newCode
|
||||
}
|
||||
/** 精简指令 */
|
||||
handleSimpleCode(code) {
|
||||
let arr = code.split('\n')
|
||||
let objArr = arr.map((e, i) => {
|
||||
let type = (!e.includes('//') && !e.includes('/**') || e.includes('"//')) ? 'code' : 'remark'
|
||||
let temp = {
|
||||
code: e,
|
||||
index: i,
|
||||
regList: e.match(/[XYZF]([-+]?\d*\.?\d+)/g) || [],
|
||||
type
|
||||
}
|
||||
return temp
|
||||
})
|
||||
let checkLines = objArr.filter(e => e.type == 'code')
|
||||
|
||||
// 校验对象 存键值
|
||||
let targetObject = {}
|
||||
for (const key in checkLines) {
|
||||
let line = checkLines[key]
|
||||
if (line.regList.length > 0) {
|
||||
for (const regVal of line.regList) {
|
||||
let reg = regVal.match(/([XYZF])([-+]?\d*\.?\d+)/)
|
||||
let FullVal = reg[0]
|
||||
let valKey = reg[1]
|
||||
let val = reg[2]
|
||||
if (Reflect.has(targetObject, valKey)) {
|
||||
if (targetObject[valKey] == val) {
|
||||
// 相同 需要精简操作
|
||||
objArr[line.index].code = objArr[line.index].code.replace(FullVal, ``)
|
||||
} else {
|
||||
// 不同 更新校验的值
|
||||
targetObject[valKey] = val
|
||||
}
|
||||
} else {
|
||||
// 键值 没有 赋给 校验对象
|
||||
Reflect.set(targetObject, valKey, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let newCode = objArr.map(e => e.code).join('\n')
|
||||
// 这里 数据中莫名其妙多了个空格 处理下
|
||||
let list = newCode.split(' ').filter(e => e != '')
|
||||
newCode = list.join(' ')
|
||||
return newCode
|
||||
}
|
||||
|
||||
/** 获取配置列表 */
|
||||
getConfigList() {
|
||||
return this.config
|
||||
}
|
||||
//** 设置配置列表 */
|
||||
setConfigList(List: configItem[]) {
|
||||
this.config = [...List]
|
||||
}
|
||||
//** 装载配置列表 */
|
||||
updateConfigList(List: configItem[]) {
|
||||
this.config = [...this.config, ...List]
|
||||
}
|
||||
/**根据数据导出文件 */
|
||||
async exportFile(data: CodeParamsList) {
|
||||
if (this.zipFile == undefined) {
|
||||
this.zipFile = new FileZip()
|
||||
}
|
||||
|
||||
let zipFileName = data.processListInfo?.zipFileName || 'test.zip'
|
||||
this.zipFile.SetZipFileName(zipFileName)
|
||||
|
||||
let fileNameInfo = data.processListInfo?.fileName
|
||||
let fileName = ''
|
||||
if (typeof (fileNameInfo) == 'string') {
|
||||
fileName = fileNameInfo
|
||||
} else {
|
||||
fileName = `${fileNameInfo?.name || 'empty'}.${fileNameInfo?.Suffix || '.txt'}`
|
||||
}
|
||||
let str = '';
|
||||
for (const item of data.processList) {
|
||||
if (!item.processItemInfo) {
|
||||
continue
|
||||
}
|
||||
let code = item.processItemInfo.code
|
||||
let order = item.processItemInfo.order
|
||||
let params = item.processItemInfo.codeParams
|
||||
|
||||
if (code == 'C') {
|
||||
params = item
|
||||
}
|
||||
str += await this.handleCode(code, order, params)
|
||||
}
|
||||
|
||||
await this.zipFile.PushFile(fileName, str)
|
||||
|
||||
|
||||
await this.zipFile.Download()
|
||||
}
|
||||
initZipFile() {
|
||||
this.zipFile = new FileZip()
|
||||
}
|
||||
/** 设置文件名 */
|
||||
async setZipFileName(zipName, fileZipObj?: FileZip) {
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
let zipFileName = zipName || 'empty.zip'
|
||||
if (fileZipObj != undefined) {
|
||||
this.fileZipObj.SetZipFileName(zipFileName)
|
||||
} else {
|
||||
this.zipFile.SetZipFileName(zipFileName)
|
||||
}
|
||||
|
||||
}
|
||||
/** 给zip文件内添加文件 */
|
||||
// async setFile(fileName, content, fileZipObj?: FileZip) {
|
||||
async setFile(data) {
|
||||
const { fileName, content, fileZipObj, isBase64, ncFileEncoding } = data
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
let _isBase64 = isBase64 || false
|
||||
let fileCode = ncFileEncoding || 'UTF-8'
|
||||
if (fileZipObj != undefined) {
|
||||
await this.fileZipObj.PushFile(fileName, content, _isBase64, fileCode)
|
||||
} else {
|
||||
await this.zipFile.PushFile(fileName, content, _isBase64, fileCode)
|
||||
}
|
||||
|
||||
}
|
||||
/** 下载zip */
|
||||
async fileDownLoad(fileZipObj?: FileZip) {
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
if (fileZipObj != undefined) {
|
||||
await this.fileZipObj.Download()
|
||||
} else {
|
||||
await this.zipFile.Download()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 处理C代码 */
|
||||
handleCode(codeType, codeKey, params?) {
|
||||
let str = ''
|
||||
if (typeof (this[codeType][codeKey]) == 'function') {
|
||||
str += this[codeType][codeKey](params)
|
||||
} else {
|
||||
str += this[codeType][codeKey]
|
||||
}
|
||||
if (!codeType) {
|
||||
str = '// 未输入代码,请重新输入\n'
|
||||
} else if (!codeKey) {
|
||||
str = '// 未输入指令,请重新输入\n'
|
||||
let k = Object.keys(this[codeType])
|
||||
str += `// 代码【${codeType}】支持的指令为:${k.join(',')}`
|
||||
} else if (str == 'undefined') {
|
||||
str = `// 代码【${codeType}】指令【${codeKey}】解析失败,请检查代码指令配置\n`
|
||||
}
|
||||
|
||||
console.log('handleCode', str)
|
||||
|
||||
// 输出文件未 清除上一条的刀路数据
|
||||
if (codeType == 'FEND') {
|
||||
this.lastCodeParams = undefined
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
/** 更新代码 */
|
||||
updateCode(data) {
|
||||
for (const codeItem of data) {
|
||||
this.updateOrder(codeItem.value, codeItem.orderArr)
|
||||
}
|
||||
}
|
||||
/** 更新指令 */
|
||||
updateOrder(code, orderArr) {
|
||||
// 更新前 指令的数据集
|
||||
let keys = Object.keys(this[code])
|
||||
|
||||
// 更新指令
|
||||
for (const orderItem of orderArr) {
|
||||
let key = orderItem.name
|
||||
let value = orderItem.value
|
||||
Reflect.set(this[code], key, value)
|
||||
}
|
||||
|
||||
// 移除指令
|
||||
let orderKeys = orderArr.map(e => e.name)
|
||||
|
||||
for (const key of keys) {
|
||||
if (!orderKeys.includes(key)) {
|
||||
if (typeof (this[code][key]) != 'function') {
|
||||
Reflect.deleteProperty(this[code], key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 添加备注 */
|
||||
blockRemark(params?) {
|
||||
let str = `//Cut Block Id=${params.boardId} No=${params.blockNo} W=${params.width} L=${params.length}\n`
|
||||
return str
|
||||
}
|
||||
/** */
|
||||
getFileHead(params?) {
|
||||
let str = `//根据参数生成文件头\n ${params}\n`
|
||||
return str
|
||||
}
|
||||
getRealPoints(list: CodeParams[]) {
|
||||
let newList = [...list]
|
||||
for (let point of newList) {
|
||||
point = this.getRealPoint(point)
|
||||
}
|
||||
}
|
||||
getRealPoint(point: CodeParams) {
|
||||
let newPoint = { ...point }
|
||||
|
||||
return newPoint
|
||||
}
|
||||
|
||||
/** 内部功能逻辑 换刀时判断是否需要 上一把刀的停止代码 */
|
||||
checkChangeKnifeEndCode(knife: _knifeType) {
|
||||
let code = '';
|
||||
/** true 需要 输出停刀代码 false 不需要 */
|
||||
let flag = false
|
||||
// 必须要有刀
|
||||
if (this.usedKnife) {
|
||||
let keys = Object.keys(knife)
|
||||
for (const key of keys) {
|
||||
if (knife[key] != this.usedKnife[key]) {
|
||||
flag = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
code += this.T.TE()
|
||||
}
|
||||
return code;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param point 加工项的点
|
||||
* @param processItemInfo 加工项的信息 水平基准、垂直基准、轴方向(x、y、z),板件方向(x、y、z)
|
||||
* // 这里加工项的点数据 都是经过数据处理的 假定这里拿到的数据都是基于左上角 台面
|
||||
*
|
||||
* @returns 实际加工项的点
|
||||
*/
|
||||
getXYZ(point: CodeParams, processItemInfo: ProcessInfo): CodeParams {
|
||||
let newPoint: any = {}
|
||||
for (const key in point) {
|
||||
if (point[key] != undefined) {
|
||||
Reflect.set(newPoint, key, parseFloat(point[key]))
|
||||
}
|
||||
}
|
||||
/** 有2个部分
|
||||
* 一个是基于机台和板件的转换 依据板件定位
|
||||
* 另外一个是基于板件和加工项的转换 依据板件长高方向*/
|
||||
switch (this.originZ0Position) {
|
||||
case 0:
|
||||
// 台面 不操作
|
||||
break;
|
||||
case 1:
|
||||
// 板面 Z坐标需要转换
|
||||
newPoint.z = newPoint.z - this.thickness
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** step 先转换板件的位置 */
|
||||
// 大板定位 不同 根据不同的定位点修改
|
||||
|
||||
// processItemInfo
|
||||
switch (this.originPointPosition) {
|
||||
case BoardPosition.LEFT_TOP:
|
||||
// 不操作
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.LEFT_BOTTOM:
|
||||
// 左下角 x坐标要转换
|
||||
newPoint.x = newPoint.x + this.boardWidth - processItemInfo.block.cutWidth //400
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.RIGHT_TOP:
|
||||
// 右上角 y坐标要转换
|
||||
newPoint.y = newPoint.y + this.boardLength - processItemInfo.block.cutLength // 600
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.RIGHT_BOTTOM:
|
||||
// 右下角 xy 坐标要转换
|
||||
newPoint.x = newPoint.x + this.boardWidth - processItemInfo.block.cutWidth
|
||||
newPoint.y = newPoint.y + this.boardLength - processItemInfo.block.cutLength
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 这里做 数值的小数点处理
|
||||
for (const key in newPoint) {
|
||||
if (['x', 'y', 'z', 'r', 'i', 'j', 'k'].includes(key)) {
|
||||
let isTofix = false
|
||||
if (this.fixFloatNumberEndZero == true) {
|
||||
// 末尾补零
|
||||
isTofix = true
|
||||
} else if (this.intNumberAddDecimalPoint == true) {
|
||||
// 整数值末尾加小数点
|
||||
if (Number.isInteger(newPoint[key])) {
|
||||
isTofix = true
|
||||
}
|
||||
}
|
||||
if (isTofix) {
|
||||
newPoint[key] = parseFloat(newPoint[key]).toFixed(this.decimalPointPrecision)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return newPoint
|
||||
}
|
||||
/** 根据 轴向变更坐标 */
|
||||
changeXYZAxiosSide(point: CodeParams) {
|
||||
let newPoint: any = {}
|
||||
for (const key in point) {
|
||||
if (point[key] != undefined) {
|
||||
Reflect.set(newPoint, key, parseFloat(point[key]))
|
||||
}
|
||||
}
|
||||
let width = this.boardWidth
|
||||
let length = this.boardLength
|
||||
let height = this.boardHeight
|
||||
|
||||
|
||||
if (this.widthSideAxis == AxisType.X_POS && this.lengthSideAxis == AxisType.Y_POS) {
|
||||
// 默认 为 X = x 正 Y = y 正 不操作
|
||||
} else if (this.widthSideAxis == AxisType.Y_POS && this.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y正 y = x正 X Y坐标 倒转
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_NEG && this.lengthSideAxis == AxisType.Y_POS) {
|
||||
// x = x负 y = y正
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y }
|
||||
} else if (this.widthSideAxis == AxisType.Y_POS && this.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y正 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_NEG && this.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x负 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y - length }
|
||||
} else if (this.widthSideAxis == AxisType.Y_NEG && this.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y负 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_POS && this.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x正 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x, y: newPoint.y - length }
|
||||
} else if (this.widthSideAxis == AxisType.Y_NEG && this.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y负 y = x正
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
if (this.heightAxis == AxisType.Z_NEG) {
|
||||
// Z轴负
|
||||
newPoint = { ...newPoint, z: newPoint.z - height }
|
||||
}
|
||||
|
||||
return newPoint
|
||||
}
|
||||
}
|
||||
|
1202
src/processParser/processConfig/device1/parser1Data.ts
Normal file
1202
src/processParser/processConfig/device1/parser1Data.ts
Normal file
File diff suppressed because it is too large
Load Diff
767
src/processParser/processConfig/device2/parser2.ts
Normal file
767
src/processParser/processConfig/device2/parser2.ts
Normal file
@ -0,0 +1,767 @@
|
||||
import { AxisType } from "../../confClass"
|
||||
import { _knifeType, _type, CodeParams, CodeParamsList, CodeParamsObj, configItem, GCodeAction, ProcessInfo, processServeBase } from "../../confClass";
|
||||
import { BoardPosition } from "../../confClass"
|
||||
import { FileZip } from "../../common/base/ZipFile";
|
||||
import { OriginZPosition } from "../../confClass"
|
||||
|
||||
/** 解析器2 */
|
||||
export class device2 extends processServeBase {
|
||||
[key: string]: any;
|
||||
|
||||
/**G代码基类 */
|
||||
_Action = new GCodeAction()
|
||||
/** 刀库 */
|
||||
knifeList?: Array<_knifeType> = []
|
||||
/** 当前使用的刀具 */
|
||||
// usedKnife?: Knife | _knifeType
|
||||
|
||||
/** 暴露给外部的参数 */
|
||||
config: configItem[] = []
|
||||
/** 板厚 -- 读数据处理的数据 */
|
||||
thickness: number = 18
|
||||
/** 文件导出工具类 */
|
||||
zipFile?: FileZip
|
||||
|
||||
/** 解析器(输出机台)的坐标系基准 x */
|
||||
boardLength: number = 2440
|
||||
/** 解析器(输出机台)的坐标系基准 y */
|
||||
boardWidth: number = 1220
|
||||
/** 解析器(输出机台)的坐标系基准 z */
|
||||
boardHeight: number = 50
|
||||
|
||||
/** 水平基准 */
|
||||
originPointPosition: BoardPosition = BoardPosition.LEFT_TOP
|
||||
/** 垂直基准 */
|
||||
originZ0Position: OriginZPosition = OriginZPosition.BoardFace
|
||||
/** 水平纵轴坐标轴向 */
|
||||
widthSideAxis: AxisType = AxisType.X_POS
|
||||
/** 水平横轴坐标轴向 */
|
||||
lengthSideAxis: AxisType = AxisType.Y_POS
|
||||
/** 垂直轴坐标轴向 */
|
||||
heightAxis: AxisType = AxisType.Z_POS
|
||||
/** 大板定位 */
|
||||
boardLocation: BoardPosition = BoardPosition.LEFT_BOTTOM
|
||||
/** 大板长轴偏置 */
|
||||
boardOffsetY: number = 0
|
||||
/** 大板短轴偏置 */
|
||||
boardOffsetX: number = 0
|
||||
/** 导出文件 保留小数点位数 */
|
||||
decimalPointPrecision: number = 3
|
||||
/** 末尾补零 */
|
||||
fixFloatNumberEndZero: boolean = true
|
||||
/** 整数值末尾加小数点 */
|
||||
intNumberAddDecimalPoint: boolean = true
|
||||
/** 辅助开料指令 */
|
||||
helpCutKnifeCode: string = ''
|
||||
/** 辅助开料指令2 */
|
||||
helpCutKnifeCode2: string = ''
|
||||
constructor() {
|
||||
super()
|
||||
this.knifeList = []
|
||||
// this.knifeList.push(knifeData)
|
||||
// this.knifeList.push(knifeData1)
|
||||
}
|
||||
/**文件头 */
|
||||
FSTART: _type = {
|
||||
F1: `G54 G90\n`,
|
||||
F2: `G53 G90\nM03\n`,
|
||||
}
|
||||
/** 换刀 */
|
||||
T: _type = {
|
||||
T6: `T6 \nG43 H6\n`,
|
||||
Ts6: `T6\n`,
|
||||
T7: `T7\nG43 H7\n`,
|
||||
// ** 默认都走这个 调刀指令 若无需调刀则返回 '' 要调刀 则返回刀具的调刀代码 */
|
||||
// 20250508 将TD 定义为
|
||||
/**根据刀直径(半径)找刀 */
|
||||
TD: (params) => {
|
||||
let code = ''
|
||||
let _knife: _knifeType | undefined = undefined
|
||||
|
||||
if (params?.diameter) {
|
||||
_knife = this.knifeList?.find(e => e.diameter == params.diameter)
|
||||
}
|
||||
if (_knife != undefined) {
|
||||
code += this.checkChangeKnifeEndCode(_knife)
|
||||
this.usedKnife = _knife
|
||||
// code += _knife.axisStartCode
|
||||
code += _knife.knifeStartCode
|
||||
}
|
||||
return code
|
||||
},
|
||||
/**根据刀名称找刀 换刀 */
|
||||
TN: (params) => {
|
||||
let code = ''
|
||||
let _knife: any = null
|
||||
if (params?.knifeName) {
|
||||
_knife = this.knifeList?.find(e => e.knifeName == params.knifeName)
|
||||
}
|
||||
|
||||
if (_knife != undefined) {
|
||||
code += this.checkChangeKnifeEndCode(_knife)
|
||||
this.usedKnife = _knife
|
||||
// code += _knife.axisStartCode
|
||||
code += _knife.knifeStartCode
|
||||
}
|
||||
return code
|
||||
},
|
||||
// 停刀 + 停轴
|
||||
TE: () => {
|
||||
let code = ''
|
||||
if (this.usedKnife) {
|
||||
code += this.usedKnife.knifeStopCode + '\n'
|
||||
code += this.usedKnife.axisStopCode + '\n'
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
}
|
||||
/**主程序 G0 - G3 辅助开料 */
|
||||
C: _type = {
|
||||
KnifeMove: (processItem: CodeParamsObj) => {
|
||||
let code = ''
|
||||
/** 获取调刀代码 */
|
||||
if (processItem.processItemInfo?.knife) {
|
||||
let changeKnifeCode = ''
|
||||
if (processItem.processItemInfo?.knife.diameter) {
|
||||
changeKnifeCode = this.T?.TD(processItem.processItemInfo?.knife.diameter)
|
||||
} else if (processItem.processItemInfo?.knife.knifeName) {
|
||||
changeKnifeCode = this.T?.TN(processItem.processItemInfo?.knife.knifeName)
|
||||
}
|
||||
|
||||
code += changeKnifeCode
|
||||
}
|
||||
|
||||
if (processItem.list.length > 0) {
|
||||
for (const item of processItem.list) {
|
||||
let point = item
|
||||
if (processItem.processItemInfo) {
|
||||
point = this.getXYZ(item, processItem.processItemInfo);
|
||||
}
|
||||
|
||||
code += `${this.KnifeMove_Arc(point)} \n`
|
||||
}
|
||||
} else {
|
||||
|
||||
this.logError('doProcessItem fail,no processItem Data')
|
||||
}
|
||||
return code
|
||||
},
|
||||
HelpCutCode1: this.helpCutKnifeCode,
|
||||
HelpCutCode2: this.helpCutKnifeCode2
|
||||
}
|
||||
/**主程序结束 */
|
||||
CE: _type = {
|
||||
CE1: `M05\n`
|
||||
}
|
||||
/**文件尾 */
|
||||
FEND: _type = {
|
||||
FEND1: `M30\n`
|
||||
}
|
||||
/** 备注 */
|
||||
MARK: _type = {
|
||||
Mark: (str) => {
|
||||
let markContent = str + '\n'
|
||||
let isShowMark = this.isNcFileComment || false
|
||||
if (isShowMark) {
|
||||
let leaderChar = this.leaderChar || ''
|
||||
markContent = `${leaderChar} ${markContent}`
|
||||
} else {
|
||||
markContent = ''
|
||||
}
|
||||
return markContent + '\n'
|
||||
},
|
||||
BLOCKREMARK1: `//Cut Block`,
|
||||
}
|
||||
/** 文件操作指令 */
|
||||
FILE: _type = {
|
||||
START: (zipName) => {
|
||||
this.initZipFile()
|
||||
this.setZipFileName(zipName)
|
||||
},
|
||||
ADDFILE: (data) => {
|
||||
this.setFile(data)
|
||||
},
|
||||
END: () => {
|
||||
this.fileDownLoad()
|
||||
}
|
||||
}
|
||||
/** 根据分组key 返回配置项 */
|
||||
getConfigListByGroupKey(groupKey: string) {
|
||||
let list = this.config.filter(e => e.groupBy == groupKey)
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
//** 使用G0代码 移动刀具 */
|
||||
KnifeMove(params: CodeParams) {
|
||||
let res = this._Action.G0(params)
|
||||
return res + '\n'
|
||||
}
|
||||
/**
|
||||
* 根据传入的数据 生成 G0 || G1 || G2 || G3 代码
|
||||
*/
|
||||
KnifeMove_Arc(params: CodeParams) {
|
||||
let res: any = ''
|
||||
|
||||
let dir = params.dir || 0
|
||||
if (typeof (dir) == 'string') {
|
||||
dir = parseInt(dir)
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case 0:
|
||||
|
||||
if (typeof (this.NcCodeFreeMove) == 'string' && this.NcCodeFreeMove != '') {
|
||||
params.codeKey = this.NcCodeFreeMove
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
params.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
params.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
params.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
params.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
res = this._Action.G0(params)
|
||||
break;
|
||||
case 1:
|
||||
if (typeof (this.NcCodeLineInterpolation) == 'string' && this.NcCodeLineInterpolation != '') {
|
||||
params.codeKey = this.NcCodeLineInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
params.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
params.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
params.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
params.fKey = this.NcCodeSpeed
|
||||
}
|
||||
res = this._Action.G1(params)
|
||||
break;
|
||||
case 2:
|
||||
if (typeof (this.NcCodeClockwiseArcInterpolation) == 'string' && this.NcCodeClockwiseArcInterpolation != '') {
|
||||
params.codeKey = this.NcCodeClockwiseArcInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
params.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
params.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
params.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
params.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
if (typeof (this.NcCodeIncrementAxisX) == 'string' && this.NcCodeIncrementAxisX != '') {
|
||||
params.iKey = this.NcCodeIncrementAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisY) == 'string' && this.NcCodeIncrementAxisY != '') {
|
||||
params.jKey = this.NcCodeIncrementAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisZ) == 'string' && this.NcCodeIncrementAxisZ != '') {
|
||||
params.kKey = this.NcCodeIncrementAxisZ
|
||||
}
|
||||
res = this._Action.G2(params)
|
||||
break;
|
||||
case 3:
|
||||
if (typeof (this.NcCodeAnticlockwiseArcInterpolation) == 'string' && this.NcCodeAnticlockwiseArcInterpolation != '') {
|
||||
params.codeKey = this.NcCodeAnticlockwiseArcInterpolation
|
||||
}
|
||||
if (typeof (this.NcCodeAxisX) == 'string' && this.NcCodeAxisX != '') {
|
||||
params.xKey = this.NcCodeAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeAxisY) == 'string' && this.NcCodeAxisY != '') {
|
||||
params.yKey = this.NcCodeAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeAxisZ) == 'string' && this.NcCodeAxisZ != '') {
|
||||
params.zKey = this.NcCodeAxisZ
|
||||
}
|
||||
if (typeof (this.NcCodeSpeed) == 'string' && this.NcCodeSpeed != '') {
|
||||
params.fKey = this.NcCodeSpeed
|
||||
}
|
||||
|
||||
if (typeof (this.NcCodeIncrementAxisX) == 'string' && this.NcCodeIncrementAxisX != '') {
|
||||
params.iKey = this.NcCodeIncrementAxisX
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisY) == 'string' && this.NcCodeIncrementAxisY != '') {
|
||||
params.jKey = this.NcCodeIncrementAxisY
|
||||
}
|
||||
if (typeof (this.NcCodeIncrementAxisZ) == 'string' && this.NcCodeIncrementAxisZ != '') {
|
||||
params.kKey = this.NcCodeIncrementAxisZ
|
||||
}
|
||||
res = this._Action.G3(params)
|
||||
break;
|
||||
default:
|
||||
res = ''
|
||||
break;
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
/** 换刀指令 --
|
||||
*
|
||||
* 因为设计的问题
|
||||
* 现在配置界面会直接配置换刀代码
|
||||
*
|
||||
*
|
||||
* 配置数据读取
|
||||
* @param str: 刀具名
|
||||
*/
|
||||
KnifeChange(str: String) {
|
||||
let res: any = null
|
||||
if (str != '') {
|
||||
res = this._Action.M06(str)
|
||||
}
|
||||
return res
|
||||
}
|
||||
/** 开始指令
|
||||
* 因为设计的问题
|
||||
* 相关配置从传入配置中读取
|
||||
*/
|
||||
startCode() {
|
||||
let res: any = null
|
||||
/** 文件头 */
|
||||
res = 'codeName\n'
|
||||
/** 轴启动 */
|
||||
res += this._Action.M03(18000)
|
||||
|
||||
/** 刀启动 */
|
||||
res = this._Action.M06('T1')
|
||||
|
||||
/** */
|
||||
return res
|
||||
}
|
||||
/** 结束指令
|
||||
* 因为设计的问题
|
||||
* 相关配置从传入配置中读取
|
||||
*/
|
||||
endCode() {
|
||||
let res: any = null
|
||||
|
||||
/**刀停止 */
|
||||
|
||||
/** 轴停止 */
|
||||
res = this._Action.M05()
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/** 处理空行过滤 */
|
||||
handleEmptyLine(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.filter(e => e != '')
|
||||
newArr = newArr.map(e => {
|
||||
return e + '\n'
|
||||
})
|
||||
|
||||
let newCode = newArr.join('')
|
||||
return newCode
|
||||
}
|
||||
/** 空行插入前缀 */
|
||||
handleEmptyLinePreFix(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.map(e => {
|
||||
if (e != '') {
|
||||
return e
|
||||
} else {
|
||||
return `${this.ncLinePrefix} ${e}\n`
|
||||
}
|
||||
})
|
||||
|
||||
let newCode = newArr.join('\n')
|
||||
return newCode
|
||||
}
|
||||
/** 空行插入后缀 */
|
||||
handleEmptyLineSuffix(code) {
|
||||
let arr = code.split('\n')
|
||||
let newArr = arr.map(e => {
|
||||
if (e != '') {
|
||||
return e
|
||||
} else {
|
||||
return `${e} ${this.ncLineSuffix}\n`
|
||||
}
|
||||
})
|
||||
|
||||
let newCode = newArr.join('\n')
|
||||
return newCode
|
||||
}
|
||||
/** 精简指令 */
|
||||
handleSimpleCode(code) {
|
||||
let arr = code.split('\n')
|
||||
let objArr = arr.map((e, i) => {
|
||||
let type = (!e.includes('//') && !e.includes('/**') || e.includes('"//')) ? 'code' : 'remark'
|
||||
let temp = {
|
||||
code: e,
|
||||
index: i,
|
||||
regList: e.match(/[XYZF]([-+]?\d*\.?\d+)/g) || [],
|
||||
type
|
||||
}
|
||||
return temp
|
||||
})
|
||||
let checkLines = objArr.filter(e => e.type == 'code')
|
||||
|
||||
// 校验对象 存键值
|
||||
let targetObject = {}
|
||||
for (const key in checkLines) {
|
||||
let line = checkLines[key]
|
||||
if (line.regList.length > 0) {
|
||||
for (const regVal of line.regList) {
|
||||
let reg = regVal.match(/([XYZF])([-+]?\d*\.?\d+)/)
|
||||
let FullVal = reg[0]
|
||||
let valKey = reg[1]
|
||||
let val = reg[2]
|
||||
if (Reflect.has(targetObject, valKey)) {
|
||||
if (targetObject[valKey] == val) {
|
||||
// 相同 需要精简操作
|
||||
objArr[line.index].code = objArr[line.index].code.replace(FullVal, ``)
|
||||
} else {
|
||||
// 不同 更新校验的值
|
||||
targetObject[valKey] = val
|
||||
}
|
||||
} else {
|
||||
// 键值 没有 赋给 校验对象
|
||||
Reflect.set(targetObject, valKey, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let newCode = objArr.map(e => e.code).join('\n')
|
||||
// 这里 数据中莫名其妙多了个空格 处理下
|
||||
let list = newCode.split(' ').filter(e => e != '')
|
||||
newCode = list.join(' ')
|
||||
return newCode
|
||||
}
|
||||
|
||||
/** 获取配置列表 */
|
||||
getConfigList() {
|
||||
return this.config
|
||||
}
|
||||
//** 设置配置列表 */
|
||||
setConfigList(List: configItem[]) {
|
||||
this.config = [...List]
|
||||
}
|
||||
//** 装载配置列表 */
|
||||
updateConfigList(List: configItem[]) {
|
||||
this.config = [...this.config, ...List]
|
||||
}
|
||||
/**根据数据导出文件 */
|
||||
async exportFile(data: CodeParamsList) {
|
||||
if (this.zipFile == undefined) {
|
||||
this.zipFile = new FileZip()
|
||||
}
|
||||
|
||||
let zipFileName = data.processListInfo?.zipFileName || 'test.zip'
|
||||
this.zipFile.SetZipFileName(zipFileName)
|
||||
|
||||
let fileNameInfo = data.processListInfo?.fileName
|
||||
let fileName = ''
|
||||
if (typeof (fileNameInfo) == 'string') {
|
||||
fileName = fileNameInfo
|
||||
} else {
|
||||
fileName = `${fileNameInfo?.name || 'empty'}.${fileNameInfo?.Suffix || '.txt'}`
|
||||
}
|
||||
let str = '';
|
||||
for (const item of data.processList) {
|
||||
if (!item.processItemInfo) {
|
||||
continue
|
||||
}
|
||||
let code = item.processItemInfo.code
|
||||
let order = item.processItemInfo.order
|
||||
let params = item.processItemInfo.codeParams
|
||||
|
||||
if (code == 'C') {
|
||||
params = item
|
||||
}
|
||||
str += await this.handleCode(code, order, params)
|
||||
}
|
||||
|
||||
await this.zipFile.PushFile(fileName, str)
|
||||
|
||||
|
||||
await this.zipFile.Download()
|
||||
}
|
||||
initZipFile() {
|
||||
this.zipFile = new FileZip()
|
||||
}
|
||||
/** 设置文件名 */
|
||||
async setZipFileName(zipName, fileZipObj?: FileZip) {
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
let zipFileName = zipName || 'empty.zip'
|
||||
if (fileZipObj != undefined) {
|
||||
this.fileZipObj.SetZipFileName(zipFileName)
|
||||
} else {
|
||||
this.zipFile.SetZipFileName(zipFileName)
|
||||
}
|
||||
|
||||
}
|
||||
/** 给zip文件内添加文件 */
|
||||
// async setFile(fileName, content, fileZipObj?: FileZip) {
|
||||
async setFile(data) {
|
||||
const { fileName, content, fileZipObj, isBase64, ncFileEncoding } = data
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
let _isBase64 = isBase64 || false
|
||||
let fileCode = ncFileEncoding || 'UTF-8'
|
||||
if (fileZipObj != undefined) {
|
||||
await this.fileZipObj.PushFile(fileName, content, _isBase64, fileCode)
|
||||
} else {
|
||||
await this.zipFile.PushFile(fileName, content, _isBase64, fileCode)
|
||||
}
|
||||
|
||||
}
|
||||
/** 下载zip */
|
||||
async fileDownLoad(fileZipObj?: FileZip) {
|
||||
if (this.zipFile == undefined) {
|
||||
return
|
||||
}
|
||||
if (fileZipObj != undefined) {
|
||||
await this.fileZipObj.Download()
|
||||
} else {
|
||||
await this.zipFile.Download()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 处理C代码 */
|
||||
handleCode(codeType, codeKey, params?) {
|
||||
let str = ''
|
||||
if (typeof (this[codeType][codeKey]) == 'function') {
|
||||
str += this[codeType][codeKey](params)
|
||||
} else {
|
||||
str += this[codeType][codeKey]
|
||||
}
|
||||
if (!codeType) {
|
||||
str = '// 未输入代码,请重新输入\n'
|
||||
} else if (!codeKey) {
|
||||
str = '// 未输入指令,请重新输入\n'
|
||||
let k = Object.keys(this[codeType])
|
||||
str += `// 代码【${codeType}】支持的指令为:${k.join(',')}`
|
||||
} else if (str == 'undefined') {
|
||||
str = `// 代码【${codeType}】指令【${codeKey}】解析失败,请检查代码指令配置\n`
|
||||
}
|
||||
console.log('handleCode', str)
|
||||
return str
|
||||
}
|
||||
/** 更新代码 */
|
||||
updateCode(data) {
|
||||
for (const codeItem of data) {
|
||||
this.updateOrder(codeItem.value, codeItem.orderArr)
|
||||
}
|
||||
}
|
||||
/** 更新指令 */
|
||||
updateOrder(code, orderArr) {
|
||||
// 更新前 指令的数据集
|
||||
let keys = Object.keys(this[code])
|
||||
|
||||
// 更新指令
|
||||
for (const orderItem of orderArr) {
|
||||
let key = orderItem.name
|
||||
let value = orderItem.value
|
||||
Reflect.set(this[code], key, value)
|
||||
}
|
||||
|
||||
// 移除指令
|
||||
let orderKeys = orderArr.map(e => e.name)
|
||||
|
||||
for (const key of keys) {
|
||||
if (!orderKeys.includes(key)) {
|
||||
if (typeof (this[code][key]) != 'function') {
|
||||
Reflect.deleteProperty(this[code], key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 添加备注 */
|
||||
blockRemark(params?) {
|
||||
let str = `//Cut Block Id=${params.boardId} No=${params.blockNo} W=${params.width} L=${params.length}\n`
|
||||
return str
|
||||
}
|
||||
/** */
|
||||
getFileHead(params?) {
|
||||
let str = `//根据参数生成文件头\n ${params}\n`
|
||||
return str
|
||||
}
|
||||
getRealPoints(list: CodeParams[]) {
|
||||
let newList = [...list]
|
||||
for (let point of newList) {
|
||||
point = this.getRealPoint(point)
|
||||
}
|
||||
}
|
||||
getRealPoint(point: CodeParams) {
|
||||
let newPoint = { ...point }
|
||||
|
||||
return newPoint
|
||||
}
|
||||
|
||||
/** 内部功能逻辑 换刀时判断是否需要 上一把刀的停止代码 */
|
||||
checkChangeKnifeEndCode(knife: _knifeType) {
|
||||
let code = '';
|
||||
/** true 需要 输出停刀代码 false 不需要 */
|
||||
let flag = false
|
||||
// 必须要有刀
|
||||
if (this.usedKnife) {
|
||||
let keys = Object.keys(knife)
|
||||
for (const key of keys) {
|
||||
if (knife[key] != this.usedKnife[key]) {
|
||||
flag = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
code += this.T.TE()
|
||||
}
|
||||
return code;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param point 加工项的点
|
||||
* @param processItemInfo 加工项的信息 水平基准、垂直基准、轴方向(x、y、z),板件方向(x、y、z)
|
||||
* // 这里加工项的点数据 都是经过数据处理的 假定这里拿到的数据都是基于左上角 台面
|
||||
*
|
||||
* @returns 实际加工项的点
|
||||
*/
|
||||
getXYZ(point: CodeParams, processItemInfo: ProcessInfo): CodeParams {
|
||||
let newPoint: any = {}
|
||||
for (const key in point) {
|
||||
if (point[key] != undefined) {
|
||||
Reflect.set(newPoint, key, parseFloat(point[key]))
|
||||
}
|
||||
}
|
||||
/** 有2个部分
|
||||
* 一个是基于机台和板件的转换 依据板件定位
|
||||
* 另外一个是基于板件和加工项的转换 依据板件长高方向*/
|
||||
switch (this.originZ0Position) {
|
||||
case 0:
|
||||
// 台面 不操作
|
||||
break;
|
||||
case 1:
|
||||
// 板面 Z坐标需要转换
|
||||
newPoint.z = newPoint.z - this.thickness
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** step 先转换板件的位置 */
|
||||
// 大板定位 不同 根据不同的定位点修改
|
||||
|
||||
// processItemInfo
|
||||
switch (this.boardLocation) {
|
||||
case BoardPosition.LEFT_TOP:
|
||||
// 不操作
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.LEFT_BOTTOM:
|
||||
// 左下角 x坐标要转换
|
||||
newPoint.x = newPoint.x + this.boardWidth - processItemInfo.block.cutWidth //400
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.RIGHT_TOP:
|
||||
// 右上角 y坐标要转换
|
||||
newPoint.y = newPoint.y + this.boardLength - processItemInfo.block.cutLength // 600
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
case BoardPosition.RIGHT_BOTTOM:
|
||||
// 右下角 xy 坐标要转换
|
||||
newPoint.x = newPoint.x + this.boardWidth - processItemInfo.block.cutWidth
|
||||
newPoint.y = newPoint.y + this.boardLength - processItemInfo.block.cutLength
|
||||
newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 这里做 数值的小数点处理
|
||||
for (const key in newPoint) {
|
||||
if (['x', 'y', 'z', 'r', 'i', 'j', 'k'].includes(key)) {
|
||||
let isTofix = false
|
||||
if (this.fixFloatNumberEndZero == true) {
|
||||
// 末尾补零
|
||||
isTofix = true
|
||||
} else if (this.intNumberAddDecimalPoint == true) {
|
||||
// 整数值末尾加小数点
|
||||
if (Number.isInteger(newPoint[key])) {
|
||||
isTofix = true
|
||||
}
|
||||
}
|
||||
if (isTofix) {
|
||||
newPoint[key] = parseFloat(newPoint[key]).toFixed(this.decimalPointPrecision)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return newPoint
|
||||
}
|
||||
/** 根据 轴向变更坐标 */
|
||||
changeXYZAxiosSide(point: CodeParams) {
|
||||
let newPoint: any = {}
|
||||
for (const key in point) {
|
||||
if (point[key] != undefined) {
|
||||
Reflect.set(newPoint, key, parseFloat(point[key]))
|
||||
}
|
||||
}
|
||||
let width = this.boardWidth
|
||||
let length = this.boardLength
|
||||
let height = this.boardHeight
|
||||
|
||||
|
||||
if (this.widthSideAxis == AxisType.X_POS && this.lengthSideAxis == AxisType.Y_POS) {
|
||||
// 默认 为 X = x 正 Y = y 正 不操作
|
||||
} else if (this.widthSideAxis == AxisType.Y_POS && this.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y正 y = x正 X Y坐标 倒转
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_NEG && this.lengthSideAxis == AxisType.Y_POS) {
|
||||
// x = x负 y = y正
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y }
|
||||
} else if (this.widthSideAxis == AxisType.Y_POS && this.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y正 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_NEG && this.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x负 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y - length }
|
||||
} else if (this.widthSideAxis == AxisType.Y_NEG && this.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y负 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
else if (this.widthSideAxis == AxisType.X_POS && this.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x正 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x, y: newPoint.y - length }
|
||||
} else if (this.widthSideAxis == AxisType.Y_NEG && this.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y负 y = x正
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
if (this.heightAxis == AxisType.Z_NEG) {
|
||||
// Z轴负
|
||||
newPoint = { ...newPoint, z: newPoint.z - height }
|
||||
}
|
||||
|
||||
return newPoint
|
||||
}
|
||||
}
|
23
src/processParser/readme.md
Normal file
23
src/processParser/readme.md
Normal file
@ -0,0 +1,23 @@
|
||||
# 解析器说明
|
||||
|
||||
## 解析器接收数据 生成加工文件
|
||||
·数据说明
|
||||
1、刀路描述
|
||||
2、调用的指令
|
||||
3、加工项附带的信息(加工项所在板件信息和所在订单信息)
|
||||
4、刀具信息
|
||||
|
||||
### 数据传输思考
|
||||
1、文件头
|
||||
|
||||
2、调刀代码 ----有调刀逻辑
|
||||
|
||||
3、主程序
|
||||
|
||||
4、主程序结束代码
|
||||
|
||||
5、文件尾
|
||||
|
||||
|
||||
|
||||
## 解析器包含坐标转换功能
|
115
tsconfig.json
Normal file
115
tsconfig.json
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"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. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user