Merge pull request '新增"机台加工项" ProcessItem类型,移除samples代码' (#4) from feature/process_item into main

Reviewed-on: #4
Reviewed-by: 谢凡 <xief@163.com>
This commit is contained in:
2025-07-30 17:28:59 +08:00
37 changed files with 28 additions and 40098 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "cut-abstractions",
"version": "0.1.13",
"version": "0.1.14",
"description": "",
"files": [
"dist/**/*"

View File

@@ -1,152 +0,0 @@
import { FaceType, PlaceBlock, PlaceBlockDetail, PlaceBorderContour, PlaceMaterial, PlaceStyle, SizeExpand } from "../confClass"
import { BlockDetailHelperBase } from "../handleAbility/blockDetailHelperBase"
import { PolylineHelper } from "../handleAbility/common/LayoutEngine/PolylineHelper"
import { KnifeHelper } from "../handleAbility/knifeHelper"
/** 小板明细 相关的计算 孔 造型 以及轮廓 */
export class BlockDetailHelper extends BlockDetailHelperBase {
/** 预铣(板件外扩)值 */
preMillingSize = 0;
constructor(config?: any) {
super()
if(config){
this.updateConfig(config)
}
}
updateConfig(config) {
if (config) {
let keys = Object.keys(config)
for (const key of keys) {
if (Reflect.has(this, key)) {
this[key] = config[key]
}
}
}
}
/** 小板初始化1各种轮廓|走刀路径|2尺寸扩展后的轮廓与走刀路径 */
initBlock(block: PlaceBlock, pm: PlaceMaterial) {
if (block.blockDetail == null) {
// 异常情况
console.error(`【initBlock()】${block.blockNo}没用板件明细,请检查`);
return
}
if (block.blockDetail.borderContour) return // 已初始化
// const pm = block.placeMaterial
const bd = block.blockDetail
if (pm == null) {
return // 异常
}
const cutKnifeR = pm.diameter / 2
const cutGap = pm.cutKnifeGap
// 开料偏移值 ,矩形波倒角
bd.offsetKnifeRadius = pm.diameter / 2
bd.isOffsetRounding = true // 默认要倒角
bd.preMillingSize = pm.preMillingSize
if (bd.points.length == 0) // 矩形板,设置不倒角
{
bd.isOffsetRounding = false// sysConfig.RegularBlockFilletCurve;
}
else // 异形板
{
// 异形板倒角
bd.isOffsetRounding = true // sysConfig.isUnRegularBlockChamfer
}
// 初始化 小板基础轮廓
// 1.原始轮廓,成品轮廓
const border_final = this.createFinalBorder(bd, block)
// 2.开料轮廓 <不含预铣>
const border_org = this.createOrgBorder(bd)
bd.borderContour = new PlaceBorderContour(PlaceStyle.FRONT, border_final, border_org)
// 3.开料轮廓_带预铣, 外扩尺寸
bd.borderContour.borderPreMilling = border_org
bd.preMillingExpandSize = new SizeExpand()
// console.log('开料轮廓_带预铣, 外扩尺寸', block, bd.preMillingExpandSize, sysConfig.preMillingSize)
if (this.preMillingSize > 0.0001) {
const rt = this.creatOrgBorderWithPrevCut(block, border_org, this.sysConfig.preMillingSize)
bd.borderContour.borderPreMilling = rt.newBorders
bd.preMillingExpandSize = rt.newSizeExpand
// console.log('开料轮廓_带预铣, 外扩尺寸==》after', block, rt.newSizeExpand)
}
// 4. 板内走刀路径,板内空间
const innerGroup = this.analyeInners(bd, cutKnifeR, cutGap)
bd.borderContour.borderModelThrough = innerGroup.borders_inner_org
bd.borderContour.borderModelThroughR = innerGroup.borders_inner_r
bd.borderContour.cutLinesModelThrough = innerGroup.borders_inner_cut
bd.borderContour.borderInnerPlace = innerGroup.borders_inner_place
bd.borderContour.blockInnerSpace = innerGroup.spaces
// 5 造型外扩轮廓, 外扩尺寸
const { pls_model, sizeout: sizeout_model } = this.createPolyline_model(block)
bd.borderContour.polylinesOutModel = pls_model
bd.modelExpandSize = sizeout_model
// 6. 2V刀路 外扩轮廓,外扩尺寸
const { pls_2v, sizeout_2v } = this.createPolyline_2vModel(block)
bd.borderContour.polylines2vModel = pls_2v
bd.vKnifeModelExpandSize = sizeout_2v
// 7.同刀辅助 外扩尺寸
let isUseSameKnifeToHelpCut = this.sysConfig.helpCutStyle || 0
let useSameKnifeToHelpCutGap = this.sysConfig.helpCutGap || 0
const isSameKnifeToCut = isUseSameKnifeToHelpCut && useSameKnifeToHelpCutGap > 0
if (isSameKnifeToCut == false || bd.isUseSameKnifeToHelpCut == false) {
// 未启动同刀辅助, 或 该小板 不需要同刀辅助
bd.useSameKnifeToHelpCutGap = 0
bd.sameKnfieHelpExpandSize = new SizeExpand()
}
else {
const gap = useSameKnifeToHelpCutGap
bd.useSameKnifeToHelpCutGap = gap
bd.sameKnfieHelpExpandSize = new SizeExpand({ left: gap, right: gap, under: gap, upper: gap })
}
// 2V刀路,预洗,同刀辅助开料,出板造型刀
this.resetBlock(block, pm)
}
/** 二维刀路初始化 */
init2VModel(blockDetail: PlaceBlockDetail, isCNC = false, _knifeHelper: KnifeHelper) {
for (let model of blockDetail.models) {
if (!model.isVKnifeModel)
continue
let vModels: any = []
model.VLines = vModels
let isFaceB = model.face == FaceType.BACK
if (model.pointList.length < 1)
continue
let ps = model.pointList.map((t) => { return { x: t.pointX, y: t.pointY, bul: t.curve } })
let pl = PolylineHelper.create(ps)
if (model.VLines?.length > 0)
return // 已经分析了
model.VLines = []
for (let os of model.offsetList) {
let knife1 = isCNC ? null : _knifeHelper.getModelKnifeByName(os.name) // getModelKnifeByName(os.name)
let knifeR = os.radius
let knifeId = knife1 ? knife1.knifeId : -1
try {
let vps_1 = PolylineHelper.getVModelPoints_offset(pl, os.offset, os.depth, os.angle)
let vLine = { isFaceB, name: os.name, value: os.offset, knife: knife1, knifeId, knifeRadius: knifeR, depth: os.depth, points: vps_1, offset: os }
vModels.push(vLine) // 偏移路径
model.VLines.push(vLine)
}
catch (err) {
console.log('v型刀走刀路径算法出错。' + err)
}
}
model.VLines = vModels
}
}
}

View File

@@ -1,283 +0,0 @@
import { ConfigBase } from "../../src/models/config";
import { confItem, HoleArrange, Knife, PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass";
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
/** 配置列表 */
export const confList: confItem[] = [
{
key: 'isCutProcess',
label: '开料机(雕刻机)加工',
value: true
},
{
key: 'isCutAndCNCProcess',
label: '开料机CNC组合',
value: false
},
{
key: 'isCustomized',
label: '定制加工',
value: false
},
{
key: 'cutBoardBorder',
label: '总修边宽度',
value: 3
},
{
key: 'blockKnifeLineSpacing',
label: '刀路间距',
value: 0
},
{
key: 'isDoubleFaceBlockFirst',
label: '双面开料优先排版',
value: true
},
{
key: 'isRectPlace',
label: '新优化规则排版',
value: false
},
{
// yuLiaoBoardDo2FaceBlock
key: 'isDoubleFaceBlockInRemain',
label: '余料板允许排入双面加工的小板',
value: true
},
]
/**
* 新优化
* 调用流程
* 1、准备工作 加载配置 + 加载刀库 + 设置 回调函数
* 2、调用 startPlaceThreed
* 入参data (数据源 必须包含 小板数据列表和小板明细数据列表)
* pm 优化的材质
* pm.diameter = knife?.diameter
pm.cutKnifeName = knife.knifeName
*/
export class BusinessRectOptimizeMachine extends ConfigBase {
/** 多线程数据集 */
workerList: WorkerItemType[] = []
/** 优化回调 */
placeCallBack: Function = () => { }
constructor() {
super()
}
/** 设置优化回调 */
setPlaceTaskCallBackFun(func: Function) {
this.placeCallBack = func
}
/**开始优化线程 输入的数据没有初始化
* @param data 数据源 包含小板列表和小板明细列表 data内需要包含blockList小板列表 和 blockDetailList小板明细列表
* @param pm 优化的材质 包含该材质的可用开料大板信息 需包含开料刀的信息 pm.diameter pm.cutKnifeName
*/
startPlaceThreed(data, pm: PlaceMaterial) {
let { blockList, blockDetailList } = data
let bList: any = []
blockList.map(e => {
if (e.goodsId == pm.goodsId) {
bList[e.blockNo] = e
let detail = blockDetailList.find(x => x.blockId == e.blockId)
if (!Reflect.has(bList[e.blockNo], 'blockDetail')) {
bList[e.blockNo].blockDetail = new PlaceBlockDetail(detail)
}
// bList[e.blockNo].isTurnFaceToPlace = !this.getDoFace(bList[e.blockNo], this.processMode())
// 是否翻面后续处理
bList[e.blockNo].isTurnFaceToPlace = true
pm.blockList.push(e)
}
})
pm.cutBorder = this.cutBoardBorder
pm.cutKnifeGap = this.blockKnifeLineSpacing
/** 小板 */
let bans: xbang[] = []
// 实际开料大板的列表
let big_Bang: Big_bang[] = []
let big_BangSL: number[] = []
let border = this.cutBoardBorder
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
// 余料板 以及实际开料大板
for (const cuttingBoard of pm.remainBoardList) {
big_Bang.push({ w: cuttingBoard.width - border * 2 + borderOff * 2, l: cuttingBoard.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
big_BangSL.push(cuttingBoard.count || 999)
}
// big_Bang = []
// big_BangSL = []
// 母板 兜底的
big_Bang.push({ w: pm.width - border * 2 + borderOff * 2, l: pm.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
// 生成小板
for (let key in bList) {
let b = bList[key]
let bid = b.blockNo
let width = b.placeFullWidth
let length = b.placeFullLength
let line = this.toLine(b.texture)
bans.push({
l: length,
w: width,
line,
face: this.toface(b),
id: bid,
bno: b.blockNo,
holeFaceCount: 3,
isRect: !b.isUnRegular,
hasHole: false,
isdtwosided: true,
})
}
let bestCount = 0
if (bans.length == 0) // 没有板了
{
let best = []
let yl: Big_bang[] = []
let fit = 0
let resObj = {
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
info: {
times: -1,
type: 'noBan'
}
}
this.placeCallBack(resObj)
return
}
let xyhcs = 50
if (bans.length > 1000) { xyhcs = 40 }
else if (bans.length > 1500) { xyhcs = 30 }
else if (bans.length > 2000) { xyhcs = 25 }
else if (bans.length > 4000) { xyhcs = 20 }
else if (bans.length > 6000) { xyhcs = 15 }
else if (bans.length > 10000) { xyhcs = 10 }
else if (bans.length > 15000) { xyhcs = 5 }
else if (bans.length > 20000) { xyhcs = 1 }
let isDoubleFaceBlockFirst = this.isDoubleFaceBlockFirst // 双面加工排前面
let gap = this.blockKnifeLineSpacing
// this.bestfit = 0
for (let j = 0; j < 1; j++) {
let w = new Worker(new URL('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker', import.meta.url), { type: 'module' })
const data = {
type: 'start',
data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.isRectPlace, this.isDoubleFaceBlockInRemain],
}
let item: WorkerItemType = {
w: w,
goodsId: pm.goodsId,
pm,
status: 'start'
}
if (this.workerList.findIndex(e => e.goodsId == item.goodsId) == -1) {
this.workerList.push(item)
}
let workItem = this.workerList.find(e => e.goodsId == pm.goodsId)
if (workItem && workItem != undefined) {
workItem.w?.postMessage(data)
workItem.w.onmessage = async (d) => {
let [best, yl, fit, info] = d.data as [any[], Big_bang[], number, any]
switch (info.type) {
case 'loop':
let resObj = {
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
info
}
this.placeCallBack(resObj)
break;
case 'stop':
console.error('stop =》dataHandleBase', info, this.workerList)
this.terminateWorker({ goodsId: pm.goodsId })
break;
case 'isStop':
// console.error('isStop', info)
break;
default:
break;
}
}
}
console.log('新算法线程启动', pm.goodsId, this.workerList)
}
}
/** 停止优化 */
async stopPlaceTask() {
for (const key in this.workerList) {
await this.terminateWorker({ key })
}
}
/**
* 获取加工面
* @param block
* @param processMode
* @returns
*/
getDoFace(block: PlaceBlock, processMode: number = 0): boolean {
// 模式0: 开料机处理排钻, 造型, 采用大孔面作为正面的模式. 正面多加工, cnc多加工
if (processMode == 0) {
// 先考虑 设计开料面
if (block.placeHole == HoleArrange.FRONT)
return true
if (block.placeHole == HoleArrange.BACK)
return false
// 造型单面 作为开料面
if (block.modelCountFront() > 0 && block.modelCountBack() == 0)
return true
if (block.modelCountFront() == 0 && block.modelCountBack() > 0)
return false
// 优先考虑 大孔面 多孔面
return this.getHoleFaceMore(block)
} else {
return false
}
}
processMode() {
let processMode = 0;
if (this.isCutProcess) processMode = 0;
if (this.isCutAndCNCProcess) processMode = 1;
if (this.isCustomized) processMode = 2;
return processMode;
}
/**
* 正纹 = 0, 可翻转 = 1, 反纹 = 2;
* 正文 Positive = 0, 反纹 Reverse = 1, 可翻转 CanReversal = 2,
*/
toLine(texture): LineType {
if (texture == 0)
return LineType.Positive
if (texture == 1)
return LineType.CanReversal
return LineType.Reverse
}
/** 小板加工面Positive=0正面 Reverse=1反面 Arbitrary=2任意 */
toface(block: PlaceBlock): ComposingType {
let turnF = block.isTurnFaceToPlace
if (this.isTurnFaceToPlace)
turnF = !turnF
if (!turnF)
return ComposingType.Positive
return ComposingType.Reverse
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
import { Processor, ProcessorCollection, ProcessorModule } from "../src/device";
export class ProcessorManager<T, R> implements ProcessorCollection<T, R> {
private processors = new Map<string, Processor<T, R>>();
private currentProcessor?: Processor<T, R>;
constructor(){
}
/** 注册模块流程 */
registerProcessor(name: string, processor: Processor<T, R>): this {
this.processors.set(name, processor);
return this;
}
/** 使用处理器 */
useProcessor(name: string): Processor<T, R> {
const processor = this.processors.get(name);
if (!processor) {
throw new Error(`Processor ${name} not found`);
}
this.currentProcessor = processor;
return processor;
}
/** 获取处理器 */
getProcessor(name: string): Processor<T, R> | undefined {
return this.processors.get(name);
}
/** 获取正在使用的处理器 */
getCurrentProcessor(): Processor<T, R> | undefined {
return this.currentProcessor;
}
}

View File

@@ -1,75 +0,0 @@
import { ProcessorModule, StepControllerProcessor } from "../../src/device";
import { RectOptimizeMachineModule } from "../moduleManager/module1";
/**
* demo 开料机处理器
*
*
*/
export class demoHandleGroupCutting {
processorName = "cutting"
processor: StepControllerProcessor<any, any>
constructor() {
const callbackStyleModule: ProcessorModule<any, any> = {
moduleName: "callbackStyle",
process(input, next, context) {
console.log("做优化");
const _input = input
const _next = next
const _context = context
// 可以在这里调用异步操作
Reflect.set(context, 'CallBack', callBack1)
// 决定是否调用 next
function callBack1(v) {
console.log('接收到其它模块回传的数据', v);
}
// 调用 next 继续流程
return next(input);
}
};
const demoModule: ProcessorModule<string, string> = {
moduleName: "demoModule",
process(input, next, context) {
// 写入上下文
context.processedAt = new Date().toLocaleString();
context.originalLength = input.length;
// 设置下一步需要的上下文
context.previousStep = "demoModule";
if (context.CallBack) {
context.CallBack("demoModule end and callback")
}
return next(input);
}
};
this.processor = new StepControllerProcessor<any, any>();
this.processor.use([
{
moduleName: "traditional",
handle(input, next) {
// 第一个流程
console.log(`第一个模块功能:有${input?.blockList.length}片小板,可以做些计算`)
return next ? next(input) : input;
}
},
callbackStyleModule,
demoModule,
RectOptimizeMachineModule,
{
moduleName: "final",
process(input, next) {
// 不调用 next终止流程
console.log('结束了')
return next(input);
}
}
])
}
}

View File

@@ -1,18 +0,0 @@
import { DeviceBase } from "../../src/device";
import { ConfigBase } from "../../src/models/config";
import { BusinessRectOptimizeMachine } from "../businessCapability/businessRectOptimizeMachine";
/**
* 某个处理器 一个 功能 和 业务功能的集合
* 可以包含多个业务功能和 基础功能
*
*
*/
export class demoHandleGroup extends DeviceBase {
constructor(){
super()
}
}

View File

@@ -1,112 +0,0 @@
import { _knifeType, Knife } from "./confClass"
/** demo 刀具数据 */
export const knifeData :Knife= {
"isEnabled": true,
"axleId": 2,
"knifeId": 2,
"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",
}
export const knifeData1:_knifeType = {
"isEnabled": true,
"axleId": 2,
"knifeId": 2,
"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",
}
export const knifeData2:_knifeType = {
"isEnabled": true,
"axleId": 2,
"knifeId": 2,
"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",
}

View File

@@ -1,126 +0,0 @@
import { ConfigBase } from "../src/base";
import { ParserBase } from "../src/parsers";
import { _knifeType, CodeParams } from "./confClass";
import { knifeData, knifeData1, knifeData2 } from "./demoKnives";
import { GCodeAction } from "./gcodes";
export class DemoParser extends ParserBase {
usedKnife?:_knifeType
knifeList:_knifeType[] = [
knifeData,
knifeData1,
knifeData2
]
constructor(config?: Record<string, ConfigBase>) {
super();
// if(config){
// if(config['knives']){
//
// }
// }
const gcodeActions = new GCodeAction();
this.codeManager['FSTART'] = {
name: 'FileStart',
// type:'Global',
// paramType:'array',
exec:(params) =>{
return `G54 G90\nM03\n`
}
};
this.codeManager['FEND'] = {
name: 'FileEnd',
exec:(params) =>{
return `M30\n`;
}
}
this.codeManager['G0'] = {
name: 'G0',
paramType: 'kv:number',
exec:(params: CodeParams) => {
return gcodeActions.G0(params) + '\n';
}
}
this.codeManager['G2'] = {
name: 'G2',
paramType: 'kv:number',
exec:(params: CodeParams)=> {
return gcodeActions.G2(params) + '\n';
}
}
this.codeManager['G3'] = {
name: 'G3',
paramType: 'kv:number',
exec:(params: CodeParams)=> {
return gcodeActions.G3(params) + '\n';
}
}
this.codeManager['TD'] = {
name: '',
exec:(diameter:number)=> {
let code = ''
let _knife: any
if (diameter) {
_knife = this.knifeList?.find(e => e.diameter == diameter)
}
if (_knife != undefined) {
code += this.checkChangeKnife(_knife)
this.usedKnife = _knife
// code += _knife.axisStartCode
code += _knife.knifeStartCode
}
return code
}
}
this.codeManager['TN'] = {
name: 'TN',
exec:(knifeName:string) =>{ /**根据刀名称找刀 换刀 */
let code = ''
let _knife: any = null
if (knifeName) {
_knife = this.knifeList?.find(e => e.knifeName == knifeName)
}
if (_knife != undefined) {
code += this.checkChangeKnife(_knife)
this.usedKnife = _knife
// code += _knife.axisStartCode
code += _knife.knifeStartCode
}
return code
}
}
this.codeManager['TE'] = {
name:'TE',
exec:()=>{
if(!this.usedKnife) return '';
return this.usedKnife.knifeStopCode+'\n';
}
}
}
/** 内部功能逻辑 换刀时判断是否需要 上一把刀的停止代码 */
private checkChangeKnife(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.exec('TE',[]);
}
return code;
}
}

View File

@@ -1,528 +0,0 @@
import { CodeParams } from "./confClass"
function checkVal(val) {
let r = true
if ((val == undefined || val == '')) {
r = false
}
return r
}
/**G代码指令
* 目前 只需要做 G0 - G3
*/
export enum GCode {
/**快速定位 空程移动 范例G00 X100 Y50刀具快速移动至X100、Y50位置 */
G0 = 'G0',
/**快速定位 空程移动 范例G00 X100 Y50刀具快速移动至X100、Y50位置*/
G00 = 'G00',
/**直线 直线插补 范例G01 X200 Y300 F150刀具以150mm/min的进给速度直线移动至X200、Y300*/
G1 = 'G1',
/**直线 范例G01 X200 Y300 F150刀具以150mm/min的进给速度直线移动至X200、Y300*/
G01 = 'G01',
/** 顺时针 范例G17 G02 X150 Y50 I50 J0 F100在XY平面以半径50mm顺时针圆弧插补至X150、Y50*/
G2 = 'G2',
/** 顺时针 范例G17 G02 X150 Y50 I50 J0 F100在XY平面以半径50mm顺时针圆弧插补至X150、Y50*/
G02 = 'G02',
/** 逆时针 范例G17 G03 X150 Y50 I50 J0 F100在XY平面以半径50mm逆时针圆弧插补至X150、Y50*/
G3 = 'G3',
/** 逆时针 范例G17 G03 X150 Y50 I50 J0 F100在XY平面以半径50mm逆时针圆弧插补至X150、Y50*/
G03 = 'G03',
/**暂停 范例G04 X2暂停2秒*/
G4 = 'G4',
/**暂停 范例G04 X2暂停2秒*/
G04 = 'G04',
/** 通过中间点圆弧插补
* 1. 通过中间点圆弧插补
* 示例G05 X60 Z50 IX50 IZ60 F120 刀具从当前位置出发,经过中间点 (50, 60),最终到达终点 (60, 50),形成圆弧轨迹。
* 2. 高精轨迹控制HPCC模式
* 功能G05 可启动高精轨迹控制模式HPCC通过曲线拟合和参数优化实现高精度、高速度的轨迹加工适用于复杂曲面或高精度零件。
* 指令格式:
* 启动G05 P10000P值设为10000
* 关闭G05 P0P值设为0
* 3、注意事项
* 轴类型限制建议线性轴参与HPCC模式旋转轴需谨慎设置。
与其他功能冲突:
在HPCC模式下G61准确停止检查、G63攻螺纹等功能可能失效需退出HPCC模式后恢复。
启用 RTCP旋转刀具中心点或 STCP 模式时禁止同时启用HPCC功能。
不支持的功能:
不支持单节停止C40/M00、反向手轮模拟、图形模拟等。
剩余距离显示可能不准确,因显示的是离曲线终点的距离,而非单节终点。
* */
G5 = 'G5',
/** 通过中间点圆弧插补 */
G05 = 'G05',
/** 抛物线插补 */
G6 = 'G6',
/** 抛物线插补 */
G06 = 'G06',
/** z样条曲线插补 */
G7 = 'G7',
/** z样条曲线插补 */
G07 = 'G07',
/** 进给加速 */
G8 = 'G8',
/** 进给加速 */
G08 = 'G08',
/** 进给减速 */
G9 = 'G9',
/** 进给减速 */
G09 = 'G09',
/** 参数写入方式有效 */
G10 = 'G10',
/** 参数写入方式取消 */
G11 = 'G11',
/** 极坐标变成 */
G16 = 'G16',
/** XY平面选择 */
G17 = 'G17',
/** XZ平面选择 */
G18 = 'G18',
/** YZ平面选择 */
G19 = 'G19',
/** 英制输入 */
G20 = 'G20',
/** 公制输入 */
G21 = 'G21',
/** 半径尺寸编程方式 */
G22 = 'G22',
/** 系统操作界面上使用 */
G220 = 'G220',
/** 直径尺寸编程方式 */
G23 = 'G23',
/** 系统操作界面上使用 */
G230 = 'G230',
/** 子程序结束 */
G24 = 'G24',
/** 跳转加工 */
G25 = 'G25',
/** 循环加工 */
G26 = 'G26',
/** 参考点返回 */
G28 = 'G28',
/** 倍率注销 */
G30 = 'G30',
/** 倍率定义 */
G31 = 'G31',
/** 等螺距螺纹切削,英制 */
G32 = 'G32',
/** 等螺距螺纹切削,公制 */
G33 = 'G33',
/** 增螺距螺纹切削 */
G34 = 'G34',
/** 减螺距螺纹切削 */
G35 = 'G35',
//** 刀具半径补正取消 */
G40 = 'G40',
//** 刀具半径补正 左*/
G41 = 'G41',
//** 刀具半径补正 右*/
G42 = 'G42',
//** 刀具长度补正+ */
G43 = 'G43',
//** 刀具长度补正- */
G44 = 'G44',
//** 道具偏置+/+ */
G45 = 'G45',
//** 道具偏置+/- */
G46 = 'G46',
//** 道具偏置-/- */
G47 = 'G47',
//** 道具偏置-/+ */
G48 = 'G48',
//** 刀具长度 补正取消 */
G49 = 'G49',
//** 局部坐标系设定 */
G52 = 'G52',
//** 机床坐标系选择 */
G53 = 'G53',
//** 工件坐标系选择1 */
G54 = 'G54',
//** 工件坐标系选择2 */
G55 = 'G55',
//** 工件坐标系选择3 */
G56 = 'G56',
//** 工件坐标系选择4 */
G57 = 'G57',
//** 工件坐标系选择5 */
G58 = 'G58',
//** 工件坐标系选择6 */
G59 = 'G59',
//** 坐标系旋转有效 */
G68 = 'G68',
//** 坐标系旋转取消 */
G69 = 'G69',
/** 高速深孔钻 */
G73 = 'G73',
/** 精搪孔 */
G76 = 'G76',
/** 固定循环取消/取消循环指令 */
G80 = 'G80',
/**程序停止 */
M00 = 'M00',
/**选择性停止 */
M01 = 'M01',
/** 程序结束 */
M02 = 'M02',
/**主轴正转 */
M03 = 'M03',
/**主轴反转 */
M04 = 'M04',
/**主轴停止 */
M05 = 'M05',
/**自动换刀 */
M06 = 'M06',
}
/**G代码基类 */
export class GCodeAction {
/** 空程直线 */
G0(params: CodeParams) {
const { x, y, z, f, xKey, yKey, zKey, fKey, codeKey } = params
let val: string = GCode.G0
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 空程直线 */
G00(params: CodeParams) {
const { x, y, z, f, xKey, yKey, zKey, fKey, codeKey } = params
let val: string = GCode.G00
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 直线 */
G1(params: CodeParams) {
const { x, y, z, f, xKey, yKey, zKey, fKey, codeKey } = params
let val: string = GCode.G1
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 直线 */
G01(params: CodeParams) {
const { x, y, z, f, xKey, yKey, zKey, fKey, codeKey } = params
let val: string = GCode.G01
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 顺时针 弧线
* @param x x坐标
* @param y y坐标
* @param z z坐标
* @param i 圆弧 对于起点的偏移量 x
* @param y 圆弧 对于起点的偏移量 Y
* @param z 圆弧 对于起点的偏移量 Y
* @param f 速度
*/
G2(params: CodeParams) {
const { x, y, z, i, j, k, r, f, xKey, yKey, zKey, fKey, iKey, jKey, kKey, codeKey } = params
let val: string = GCode.G2
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
let _iKey = iKey || 'I'
let _jKey = jKey || 'J'
let _kKey = kKey || 'K'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(r)) {
val += ` R${r}`
} else {
if (checkVal(i)) {
val += ` ${_iKey}${i}`
}
if (checkVal(j)) {
val += ` ${_jKey}${j}`
}
if (checkVal(k)) {
val += ` ${_kKey}${k}`
}
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 顺时针 弧线
* @param x x坐标
* @param y y坐标
* @param z z坐标
* @param i 圆弧 对于起点的偏移量 x
* @param y 圆弧 对于起点的偏移量 Y
* @param z 圆弧 对于起点的偏移量 Y
* @param f 速度
*/
G02(params: CodeParams) {
const { x, y, z, i, j, k, r, f, xKey, yKey, zKey, fKey, iKey, jKey, kKey, codeKey } = params
let val: string = GCode.G02
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
let _iKey = iKey || 'I'
let _jKey = jKey || 'J'
let _kKey = kKey || 'K'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(r)) {
val += ` R${r}`
} else {
if (checkVal(i)) {
val += ` ${_iKey}${i}`
}
if (checkVal(j)) {
val += ` ${_jKey}${j}`
}
if (checkVal(k)) {
val += ` ${_kKey}${k}`
}
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 逆时针 弧线
* @param x x坐标
* @param y y坐标
* @param z z坐标
* @param i 圆弧 对于起点的偏移量 x
* @param y 圆弧 对于起点的偏移量 Y
* @param z 圆弧 对于起点的偏移量 Y
* @param f 速度
*/
G3(params: CodeParams) {
const { x, y, z, i, j, k, r, f, xKey, yKey, zKey, fKey, iKey, jKey, kKey, codeKey } = params
let val: string = GCode.G3
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
let _iKey = iKey || 'I'
let _jKey = jKey || 'J'
let _kKey = kKey || 'K'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(r)) {
val += ` R${r}`
} else {
if (checkVal(i)) {
val += ` ${_iKey}${i}`
}
if (checkVal(j)) {
val += ` ${_jKey}${j}`
}
if (checkVal(k)) {
val += ` ${_kKey}${k}`
}
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 逆时针 弧线
* @param x x坐标
* @param y y坐标
* @param z z坐标
* @param i 圆弧 对于起点的偏移量 x
* @param y 圆弧 对于起点的偏移量 Y
* @param z 圆弧 对于起点的偏移量 Y
* @param f 速度
*/
G03(params: CodeParams) {
const { x, y, z, i, j, k, r, f, xKey, yKey, zKey, fKey, iKey, jKey, kKey, codeKey } = params
let val: string = GCode.G03
if (typeof (codeKey) == 'string' && codeKey != '') {
val = codeKey
}
let _xkey = xKey || 'X'
let _yKey = yKey || 'Y'
let _zKey = zKey || 'Z'
let _fKey = fKey || 'F'
let _iKey = iKey || 'I'
let _jKey = jKey || 'J'
let _kKey = kKey || 'K'
if (checkVal(x)) {
val += ` ${_xkey}${x}`
}
if (checkVal(y)) {
val += ` ${_yKey}${y}`
}
if (checkVal(z)) {
val += ` ${_zKey}${z}`
}
if (checkVal(r)) {
val += ` R${r}`
} else {
if (checkVal(i)) {
val += ` ${_iKey}${i}`
}
if (checkVal(j)) {
val += ` ${_jKey}${j}`
}
if (checkVal(k)) {
val += ` ${_kKey}${k}`
}
}
if (checkVal(f)) {
val += ` ${_fKey}${f}`
}
return val
}
/** 主轴正转
*
* @param s 转速
*/
M03(s: any) {
let val: string = GCode.M03
if (checkVal(s)) {
val += ` S${s}`
}
return val
}
/**主轴反向转
* @param s 转速
*/
M04(s: any) {
let val: string = GCode.M04
if (checkVal(s)) {
val += ` S${s}`
}
return val
}
/**主轴停止 */
M05() {
let val: string = GCode.M05
return val
}
/**换刀指令 */
M06(t: String) {
let val: string = GCode.M06
if (checkVal(t)) {
val += ` T${t}`
}
return val
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
import type { Big_bang, xbang } from './bang'
import { RectOptimizeMachine } from './RectOptimizeMachine'
import {Worker} from "worker_threads"
const ctx: Worker = self as any
ctx.addListener('message', async (event) => {
let m = new RectOptimizeMachine()
m.CallBack = async (best, fit, arg, info) => {
ctx.postMessage([best, fit, arg, info])
}
if (event.data.type == 'start') {
/**
* blockList 小板列表
* boardList 大板(N个元素前N-1个元素表示余料板且余料板须为矩形第N个元素表示大板)
* boardCount 余料板数量(bigBang中前N-1个元素对应的数量如果bigBang中只有一个元素即只有大板没有余料板则为空数组)
* optimizeTimes 新优化次数
* isDoubleFaceBlockFirst 双面加工的小板是否优先排入
* gap 排版缝隙 = 开料刀直径 + 缝隙
* gzpb 规则排版
* isDoubleFaceBlockInRemain 余料板是否排入双面加工的小板
*/
let [blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain] = (event.data.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean]
m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain)
} else {
const info = {
type: 'isStop',
}
await m.Stop(info)
ctx.postMessage([[], null, null, info])
ctx?.terminate()
}
})
// ctx.addEventListener('message', async (event) => {
// let m = new RectOptimizeMachine()
// m.CallBack = async (best, fit, arg, info) => {
// ctx.postMessage([best, fit, arg, info])
// }
// if (event.data.type == 'start') {
// /**
// * blockList 小板列表
// * boardList 大板(N个元素前N-1个元素表示余料板且余料板须为矩形第N个元素表示大板)
// * boardCount 余料板数量(bigBang中前N-1个元素对应的数量如果bigBang中只有一个元素即只有大板没有余料板则为空数组)
// * optimizeTimes 新优化次数
// * isDoubleFaceBlockFirst 双面加工的小板是否优先排入
// * gap 排版缝隙 = 开料刀直径 + 缝隙
// * gzpb 规则排版
// * isDoubleFaceBlockInRemain 余料板是否排入双面加工的小板
// */
// let [blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain] = (event.data.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean]
// m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain)
// } else {
// const info = {
// type: 'isStop',
// }
// await m.Stop(info)
// ctx.postMessage([[], null, null, info])
// ctx?.terminate()
// }
// })
export default {} as typeof Worker & (new () => Worker)

View File

@@ -1,115 +0,0 @@
export type Con = YH_bang[];//单块1220*2440的板结果 Container
export type Inv = Con[]; //个体:优化结果 包含多个大板 Individual
/**纹路类型 Positive=0正纹 Reverse=1反纹 CanReversal=2可翻转 */
export enum LineType
{
/**正纹 */
Positive = 0,
/**反纹 */
Reverse = 1,
/**可翻转 */
CanReversal = 2,
}
/**优化的大板 */
export interface YH_bang
{
/**板ID */
bangid: number;
/**纹路 */
line: LineType;
x: number;
y: number;
/**排版高 */
pbg: number;
/**排版宽 */
pbk: number;
ishb?: boolean;//是否参与合并的板
hb?: number[]; //合在并的板
isgr?: boolean; //是否关连
gr?: number[];//关联的板的集合
grid?: number; //跟别的板关联的ID
isbig?: boolean;//是否为合并的大板
isqg?: boolean;//是否被切掉的板
}
/**版面类型: Positive=0正面 Reverse=1反面 Arbitrary=2任意面 */
export enum ComposingType
{
/**正面 */
Positive = 0,
/**反面 */
Reverse = 1,
/**任意面 */
Arbitrary = 2
}
/**孔类型 None=0无 Positive=1正面 Reverse=2反面 Two=3正反 */
export enum HoleType
{
/**无 */
None = 0,
/**正面 */
Positive = 1,
/**反面 */
Reverse = 2,
/**正反 */
Two = 3
}
/** 小板 */
export interface xbang
{
/**长 */
l: number;
/**宽 */
w: number;
/**纹路 */
line: LineType;
/**排版面 */
face: ComposingType;
/**小板ID */
id: number;
/**小板编号 */
bno: string;
/**孔面: 0无孔 1正面有孔 2反面有孔 3正反面都有孔 */
holeFaceCount: HoleType;
/**是矩形 */
isRect?: boolean;
/**有孔 */
hasHole?: boolean;
/**false单面 true双面 */
isdtwosided?: boolean;
}
/** 大板 */
export interface Big_bang //待优化的板
{
l: number; //长
w: number; //宽
x: number; //x
y: number; //y
}
export enum BlockRegion
{
/** 左下 = 0 */
LEFT_BOTTOM = 0,
/** 右下 = 1 */
RIGHT_BOTTOM = 1,
/** 右上 = 2 */
RIGHT_TOP = 2,
/** 左上 = 3 */
LEFT_TOP = 3,
}
export class WorkerItemType {
w?: Worker
goodsId?: string | number
pm?: any
status?: 'start' | 'stop'
}

View File

@@ -1 +0,0 @@
# 新优化

View File

@@ -1,13 +0,0 @@
import { ConfigBase } from "../../src/models/config";
/** 基础功能1 -- 新优化 */
export class BlockPlace extends ConfigBase{
// 优化过程中的回调函数
callBack?:Function
constructor(){
super()
this.name = 'XXX'
this.version = '20250707'
}
}

View File

@@ -1,132 +0,0 @@
import { FaceType, PlaceBlock, PlaceBlockDetail, PlaceBorderContour, PlaceMaterial, PlaceStyle, SizeExpand } from "../confClass"
import { Arc2d, Curve2d, Line2d } from "./common/base/CAD"
import { PolylineHelper } from "./common/LayoutEngine/PolylineHelper"
import { KnifeHelper } from "./knifeHelper"
/** 小板明细 相关的计算 孔 造型 以及轮廓 */
export class BlockDetailHelperBase {
/** 造型轮廓(含封边),扣除封边, 变成开料坐标 */
resetModelContour(bd: PlaceBlockDetail) {
let ox = bd.offsetX
let oy = bd.offsetY
for (let m of bd.models) {
if (m.hasContour()) {
let ptsArr = m.originModeling.outline.map(e => e.pts)
for (let pt of ptsArr) {
// 23.8.5 发现矩形的挖穿轮廓坐标是不含封边的
pt.x -= ox
pt.y -= oy
}
}
}
}
// 原始轮廓,成品轮廓
createFinalBorder(bd: PlaceBlockDetail, block: PlaceBlock): Curve2d[] {
const orgPoints = bd.orgPoints
const orgBorderCurveList = new Array<Curve2d>()
if (orgPoints && orgPoints.length > 1) // 异形
{
const count = orgPoints.length
for (let i = 0; i < count; i++) {
const p0 = orgPoints[i]
const p1 = i == count - 1 ? orgPoints[0] : orgPoints[i + 1]
const sideHoleCount = bd.holeListSide.filter(t => t.faceId == i).length
if (p0.curve == 0) // 直线
{
const newLine = Line2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY)
newLine.tagData = p0.sealSize
newLine.tagData2 = sideHoleCount
orgBorderCurveList.push(newLine)
p0.radius = 0
}
else // 曲线
{
const crc = Arc2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY, p0.curve)
crc.tagData = p0.sealSize
crc.tagData2 = sideHoleCount
p0.radius = crc.m_Radius
orgBorderCurveList.push(crc)
}
}
}
else // 矩形板
{
const w = block.width
const l = block.length
const line0 = Line2d.New(0, 0, w, 0)
line0.tagData = block.sealBottom
line0.tagData2 = block.holeCountBottom() || 0
const line1 = Line2d.New(w, 0, w, l)
line1.tagData = block.sealRight
line1.tagData2 = block.holeCountRight() || 0
const line2 = Line2d.New(w, l, 0, l)
line2.tagData = block.sealTop
line2.tagData2 = block.holeCountTop() || 0
const line3 = Line2d.New(0, l, 0, 0)
line3.tagData = block.sealLeft
line3.tagData2 = block.holeCountLeft() || 0
orgBorderCurveList.push(line0)
orgBorderCurveList.push(line1)
orgBorderCurveList.push(line2)
orgBorderCurveList.push(line3)
}
return orgBorderCurveList
}
/** 创建 开料轮廓不含预铣 */
createOrgBorder(bd: PlaceBlockDetail): Curve2d[] {
const borders = new Array<Curve2d>()
if (bd.points && bd.points.length > 1) // 异形
{
const count = bd.points.length
for (let i = 0; i < count - 1; i++) // 异形点(无封边,起点终点 是重复的)
{
const p0 = bd.points[i]
const p1 = i == count - 1 ? bd.points[0] : bd.points[i + 1]
const sideHoleCount = bd.holeListSide.filter(t => t.faceId == i).length
if (p0.curve == 0) // 直线
{
const newLine = Line2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY)
newLine.tagData = p0.sealSize
newLine.tagData2 = sideHoleCount
borders.push(newLine)
p0.radius = 0
}
else // 曲线
{
const crc = Arc2d.New(p0.pointX, p0.pointY, p1.pointX, p1.pointY, p0.curve)
crc.tagData = p0.sealSize
crc.tagData2 = sideHoleCount
p0.radius = crc.m_Radius
if (p0.radius < 2) {
p0.curve = 0
p0.radius = 0
}
borders.push(crc)
}
}
}
else // 矩形板
{
const w = bd.cutWidth
const l = bd.cutLength
const line0 = Line2d.New(0, 0, w, 0)
const line1 = Line2d.New(w, 0, w, l)
const line2 = Line2d.New(w, l, 0, l)
const line3 = Line2d.New(0, l, 0, 0)
borders.push(line0)
borders.push(line1)
borders.push(line2)
borders.push(line3)
}
return borders
}
}

View File

@@ -1,4 +0,0 @@
/** 小板相关的计算 */
export class BlockHelper{
}

View File

@@ -1,392 +0,0 @@
export class List<Item> extends Array<Item>
{
/** 返回符合条件的第一个元素 */
first(fn: (item: Item) => boolean): Item
{
if (this.length == 0)
return null
for (const item of this)
{
if (fn(item))
return item
}
return null
}
/** 返回符合条件的最后一元素 */
last(fn: (t: Item) => boolean): Item
{
if (this.length == 0)
return null
for (let i = this.length - 1; i >= 0; i--)
{
if (fn(this[i]))
return this[i]
}
return null
}
/** 最大值 */
max<T>(fn: (item: Item) => T): T
{
let maxV: T
for (const i of this)
{
let v = fn(i)
if (maxV == null)
{
maxV = fn(i)
}
else
{
if (v > maxV)
{
maxV = v
}
}
}
return maxV
}
/** 最小值 */
min<T>(fn: (item: Item) => T): T
{
let minV: T
for (const i of this)
{
let v = fn(i)
if (minV == null)
{
minV = fn(i)
}
else
{
if (v < minV)
{
minV = v
}
}
}
return minV
}
/** 累加 */
sum(fn: (item: Item) => number): number
{
let v: number = 0
for (const t of this)
{
v = v + fn(t)
}
return v
}
/** 平均值 */
avg(fn: (item: Item) => number): number
{
if (this.length == 0)
return 0
let sum = this.sum(fn)
return sum / this.length
}
/** 满足条件的元素数量 */
count(fn: (item: Item) => number): number
{
if (this.length == 0)
return 0
let c = 0
for (const item of this)
{
if (fn(item))
c = c + 1
}
return c
}
FindMax<T>(fn: (item: Item) => T): Item
{
return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b)
}
}
export class ArrayExt
{
/** 返回满足条件的元素数量 */
static count<Item>(list: Item[], fn: (item: Item) => boolean): number
{
if (list.length == 0)
return 0
let c = 0
for (const item of list)
{
if (fn(item))
c = c + 1
}
return c
}
/** 移除 */
static remove<Item>(list: Item[], obj: Item)
{
let index = list.findIndex(t => t == obj)
if (index == -1)
return
list.splice(index, 1)
}
/** 返回符合条件的第一个元素 */
static first<Item>(list: Item[], fn: (item: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
{
if (list.length == 0)
return null
if (orderFn1 == null)
{
for (const item of list)
{
if (fn(item))
return item
}
return null
}
let minValue1: number
let minValue2: number
let minItem: Item
for (const item of list)
{
if (fn(item) == false)
continue
let v1 = orderFn1(item)
let v2 = orderFn2 != null ? orderFn2(item) : 0
if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2))
{
minValue1 = v1
minValue2 = v2
minItem = item
}
}
return minItem
}
/** 返回符合条件的最后一元素 */
static last<Item>(list: Item[], fn: (t: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
{
if (list.length == 0)
return null
if (orderFn1 == null)
{
for (let i = list.length - 1; i >= 0; i--)
{
if (fn(list[i]))
return list[i]
}
return null
}
//
let maxValue1: number
let maxValue2: number
let maxItem: Item
for (const item of list)
{
if (fn(item) == false)
continue
let v1 = orderFn1(item)
let v2 = orderFn2 ? orderFn2(item) : 0
if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2))
{
maxValue1 = v1
maxValue2 = v2
maxItem = item
}
}
return maxItem
}
/** 取最大值 */
static max<T1, T2>(list: T1[], fn: (item: T1) => T2, whereF: (item: T1) => boolean = null, defaultV: T2 = null): T2
{
let maxV: T2
for (const i of list)
{
if (whereF && whereF(i) == false)
continue
let v = fn(i)
if (maxV == undefined)
{
maxV = fn(i)
}
else
{
if (v > maxV)
{
maxV = v
}
}
}
if (maxV != undefined)
return maxV
return defaultV
}
/** 最小值 */
static min<Item, T>(list: Item[], fn: (item: Item) => T, whereF: (item: Item) => boolean = null, defaultV: T = null): T
{
let minV: T
for (const i of list)
{
if (whereF && whereF(i) == false)
continue
let v = fn(i)
if (minV == undefined)
{
minV = v
}
else
{
if (v < minV)
{
minV = v
}
}
}
if (minV != undefined)
return minV
return defaultV
}
/** 累加 */
static sum<Item>(list: Item[], fn: (item: Item) => number, wn?: (item: Item) => boolean): number
{
let v: number = 0
for (const t of list)
{
if (wn && wn(t) == false)
continue
v = v + fn(t)
}
return v
}
/** 平均值 */
static avg<Item>(list: Item[], fn: (item: Item) => number): number
{
if (this.length == 0)
return 0
let sum = ArrayExt.sum(list, fn)
return sum / this.length
}
/** 排序 */
static sortBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null)
{
if (fn2 == null)
return list.sort((a: Item, b: Item): number => fn(a) - fn(b))
else
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
}
/** 降序 排序 */
static sortByDescending<Item>(list: Item[], fn: (item: Item) => number)
{
list.sort((a: Item, b: Item): number => fn(b) - fn(a))
}
/** 排序成新的数组 */
static orderBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
{
let newList = list.concat([])
if (fn2 == null)
return newList.sort((a: Item, b: Item): number => fn(a) - fn(b))
else
return newList.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
}
/** 降序成新的数组 */
static orderByDescending<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
{
let newList = list.concat([])
if (fn2 == null)
return list.sort((a: Item, b: Item): number => fn(b) - fn(a))
else
return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a))
}
/** 分组 */
static groupBy<Item, gT>(list: Item[], fn: (item: Item) => gT): GroupItem<gT, Item>[]
{
let groups = new Array<GroupItem<gT, Item>>()
for (const item of list)
{
let key = fn(item)
let group = groups.find(t => t.key == key)
if (group == null)
{
group = new GroupItem(key)
groups.push(group)
}
group.push(item)
}
return groups
}
/**
* 选择
* let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
*/
static Select<Item, rT>(list: Item[], fn: (item: Item) => rT): rT[]
{
let newList = new Array<rT>()
for (const t of list)
{
newList.push(fn(t))
}
return newList
}
/** 过来,并按顺序排序 */
static where<Item>(list: Item[], whereFn: (item: Item) => boolean, orderfn1: (item: Item) => number = null, orderfn2: (item: Item) => number = null): Item[]
{
let newList = list.filter(whereFn)
if (orderfn1 == null && orderfn2 == null)
return newList
return ArrayExt.sortBy(newList, orderfn1, orderfn2)
}
}
export class GroupItem<gT, Item>
{
key: gT
get count() { return this.list.length }
list: Item[]
constructor(k: gT)
{
this.key = k
this.list = []
}
push(d: Item)
{
this.list.push(d)
}
}
/**
* 对排序好的数组进行去重操作
* @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
* @returns {Array<T>} 返回自身
*/
export function arrayRemoveDuplicateBySort<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
{
if (arr.length < 2)
return arr
let j = 1
for (let i = 1, l = arr.length; i < l; i++)
if (!checkFuction(arr[j - 1], arr[i]))
arr[j++] = arr[i]
arr.length = j
return arr
}

View File

@@ -1,183 +0,0 @@
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)
}
};

View File

@@ -1,25 +0,0 @@
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('载入成功!')
})
})
}

