Files
ProcessCodeConverter/src/processors/NcConverter/NcConverter.ts

794 lines
26 KiB
TypeScript
Raw Normal View History

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 xyzxyz
* // 这里加工项的点数据 都是经过数据处理的 假定这里拿到的数据都是基于左上角 台面
*
* @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线
* 1180 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);
}
}