提交:1、导XML格式的CNC 2、NCwriter功能填充
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
mes-processors-libs-*.tgz
|
||||
node_modules
|
||||
dist/*
|
111
README.md
Normal file
111
README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# @mes-processors/libs
|
||||
|
||||
这是一个用于处理MES(制造执行系统)相关工作流的处理器类库。
|
||||
|
||||
## 安装
|
||||
|
||||
在内网环境下执行以下脚本进行安装
|
||||
|
||||
```sh
|
||||
pnpm add http://gitea.cf/MES-FE/mes-packages/releases/download/0.1/mes-processors-libs-0.1.0.tgz
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> 在安装库之前,请确认以下信息:
|
||||
>
|
||||
> - 该库发布于内网gitea仓库的release中,所以你需要提前在gitea中进行登录,并确保你有该仓库的访问权限。
|
||||
> - 库的版本需要手动进行控制,注意上述链接中的版本信息,在安装前需要主动修改版本号,请前往<http://gitea.cf/MES-FE/mes-packages/releases>来确认最新版本。
|
||||
|
||||
## 使用
|
||||
|
||||
该库提供了MES/iMES公用的处理器,并已配置为导出项,请参考以下Typescript代码进行使用:
|
||||
|
||||
```ts
|
||||
// 引入矩形优化处理器
|
||||
import { RectLayoutProcConfig } from 'cut-abstractions';
|
||||
import { RectLayoutProc } from '@mes-processors/libs';
|
||||
|
||||
// 实例化处理器
|
||||
const proc = new RectLayoutProc();
|
||||
// 构建上下文f proc.exec>[0] = {
|
||||
input: testObj,
|
||||
params: new RectLayoutProcConfig()
|
||||
};
|
||||
// 异步执行
|
||||
const ctx: Parameters<typeo
|
||||
await proc.exec(ctx);
|
||||
|
||||
// 从上下文对象中获取输出内容
|
||||
console.log("RESULT: ", ctx.output);
|
||||
```
|
||||
|
||||
## Q&A
|
||||
|
||||
### 运行某些处理器时出现404(Not Found)错误
|
||||
|
||||
**错误描述**
|
||||
当执行处理器时,出现类似下面的错误:
|
||||
|
||||
```log
|
||||
GET http://localhost:5173/node_modules/.vite/deps/assets/RectOptimizeMachine.worker-BO2fmpVH.js 404 (Not Found)
|
||||
```
|
||||
|
||||
**根本原因**
|
||||
该库中某些处理器使用了Web Worker来实现多线程异步处理(例如矩形优化处理器)。
|
||||
|
||||
Web Worker为单独打包的资产文件,但某些打包工具可能会对`node_modules`中的依赖进行预构建来提高性能,如果Worker文件被视为了预构建的一部分,就可能导致处理器无法正确地处理Worker文件的相对引用路径,导致在运行时尝试从`node_modules/.vite/deps/assets/`这样的内部路径加载,而这个路径在实际部署或服务时是不存在的。
|
||||
|
||||
**解决方法**
|
||||
在打包工具中对该库进行配置,禁用对该库的优化和预构建,以`vite`为例:
|
||||
```ts
|
||||
// vite.config.ts
|
||||
...
|
||||
optimizeDeps: {
|
||||
exclude: ["@mes-processors/libs"] // 从optimizeDeps中排除该库
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## 开发
|
||||
|
||||
### 发布并打包项目
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
pnpm pack
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 发布前记得更改版本号
|
||||
|
||||
### 约定
|
||||
|
||||
**目录**
|
||||
|
||||
```
|
||||
src
|
||||
├── modules 项目模块分组
|
||||
├── processors 处理器
|
||||
└── utils 公用的工具类
|
||||
```
|
||||
|
||||
**导出和打包**
|
||||
|
||||
- 编写的处理器请在`src/index.ts`中进行导出
|
||||
- 编写的工具类请在`src/utils/index.ts`中进行导出
|
||||
- 在打包时项目仅会对`src/index.ts`进行打包,工具类相关模块不会进行打包
|
||||
- 关于打包相关明细请自行查看相关文件
|
||||
- [package.json](package.json)
|
||||
- [vite.config.ts](vite.config.ts)
|
||||
|
||||
> [!WARNING]
|
||||
> 在该工作区中编写模块时,禁止使用绝对路径进行导入,禁止在`tsconfig.json`或`vite.config.ts`中添加"@"别名,所有导入语句请使用相对路径进行引入,否则会因monorepo内部导入混乱导致模块解析失败。
|
||||
|
||||
### 测试
|
||||
|
||||
项目使用[Vitest](http://vitest.dev/)作为单元测试框架,若要对TS文件编写单元测试,请在文件的同目录下创建`<文件名>.test.ts`文件,并遵循Vitest规范编写单元测试。
|
||||
|
||||
要执行单元测试,请运行下面的命令:
|
||||
```sh
|
||||
pnpm test
|
||||
```
|
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@mes-process-code-converter/libs",
|
||||
"version": "0.1.4",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"test": "vitest",
|
||||
"check": "tsc --noEmit --skipLibCheck -p tsconfig.app.json"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"package.json",
|
||||
"src",
|
||||
"tsconfig.json",
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.node.json"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./utils": "./src/utils/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"cut-abstractions": "http://gitea.cf/MES-FE/cut-abstractions/releases/download/0.3/cut-abstractions-0.3.3.tgz",
|
||||
"mes-processors":"http://gitea.cf/MES-FE/mes-packages/releases/download/0.2/mes-processors-libs-0.2.1.tgz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.10",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vite-plugin-resolve": "^2.5.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
2471
pnpm-lock.yaml
generated
Normal file
2471
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
src/cncTest.test.ts
Normal file
68
src/cncTest.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { test } from "vitest";
|
||||
import { CncConverter, CncTemplateParams, TemplateEndTargetType } from "./processors/CncConverter/CncConverter";
|
||||
|
||||
test('cncTest', async () => {
|
||||
const testData: CncTemplateParams = {
|
||||
templateName: 'MicroDrawBan_XML',
|
||||
propertyList: [{
|
||||
propertyName: 'Version',
|
||||
propertyValue: '3.0',
|
||||
}, {
|
||||
propertyName: 'Time',
|
||||
propertyValue: '20250903',
|
||||
}, {
|
||||
propertyName: 'Source',
|
||||
propertyValue: '福州晨丰科技有限公司',
|
||||
}, {
|
||||
propertyName: 'SourceType',
|
||||
propertyValue: 'CNC-Drilling',
|
||||
}],
|
||||
children: [{
|
||||
templateName: 'Plane',
|
||||
propertyList: [{
|
||||
propertyName: 'Name',
|
||||
propertyValue: '板名称',
|
||||
}, {
|
||||
propertyName: 'Code',
|
||||
propertyValue: '板编号',
|
||||
},
|
||||
{
|
||||
propertyName: 'Order',
|
||||
propertyValue: '生产单号',
|
||||
}],
|
||||
children: [{
|
||||
templateName: 'Outline',
|
||||
propertyList: [],
|
||||
children: [
|
||||
{
|
||||
templateName: 'Point',
|
||||
propertyList: [{
|
||||
propertyName:'Value',
|
||||
propertyValue: 213,
|
||||
}],
|
||||
children: [],
|
||||
templateEndType: TemplateEndTargetType.SingleEnd
|
||||
}
|
||||
],
|
||||
templateEndType: TemplateEndTargetType.DoubleEnd
|
||||
},
|
||||
{
|
||||
templateName:'HoleV',
|
||||
propertyList:[
|
||||
{
|
||||
propertyName:'Name',
|
||||
propertyValue:"HoleV"
|
||||
},
|
||||
],
|
||||
children:[],
|
||||
templateEndType: TemplateEndTargetType.SingleEnd
|
||||
}
|
||||
],
|
||||
templateEndType: TemplateEndTargetType.DoubleEnd
|
||||
}],
|
||||
templateEndType: TemplateEndTargetType.DoubleEnd
|
||||
}
|
||||
let cncWriter = new CncConverter()
|
||||
await cncWriter.doXML([testData])
|
||||
debugger
|
||||
})
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
// processors
|
||||
export * from "./processors/processCodeConverter/processCodeConverter";
|
||||
export * from "./processors/NcConverter/NcConverter"
|
2
src/modules/README.md
Normal file
2
src/modules/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### react-layout
|
||||
矩形优化算法(陈总新优化)
|
1523
src/modules/rect-layout/KLSCclass.ts
Normal file
1523
src/modules/rect-layout/KLSCclass.ts
Normal file
File diff suppressed because it is too large
Load Diff
1993
src/modules/rect-layout/RectOptimizeMachine.ts
Normal file
1993
src/modules/rect-layout/RectOptimizeMachine.ts
Normal file
File diff suppressed because it is too large
Load Diff
17
src/modules/rect-layout/RectOptimizeMachine.worker.ts
Normal file
17
src/modules/rect-layout/RectOptimizeMachine.worker.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {type Big_bang,type xbang } from "./bang";
|
||||
import { RectOptimizeMachine } from "./RectOptimizeMachine";
|
||||
|
||||
const ctx: Worker = self as any;
|
||||
|
||||
ctx.addEventListener("message", async (event) =>
|
||||
{
|
||||
// 小板 / 大板 / 大板数量 / 优化迭代次数 / 双面加工优先排版? / 刀路间隙 / 电子锯算法 / ???
|
||||
let [xbangs, B_bang, B_bangsl, yhcs, isdtwosided, gap, dzjsf, yuliaodo2face] = (event.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean];
|
||||
|
||||
let m = new RectOptimizeMachine(async (best, scrap, fit) =>
|
||||
{
|
||||
ctx.postMessage([best, scrap, fit]);
|
||||
});
|
||||
|
||||
m.Start(xbangs, B_bang, B_bangsl, yhcs, isdtwosided, gap, dzjsf, yuliaodo2face);
|
||||
});
|
42
src/modules/rect-layout/bang.ts
Normal file
42
src/modules/rect-layout/bang.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ComposingType, HoleType, WaveType } from "cut-abstractions";
|
||||
|
||||
/**
|
||||
* 单块1220*2440的板结果 Container
|
||||
*/
|
||||
export type Con = YH_bang[];
|
||||
/**
|
||||
* 个体:优化结果 包含多个大板 Individual
|
||||
*/
|
||||
export type Inv = Con[];
|
||||
|
||||
export interface YH_bang {
|
||||
bangid: number;
|
||||
line: WaveType;
|
||||
x: number;
|
||||
y: number;
|
||||
pbg: number;
|
||||
pbk: number;
|
||||
}
|
||||
|
||||
/** 待优化小板 */
|
||||
export interface xbang {
|
||||
l: number;
|
||||
w: number;
|
||||
line: WaveType;
|
||||
face: ComposingType;
|
||||
id: number;
|
||||
bno: string;
|
||||
holeFaceCount: HoleType;
|
||||
isRect?: boolean;
|
||||
hasHole?: boolean;
|
||||
isdtwosided?: boolean;
|
||||
}
|
||||
|
||||
/** 待优化大板 */
|
||||
export interface Big_bang
|
||||
{
|
||||
l: number;
|
||||
w: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
29
src/modules/rect-layout/utils.ts
Normal file
29
src/modules/rect-layout/utils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export async function Sleep(time: number)
|
||||
{
|
||||
return new Promise(res =>
|
||||
{
|
||||
setTimeout(res, time);
|
||||
});
|
||||
}
|
||||
|
||||
export function arrayLast<T>(arr: { [key: number]: T, length: number; }): T
|
||||
{
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
|
||||
export function arrayMax<T>(arr: T[], f: (item: T) => number = a => (a as unknown as number)): [T, number]
|
||||
{
|
||||
let max = -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];
|
||||
}
|
149
src/processors/CncConverter/CncConverter.ts
Normal file
149
src/processors/CncConverter/CncConverter.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
|
||||
export class CncConverter implements ICncWriter {
|
||||
private lines: string[] = []
|
||||
private nodes: any[] = [];
|
||||
private actionRecord: CncAction[] = []
|
||||
get cncActions(): CncAction[] {
|
||||
return this.actionRecord
|
||||
}
|
||||
config: CncConverterConfig = {
|
||||
isNcFileComment: true,
|
||||
/** 换行符 个别文件需要用 ;\n 结束*/
|
||||
lineBreak: '\n',
|
||||
|
||||
leaderChar: '//',
|
||||
}
|
||||
async doXML(data: CncTemplateParams[]) {
|
||||
// let line = []
|
||||
for (const template of data) {
|
||||
await this.doCncTemplate(template)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async doCncTemplate(templateItem: CncTemplateParams, level: number = 0) {
|
||||
/** 行缩进 内容 */
|
||||
let tabContent = ` `
|
||||
/** 实际行缩进输出内容 */
|
||||
let tabVal = '';
|
||||
|
||||
for (let i = 0; i < level; i++) {
|
||||
// 按照节点层级 生成缩进内容
|
||||
tabVal = tabVal + tabContent;
|
||||
}
|
||||
|
||||
let node: any[] = []
|
||||
node.push(tabVal)
|
||||
let startCode = `<${templateItem.templateName}`
|
||||
node.push(startCode)
|
||||
for (const propertyInfo of templateItem.propertyList) {
|
||||
let propertyItem = `${propertyInfo.propertyName}="${propertyInfo.propertyValue}"`
|
||||
node.push(propertyItem)
|
||||
}
|
||||
|
||||
let endStr = ' >'
|
||||
if (templateItem.templateEndType == TemplateEndTargetType.SingleEnd) {
|
||||
endStr = ' />'
|
||||
}
|
||||
|
||||
node.push(endStr)
|
||||
|
||||
this.lines.push(node.join(' '))
|
||||
if (Array.isArray(templateItem.children) && templateItem.children.length > 0) {
|
||||
// 子节点 内容
|
||||
for (const kid of templateItem.children) {
|
||||
await this.doCncTemplate(kid, level + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/** 结尾 */
|
||||
let endCode = templateItem.templateEndType == TemplateEndTargetType.DoubleEnd ? `</ ${templateItem.templateName}>` : ''
|
||||
if(endCode){
|
||||
this.lines.push((tabVal + endCode))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
recordAction(type: CncActionType): string {
|
||||
const id = this.createActionId();
|
||||
const act: CncAction = {
|
||||
id: id,
|
||||
type,
|
||||
lineIndex: this.lines.length
|
||||
};
|
||||
this.comment(`CMP ${act.id} ${act.type}`);
|
||||
this.actionRecord.push(act);
|
||||
return id;
|
||||
}
|
||||
private _actionIdx = 0;
|
||||
private createActionId() {
|
||||
return `A${this._actionIdx++}`;
|
||||
}
|
||||
comment(content: string): string {
|
||||
let markContent = content + this.config.lineBreak
|
||||
let isShowMark = this.config.isNcFileComment || false
|
||||
if (isShowMark) {
|
||||
let leaderChar = this.config.leaderChar || ''
|
||||
markContent = `${leaderChar} ${markContent}`
|
||||
} else {
|
||||
markContent = ''
|
||||
}
|
||||
|
||||
return markContent + this.config.lineBreak
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICncWriter {
|
||||
get cncActions(): CncAction[]
|
||||
}
|
||||
|
||||
export interface CncAction {
|
||||
readonly id: string;
|
||||
readonly type: CncActionType;
|
||||
readonly lineIndex: number;
|
||||
// parent: CncAction;
|
||||
// children: CncAction[]
|
||||
}
|
||||
|
||||
export type CncActionType = string
|
||||
|
||||
export type CncTemplateParams = {
|
||||
/** 标签名 */
|
||||
templateName: string
|
||||
/** 属性列表 */
|
||||
propertyList: CncTemplatePropertyType[]
|
||||
/** 子标签 */
|
||||
children?: CncTemplateParams[]
|
||||
/** 标签结束方式 */
|
||||
templateEndType?: TemplateEndTargetType
|
||||
}
|
||||
|
||||
export interface CncTemplatePropertyType {
|
||||
/** 属性名 */
|
||||
propertyName: string
|
||||
/** 属性值 */
|
||||
propertyValue?: string | number
|
||||
}
|
||||
/**
|
||||
* 节点结束类型
|
||||
* 标记文本语言的结尾形式
|
||||
*
|
||||
* <div></div>
|
||||
* <div />
|
||||
*/
|
||||
export enum TemplateEndTargetType {
|
||||
/** 这种 <div></div>*/
|
||||
DoubleEnd = 0,
|
||||
/** 这种 <div />*/
|
||||
SingleEnd = 1
|
||||
}
|
||||
|
||||
export type CncConverterConfig = {
|
||||
/** 换行符 个别文件需要用 ;\n 结束*/
|
||||
lineBreak?: string
|
||||
/** 是否添加注释信息 */
|
||||
isNcFileComment?: boolean
|
||||
/** 注释标识符 */
|
||||
leaderChar?: string
|
||||
}
|
5
src/processors/NcConverter/NcConverter.test.ts
Normal file
5
src/processors/NcConverter/NcConverter.test.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { test } from "vitest";
|
||||
|
||||
test('NcConverterTest',()=>{
|
||||
|
||||
})
|
794
src/processors/NcConverter/NcConverter.ts
Normal file
794
src/processors/NcConverter/NcConverter.ts
Normal file
@@ -0,0 +1,794 @@
|
||||
import { CCode, CCodeParams, FaceType, GCode, GCodeParams, INcWriter, IPoint, Knife, NcAction, NcActionType, } from "cut-abstractions";
|
||||
// // import { CArc2GCode, NcArcType, NcReductionType } from "mes-processors"
|
||||
// import { Vector2 } from "node_modules/mes-processors/src/math/vector2";
|
||||
|
||||
|
||||
/** 用以对接 NC类型的加工文件--即 解析器 */
|
||||
export class NcConverter implements INcWriter {
|
||||
/** NC加工动作记录 */
|
||||
|
||||
private lines: string[] = [];
|
||||
/** 刀库 */
|
||||
knifeList: Array<Knife> = []
|
||||
/** 当前刀具 */
|
||||
private currentKnife?: Knife = undefined;
|
||||
private actionRecord: NcAction[] = [];
|
||||
arcType: NcArcType = 'R';
|
||||
/** 最后一行代码参数 */
|
||||
// private lastParams?: any
|
||||
private lastParams: GCodeParams = new GCodeParams();
|
||||
private lastCode: string = '';
|
||||
/** 可以做代码转换 如G2转G3,G3转G2等 */
|
||||
codeMap: Record<string, string> = {};
|
||||
get ncActions(): NcAction[] {
|
||||
return this.actionRecord;
|
||||
}
|
||||
reductionType: NcReductionType = NcReductionType.None;
|
||||
/** 配置 这里给默认值*/
|
||||
config: NcConverterConfig = {
|
||||
isEnableConverterAxis: false,
|
||||
thickness: 18,
|
||||
doSimpleFirstCode: false,
|
||||
isNcFileComment: true,
|
||||
isNcLinePrefixEnabled: false,
|
||||
ncLinePrefix: '',
|
||||
isUseSimpleCode: false,
|
||||
isSimpleFirstCode: false,
|
||||
arcType: 'R',
|
||||
reverseArcCode: false,
|
||||
NcCodeFreeMove: 'G00',
|
||||
NcCodeLineInterpolation: 'G01',
|
||||
NcCodeClockwiseArcInterpolation: 'G02',
|
||||
NcCodeAnticlockwiseArcInterpolation: 'G03',
|
||||
NcCodeAxisX: 'X',
|
||||
NcCodeAxisY: 'Y',
|
||||
NcCodeAxisZ: 'Z',
|
||||
NcCodeSpeed: 'F',
|
||||
NcCodeIncrementAxisX: 'I',
|
||||
NcCodeIncrementAxisY: 'J',
|
||||
NcCodeIncrementAxisZ: 'K',
|
||||
leaderChar: '//',
|
||||
boardLength: 2440,
|
||||
boardWidth: 1220,
|
||||
boardHeight: 50,
|
||||
originPointPosition: BoardPosition.LEFT_TOP,
|
||||
originZ0Position: OriginZPosition.WorkTop,
|
||||
heightAxis: AxisType.Z_POS,
|
||||
decimalPointPrecision: 3,
|
||||
fixFloatNumberEndZero: true,
|
||||
intNumberAddDecimalPoint: true,
|
||||
lineBreak: '\n'
|
||||
}
|
||||
/** G代码转换关系 */
|
||||
codeTransform = {
|
||||
[GCode.G0]: this.config.NcCodeFreeMove,
|
||||
[GCode.G1]: this.config.NcCodeLineInterpolation,
|
||||
[GCode.G2]: this.config.NcCodeClockwiseArcInterpolation,
|
||||
[GCode.G3]: this.config.NcCodeAnticlockwiseArcInterpolation
|
||||
}
|
||||
initConfig(conf: NcConverterConfig) {
|
||||
this.config = { ...this.config, ...conf }
|
||||
|
||||
/** 更新下代码转换关系 */
|
||||
this.codeTransform = {
|
||||
[GCode.G0]: this.config.NcCodeFreeMove,
|
||||
[GCode.G1]: this.config.NcCodeLineInterpolation,
|
||||
[GCode.G2]: this.config.NcCodeClockwiseArcInterpolation,
|
||||
[GCode.G3]: this.config.NcCodeAnticlockwiseArcInterpolation
|
||||
}
|
||||
}
|
||||
|
||||
protected code(code: keyof typeof GCode, params: Partial<GCodeParams>) {
|
||||
const line: any[] = [];
|
||||
|
||||
/**
|
||||
* G0 - G3 代码 显示的时候可以配置
|
||||
* 例
|
||||
* G00 - G03
|
||||
*/
|
||||
|
||||
if (this.reductionType & NcReductionType.Code) {
|
||||
if (this.lastCode != code) {
|
||||
line.push(this.codeTransform[code]);
|
||||
}
|
||||
} else {
|
||||
line.push(this.codeTransform[code]);
|
||||
}
|
||||
|
||||
this.lastCode = code;
|
||||
|
||||
let x = params.x ??= this.lastParams.x;
|
||||
let y = params.y ??= this.lastParams.y;
|
||||
let z = params.z ??= this.lastParams.z;
|
||||
|
||||
let x_val = this.handleValue_DecimalPointPrecision(x)
|
||||
let y_val = this.handleValue_DecimalPointPrecision(y)
|
||||
let z_val = this.handleValue_DecimalPointPrecision(z)
|
||||
|
||||
let x_code = this.config.NcCodeAxisX || 'X'
|
||||
let y_code = this.config.NcCodeAxisY || 'Y'
|
||||
let z_code = this.config.NcCodeAxisZ || 'Z'
|
||||
|
||||
if (this.reductionType & NcReductionType.Position) {
|
||||
if (x != this.lastParams.x) {
|
||||
line.push(x_code + x_val);
|
||||
}
|
||||
if (y != this.lastParams.y) {
|
||||
line.push(y_code + y_val);
|
||||
}
|
||||
if (z != this.lastParams.z) {
|
||||
line.push(z_code + z_val);
|
||||
}
|
||||
} else {
|
||||
line.push(x_code + x_val);
|
||||
line.push(y_code + y_val);
|
||||
line.push(z_code + z_val);
|
||||
}
|
||||
|
||||
if (code == 'G2' || code == 'G3') {
|
||||
if (this.config.arcType == 'R') {
|
||||
|
||||
let r = params.r ??= this.lastParams.r;
|
||||
|
||||
let r_val = this.handleValue_DecimalPointPrecision(r)
|
||||
|
||||
line.push('R' + r_val);
|
||||
}
|
||||
if (this.config.arcType == 'IJK') {
|
||||
let i = params.i ??= this.lastParams.i;
|
||||
let j = params.j ??= this.lastParams.j;
|
||||
|
||||
let i_val = this.handleValue_DecimalPointPrecision(i)
|
||||
let j_val = this.handleValue_DecimalPointPrecision(j)
|
||||
|
||||
line.push('I' + i_val);
|
||||
line.push('J' + j_val);
|
||||
}
|
||||
}
|
||||
|
||||
const speed = params.f ??= this.lastParams.f
|
||||
if (speed != 0) {
|
||||
if (this.reductionType & NcReductionType.Speed) {
|
||||
if (speed != this.lastParams.f) {
|
||||
line.push('F' + speed);
|
||||
}
|
||||
} else {
|
||||
line.push('F' + speed);
|
||||
}
|
||||
}
|
||||
Object.assign(this.lastParams, params); // 更新上一次参数
|
||||
if (this.codeMap[line[0]]) { // 命令转换
|
||||
line[0] = this.codeMap[line[0]];
|
||||
}
|
||||
this.lines.push(line.join(' '));
|
||||
}
|
||||
|
||||
gCode<TCode extends (keyof typeof GCode | keyof typeof CCode)>(code: TCode, params: Partial<TCode extends keyof typeof GCode ? GCodeParams : CCodeParams>): void {
|
||||
switch (code) {
|
||||
case 'G0':
|
||||
case 'G1':
|
||||
case 'G2':
|
||||
case 'G3': {
|
||||
this.code(code, params);
|
||||
break;
|
||||
}
|
||||
// 自定义代码
|
||||
case 'CArc': {
|
||||
// 凸度转GCode
|
||||
const cParam = params as Partial<CCodeParams>;
|
||||
if (!cParam.x || !cParam.y || !cParam.b) throw new Error("CArc命令缺少必要参数(X, Y, B)");
|
||||
const targetPoint = { x: cParam.x, y: cParam.y };
|
||||
if (this.config.arcType === 'R') {
|
||||
const result = CArc2GCode(this.lastParams, targetPoint, cParam.b, 'R');
|
||||
this.code(result.gCode, { ...targetPoint, r: result.r });
|
||||
} else {
|
||||
const result = CArc2GCode(this.lastParams, targetPoint, cParam.b, 'IJK');
|
||||
this.code(result.gCode, { ...targetPoint, i: result.i, j: result.j });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理值 最终显示的值 小数点后X位功能 */
|
||||
handleValue_DecimalPointPrecision(val: number) {
|
||||
const { fixFloatNumberEndZero, intNumberAddDecimalPoint, decimalPointPrecision } = this.config
|
||||
/**
|
||||
* 2种方式 末尾补零 或者 直接保留小数点后N位
|
||||
*/
|
||||
let isToFix = false
|
||||
|
||||
let resVal
|
||||
|
||||
if (fixFloatNumberEndZero == true) {
|
||||
// 末尾补零
|
||||
isToFix = true
|
||||
} else if (intNumberAddDecimalPoint == true) {
|
||||
// 整数值末尾加小数点
|
||||
if (Number.isInteger(val)) {
|
||||
isToFix = true
|
||||
}
|
||||
}
|
||||
if (isToFix) {
|
||||
resVal = val.toFixed(decimalPointPrecision)
|
||||
} else {
|
||||
resVal = val.toString()
|
||||
}
|
||||
|
||||
return resVal
|
||||
}
|
||||
|
||||
|
||||
/** 更换刀具 */
|
||||
changeKnife() {
|
||||
|
||||
}
|
||||
|
||||
/** 校验值是否有效 不为'' 或 undefined */
|
||||
checkVal(val: any): boolean {
|
||||
let r = true
|
||||
if ((val == undefined || val == '')) {
|
||||
r = false
|
||||
}
|
||||
return r
|
||||
}
|
||||
toString() {
|
||||
return this.lines.join(this.config.lineBreak);
|
||||
}
|
||||
comment(content: string): string {
|
||||
|
||||
let markContent = content + this.config.lineBreak
|
||||
let isShowMark = this.config.isNcFileComment || false
|
||||
if (isShowMark) {
|
||||
let leaderChar = this.config.leaderChar || ''
|
||||
markContent = `${leaderChar} ${markContent}`
|
||||
} else {
|
||||
markContent = ''
|
||||
}
|
||||
|
||||
return markContent + this.config.lineBreak
|
||||
}
|
||||
|
||||
recordAction(type: NcActionType): string {
|
||||
const id = this.createActionId();
|
||||
const act: NcAction = {
|
||||
id: id,
|
||||
type,
|
||||
lineIndex: this.lines.length
|
||||
};
|
||||
this.comment(`CMP ${act.id} ${act.type}`);
|
||||
this.actionRecord.push(act);
|
||||
return id;
|
||||
}
|
||||
private _actionIdx = 0;
|
||||
private createActionId() {
|
||||
return `A${this._actionIdx++}`;
|
||||
}
|
||||
append(str: string) {
|
||||
// this.lines.push(str);
|
||||
}
|
||||
|
||||
appendLine(str: string) {
|
||||
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param point 加工项的点
|
||||
* @param processItemInfo 加工项的信息 水平基准、垂直基准、轴方向(x、y、z),板件方向(x、y、z)
|
||||
* // 这里加工项的点数据 都是经过数据处理的 假定这里拿到的数据都是基于左上角 台面
|
||||
*
|
||||
* @returns 实际加工项的点
|
||||
*/
|
||||
getXYZ(point: CodeParams, processItemInfo: ProcessInfo): CodeParams {
|
||||
let newPoint: any = {}
|
||||
|
||||
if (this.config.isEnableConverterAxis) {
|
||||
// 进行坐标轴转换
|
||||
for (const key in point) {
|
||||
if (point[key] != undefined) {
|
||||
Reflect.set(newPoint, key, parseFloat(point[key]))
|
||||
}
|
||||
}
|
||||
|
||||
// /** 有2个部分
|
||||
// * 一个是基于机台和板件的转换 依据板件定位
|
||||
// * 另外一个是基于板件和加工项的转换 依据板件长高方向*/
|
||||
// switch (this.config.originZ0Position) {
|
||||
// case 0:
|
||||
// // 台面 不操作
|
||||
// break;
|
||||
// case 1:
|
||||
// // 板面 Z坐标需要转换
|
||||
// newPoint.z = newPoint.z - this.config.thickness
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
// /** step 先转换板件的位置 */
|
||||
// // 大板定位 不同 根据不同的定位点修改
|
||||
|
||||
// // processItemInfo
|
||||
// switch (this.config.originPointPosition) {
|
||||
// case BoardPosition.LEFT_TOP:
|
||||
// // 不操作
|
||||
// newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
// break;
|
||||
// case BoardPosition.LEFT_BOTTOM:
|
||||
// // 左下角 x坐标要转换
|
||||
// newPoint.x = newPoint.x + this.config.boardWidth - processItemInfo.block.cutWidth //400
|
||||
// newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
// break;
|
||||
// case BoardPosition.RIGHT_TOP:
|
||||
// // 右上角 y坐标要转换
|
||||
// newPoint.y = newPoint.y + this.config.boardLength - processItemInfo.block.cutLength // 600
|
||||
// newPoint = this.changeXYZAxiosSide(newPoint)
|
||||
// break;
|
||||
// case BoardPosition.RIGHT_BOTTOM:
|
||||
// // 右下角 xy 坐标要转换
|
||||
// newPoint.x = newPoint.x + this.config.boardWidth - processItemInfo.block.cutWidth
|
||||
// newPoint.y = newPoint.y + this.config.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.config.fixFloatNumberEndZero == true) {
|
||||
// 末尾补零
|
||||
isTofix = true
|
||||
} else if (this.config.intNumberAddDecimalPoint == true) {
|
||||
// 整数值末尾加小数点
|
||||
if (Number.isInteger(newPoint[key])) {
|
||||
isTofix = true
|
||||
}
|
||||
}
|
||||
if (isTofix) {
|
||||
newPoint[key] = parseFloat(newPoint[key]).toFixed(this.config.decimalPointPrecision)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newPoint
|
||||
} else {
|
||||
return point
|
||||
}
|
||||
|
||||
}
|
||||
/** 根据 轴向变更坐标 */
|
||||
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.config.boardWidth
|
||||
let length = this.config.boardLength
|
||||
let height = this.config.boardHeight
|
||||
|
||||
|
||||
if (this.config.widthSideAxis == AxisType.X_POS && this.config.lengthSideAxis == AxisType.Y_POS) {
|
||||
// 默认 为 X = x 正 Y = y 正 不操作
|
||||
} else if (this.config.widthSideAxis == AxisType.Y_POS && this.config.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y正 y = x正 X Y坐标 倒转
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.config.widthSideAxis == AxisType.X_NEG && this.config.lengthSideAxis == AxisType.Y_POS) {
|
||||
// x = x负 y = y正
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y }
|
||||
} else if (this.config.widthSideAxis == AxisType.Y_POS && this.config.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y正 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x }
|
||||
}
|
||||
|
||||
else if (this.config.widthSideAxis == AxisType.X_NEG && this.config.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x负 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x - width, y: newPoint.y - length }
|
||||
} else if (this.config.widthSideAxis == AxisType.Y_NEG && this.config.lengthSideAxis == AxisType.X_NEG) {
|
||||
// x = y负 y = x负
|
||||
newPoint = { ...newPoint, x: newPoint.y - width, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
else if (this.config.widthSideAxis == AxisType.X_POS && this.config.lengthSideAxis == AxisType.Y_NEG) {
|
||||
// x = x正 y = y负
|
||||
newPoint = { ...newPoint, x: newPoint.x, y: newPoint.y - length }
|
||||
} else if (this.config.widthSideAxis == AxisType.Y_NEG && this.config.lengthSideAxis == AxisType.X_POS) {
|
||||
// x = y负 y = x正
|
||||
newPoint = { ...newPoint, x: newPoint.y, y: newPoint.x - length }
|
||||
}
|
||||
|
||||
if (this.config.heightAxis == AxisType.Z_NEG) {
|
||||
// Z轴负
|
||||
newPoint = { ...newPoint, z: newPoint.z - height }
|
||||
}
|
||||
|
||||
return newPoint
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export type NcConverterConfig = {
|
||||
/** 是否启用解析器的坐标系转换 */
|
||||
isEnableConverterAxis?: boolean,
|
||||
/** 板厚 */
|
||||
thickness: number,
|
||||
/**是否执行换刀后的第一行精简指令 */
|
||||
doSimpleFirstCode?: boolean
|
||||
/** 是否添加注释信息 */
|
||||
isNcFileComment?: boolean
|
||||
/** 是否空行插入前缀 */
|
||||
isNcLinePrefixEnabled?: boolean
|
||||
/** 空行插入前缀 前缀内容*/
|
||||
ncLinePrefix?: string
|
||||
/** 使用精简指令 */
|
||||
isUseSimpleCode?: boolean
|
||||
/** 精简换刀后第一行指令 */
|
||||
isSimpleFirstCode?: boolean
|
||||
/** 圆弧指令模式类型 */
|
||||
arcType?: NcArcType
|
||||
/** 反转圆弧指令 */
|
||||
reverseArcCode?: boolean
|
||||
/** 空程移动指令 */
|
||||
NcCodeFreeMove?: string
|
||||
/** 直线插补标识 */
|
||||
NcCodeLineInterpolation?: string
|
||||
/** 顺时针圆弧插补标识 */
|
||||
NcCodeClockwiseArcInterpolation?: string
|
||||
/** 逆时针圆弧插补标识 */
|
||||
NcCodeAnticlockwiseArcInterpolation?: string
|
||||
/** 水平坐标横轴标识 */
|
||||
NcCodeAxisX?: string
|
||||
/** 水平坐标纵轴标识 */
|
||||
NcCodeAxisY?: string
|
||||
/** 垂直坐标轴标识 */
|
||||
NcCodeAxisZ?: string
|
||||
/** 速度标识 */
|
||||
NcCodeSpeed?: string
|
||||
/** 水平坐标横轴增量标识 */
|
||||
NcCodeIncrementAxisX?: string
|
||||
/** 水平坐标纵轴增量标识 */
|
||||
NcCodeIncrementAxisY?: string
|
||||
/** 垂直坐标轴增量标识 */
|
||||
NcCodeIncrementAxisZ?: string
|
||||
/** 注释标识符 */
|
||||
leaderChar?: string
|
||||
/** 工作区域长 x */
|
||||
boardLength: number
|
||||
/** 工作区域宽 y */
|
||||
boardWidth: number
|
||||
/** 工作区域高 z */
|
||||
boardHeight: number
|
||||
/** 水平基准点 */
|
||||
originPointPosition?: BoardPosition
|
||||
/** 垂直基准点 */
|
||||
originZ0Position?: OriginZPosition
|
||||
/** 水平纵轴坐标轴向 */
|
||||
widthSideAxis?: AxisType
|
||||
/** 水平横轴坐标轴向 */
|
||||
lengthSideAxis?: AxisType
|
||||
/** 垂直轴坐标轴向 */
|
||||
heightAxis?: AxisType
|
||||
/** 保留小数点位数 */
|
||||
decimalPointPrecision?: number
|
||||
/** 末尾补零 */
|
||||
fixFloatNumberEndZero?: boolean
|
||||
/** 整数值末尾加小数点 */
|
||||
intNumberAddDecimalPoint?: boolean
|
||||
/** 换行符 个别文件需要用 ;\n 结束*/
|
||||
lineBreak?: string
|
||||
|
||||
}
|
||||
|
||||
/** 枚举 大板边角位置 */
|
||||
export enum BoardPosition {
|
||||
/** 左上角 */
|
||||
LEFT_TOP = 3,
|
||||
/** 左下角 */
|
||||
LEFT_BOTTOM = 0,
|
||||
/** 右下角 */
|
||||
RIGHT_BOTTOM = 1,
|
||||
/** 右上角 */
|
||||
RIGHT_TOP = 2,
|
||||
}
|
||||
/** 枚举 坐标轴类型 */
|
||||
export enum OriginZPosition {
|
||||
/** 台面向上Z轴正 */
|
||||
WorkTop = 0,
|
||||
/** 板面向上Z轴正 */
|
||||
BoardFace = 1,
|
||||
}
|
||||
|
||||
/** 枚举 坐标轴类型 */
|
||||
export enum AxisType {
|
||||
/** X轴正 */
|
||||
X_POS = 0,
|
||||
/** X轴负 */
|
||||
X_NEG = 1,
|
||||
/** Y轴正 */
|
||||
Y_POS = 2,
|
||||
/** Y轴负 */
|
||||
Y_NEG = 3,
|
||||
/** 向上Z轴正 */
|
||||
Z_POS = 4,
|
||||
/** 向下Z轴负 */
|
||||
Z_NEG = 5,
|
||||
}
|
||||
// 加工项 点数据
|
||||
export class CodeParams {
|
||||
/** x坐标 */
|
||||
x?: Number | String
|
||||
/** y坐标 */
|
||||
y?: Number | String
|
||||
/** z坐标 */
|
||||
z?: Number | String
|
||||
/** 调用的代码编号 */
|
||||
dir?: Number | String
|
||||
/** 圆弧半径 */
|
||||
r?: Number | String
|
||||
/** 速度 */
|
||||
f?: Number | String
|
||||
/** IJK 模式的i */
|
||||
i?: Number | String
|
||||
/** IJK 模式的j */
|
||||
j?: Number | String
|
||||
/** IJK 模式的k */
|
||||
k?: Number | String
|
||||
|
||||
/** 代码标识 */
|
||||
codeKey?: String
|
||||
/** x坐标 */
|
||||
xKey?: String
|
||||
/** y坐标 */
|
||||
yKey?: String
|
||||
/** z坐标 */
|
||||
zKey?: String
|
||||
/** 圆弧半径 */
|
||||
rKey?: String
|
||||
/** 速度 */
|
||||
fKey?: String
|
||||
/** IJK 模式的i */
|
||||
iKey?: String
|
||||
/** IJK 模式的j */
|
||||
jKey?: String
|
||||
/** IJK 模式的k */
|
||||
kKey?: String
|
||||
}
|
||||
|
||||
/** 加工项对应的信息 */
|
||||
export class ProcessInfo {
|
||||
/**当前加工项的下标*/
|
||||
i?: Number
|
||||
/** 加工项 对应刀具的数据 */
|
||||
knife?: Knife
|
||||
/** 加工项的类型 */
|
||||
type?: processItemType
|
||||
/** 加工项所在的 文件名 */
|
||||
belong?: string
|
||||
/** 该加工项基于哪个加工面 传入数据 主要用于 加工项坐标转换 感觉可能没用 */
|
||||
belongFace?: FaceType
|
||||
/** 板件信息 */
|
||||
block?: any
|
||||
// /** 垂直基准点 */
|
||||
// originZ0Position?: OriginZPosition
|
||||
// /** 水平基准点 */
|
||||
// originPointPosition?: BoardPosition
|
||||
// /** 大板定位 */
|
||||
// boardLocation?: BoardPosition
|
||||
// /** 加工项的宽方向 x */
|
||||
// widthSideAxis?: AxisType
|
||||
// /** 加工项的长方向 y */
|
||||
// lengthSideAxis ?: AxisType
|
||||
// /** 加工项的高方向 */
|
||||
// heightSideAxis ?: AxisType
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** 加工项的类别
|
||||
* 用于区分加工项的类别,不同的类别 在文件生成的时候 可能需要对应某些独立的节点
|
||||
*/
|
||||
export enum processItemType {
|
||||
/**排钻 */
|
||||
Hole = 'hole',
|
||||
/** 铣孔 */
|
||||
MillingHole = 'millingHole',
|
||||
/** 造型 非槽加工 铣型 */
|
||||
Model = 'model',
|
||||
/** 造型 槽-- 拉槽 */
|
||||
Grooves = 'grooves',
|
||||
/** 造型 铣槽 */
|
||||
MillingGrooves = 'millingGrooves',
|
||||
/** 侧面造型 */
|
||||
SideModel = 'SideModel',
|
||||
/** 侧面槽 - 拉槽 */
|
||||
SideGrooves = 'sideGrooves',
|
||||
/** 侧面槽 - 拉槽 */
|
||||
SideMillingGrooves = 'SideMillingGrooves',
|
||||
/** 侧面孔 排钻 */
|
||||
SideHole = 'sideHole',
|
||||
/** 侧面孔 铣孔 */
|
||||
MillingSideHole = 'MillingSideHole',
|
||||
/** 开料 */
|
||||
CutBlock = 'cutBlock',
|
||||
/** 修边 */
|
||||
MillingBlockBoard = 'MillingBlockBoard'
|
||||
}
|
||||
|
||||
export type NcArcType = 'R' | 'IJK';
|
||||
|
||||
/**
|
||||
* 将基于凸度的圆弧转换为基于圆弧半径/圆心坐标表示的圆弧,暂不支持三维圆弧(Z轴或K分量)
|
||||
* @param source 圆弧起始点
|
||||
* @param target 圆弧终点
|
||||
* @param bulge 凸度值
|
||||
* @param mode 圆弧模式
|
||||
* @returns 圆弧参数(IJ或R)
|
||||
*/
|
||||
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: 'R'): { gCode: 'G2' | 'G3'; r: number; };
|
||||
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: 'IJK'): { gCode: 'G2' | 'G3'; i: number; j: number; };
|
||||
export function CArc2GCode(source: IPoint, target: IPoint, bulge: number, mode: NcArcType): { gCode: 'G2' | 'G3'; r?: number; i?: number; j?: number; } {
|
||||
/*
|
||||
* ♿♿♿ 修改必看!!!
|
||||
* 凸度为圆弧圆心角的1/4正切值
|
||||
* Bulge = tan(θ / 4) θ为圆心角
|
||||
* 凸度为正数,则从起点顺时针绘制圆弧到终点
|
||||
* 凸度为负数,则逆时针绘制
|
||||
* 当凸度为0时,绘制直线
|
||||
* 当凸度为1时,圆心角为180度 tan(180 / 4) = 1
|
||||
* 凸度转换公式见 https://www.lee-mac.com/bulgeconversion.html
|
||||
*
|
||||
* NC圆弧规则
|
||||
* 圆弧半径(R)模式:
|
||||
* 从起点到终点绘制半径为|R|的圆弧,R越大圆弧越平滑,越小圆弧越弯曲
|
||||
* 当R为正数时,表示绘制圆的短弧
|
||||
* 当R为负数时,表示绘制圆的长弧
|
||||
* ⚠️注意 起点到终点的距离不可大于 2 * |R|,否则应当抛出错误
|
||||
*
|
||||
* 圆心坐标(IJ)模式:
|
||||
* 从起点到终点绘制圆弧,圆心坐标为(I, J),I为圆心在X轴上的坐标(带符号),J为圆心在Y轴上的坐标(带符号)
|
||||
* ⚠️注意 圆心到圆弧起点和圆心到圆弧终点的距离必须相等(都等于圆弧的半径),否则应当抛出错误
|
||||
*/
|
||||
|
||||
if (bulge === 0) {
|
||||
throw new Error("圆弧模式下,凸度值不能为0");
|
||||
}
|
||||
|
||||
const p1 = Vector2.FromPoint(source);
|
||||
const p2 = Vector2.FromPoint(target);
|
||||
|
||||
const gCode = bulge > 0 ? 'G2' : 'G3';
|
||||
|
||||
const delta = p2.Subtract(p1);
|
||||
const dist = delta.Magnitude;
|
||||
|
||||
if (dist < 1e-6) {
|
||||
throw new Error("起点与终点距离过近");
|
||||
}
|
||||
|
||||
// 通过凸度值计算圆弧半径
|
||||
// 圆弧半径 r = (弦长 / 2) * (1 + bulge²) / (2 * |bulge|)
|
||||
// 详见: https://www.lee-mac.com/bulgeconversion.html#bulgeradius
|
||||
const chordLength = delta.Magnitude;
|
||||
const radius = (chordLength / 2) * (1 + bulge * bulge) / (2 * Math.abs(bulge));
|
||||
|
||||
// R模式
|
||||
if (mode === 'R') {
|
||||
return { gCode: gCode, r: radius };
|
||||
}
|
||||
|
||||
// IJK模式
|
||||
// 圆心位于弦的垂直平分线
|
||||
// 计算弦的中点
|
||||
const midPoint = p1.Add(delta.Multiply(0.5));
|
||||
// 计算弦心距d
|
||||
// 公式:d = √(r² - a²),r为半径,a为弦长的一半(勾股定理)
|
||||
const d = Math.sqrt(radius * radius - (dist / 2) * (dist / 2));
|
||||
|
||||
// 垂直平分线单位向量
|
||||
const perpVec = new Vector2(-delta.y, delta.x).Normalize();
|
||||
|
||||
// 从弦的中点向垂直平分线向量移动弦心距d来计算出圆心的坐标
|
||||
const center = midPoint.Add(perpVec.Multiply(d * -Math.sign(bulge)));
|
||||
|
||||
const i = center.x - p1.x;
|
||||
const j = center.y - p1.y;
|
||||
|
||||
return { gCode, i, j };
|
||||
}
|
||||
|
||||
export enum NcReductionType {
|
||||
None = 0,
|
||||
Position = 1 << 0,
|
||||
Speed = 1 << 1,
|
||||
Code = 1 << 2,
|
||||
/**
|
||||
* 换刀重置
|
||||
*/
|
||||
SwitchKnifeReset = 1 << 3
|
||||
}
|
||||
|
||||
export class Vector2 implements IPoint {
|
||||
static Zero = new Vector2(0, 0);
|
||||
static One = new Vector2(1, 1);
|
||||
static Up = new Vector2(0, 1);
|
||||
static Down = new Vector2(0, -1);
|
||||
static Left = new Vector2(-1, 0);
|
||||
static Right = new Vector2(1, 0);
|
||||
static FromPoint(pt: IPoint) {
|
||||
if (pt instanceof Vector2) return pt as Vector2;
|
||||
return new Vector2(pt.x, pt.y);
|
||||
}
|
||||
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
/** 获取向量的模长 */
|
||||
get Magnitude() {
|
||||
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
|
||||
}
|
||||
|
||||
/** 获取向量的平方模长,这个属性比 `Magnitude` 属性更快 */
|
||||
get SquaredMagnitude() {
|
||||
return Math.pow(this.x, 2) + Math.pow(this.y, 2);
|
||||
}
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/** 克隆向量 */
|
||||
Clone(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
/** 计算两个向量之间的距离 */
|
||||
Distance(other: Vector2): number {
|
||||
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
|
||||
}
|
||||
|
||||
/** 计算两个向量之间的平方距离,这个属性比 `Distance` 属性更快 */
|
||||
SquaredDistance(other: Vector2): number {
|
||||
return Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2);
|
||||
}
|
||||
|
||||
/** 向量和 */
|
||||
Add(other: Vector2): Vector2 {
|
||||
return new Vector2(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
|
||||
/** 向量差 */
|
||||
Subtract(other: Vector2): Vector2 {
|
||||
return new Vector2(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
|
||||
/** 向量点乘 */
|
||||
Dot(other: Vector2): number {
|
||||
return this.x * other.x + this.y * other.y;
|
||||
}
|
||||
|
||||
/** 向量叉乘 */
|
||||
Cross(other: Vector2): number {
|
||||
return this.x * other.y - this.y * other.x;
|
||||
}
|
||||
|
||||
/** 向量与标量相乘 */
|
||||
Multiply(scalar: number): Vector2 {
|
||||
return new Vector2(this.x * scalar, this.y * scalar);
|
||||
}
|
||||
|
||||
/** 向量归一化 */
|
||||
Normalize(): Vector2 {
|
||||
const magnitude = this.Magnitude;
|
||||
if (magnitude === 0) {
|
||||
return Vector2.Zero;
|
||||
}
|
||||
return new Vector2(this.x / magnitude, this.y / magnitude);
|
||||
}
|
||||
}
|
4528
src/processors/processCodeConverter/processCodeConverter.test.ts
Normal file
4528
src/processors/processCodeConverter/processCodeConverter.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
114
src/processors/processCodeConverter/processCodeConverter.ts
Normal file
114
src/processors/processCodeConverter/processCodeConverter.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { ConfigBase, ProcessorBase, ProcessorContext, IProcessingItem } from "cut-abstractions";
|
||||
import { NcConverter } from "../NcConverter/NcConverter";
|
||||
|
||||
/**
|
||||
* 加工代码转换器
|
||||
*/
|
||||
export class ProcessCodeConverterProc extends ProcessorBase<ProcessCodeConverterProcInput, ProcessCodeConverterProcOutput, ProcessCodeConverterProcConfig> {
|
||||
get name(): string {
|
||||
return 'ProcessCodeConverter';
|
||||
}
|
||||
get version(): string {
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
|
||||
/** 正在使用的解析器 */
|
||||
private _usedConverter?: NcConverter;
|
||||
exec(context: ProcessorContext<ProcessCodeConverterProcInput, ProcessCodeConverterProcOutput, ProcessCodeConverterProcConfig>): Promise<void> | void {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 精简指令 NC代码精简指令处理
|
||||
* 兼容注释的行处理 精简指令只处理G代码所在的行
|
||||
*/
|
||||
handleNcSimpleCode(code: string) {
|
||||
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: any = {}
|
||||
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+)/)
|
||||
if (!reg) {
|
||||
|
||||
}
|
||||
else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export type ProcessCodeConverterProcInput = {
|
||||
}
|
||||
|
||||
export type ProcessCodeConverterProcOutput = {
|
||||
|
||||
}
|
||||
|
||||
export declare class ProcessCodeConverterProcConfig extends ConfigBase {
|
||||
|
||||
}
|
||||
/** 加工流程类型 加工项与加工项直接的流程类型 */
|
||||
export enum ProcessStepType {
|
||||
/** 开料加工 */
|
||||
blockProcess = "processBlock",
|
||||
/** 修边加工 */
|
||||
blockBoardProcess = "processBlockBoard",
|
||||
/** 孔加工 */
|
||||
holeProcess = "holeProcess",
|
||||
/** 造型加工 */
|
||||
modelProcess = "modelProcess",
|
||||
/** 槽加工 */
|
||||
grooveProcess = "grooveProcess"
|
||||
}
|
||||
|
||||
/** 单一加工项内的业务流程类型 */
|
||||
export enum ProcessItemStepType {
|
||||
/** 快速定位 --空程移动 */
|
||||
knifeRapidMove = 'knifeRapidMove'
|
||||
/** */
|
||||
}
|
25
src/utils/corsWorker.ts
Normal file
25
src/utils/corsWorker.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export class CorsWorker {
|
||||
private readonly url: string | URL;
|
||||
private readonly options?: WorkerOptions;
|
||||
|
||||
// Have this only to trick Webpack into properly transpiling
|
||||
// the url address from the component which uses it
|
||||
constructor(url: string | URL, options?: WorkerOptions) {
|
||||
this.url = url;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
async createWorker(): Promise<Worker> {
|
||||
if (import.meta.env.DEV) { // 修复调试模式
|
||||
return new Worker(this.url, this.options);
|
||||
}
|
||||
const f = await fetch(this.url);
|
||||
const t = await f.text();
|
||||
const b = new Blob([t], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
const url = URL.createObjectURL(b);
|
||||
const worker = new Worker(url, this.options);
|
||||
return worker;
|
||||
}
|
||||
}
|
116
src/utils/helper.array.ts
Normal file
116
src/utils/helper.array.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { AssignByPath, Resolve, type Paths, type PathValue } from "./helper.lang";
|
||||
|
||||
/**
|
||||
* 将对象数组按指定字段分组,并返回分组结果的数组。
|
||||
*
|
||||
* @template T - 输入数组中对象的类型。
|
||||
* @param data - 要分组的对象数组。
|
||||
* @param fields - 用于分组对象的键(字段)数组。
|
||||
* @param sort - 一个布尔值,指示是否按键对分组结果进行排序。默认为 `true`。
|
||||
* @param separator - 用于连接字段值以形成分组键的字符串。默认为 `'-'`。
|
||||
* @returns 分组结果的数组
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const data = [
|
||||
* { category: 'A', type: 'X', value: 10 },
|
||||
* { category: 'A', type: 'Y', value: 20 },
|
||||
* { category: 'B', type: 'X', value: 30 },
|
||||
* ];
|
||||
* const grouped = GroupBy(data, ['category', 'type']);
|
||||
* console.log(grouped);
|
||||
* // 输出:
|
||||
* // [
|
||||
* // {
|
||||
* // key: 'A-X',
|
||||
* // keyList: ['A', 'X'],
|
||||
* // value: [{ category: 'A', type: 'X', value: 10 }],
|
||||
* // keyObj: { category: 'A', type: 'X' }
|
||||
* // },
|
||||
* // {
|
||||
* // key: 'A-Y',
|
||||
* // keyList: ['A', 'Y'],
|
||||
* // value: [{ category: 'A', type: 'Y', value: 20 }],
|
||||
* // keyObj: { category: 'A', type: 'Y' }
|
||||
* // },
|
||||
* // {
|
||||
* // key: 'B-X',
|
||||
* // keyList: ['B', 'X'],
|
||||
* // value: [{ category: 'B', type: 'X', value: 30 }],
|
||||
* // keyObj: { category: 'B', type: 'X' }
|
||||
* // }
|
||||
* // ]
|
||||
* ```
|
||||
*/
|
||||
export function GroupBy<T extends object, TKeys extends (Paths<T>)[]>(
|
||||
data: T[],
|
||||
fields: TKeys,
|
||||
sort = true,
|
||||
separator = '-',
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const groupList = {} as any;
|
||||
const groupArray = [] as IGrouping<T, { [Key in TKeys[number]]: PathValue<T, Key> }>[];
|
||||
if (fields.length === 0) return [];
|
||||
for (const item of data) {
|
||||
const keyList = fields.map((field) => Resolve(item, field));
|
||||
const key = keyList.join(separator);
|
||||
groupList[key] = groupList[key] || [];
|
||||
groupList[key].push(item);
|
||||
groupList[key].keyList = keyList;
|
||||
const keyObj = {} as T;
|
||||
fields.forEach((t) => {
|
||||
AssignByPath(keyObj, t, Resolve(item, t) as PathValue<T, typeof t>);
|
||||
});
|
||||
groupList[key].keyObj = keyObj;
|
||||
}
|
||||
for (const key in groupList) {
|
||||
groupArray.push({
|
||||
key,
|
||||
keyList: groupList[key].keyList,
|
||||
value: groupList[key],
|
||||
keyObj: groupList[key].keyObj,
|
||||
});
|
||||
}
|
||||
if (sort)
|
||||
return groupArray.sort(function (a, b) {
|
||||
return a.key.localeCompare(b.key, 'zh-CN', { numeric: true });
|
||||
});
|
||||
else return groupArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象数组按指定字段分组,并返回分组结果的数组。
|
||||
*/
|
||||
export interface IGrouping<T, TKeyRecord> {
|
||||
/**
|
||||
* 分组的键,若有多个键,则用连接符连接
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* 分组的键列表
|
||||
*/
|
||||
keyList: unknown[];
|
||||
/**
|
||||
* 分组的对象数组
|
||||
*/
|
||||
value: T[];
|
||||
/**
|
||||
* 分组键对象,其属性名为分组键名,属性值为分组键
|
||||
*/
|
||||
keyObj: TKeyRecord;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
GroupBy: <TKeys extends (Paths<T>)[]>(
|
||||
fields: TKeys,
|
||||
sort?: boolean,
|
||||
separator?: string,
|
||||
) => Array<IGrouping<T, { [Key in TKeys[number]]: PathValue<T, Key> }>>;
|
||||
}
|
||||
}
|
||||
|
||||
Array.prototype.GroupBy = function (fields, sort, separator) {
|
||||
return GroupBy(this, fields, sort, separator);
|
||||
};
|
21
src/utils/helper.async.ts
Normal file
21
src/utils/helper.async.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 异步等待
|
||||
* @param ms 等待毫秒数
|
||||
* @param value 等待完成后返回的值
|
||||
* @returns
|
||||
*/
|
||||
export function AsyncDelay<T>(ms: number, value?: T) {
|
||||
return new Promise<T>((resolve) => setTimeout(resolve, ms, value));
|
||||
}
|
||||
|
||||
export async function AsyncWaitUntil(predicate: () => boolean, waitTime = 100) {
|
||||
while (!predicate()) {
|
||||
await AsyncDelay(waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
export async function AsyncWaitForChange(obj: () => any, waitTime = 100) {
|
||||
while (obj() === undefined) {
|
||||
await AsyncDelay(waitTime);
|
||||
}
|
||||
}
|
128
src/utils/helper.lang.ts
Normal file
128
src/utils/helper.lang.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 检查枚举值中是否包含某个标志位
|
||||
* @param $enum 枚举值
|
||||
* @param flag 标志位
|
||||
* @returns 是否包含标志位
|
||||
*/
|
||||
export function HasFlag($enum: number, ...flags: number[]) {
|
||||
return flags.every(flag => ($enum & flag) === flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* @param obj 对象
|
||||
* @returns 深拷贝后的对象
|
||||
*/
|
||||
export function Clone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function ToPercent(num: number, precision = 2) {
|
||||
return (num * 100).toFixed(precision) + '%';
|
||||
}
|
||||
|
||||
|
||||
/** 访问对象的成员 */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function Resolve<T extends object, TPath extends Paths<T>>(obj: T, path: TPath): PathValue<T, TPath> | T {
|
||||
return path.split('.').reduce(function (item, key) {
|
||||
return item ? (item as any)[key] : null;
|
||||
}, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据字符串路径向对象深层嵌套的属性赋值。
|
||||
* 如果路径中的对象不存在,则会自动创建。
|
||||
* @param obj 要修改的对象。注意:这个函数会直接修改传入的对象(in-place mutation)。
|
||||
* @param path 属性路径字符串,例如 "a.b.c"。
|
||||
* @param value 要赋给目标属性的值。
|
||||
*
|
||||
* @example
|
||||
* const obj = { a: { b: { c: 1 } } };
|
||||
* AssignByPath(obj, "a.b.c", 2);
|
||||
* console.log(obj); // { a: { b: { c: 2 } } }
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function AssignByPath<T extends object, TPath extends Paths<T>>(obj: T, path: TPath, value: PathValue<T, TPath>) {
|
||||
const keys = path.split(".");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let current: any = obj;
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let key = keys[i];
|
||||
|
||||
// 如果当前层级的对象不存在,或者不是一个对象,就需要创建它
|
||||
// 这是一个关键步骤,确保路径是可写的
|
||||
if (current[key] === undefined || typeof current[key] !== 'object' || current[key] === null) {
|
||||
// 检查下一个键是否是数字,来决定创建对象还是数组。
|
||||
// 这是一个启发式方法,对于像 'a.0.b' 这样的路径,它会创建 { a: [ { b: ... } ] }
|
||||
// 注意:这个逻辑对于严格的类型安全来说是一个简化。
|
||||
// 我们的 Paths<T> 类型目前不支持数字索引,所以这个逻辑是为了运行时健壮性。
|
||||
const nextKey = keys[i + 1];
|
||||
current[key] = /^\d+$/.test(nextKey) ? [] : {};
|
||||
}
|
||||
|
||||
// 移动到下一层
|
||||
current = current[key];
|
||||
}
|
||||
// 获取最后一个键,并进行赋值
|
||||
const lastKey = keys[keys.length - 1];
|
||||
current[lastKey] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查对象是否是构造函数的直接实例(不检查继承关系)
|
||||
* @param obj 对象
|
||||
* @param constructor 构造函数
|
||||
*/
|
||||
export function IsDirectInstanceOf(obj: object, constructor: Function) {
|
||||
// 确保 obj 是一个对象,并且 Constructor 是一个函数/类
|
||||
if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof constructor !== 'function') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 比较对象的直接原型与构造函数的 prototype 属性
|
||||
return Object.getPrototypeOf(obj) === constructor.prototype;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 递归生成对象所有可能的属性访问路径。
|
||||
* 类型T必须是object,不支持数组对象
|
||||
* 例如:{ a: { b: string } } -> "a" | "a.b"
|
||||
*/
|
||||
export type Paths<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]:
|
||||
// K 本身是一个有效的路径
|
||||
| `${K & string}`
|
||||
// 如果 T[K] 也是一个对象,则递归地为其生成路径,并加上前缀 "K."
|
||||
// 使用NonNullable来解决T[K]可能为null | undefined的情况
|
||||
| (NonNullable<T[K]> extends object ?
|
||||
// 对于数组类型的子对象,不进行解析,防止TS类型死循环
|
||||
NonNullable<T[K]> extends any[] ? never
|
||||
: `${K & string}.${NonNullable<Paths<T[K]>>}` : never);
|
||||
}[keyof T] // 取出所有可能路径的联合类型
|
||||
: never;
|
||||
|
||||
/**
|
||||
* @description 根据给定的路径字符串 P,在对象 T 中查找并返回对应值的类型。
|
||||
* 例如:PathValue<{ a: { b: string } }, "a.b"> -> string
|
||||
*/
|
||||
export type PathValue<T, P extends string> =
|
||||
// 使用条件类型和 infer 来分割路径字符串 P
|
||||
P extends `${infer Key}.${infer Rest}`
|
||||
? // 如果 Key 是 T 的一个键
|
||||
Key extends keyof T
|
||||
? // 则在 T[Key] 类型中递归查找剩余路径 Rest 的值类型
|
||||
NonNullable<PathValue<T[Key], Rest>>
|
||||
: never
|
||||
: // 如果路径 P 不包含 ".", 说明是最后一层
|
||||
P extends keyof T
|
||||
? // 直接返回 T[P] 的类型
|
||||
T[P]
|
||||
: never;
|
33
src/utils/helper.string.ts
Normal file
33
src/utils/helper.string.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function IsNullOrEmpty(str?: string | null) {
|
||||
return str === null || str === undefined || str === "";
|
||||
}
|
||||
|
||||
export function IsNullOrWhitespace(str?: string | null) {
|
||||
return str === null || str === undefined || str.trim() === "";
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface String {
|
||||
/** 将字符串转换为 camelCase 格式 */
|
||||
ToCamelCase(): string
|
||||
}
|
||||
}
|
||||
|
||||
String.prototype.ToCamelCase = function (this: string) {
|
||||
return ToCamelCase(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转换为 camelCase 格式
|
||||
* @param str 输入字符串
|
||||
* @returns camelCase 格式的字符串
|
||||
*/
|
||||
export function ToCamelCase(str: string): string {
|
||||
return str
|
||||
.replace(/([-_][a-z])/g, group =>
|
||||
group.toUpperCase()
|
||||
.replace('-', '')
|
||||
.replace('_', '')
|
||||
)
|
||||
.replace(/^[A-Z]/, firstChar => firstChar.toLowerCase());
|
||||
}
|
35
src/utils/helper.version.ts
Normal file
35
src/utils/helper.version.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export class Version {
|
||||
major: number;
|
||||
minor: number;
|
||||
build: number;
|
||||
revision: number;
|
||||
toString(): string {
|
||||
return `${this.major}.${this.minor}.${this.build}.${this.revision}`
|
||||
}
|
||||
constructor(major: number, minor: number, build: number = 0, revision: number = 0) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.build = build;
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
compareTo(obj: Version): number {
|
||||
if (this.major > obj.major) return 1;
|
||||
if (this.major < obj.major) return -1;
|
||||
if (this.minor > obj.minor) return 1;
|
||||
if (this.minor < obj.minor) return -1;
|
||||
if (this.build > obj.build) return 1;
|
||||
if (this.build < obj.build) return -1;
|
||||
if (this.revision > obj.revision) return 1;
|
||||
if (this.revision < obj.revision) return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseVersion(verStr: string): Version {
|
||||
const arr = verStr.split(".").map(c => parseInt(c));
|
||||
if (arr.length < 2 || arr.length > 4)
|
||||
throw new Error("无效的版本号");
|
||||
|
||||
return new Version(arr[0], arr[1], arr[2] || 0, arr[3] || 0);
|
||||
}
|
14
src/utils/index.ts
Normal file
14
src/utils/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @package @mes-processors/libs/utils
|
||||
* @author CZY
|
||||
* @description 帮助类库,这个导出仅供内部使用,不在打包中暴露,在这个文件中使用导出时,不要在路径中使用'@',否则模块无法加载
|
||||
*/
|
||||
|
||||
|
||||
export * from "./corsWorker";
|
||||
export * from "./helper.array";
|
||||
export * from "./helper.async";
|
||||
export * from "./helper.lang";
|
||||
export * from "./helper.string";
|
||||
export * from "./helper.version";
|
||||
export * from "./utilTypes";
|
27
src/utils/utilTypes.ts
Normal file
27
src/utils/utilTypes.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 读取泛型T中的KEY作为类型,支持嵌套属性
|
||||
*/
|
||||
export type NestedKey<T extends object> = {
|
||||
[K in keyof T & string]: T[K] extends object ? `${K}` | `${K}.${NestedKey<T[K]>}` : `${K}`;
|
||||
}[keyof T & string];
|
||||
|
||||
/**
|
||||
* 读取泛型T中的VALUE作为类型,支持嵌套属性,你可能需要把类型T设为标量(`as const`)
|
||||
*/
|
||||
export type NestedValue<T extends object> = {
|
||||
[K in keyof T & string]: T[K] extends object ? T[K] | NestedValue<T[K]> : T[K];
|
||||
}[keyof T & string];
|
||||
|
||||
/**
|
||||
* 将泛型T中的属性P设置为必选
|
||||
*/
|
||||
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }
|
||||
|
||||
/**
|
||||
* 递归地将泛型T的所有属性变为只读
|
||||
*/
|
||||
export type DeepReadonly<T> = T extends object
|
||||
? {
|
||||
readonly [P in keyof T]: DeepReadonly<T[P]>;
|
||||
}
|
||||
: T;
|
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
39
tsconfig.app.json
Normal file
39
tsconfig.app.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
// TODO: Warn: 在进行TS类型检查的时候会检查到workflow工作区中的文件,原因未知
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"ES2021",
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"noEmit": true,
|
||||
"target": "esnext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
// 为了保证导入不与被引入项目冲突,不应该配置'@/*'别名
|
||||
"@libs/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"erasableSyntaxOnly": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/*",
|
||||
"dist",
|
||||
"node_modules/**"
|
||||
]
|
||||
}
|
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
25
tsconfig.node.json
Normal file
25
tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
34
vite.config.ts
Normal file
34
vite.config.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/// <reference types="vitest/config" />
|
||||
import { defineConfig } from 'vite'
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
import { resolve } from 'node:path';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
||||
let basePath = process.env.basePath ?? '';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: basePath,
|
||||
plugins: [
|
||||
nodePolyfills(),
|
||||
dts({rollupTypes: true, tsconfigPath: './tsconfig.app.json',insertTypesEntry: true}),
|
||||
],
|
||||
build: {
|
||||
modulePreload: {
|
||||
resolveDependencies() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'MesProcessors',
|
||||
fileName(format) {
|
||||
return `mes-processors.${format}.js`
|
||||
},
|
||||
formats: ['es', 'umd', 'iife']
|
||||
}
|
||||
},
|
||||
esbuild: {
|
||||
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
|
||||
},
|
||||
})
|
Reference in New Issue
Block a user