View File

@@ -1,31 +0,0 @@
/**
* 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++]
}
}

View File

@@ -1,255 +0,0 @@
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
}

View File

@@ -1,369 +0,0 @@
import type { PolylineProps } from 'cadapi'
import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi'
import type { Box3 } from 'three'
import { Vector2, Vector3 } from 'three'
import { arrayRemoveDuplicateBySort } from '../ArrayExt'
import type { Curve2d } from '../base/CAD'
import { Arc2d, Point2d, copyTextToClipboard } from '../base/CAD'
import { CurveWrap } from './Curves2Parts'
// 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()
}
}

View File

@@ -1,85 +0,0 @@
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]
}

View File

@@ -1,5 +0,0 @@
export interface Point
{
x: number
y: number
}

View File

@@ -1,38 +0,0 @@
/** 判断两个值是否相等 */
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
}

View File

@@ -1,335 +0,0 @@
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
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,126 +0,0 @@
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])
}
}

View File

@@ -1,381 +0,0 @@
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
}

View File

@@ -1,202 +0,0 @@
import { _knifeType, Knife, SideModel } from "../confClass";
import { knifeData,knifeData1,knifeData2 } from "../demoKnives";
/** 刀库 管理刀具*/
export class KnifeHelper {
knifeList: Knife[]
constructor(_knifeList) {
this.knifeList.push(knifeData);
if (Array.isArray(_knifeList) && _knifeList.length >0 ) {
this.loadKnifeList(_knifeList)
}
}
loadKnifeList(_knifeList) {
this.knifeList = _knifeList || []
}
/** 根据刀名称获取造型刀 */
getModelKnifeByName(name: string): Knife | undefined {
return this.knifeList.find(t => t.knifeName.trim() == name.trim() && t.isModelingKnife());
}
/** 根据半径获取造型刀 */
getModelKnifeByRadius(radius: number, depth: number): Knife | undefined {
return this.knifeList.find(t => t.isModelingKnife() && Math.abs(t.diameter - 2 * radius) < 0.02 && t.length >= depth - 0.01);
}
/** 获取合适的开料刀作为造型刀 */
getFittedModelKnife(radius: number, depth: number): Knife | null {
const knives = this.knifeList.filter(t => t.isModelingKnife() && t.length > depth - 0.01 && t.diameter / 2 < radius - 0.02);
if (knives) {
knives.sort((a, b) => b.diameter - a.diameter); //按刀直径倒序排序
return knives[0];
}
return null;
}
/** 根据厚度获取开料刀 */
getKnifeByThickness(thickness: number): Knife | undefined {
return this.knifeList.find(t => t.length >= thickness);
}
/** 通用 找刀具 根据查询条件 */
getKnifeByParams(params: _knifeType) {
let knife: Knife | null = null
if (params) {
let tempKnifeList: Knife[] = [...this.knifeList] // []
let keys = Object.keys(params)
if (keys.length > 0) {
keys.forEach(key => {
if (Array.isArray(params[key]) && key == 'ability') {
// 进来的应该是ability 是数组 判断刀的能力
for (const arrItem of params[key]) {
let _knifeList = this.knifeList.filter(e => e.ability.includes(arrItem))
_knifeList.forEach(k => {
if (!this.KnifeIsInKnifeList(k, tempKnifeList)) {
tempKnifeList.push(k)
}
})
}
} else if (['string', 'number'].includes(typeof (params[key]))) {
if (params && params[key] && typeof (params[key]) == 'number') {
if (key == 'length') {
tempKnifeList = tempKnifeList.filter(e => e[key] >= params[key])
} else {
tempKnifeList = tempKnifeList.filter(e => e[key] == params[key])
}
}
}
});
if (tempKnifeList.length > 0) {
knife = tempKnifeList[0]
}
} else {
console.log('传入的查询条件 没有参数')
}
}
return knife
}
/** 判断 刀是否在 刀列表内
* true 存在
* false 不存在
*/
KnifeIsInKnifeList(_knife: Knife, _knifeList: Knife[]) {
let k = _knifeList.find(e => _knife.axleId == e.axleId
&& _knife.diameter == e.diameter
&& _knife.knifeId == e.knifeId)
if (k) {
return true
} else {
return false
}
}
getSideModelKnife(model: SideModel): Knife | undefined | null {
if (model.knifeName && model.knifeName.trim() != '') {
return this.getModelKnifeByName(model.knifeName.trim());
}
//获取刀半径(直径)和长度与造型定义的刀半径和深度一致的刀
const knife = this.getSideModelKnifeByRadius(model.knifeRadius, model.depth, model.direction);
if (knife) return knife;
//获取刀半径(直径)小于造型定义的刀半径、长度不小于造型深度的刀
return this.getFittedSideModelKnife(model.knifeRadius, model.depth, model.direction);
}
/** 获取合适的开料刀作为造型刀 */
getFittedSideModelKnife(radius: number, depth: number, direction): Knife | null {
let processFaceArr: number[] = [];
processFaceArr.push(5)
switch (direction) {
case 0:
processFaceArr.push(4)
break;
case 1:
processFaceArr.push(2)
break;
case 2:
processFaceArr.push(3)
break;
case 3:
processFaceArr.push(1)
break;
case 10:
break;
case 11:
break;
case 12:
break;
case 13:
break;
default:
break;
}
const knives = this.knifeList.filter(t => t.isModelingKnife() && t.length > depth - 0.01 && t.diameter / 2 < radius - 0.02 && processFaceArr.includes(t.processFace));
if (knives) {
knives.sort((a, b) => b.diameter - a.diameter); //按刀直径倒序排序
return knives[0];
}
return null;
}
/** 根据半径获取造型刀 */
// direction 0 下 1右 2上 3左 10 左下斜 11 右下斜 12右上斜 13 左上斜
// processFace: number // 加工面 2024 11 21 : 正面 0 反面 1 左侧面 2 右侧面 3 上侧面 4 下侧面 5 任意 6
getSideModelKnifeByRadius(radius: number, depth: number, direction): Knife | undefined {
let processFaceArr: number[] = [];
processFaceArr.push(5)
switch (direction) {
case 0:
processFaceArr.push(4)
break;
case 1:
processFaceArr.push(2)
break;
case 2:
processFaceArr.push(3)
break;
case 3:
processFaceArr.push(1)
break;
case 10:
break;
case 11:
break;
case 12:
break;
case 13:
break;
default:
break;
}
let knife = this.knifeList.find(t => t.isModelingKnife() && Math.abs(t.diameter - 2 * radius) < 0.02 && t.length >= depth - 0.01 && processFaceArr.includes(t.processFace))
return knife;
}
}

View File

@@ -1,2 +0,0 @@
# 基础功能目录
该目录下的功能函数尽量不包含业务逻辑

View File

@@ -1,262 +0,0 @@
import { ProcessorModule } from "../../src/device";
import { confItem, PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass";
import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "../handleAbility/RectOptimizeWorker/bang";
import resData from './res1.json'
import {Worker} from 'worker_threads'
/** 模块 新优化
*
* input 入参
*
* ProcessorModule<any, any>这里的any 可以自定义 根据组件需求
*/
export const RectOptimizeMachineModule: ProcessorModule<any, any> = {
moduleName: "RectOptimizeMachine",
config: {
workerList: [],
placeStyle: 1,
},
setConfig(config) {
this.config = { ...this.config, ...config };
},
before(input) {
// console.log(`优化前要做的事情`, input);
},
process(input, next, context) {
let { blockList, materialList } = input
let bList: any = []
let pm = materialList[0]
blockList.map(e => {
if (e.goodsId == pm.goodsId) {
bList[e.blockNo] = e
// let detail = blockDetailList.find(x => x.blockId == e.blockId)
// // 处理可能出现的异常 --没有板明细
// if (!Reflect.has(bList[e.blockNo], 'blockDetail')) {
// bList[e.blockNo].blockDetail = new PlaceBlockDetail(detail)
// }
// bList[e.blockNo].isTurnFaceToPlace = !this.getDoFace(bList[e.blockNo], this.processMode())
// 是否翻面后续处理
bList[e.blockNo].isTurnFaceToPlace = true
if (Array.isArray(pm.blockList)) {
pm.blockList.push(e)
} else {
pm.blockList = [e]
}
}
})
pm.cutBorder = this.cutBoardBorder
pm.cutKnifeGap = this.blockKnifeLineSpacing
/** 小板 */
let bans: xbang[] = []
// 实际开料大板的列表
let big_Bang: Big_bang[] = []
let big_BangSL: number[] = []
let border = this.cutBoardBorder
let borderOff = (pm.diameter + pm.cutKnifeGap) / 2
// 余料板 以及实际开料大板
for (const cuttingBoard of pm.remainBoardList) {
big_Bang.push({ w: cuttingBoard.width - border * 2 + borderOff * 2, l: cuttingBoard.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
big_BangSL.push(cuttingBoard.count || 999)
}
// big_Bang = []
// big_BangSL = []
// 母板 兜底的
big_Bang.push({ w: pm.width - border * 2 + borderOff * 2, l: pm.length - border * 2 + borderOff * 2, x: border - borderOff, y: border - borderOff })
// 生成小板
for (let key in bList) {
let b = bList[key]
let bid = b.blockNo
let width = b.placeFullWidth
let length = b.placeFullLength
let line = toLine(b.texture)
bans.push({
l: length,
w: width,
line,
face: toface(b),
id: bid,
bno: b.blockNo,
holeFaceCount: 3,
isRect: !b.isUnRegular,
hasHole: false,
isdtwosided: true,
})
}
let bestCount = 0
if (bans.length == 0) // 没有板了
{
if (context.CallBack) {
let best = []
let yl: Big_bang[] = []
let fit = 0
let resObj = {
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
info: {
times: -1,
type: 'noBan'
}
}
context.CallBack(resObj)
}
return
}
let xyhcs = 50
if (bans.length > 1000) { xyhcs = 40 }
else if (bans.length > 1500) { xyhcs = 30 }
else if (bans.length > 2000) { xyhcs = 25 }
else if (bans.length > 4000) { xyhcs = 20 }
else if (bans.length > 6000) { xyhcs = 15 }
else if (bans.length > 10000) { xyhcs = 10 }
else if (bans.length > 15000) { xyhcs = 5 }
else if (bans.length > 20000) { xyhcs = 1 }
let isDoubleFaceBlockFirst = this.isDoubleFaceBlockFirst // 双面加工排前面
let gap = this.blockKnifeLineSpacing
// this.bestfit = 0
/** 这里要做个多线程 测试环境下 这部分有问题 暂时略过 */
for (let j = 0; j < 1; j++) {
// let w = new Worker(new URL('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker', import.meta.url), { type: 'module' })
let w= new Worker('../handleAbility/RectOptimizeWorker/RectOptimizeWorker.worker')
const data = {
type: 'start',
data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this.isRectPlace, this.isDoubleFaceBlockInRemain],
}
let item: WorkerItemType = {
w: w,
goodsId: pm.goodsId,
pm,
status: 'start'
}
if (this.config.workerList.findIndex(e => e.goodsId == item.goodsId) == -1) {
this.config.workerList.push(item)
}
let workItem = this.config.workerList.find(e => e.goodsId == pm.goodsId)
if (workItem && workItem != undefined) {
workItem.w?.postMessage(data)
workItem.w.onmessage = async (d) => {
let [best, yl, fit, info] = d.data as [any[], Big_bang[], number, any]
switch (info.type) {
case 'loop':
let resObj = {
data: { bList, best, yl, fit, bans, width: pm.width, length: pm.length, bestCount: bestCount++, pm },
info
}
if (context.CallBack) {
context.CallBack(resObj)
}
break;
case 'stop':
console.error('stop =》dataHandleBase', info, this.config.workerList)
this.terminateWorker({ goodsId: pm.goodsId })
break;
case 'isStop':
// console.error('isStop', info)
break;
default:
break;
}
}
}
}
if (context.CallBack) {
context.CallBack(resData)
}
return next ? next(input) : input;
},
after(result) {
console.log(`优化后要做的事情`,);
},
onError(error) {
console.log('出错了哦', error);
}
};
function toLine(texture): LineType {
if (texture == 0)
return LineType.Positive
if (texture == 1)
return LineType.CanReversal
return LineType.Reverse
}
/** 小板加工面Positive=0正面 Reverse=1反面 Arbitrary=2任意 */
function toface(block: PlaceBlock): ComposingType {
let turnF = block.isTurnFaceToPlace
// if (this.isTurnFaceToPlace)
if (true)
turnF = !turnF
if (!turnF)
return ComposingType.Positive
return ComposingType.Reverse
}
/** 配置列表 */
export const confList: confItem[] = [
{
key: 'isCutProcess',
label: '开料机(雕刻机)加工',
value: true
},
{
key: 'isCutAndCNCProcess',
label: '开料机CNC组合',
value: false
},
{
key: 'isCustomized',
label: '定制加工',
value: false
},
{
key: 'cutBoardBorder',
label: '总修边宽度',
value: 3
},
{
key: 'blockKnifeLineSpacing',
label: '刀路间距',
value: 0
},
{
key: 'isDoubleFaceBlockFirst',
label: '双面开料优先排版',
value: true
},
{
key: 'isRectPlace',
label: '新优化规则排版',
value: false
},
{
// yuLiaoBoardDo2FaceBlock
key: 'isDoubleFaceBlockInRemain',
label: '余料板允许排入双面加工的小板',
value: true
},
]

File diff suppressed because it is too large Load Diff

27
src/models/processItem.ts Normal file
View File

@@ -0,0 +1,27 @@
export interface IPoint { x: number, y: number; }
/**
* 加工数据
*/
export interface IProcessingItem {
/**
* 加工点数组
*/
pts: IPoint[];
/**
* 凸度数组
*/
buls: number[];
/**
* 加工深度
*/
depth: number;
/**
* 半径
*/
radius: number;
/**
* 刀ID
*/
knifeId?: number;
}