From 2ebb3e1abe11d73f007770cd8ab449d8ce82eeed Mon Sep 17 00:00:00 2001 From: lixiang <504331699@qq.com> Date: Tue, 22 Jul 2025 18:22:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/dev1/dataHandle/ArrayExt.ts | 392 + tests/dev1/dataHandle/ClipperCpp.ts | 25 + tests/dev1/dataHandle/PolylineHelper.ts | 369 + tests/dev1/dataHandle/Simplify2.ts | 85 + tests/dev1/dataHandle/StringFormat.ts | 126 + tests/dev1/dataHandle/base.ts | 42 + tests/dev1/dataHandle/common/ArrayExt.ts | 187 + .../dataHandle/common/BlockBorderPoint.ts | 38 + tests/dev1/dataHandle/common/BlockSealEdge.ts | 66 + tests/dev1/dataHandle/common/Box2.ts | 183 + tests/dev1/dataHandle/common/ClipperCpp.ts | 25 + tests/dev1/dataHandle/common/ComparePoint.ts | 62 + tests/dev1/dataHandle/common/ConvexHull2D.ts | 9 + tests/dev1/dataHandle/common/Filer.ts | 31 + .../common/LayoutEngine/BlockDataPlus.ts | 1713 ++++ .../common/LayoutEngine/BlockDoFace.ts | 365 + .../common/LayoutEngine/BlockHelper.ts | 1300 +++ .../common/LayoutEngine/BlockOverlap.ts | 300 + .../common/LayoutEngine/BlockPlus.ts | 2395 ++++++ .../common/LayoutEngine/BlockSizePlus.ts | 473 ++ .../common/LayoutEngine/Curves2Parts.ts | 266 + .../LayoutEngine/DisposeModelInBoardBorder.ts | 321 + .../common/LayoutEngine/PlaceBase.ts | 94 + .../common/LayoutEngine/PolylineHelper.ts | 359 + .../common/LayoutEngine/RemainHelper.ts | 404 + .../common/LayoutEngine/Simplify2.ts | 85 + .../common/LayoutEngine/SpacePlus.ts | 773 ++ .../dataHandle/common/LayoutEngine/writeP.ts | 104 + tests/dev1/dataHandle/common/PlacePosition.ts | 143 + .../dataHandle/common/PlacePostionHelper.ts | 176 + tests/dev1/dataHandle/common/Random.ts | 11 + tests/dev1/dataHandle/common/Sleep.ts | 7 + tests/dev1/dataHandle/common/Util.ts | 38 + tests/dev1/dataHandle/common/Vector2.ts | 348 + tests/dev1/dataHandle/common/base/ArrayExt.ts | 375 + tests/dev1/dataHandle/common/base/CAD.ts | 1627 ++++ tests/dev1/dataHandle/common/base/Dxf.ts | 490 ++ tests/dev1/dataHandle/common/base/File.ts | 122 + .../common/base/MaskedNumberRange.ts | 118 + tests/dev1/dataHandle/common/base/MathComm.ts | 128 + .../common/base/PlaceStyleHelper.ts | 170 + .../dataHandle/common/base/StringBase64.ts | 152 + .../dataHandle/common/base/StringBuidler.ts | 82 + .../dataHandle/common/base/StringFormat.ts | 126 + tests/dev1/dataHandle/common/base/TextFile.ts | 119 + tests/dev1/dataHandle/common/base/ZipFile.ts | 138 + tests/dev1/dataHandle/common/base/gb2312.json | 6965 +++++++++++++++++ tests/dev1/dataHandle/common/bmp.ts | 176 + .../dev1/dataHandle/common/core/Container.ts | 399 + .../dataHandle/common/core/GNestConfig.ts | 5 + .../dev1/dataHandle/common/core/Individual.ts | 274 + .../dev1/dataHandle/common/core/NestCache.ts | 37 + .../dataHandle/common/core/NestDatabase.ts | 82 + .../dataHandle/common/core/OptimizeMachine.ts | 163 + .../dataHandle/common/core/ParseOddments.ts | 238 + tests/dev1/dataHandle/common/core/Part.ts | 394 + .../dev1/dataHandle/common/core/PartState.ts | 61 + tests/dev1/dataHandle/common/core/Path.ts | 381 + .../dataHandle/common/core/PathGenerator.ts | 87 + .../dev1/dataHandle/common/core/PlaceType.ts | 11 + .../dataHandle/common/cutorder/CutOrder.ts | 387 + .../common/cutorder/CutPointHelper.ts | 440 ++ .../dev1/dataHandle/common/decorators/json.ts | 31 + .../dataHandle/common/decorators/model.ts | 39 + tests/dev1/dataHandle/common/drawing/base.ts | 98 + .../common/drawing/canvasDrawing.ts | 197 + .../dataHandle/common/drawing/canvasUtil.ts | 39 + .../dataHandle/common/drawing/imageCode.ts | 33 + .../dataHandle/common/drawing/imageUtil.ts | 12 + tests/dev1/dataHandle/common/drawing/index.ts | 3 + .../dataHandle/common/drawing/svgDrawing.ts | 251 + tests/dev1/dataHandle/common/file.ts | 22 + tests/dev1/dataHandle/common/log.ts | 33 + tests/dev1/dataHandle/common/zip.ts | 151 + tests/dev1/dataHandle/confClass.ts | 4814 ++++++++++++ tests/dev1/dataHandle/models/config.ts | 28 + tests/dev1/dataHandle/models/file.ts | 8 + .../RectOptimizeWorker/RectOptimizeMachine.ts | 1745 +++++ .../RectOptimizeWorkerWorker.ts | 43 + .../optimizeLayout/RectOptimizeWorker/bang.ts | 1605 ++++ .../optimizeLayout/optimizeLayout.ts | 400 + .../dataHandle/optimizeLayout/优化排版.md | 0 tests/dev1/dataHandle/tools/tool.ts | 253 + tests/dev1/dataHandle/tools/刀库.md | 0 tests/dev1/index3.vue | 1513 ++++ 85 files changed, 36380 insertions(+) create mode 100644 tests/dev1/dataHandle/ArrayExt.ts create mode 100644 tests/dev1/dataHandle/ClipperCpp.ts create mode 100644 tests/dev1/dataHandle/PolylineHelper.ts create mode 100644 tests/dev1/dataHandle/Simplify2.ts create mode 100644 tests/dev1/dataHandle/StringFormat.ts create mode 100644 tests/dev1/dataHandle/base.ts create mode 100644 tests/dev1/dataHandle/common/ArrayExt.ts create mode 100644 tests/dev1/dataHandle/common/BlockBorderPoint.ts create mode 100644 tests/dev1/dataHandle/common/BlockSealEdge.ts create mode 100644 tests/dev1/dataHandle/common/Box2.ts create mode 100644 tests/dev1/dataHandle/common/ClipperCpp.ts create mode 100644 tests/dev1/dataHandle/common/ComparePoint.ts create mode 100644 tests/dev1/dataHandle/common/ConvexHull2D.ts create mode 100644 tests/dev1/dataHandle/common/Filer.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/DisposeModelInBoardBorder.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts create mode 100644 tests/dev1/dataHandle/common/LayoutEngine/writeP.ts create mode 100644 tests/dev1/dataHandle/common/PlacePosition.ts create mode 100644 tests/dev1/dataHandle/common/PlacePostionHelper.ts create mode 100644 tests/dev1/dataHandle/common/Random.ts create mode 100644 tests/dev1/dataHandle/common/Sleep.ts create mode 100644 tests/dev1/dataHandle/common/Util.ts create mode 100644 tests/dev1/dataHandle/common/Vector2.ts create mode 100644 tests/dev1/dataHandle/common/base/ArrayExt.ts create mode 100644 tests/dev1/dataHandle/common/base/CAD.ts create mode 100644 tests/dev1/dataHandle/common/base/Dxf.ts create mode 100644 tests/dev1/dataHandle/common/base/File.ts create mode 100644 tests/dev1/dataHandle/common/base/MaskedNumberRange.ts create mode 100644 tests/dev1/dataHandle/common/base/MathComm.ts create mode 100644 tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts create mode 100644 tests/dev1/dataHandle/common/base/StringBase64.ts create mode 100644 tests/dev1/dataHandle/common/base/StringBuidler.ts create mode 100644 tests/dev1/dataHandle/common/base/StringFormat.ts create mode 100644 tests/dev1/dataHandle/common/base/TextFile.ts create mode 100644 tests/dev1/dataHandle/common/base/ZipFile.ts create mode 100644 tests/dev1/dataHandle/common/base/gb2312.json create mode 100644 tests/dev1/dataHandle/common/bmp.ts create mode 100644 tests/dev1/dataHandle/common/core/Container.ts create mode 100644 tests/dev1/dataHandle/common/core/GNestConfig.ts create mode 100644 tests/dev1/dataHandle/common/core/Individual.ts create mode 100644 tests/dev1/dataHandle/common/core/NestCache.ts create mode 100644 tests/dev1/dataHandle/common/core/NestDatabase.ts create mode 100644 tests/dev1/dataHandle/common/core/OptimizeMachine.ts create mode 100644 tests/dev1/dataHandle/common/core/ParseOddments.ts create mode 100644 tests/dev1/dataHandle/common/core/Part.ts create mode 100644 tests/dev1/dataHandle/common/core/PartState.ts create mode 100644 tests/dev1/dataHandle/common/core/Path.ts create mode 100644 tests/dev1/dataHandle/common/core/PathGenerator.ts create mode 100644 tests/dev1/dataHandle/common/core/PlaceType.ts create mode 100644 tests/dev1/dataHandle/common/cutorder/CutOrder.ts create mode 100644 tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts create mode 100644 tests/dev1/dataHandle/common/decorators/json.ts create mode 100644 tests/dev1/dataHandle/common/decorators/model.ts create mode 100644 tests/dev1/dataHandle/common/drawing/base.ts create mode 100644 tests/dev1/dataHandle/common/drawing/canvasDrawing.ts create mode 100644 tests/dev1/dataHandle/common/drawing/canvasUtil.ts create mode 100644 tests/dev1/dataHandle/common/drawing/imageCode.ts create mode 100644 tests/dev1/dataHandle/common/drawing/imageUtil.ts create mode 100644 tests/dev1/dataHandle/common/drawing/index.ts create mode 100644 tests/dev1/dataHandle/common/drawing/svgDrawing.ts create mode 100644 tests/dev1/dataHandle/common/file.ts create mode 100644 tests/dev1/dataHandle/common/log.ts create mode 100644 tests/dev1/dataHandle/common/zip.ts create mode 100644 tests/dev1/dataHandle/confClass.ts create mode 100644 tests/dev1/dataHandle/models/config.ts create mode 100644 tests/dev1/dataHandle/models/file.ts create mode 100644 tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeMachine.ts create mode 100644 tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeWorkerWorker.ts create mode 100644 tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts create mode 100644 tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts create mode 100644 tests/dev1/dataHandle/optimizeLayout/优化排版.md create mode 100644 tests/dev1/dataHandle/tools/tool.ts create mode 100644 tests/dev1/dataHandle/tools/刀库.md create mode 100644 tests/dev1/index3.vue diff --git a/tests/dev1/dataHandle/ArrayExt.ts b/tests/dev1/dataHandle/ArrayExt.ts new file mode 100644 index 0000000..2a446dd --- /dev/null +++ b/tests/dev1/dataHandle/ArrayExt.ts @@ -0,0 +1,392 @@ +export class List extends Array +{ + /** 返回符合条件的第一个元素 */ + 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(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(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(fn: (item: Item) => T): Item + { + return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b) + } +} + +export class ArrayExt +{ + /** 返回满足条件的元素数量 */ + static count(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(list: Item[], obj: Item) + { + let index = list.findIndex(t => t == obj) + if (index == -1) + return + list.splice(index, 1) + } + + /** 返回符合条件的第一个元素 */ + static first(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(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(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(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(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(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(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(list: Item[], fn: (item: Item) => number) + { + list.sort((a: Item, b: Item): number => fn(b) - fn(a)) + } + + /** 排序成新的数组 */ + static orderBy(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(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(list: Item[], fn: (item: Item) => gT): GroupItem[] + { + let groups = new Array>() + + 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(list: Item[], fn: (item: Item) => rT): rT[] + { + let newList = new Array() + for (const t of list) + { + newList.push(fn(t)) + } + return newList + } + + /** 过来,并按顺序排序 */ + static where(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 +{ + 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} 返回自身 + */ +export function arrayRemoveDuplicateBySort(arr: Array, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array +{ + 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 +} diff --git a/tests/dev1/dataHandle/ClipperCpp.ts b/tests/dev1/dataHandle/ClipperCpp.ts new file mode 100644 index 0000000..5afaf65 --- /dev/null +++ b/tests/dev1/dataHandle/ClipperCpp.ts @@ -0,0 +1,25 @@ +import * as clipperLib from 'js-angusj-clipper/web/index.js' + +// nodejs style require +// 多边形和线条裁剪及偏移库 +export const clipperCpp: { lib?: clipperLib.ClipperLibWrapper } = {} +export function InitClipperCpp(): Promise +{ + 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('载入成功!') + }) + }) +} diff --git a/tests/dev1/dataHandle/PolylineHelper.ts b/tests/dev1/dataHandle/PolylineHelper.ts new file mode 100644 index 0000000..d0aa429 --- /dev/null +++ b/tests/dev1/dataHandle/PolylineHelper.ts @@ -0,0 +1,369 @@ +import type { PolylineProps } from 'cadapi' +import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi' +import type { Box3 } from 'three' +import { Vector2, Vector3 } from 'three' +import { arrayRemoveDuplicateBySort } from './ArrayExt' +import type { Curve2d } from './common/base/CAD' +import { Arc2d, Point2d, copyTextToClipboard } from './common/base/CAD' +import { CurveWrap } from './common/LayoutEngine/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() + } +} diff --git a/tests/dev1/dataHandle/Simplify2.ts b/tests/dev1/dataHandle/Simplify2.ts new file mode 100644 index 0000000..e4afffd --- /dev/null +++ b/tests/dev1/dataHandle/Simplify2.ts @@ -0,0 +1,85 @@ +import { Vector2 } from './Vector2' + +interface P { + x: number + y: number +} + +export interface IOffset { + negativeOffset: number + positiveOffset: number +} + +/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */ +function GetSqSegDist(p: P, p1: P, p2: P): number { + let x = p1.x + let y = p1.y + let dx = p2.x - x + let dy = p2.y - y + + if (dx !== 0 || dy !== 0)// 不是0长度线 + { + const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy) + if (t > 1) { + x = p2.x + y = p2.y + } + else if (t > 0) { + x += dx * t + y += dy * t + } + } + dx = p.x - x + dy = p.y - y + return dx * dx + dy * dy +} + +function CrossVector2(a: P, b: P) +{ + return a.x * b.y - a.y * b.x +} + +// Ramer-Douglas-Peucker algorithm 用于简化曲线或折线 +function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void { + let maxSqDist = 0 + let index: number + const fp = points[first] + const lp = points[last] + + for (let i = first + 1; i < last; i++) { + const p = points[i] + const sqDist = GetSqSegDist(p, fp, lp) + if (sqDist > maxSqDist) { + index = i + maxSqDist = sqDist + } + } + + if (maxSqDist > sqTolerance) { + if (index - first > 1) + SimplifyDPStep(points, first, index, sqTolerance, simplified, offset) + simplified.push(points[index]) + if (last - index > 1) + SimplifyDPStep(points, index, last, sqTolerance, simplified, offset) + } + else { + // 记录偏移 + const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize() + for (let i = first + 1; i < last; i++) { + const p = points[i] + const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y }) + offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist) + offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist) + } + } +} + +// Ramer-Douglas-Peucker 算法 用于简化曲线或折线 +export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] { + const last = points.length - 1 + const simplified: P[] = [points[0]] + const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 } + SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset) + simplified.push(points[last]) + return [simplified, offset] +} diff --git a/tests/dev1/dataHandle/StringFormat.ts b/tests/dev1/dataHandle/StringFormat.ts new file mode 100644 index 0000000..6ed1616 --- /dev/null +++ b/tests/dev1/dataHandle/StringFormat.ts @@ -0,0 +1,126 @@ +export class StringFormat +{ + /** + * 对Date的扩展,将 Date 转化为指定格式的String + * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符 + * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) + * eg: + * (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 + * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04 + * (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04 + * (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04 + * (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 + */ + static Date(date: Date, fmt): string + { + let o = { + 'M+': date.getMonth() + 1, // 月份 + 'd+': date.getDate(), // 日 + 'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时 + 'H+': date.getHours(), // 小时 + 'm+': date.getMinutes(), // 分 + 's+': date.getSeconds(), // 秒 + 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 + 'S': date.getMilliseconds(), // 毫秒 + } + let week = { + 0: '/u65e5', + 1: '/u4e00', + 2: '/u4e8c', + 3: '/u4e09', + 4: '/u56db', + 5: '/u4e94', + 6: '/u516d', + } + if (/(y+)/.test(fmt)) + { + fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length)) + } + if (/(E+)/.test(fmt)) + { + fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`]) + } + for (let k in o) + { + if (new RegExp(`(${k})`).test(fmt)) + { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length))) + } + } + return fmt + } + + /** 返回数字的固定小数点 123.00 */ + static number(value: number, bit: number): string + { + return value?.toFixed(bit).toString() + } + + static toFixed(value: number, bit: number = 3): number + { + let tt = 10 ** bit + return Math.round(value * tt) / tt + } + + static filterIllegalChar(str: string): string + { + // var pattern = new RegExp("[/:*?'<>|\\]"); + // var rs = ""; + // for (var i = 0; i < str.length; i++) + // { + // rs = rs + str.substr(i, 1).replace(pattern, ''); + // } + // return rs; + + let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\/g, '').replace(/\|/g, '').replace(/\'/g, '') + 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]) + } +} diff --git a/tests/dev1/dataHandle/base.ts b/tests/dev1/dataHandle/base.ts new file mode 100644 index 0000000..5474169 --- /dev/null +++ b/tests/dev1/dataHandle/base.ts @@ -0,0 +1,42 @@ +import { ConfigBase } from "./models/config"; + + +/** + * 加工处理器上下文 + */ +export abstract class ProcessorContext{ + /** + * 输入数据 + */ + public input?:TInput; + /** + * 合并配置文件与临时输入参 + */ + public params?:TConfig; + /** + * 输出数据 + */ + public output?:TOutput; +} + +/** + * 处理器基类 + */ +export abstract class ProcessorBase { + public abstract get name():string; + public abstract get version(): string; + public abstract exec(context:ProcessorContext):Promise|void +} + +export interface resultInfo { + code: ResCodeType; + data?: any; + success?: boolean; + message?: string; +} + +export enum ResCodeType{ + SUCCESS = 1, + ERROR = 0, + WARNING = -1 +} \ No newline at end of file diff --git a/tests/dev1/dataHandle/common/ArrayExt.ts b/tests/dev1/dataHandle/common/ArrayExt.ts new file mode 100644 index 0000000..1fa4e41 --- /dev/null +++ b/tests/dev1/dataHandle/common/ArrayExt.ts @@ -0,0 +1,187 @@ +/** + * 删除数组中指定的元素,返回数组本身 + * @param {Array} arr 需要操作的数组 + * @param {*} el 需要移除的元素 + */ +export function arrayRemove(arr: Array, el: T): Array +{ + let j = 0 + for (let i = 0, l = arr.length; i < l; i++) + { + if (arr[i] !== el) + { + arr[j++] = arr[i] + } + } + arr.length = j + + return arr +} + +export function arrayRemoveOnce(arr: Array, el: T): Array +{ + let index = arr.indexOf(el) + if (index !== -1) + arr.splice(index, 1) + return arr +} + +/** + * 删除通过函数校验的元素 + * @param {(e: T) => boolean} checkFuntion 校验函数 + */ +export function arrayRemoveIf(arr: Array, checkFuntion: (e: T) => boolean): Array +{ + let j = 0 + for (let i = 0, l = arr.length; i < l; i++) + { + if (!checkFuntion(arr[i])) + { + arr[j++] = arr[i] + } + } + arr.length = j + + return arr +} + +/** 获取数据第一个元素 */ +export function arrayFirst(arr: Array): T +{ + return arr[0] +} + +/** 获取数组最后一个元素 */ +export function arrayLast(arr: { [key: number]: T; length: number }): T +{ + return arr[arr.length - 1] +} + +/** + * 根据数值从小到大排序数组 + * @param {Array} arr + * @returns {Array} 返回自身 + */ +export function arraySortByNumber(arr: Array): Array +{ + arr.sort(sortNumberCompart) + return arr +} + +/** + * 对排序好的数组进行去重操作 + * @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数 + * @returns {Array} 返回自身 + */ +export function arrayRemoveDuplicateBySort(arr: Array, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array +{ + if (arr.length < 2) + return arr + let j = 1 + for (let i = 1, l = arr.length; i < l; i++) + if (!checkFuction(arr[j - 1], arr[i])) + arr[j++] = arr[i] + arr.length = j + return arr +} + +/** 原地更新数组,注意这个函数并不会比map快 */ +export function arrayMap(arr: Array, mapFunc: (v: T) => T): Array +{ + for (let i = 0, count = arr.length; i < count; i++) + arr[i] = mapFunc(arr[i]) + return arr +} + +/** 排序数比较 */ +function sortNumberCompart(e1: any, e2: any) +{ + return e1 - e2 +} + +/** 判断两个数是否相等 */ +function checkEqual(e1: any, e2: any): boolean +{ + return e1 === e2 +} + +/** + * 改变数组的值顺序 + * @param arr 需要改变初始值位置的数组 + * @param index //将index位置以后的值放到起始位置 + */ +export function changeArrayStartIndex(arr: T[], index: number): T[] +{ + arr.unshift(...arr.splice(index)) + return arr +} + +/** 判断两个数组是否相等 */ +export function equalArray(a: T[], b: T[], checkF = checkEqual) +{ + if (a === b) + return true + if (a.length !== b.length) + return false + for (let i = 0; i < a.length; ++i) + if (!checkF(a[i], b[i])) + return false + return true +} + +/** 数组克隆 */ +export function arrayClone(arr: T[]): T[] +{ + return arr.slice() +} + +/** 数组2元素合并到数组1末尾 */ +// https://jsperf.com/merge-array-implementations/30 +export function arrayPushArray(arr1: T[], arr2: T[]): T[] +{ + let arr1Length = arr1.length + let arr2Length = arr2.length + arr1.length = arr1Length + arr2Length + for (let i = 0; i < arr2Length; i++) + arr1[arr1Length + i] = arr2[i] + + return arr1 +} + +/** 数组元素求合 */ +export function arraySum(arr: number[]) +{ + let sum = 0 + for (let n of arr) sum += n + return sum +} + +/** 条件过滤集合 */ +export function FilterSet(s: Set, fn: (el: T) => boolean): Set +{ + let ns = new Set() + for (let el of s) + { + if (fn(el)) + ns.add(el) + } + return ns +} + +/** 查找数组中最大的元素 */ +export function arrayMax(arr: T[], f: (item: T) => number = a => (a as unknown as number)): [T, number] +{ + let max = Number.NEGATIVE_INFINITY + let maxIndex = -1 + for (let i = 0; i < arr.length; i++) + { + let item = arr[i] + let v = f(item) + if (v > max) + { + maxIndex = i + max = v + } + } + return [arr[maxIndex], maxIndex] +} diff --git a/tests/dev1/dataHandle/common/BlockBorderPoint.ts b/tests/dev1/dataHandle/common/BlockBorderPoint.ts new file mode 100644 index 0000000..9a51737 --- /dev/null +++ b/tests/dev1/dataHandle/common/BlockBorderPoint.ts @@ -0,0 +1,38 @@ +import type { PlaceBlock } from "../confClass" + +/** 拖拉的小板顶点 */ +export class BlockBorderPoint +{ + /** 板 */ + block: PlaceBlock + /** 原坐标x */ + x: number + /** 原坐标y */ + y: number + /** 大板坐标X */ + placeX: number + /** 大板坐标Y */ + placeY: number + + /** 拖拉点距切割后板的坐标偏移x */ + bx = 0 + /** 拖拉点距切割后板的坐标偏移x */ + by = 0 + /** 在placed线框中的序号 */ + curveIndex: number + /** 在板的位置: 10:基点(左下点);1:右下角;2:右上角;3:左上角; -1:表示异形非顶点 areaID */ + posId: number + + constructor(block: PlaceBlock, x: number, y: number, index: number, posId: number, bx = 0, by = 0) + { + this.block = block + this.x = x + this.y = y + this.placeX = (block ? block.placeX : 0) + x + this.placeY = (block ? block.placeY : 0) + y + this.curveIndex = index + this.posId = posId + this.bx = bx + this.by = by + } +} diff --git a/tests/dev1/dataHandle/common/BlockSealEdge.ts b/tests/dev1/dataHandle/common/BlockSealEdge.ts new file mode 100644 index 0000000..7d7f3a9 --- /dev/null +++ b/tests/dev1/dataHandle/common/BlockSealEdge.ts @@ -0,0 +1,66 @@ +import { Arc2d,Point2d } from "./base/CAD" +import type { PlaceMaterial } from "../confClass" + +export function getMaterialSealEdge(pm: PlaceMaterial) +{ + let ext = 30 // 每条边 浪费的长度 + let fbs = [] + for (let block of pm.blockList) + { + if (!block.isUnRegular) + { + pushLength(block.sealLeft, block.length) + pushLength(block.sealRight, block.length) + pushLength(block.sealBottom, block.width) + pushLength(block.sealTop, block.width) + } + else + { + for (let i = 0; i < block.orgPoints.length; i++) + { + let p1 = block.orgPoints[i] + if (Math.abs(p1.sealSize) < 0.001) + continue + let j = i + 1 + if (j == block.orgPoints.length) + j = 0 + let p2 = block.orgPoints[j] + let len = 0 + if (p1.curve == 0) + { + len = Math.sqrt((p1.pointX - p2.pointX) * (p1.pointX - p2.pointX) + (p1.pointY - p2.pointY) * (p1.pointY - p2.pointY)) + } + else + { + let arc = new Arc2d(new Point2d(p1.pointX, p1.pointY), new Point2d(p2.pointX, p2.pointY), p1.curve) + len = Math.abs(arc.m_Radius * arc.m_AllAngle) + } + pushLength(p1.sealSize, len) + } + } + } + + let rlfbs = [] + // 转换成米 + for (let key in fbs) + { + let rt = fbs[key] + rt.l = Math.ceil(rt.l / 100) / 10 + rlfbs.push(rt) + } + return rlfbs + + function pushLength(fb: number, len: number) + { + if (Math.abs(fb) < 0.001) + return// + let str = fb.toFixed(2) + let sul = fbs[str] + if (sul == null) + { + sul = { t: fb, l: 0 } + fbs[str] = sul + } + sul.l += len + ext + } +} diff --git a/tests/dev1/dataHandle/common/Box2.ts b/tests/dev1/dataHandle/common/Box2.ts new file mode 100644 index 0000000..e7eab97 --- /dev/null +++ b/tests/dev1/dataHandle/common/Box2.ts @@ -0,0 +1,183 @@ +import { Vector2 } from './Vector2.js' +import type { Point } from '../common/Vector2.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): 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) + } +}; diff --git a/tests/dev1/dataHandle/common/ClipperCpp.ts b/tests/dev1/dataHandle/common/ClipperCpp.ts new file mode 100644 index 0000000..7bb2a5f --- /dev/null +++ b/tests/dev1/dataHandle/common/ClipperCpp.ts @@ -0,0 +1,25 @@ +import * as clipperLib from 'js-angusj-clipper/web/index.js' + +// nodejs style require + +export const clipperCpp: { lib?: clipperLib.ClipperLibWrapper } = {} +export function InitClipperCpp(): Promise +{ + 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('载入成功!') + }) + }) +} diff --git a/tests/dev1/dataHandle/common/ComparePoint.ts b/tests/dev1/dataHandle/common/ComparePoint.ts new file mode 100644 index 0000000..b701ce6 --- /dev/null +++ b/tests/dev1/dataHandle/common/ComparePoint.ts @@ -0,0 +1,62 @@ +interface Vec2 { x: number; y: number } + +export type CompareVectorFn = (v1: Vec2, v2: Vec2) => number + +const comparePointCache: Map = new Map() + +const ALLKEY = ['x', 'X', 'y', 'Y', 'z', 'Z'] +const KEY = ['x', 'y', 'z'] + +/** + * 构建返回一个用来排序的函数.根据key创建排序规则. + * + * 当key = "xyz" 时,点集按 x从小到大,y从小到大 z从小到大 + * key = "X" 时,点集按 x从大到小 + * 以此类推. + * + * 例子: + * let pts:Vector3[] =...; + * pts.sort(comparePoint("x")); //x从小到大排序 + * pts.sort(comparePoint("zX")); //z从小到大 x从大到小 + * + * @export + * @param {string} sortKey + */ +export function ComparePoint(sortKey: string): CompareVectorFn +{ + if (comparePointCache.has(sortKey)) + return comparePointCache.get(sortKey) + + let sortIndex = [] + + for (let char of sortKey) + { + let index = ALLKEY.indexOf(char) + + let i2 = index / 2 + let ci = Math.floor(i2) + sortIndex.push([KEY[ci], i2 > ci ? 1 : -1]) + } + + let compareFunction = (v1: Vec2, v2: Vec2): number => + { + if (!v1) + return -1 + if (!v2) + return 1 + for (let s of sortIndex) + { + let vv1 = v1[s[0]] + let vv2 = v2[s[0]] + if (vv1 === vv2) + continue + if (vv2 > vv1) + return s[1] + else return -s[1] + } + return 0 + } + + comparePointCache.set(sortKey, compareFunction) + return compareFunction +} diff --git a/tests/dev1/dataHandle/common/ConvexHull2D.ts b/tests/dev1/dataHandle/common/ConvexHull2D.ts new file mode 100644 index 0000000..b5850c3 --- /dev/null +++ b/tests/dev1/dataHandle/common/ConvexHull2D.ts @@ -0,0 +1,9 @@ +import convexHull from 'monotone-convex-hull-2d' +import type { Point } from '../common/Vector2' + +export function ConvexHull2D(points: Point[]): Point[] +{ + let pts = points.map(p => [p.x, p.y]) + let indexs: number[] = convexHull(pts) + return indexs.map(i => points[i]) +} diff --git a/tests/dev1/dataHandle/common/Filer.ts b/tests/dev1/dataHandle/common/Filer.ts new file mode 100644 index 0000000..a319f18 --- /dev/null +++ b/tests/dev1/dataHandle/common/Filer.ts @@ -0,0 +1,31 @@ +/** + * CAD文件数据 + */ +export class NestFiler +{ + private readIndex: number = 0 + constructor(public _datas: any[] = []) { } + + Clear() + { + this._datas.length = 0 + return this.Reset() + } + + Reset() + { + this.readIndex = 0 + return this + } + + Write(data: any) + { + this._datas.push(data) + return this + } + + Read(): any + { + return this._datas[this.readIndex++] + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts new file mode 100644 index 0000000..4dd9339 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts @@ -0,0 +1,1713 @@ +import { Production } from 'cadapi' +import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' +import { ArrayExt } from '../../common/base/ArrayExt.js' +import { CADExt } from '../../common/base/CAD.js' +import { equal } from '../../common/base/MathComm.js' + +import { getDis_PointLine, getDis2 } from '@/imes/common/base/MathComm.js' + +// import { SideModel } from '../model/SideModel.js' +import { BlockHelper } from './BlockHelper.js' +import { SideModel, PlaceBlock, PlaceBlockDetail, FaceType, HoleType, BlockHole, BlockModelPoint, BlockSideModelPoint, SideFaceType, HoleArrange } from '../../confClass.js' +import { getDoFace } from './BlockDoFace.js' +import { ToolsHelper } from '../../tools/tool.js' +/** + * 小板原始数据的修改|计算 + * 造型点阵,屏蔽一些不合理数据等 + * 造型换刀铣 + * 计算侧孔方向 + * 计算异形板的封边 + */ + +/** 强制造型内往外铣 */ +export function reverseModelPoints(bd: PlaceBlockDetail) { + // 默认:正面造型,外->内铣;反面造型,内->外铣;因此修改正面造型的点阵顺序 + for (let model of bd.models) { + if (model.offsetList.length > 0) + continue // 2维刀路 不处理 + if (model.depth > bd.thickness - 0.001) + continue // 挖穿的造型不算 + if (model.pointList.length < 3) + continue + if (model.face != FaceType.FRONT) + continue // 正面 + + // 找出最外圈 + let p0 = model.pointList[0] + let index = -1 + for (let i = 1; i < model.pointList.length; i++) { + if (model.pointList[i].pointX == p0.pointX && model.pointList[i].pointY == p0.pointY) { + index = i + break + } + } + if (index == model.pointList.length - 1) + continue + if (index == -1) + continue // 找不到 + // 最外圈 + let ps_0 = model.pointList.splice(0, index + 1) + + // 剩下倒序 + model.pointList.reverse() + // 换 bul + for (let i = 0; i < model.pointList.length; i++) { + let j = i + 1 + if (j < model.pointList.length) { + model.pointList[i].curve = -model.pointList[j].curve + model.pointList[i].radius = model.pointList[j].radius + } + else { + model.pointList[i].curve = 0 + model.pointList[i].radius = 0 + } + } + model.pointList = model.pointList.concat(ps_0) + // 最开始添加一点,避免cnc 当做槽处理 + let pf = model.pointList[0].copy() + model.pointList.splice(0, 0, pf) + } +} + +// /** 排钻转造型 */ +// export function Hole2Model(bds: PlaceBlockDetail[], sysConfig: SysConfig) +// { +// let knifes = sysConfig.HoleToModelKnifes; +// knifes = knifes.filter(t => t.isUse && t.knifeId > 0); + +// for (let knife of knifes) +// { +// let k = sysConfig.getKnifeById(knife.knifeId); +// if (k == null) continue; +// knife.knifeR = -1; +// if (k.AllowCut || k.AllowModel) +// { +// knife.knifeR = k.diameter / 2; +// } +// } +// knifes = knifes.filter(t => t.knifeR > 0); + +// for (let bd of bds) +// { +// for (let i = bd.holes.length - 1; i >= 0; i--) +// { +// let hole = bd.holes[i]; +// let k = knifes.find(t => t.minR <= hole.radius + 0.00001 && t.maxR > hole.radius + 0.0000001); +// if (k == null) continue; +// let model = toModel(bd.thickness, hole, k.knifeR); +// if (model == null) continue; +// bd.models.push(model); +// bd.holes.splice(i, 1); +// } +// } + +// function toModel(t: number, hole: BlockHole, r: number) +// { +// let depth = hole.depth; +// let hR = hole.radius; +// if (hR < r + 0.001) return null; + +// let model = new BlockModel(); +// model.face = hole.face; +// model.knifeRadius = r; +// model.knifeName = ''; +// model.realKnifeRadius = r; +// model.depth = hole.depth; +// model.canSpace = false; +// model.pointList = []; +// model.realPointList = model.pointList; +// let cx = hole.pointX; +// let cy = hole.pointY; + +// let r0 = hR - r; +// if (r0 < 0) r0 = 0; +// //最外层先走一圈,顺时针 + +// //左,上,右,下 +// model.pointList.push( new BlockModelPoint({ pointX: cx - r0, pointY: cy, radius: r0, curve: -0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx, pointY: cy + r0, radius: r0, curve: -0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx + r0, pointY: cy, radius: r0, curve: -0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx, pointY: cy - r0, radius: r0, curve: -0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx - r0, pointY: cy, radius: 0, curve: 0, depth: depth }) ); + +// //挖穿 +// if (depth >= t - 0.001) return model; + +// //非挖穿的 +// r0 = r0 - r; +// while (r0 > 0) +// { +// //左 ,下,右,上 逆时针 +// model.pointList.push( new BlockModelPoint({ pointX: cx - r0, pointY: cy, radius: r0, curve: 0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx, pointY: cy - r0, radius: r0, curve: 0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx + r0, pointY: cy, radius: r0, curve: 0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx, pointY: cy + r0, radius: r0, curve: 0.41421356237309503, depth: depth }) ); +// model.pointList.push( new BlockModelPoint({ pointX: cx - r0, pointY: cy, radius: 0, curve: 0, depth: depth }) ); +// r0 = r0 - r; +// } +// //圆心 +// model.pointList.push( new BlockModelPoint({ pointX: cx, pointY: cy, radius: 0, curve: 0, depth: depth }) ); +// return model; +// } + +// } + +/** 移除无效的面孔:孔不在板内/孔径太小/孔深不够/圆心在外面的孔 */ + +export function removeInvalidHoles(block: PlaceBlockDetail, minHoleRadius: number, minHoleDepth: number) { + let count = block.holes.length + + for (let i = count - 1; i >= 0; i--) { + let hole = block.holes[i] + // 是否无效, 板外,孔径太小,孔深度太浅 + let invalid = isInvalidHole(block, hole, 1) + + if (invalid) { + block.holes.splice(i, 1) + continue + } + + if (hole.face != FaceType.SIDE && hole.depth > block.thickness) { + hole.depth = block.thickness + } + } + + + function isInvalidHole(blockDetail: PlaceBlockDetail, hole: BlockHole, offset: number): boolean { + if (hole.radius < minHoleRadius) + return true // 孔径太小 + if (hole.depth < minHoleDepth) + return true // 孔深度太浅 + // 圆心不在板内 + if (hole.pointX < -offset) + return true + // if (hole.PointX > block.KaiLiaoWidth) return true; + if (hole.pointY < -offset) + return true + // if (hole.PointY > block.KaiLiaoLength + offset) return true; + return false + } +} + +/** 移除二合一的面孔 */ + +export function remove2in1Hole(block: PlaceBlockDetail, gap: number) { + /** 二合一侧孔离边距离 */ + + let removeSideHoles = new Array() + let count = block.holes.length + + for (let i = count - 1; i >= 0; i--) { + let hole = block.holes[i] + + if (hole.face != FaceType.SIDE) { + let sideHolesL = block.holes.filter(t => t.face == FaceType.SIDE && t.pointY == hole.pointY && t.pointX < hole.pointX && t.pointX >= hole.pointX - hole.radius - gap) + let sideHolesR = block.holes.filter(t => t.face == FaceType.SIDE && t.pointY == hole.pointY && t.pointX > hole.pointX && t.pointX <= hole.pointX + hole.radius + gap) + let sideHolesS = block.holes.filter(t => t.face == FaceType.SIDE && t.pointX == hole.pointX && t.pointY > hole.pointY && t.pointY < hole.pointY + hole.radius + gap) + let sideHolesX = block.holes.filter(t => t.face == FaceType.SIDE && t.pointX == hole.pointX && t.pointY < hole.pointY && t.pointY > hole.pointY - hole.radius - gap) + removeSideHoles = removeSideHoles.concat(sideHolesL, sideHolesR, sideHolesS, sideHolesX) + } + } + + + for (let removeHole of removeSideHoles) { + let index = block.holes.indexOf(removeHole) + + if (index >= 0) { + block.holes.splice(index, 1) + } + } +} + +/** 穿孔或打穿造型通孔一刀加工 */ +/** + * + * @param bd + * @param config + config + * throughHoleOneTime 通孔穿孔一刀加工 + * * throughHoleProcessMode 穿孔对面加工方式: + ** 0随意面,根据排版尽量正面加工(少翻板); + ** 1孔面,按碰撞面排孔所在面加工; + ** 2孔对面,按碰撞面排孔所在面的对面打孔 + */ +export function resetThroughHoleModes(bd: PlaceBlockDetail, config: any) { + if (!bd.points) + bd.points = [] + if (!bd.holes) + bd.holes = [] + if (!bd.models) + bd.models = [] + + const { + throughHoleOneTime = false, + throughHoleProcessMode = 0, + } = config + if (throughHoleOneTime) // 通孔穿孔一刀加工 + { + // 判断正反面匹配的 + bd.holeListThrough = bd.holes.filter(t => t.depth >= bd.thickness - 0.001) // 穿孔 + bd.holeListFaceA = bd.holes.filter(t => t.face == FaceType.FRONT && t.depth < bd.thickness - 0.001) + bd.holeListFaceB = bd.holes.filter(t => t.face == FaceType.BACK && t.depth < bd.thickness - 0.001) + + // 通孔判断 + + for (let i = bd.holeListFaceA.length - 1; i >= 0; i--) { + let hole1 = bd.holeListFaceA[i] + let isThrough = false + + for (let j = bd.holeListFaceB.length - 1; j >= 0; j--) { + let hole2 = bd.holeListFaceB[j] + + if (isThroughHole(hole1, hole2)) { + bd.holeListFaceB.splice(j, 1) + isThrough = true + } + } + + if (isThrough) { + bd.holeListFaceA.splice(i, 1) + hole1.depth = bd.thickness + bd.holeListThrough.push(hole1) + } + } + } + + else { + if (throughHoleProcessMode == 2) // 通孔对面加工 + { + + for (let hole of bd.holes) { + if (hole.holeType == HoleType.THROUGH_HOLE || hole.depth >= bd.thickness - 0.01) { + let oldFace = hole.face + + if (oldFace == FaceType.FRONT) { + hole.face = FaceType.BACK + } + + else if (oldFace == FaceType.BACK) { + hole.face = FaceType.FRONT + } + } + } + } + + bd.holeListFaceA = bd.holes.filter(t => t.face == FaceType.FRONT) + bd.holeListFaceB = bd.holes.filter(t => t.face == FaceType.BACK) + bd.holeListThrough = [] + } + + bd.modelListFaceA = bd.models.filter(t => t.face == FaceType.FRONT && (t.depth < bd.thickness - 0.01 || t.isVKnifeModel())) + bd.modelListFaceB = bd.models.filter(t => t.face == FaceType.BACK && (t.depth < bd.thickness - 0.01 || t.isVKnifeModel())) + bd.modelListThrough = bd.models.filter(t => t.depth >= bd.thickness - 0.01 && t.isVKnifeModel() == false) + + bd.holeCountFront = bd.holeListFaceA.length + bd.holeCountBack = bd.holeListFaceB.length + bd.holeCountThrough = bd.holeListThrough.length + bd.holeCountSide = bd.holeListSide.length + + bd.modelCountFront = bd.modelListFaceA.length + bd.modelCountBack = bd.modelListFaceB.length + bd.modelCountThrough = bd.modelListThrough.length + bd.hasModelThrogh = bd.modelListThrough.length > 0 + + bd.bigHoleInFaceA = bd.holeListFaceA.some(t => t.holeType == HoleType.BIG_HOLE) + bd.isTwoFaceProcess = bd.holeCountFront + bd.modelCountFront > 0 && bd.holeCountBack + bd.modelCountBack > 0 + + /** 判断两孔是否是通孔 */ + + function isThroughHole(hole1, hole2) { + // 孔半径一样,位置一样,深度和大于板厚 + return equal(hole1.radius, hole2.radius) + + && equal(hole1.pointX, hole2.pointX) + && equal(hole1.pointY, hole2.pointY) + && hole1.depth + hole2.depth >= bd.thickness - 0.01 + } +} + +/** 设置侧孔的方向 */ + +export function setSideHoleFace(block: PlaceBlockDetail) { + + // 矩形板的侧孔face原始数据无法保证正确,所有都当做起点,终点来计算侧孔的面与方向 + + for (const hole of block.holeListSide) { + if (Math.abs(hole.pointX - hole.pointX2) < 1 && hole.pointY2 > hole.pointY) { + hole.direct = 0 // 下侧边,向上 + hole.sideFace = SideFaceType.BOTTOM_SIDE + } + else if (Math.abs(hole.pointX - hole.pointX2) < 1 && hole.pointY2 < hole.pointY) { + hole.direct = 2 // 上侧边 向下 + hole.sideFace = SideFaceType.TOP_SIDE + } + + else if (Math.abs(hole.pointY - hole.pointY2) < 1 && hole.pointX2 > hole.pointX) { + hole.direct = 3 // 在左侧板 向右 + hole.sideFace = SideFaceType.LEFT_SIDE + } + + else if (Math.abs(hole.pointY - hole.pointY2) < 1 && hole.pointX2 < hole.pointX) { + hole.direct = 1 // 右侧边, 向左 + hole.sideFace = SideFaceType.RIGHT_SIDE + } + else // 斜边 + { + hole.direct = -1 + hole.sideFace = SideFaceType.SPECIAL_SHAPED_SIDE + } + } + block.holeCountLeft = ArrayExt.count(block.holeListSide, t => t.faceId == 3) + block.holeCountRight = ArrayExt.count(block.holeListSide, t => t.faceId == 1) + block.holeCountTop = ArrayExt.count(block.holeListSide, t => t.faceId == 2) + block.holeCountBottom = ArrayExt.count(block.holeListSide, t => t.faceId == 0) + block.holeCountBevelled = ArrayExt.count(block.holeListSide, t => ![0, 1, 2, 3].includes(t.faceId)) +} +/** 初始化 侧面造型 + * + * * 侧面造型的点阵判断逻辑 + * + * 提要: + * 1、 face 为 该造型在该板件的第几条边上 + * 2、 originModeling 内的造型轮廓点阵的 X Y轴 为 : + * 以该造型为正面 且小板板面朝上 造型为正面的左下角为原点 板厚 为 Y 横象为X + * 注:最终使用的时候 要得到该造型 对应机台的 轮廓数据和刀路数据 + * + * 平行判断 参考 checkIsTilt 修改一个新的方法 + * 是否在板内 可使用 isPointInBlock + * + * 要求:要得到 + * + * 转换逻辑 + * 1、通过face 获取 该造型所在的边 + * 2、将这条边 与 板件的坐标轴做比较 判断平行 + * 情况1:与板件的X轴 平行 则造型 可能为 上 || 下 ,使用 isPointInBlock 判断 具体是上 还是下 + * 情况2:与板件的Y轴 平行 则造型 可能为 左 || 右 , 使用 isPointInBlock 判断 具体是左 还是右 + * 情况3:都不平行 则 朝向的值为斜边 + * 最终得到造型的朝向 + * 3、依据边的坐标和造型的朝向 可以 根据造型的轮廓数据转为 对应机台的轮廓数据 和刀路数据 + * + * + * 注:这里解析出的是设计端的数据 +*/ +export async function initSideModel(block: PlaceBlock) { + if (!block.blockDetail) { + console.error('initSideModel: blockDetail is undefind') + return + } + let detail = block.blockDetail + if (detail.modelListSide.length > 0) { + + // 判断时侧面造型 还是 侧面槽 以及计算槽的相关信息 + for (const sideModel of detail.modelListSide) { + let min = 0; // 宽 + let max = 0; // 长 + // let temp: any = [] + let isRect = true // 是否矩形 + let lines: Array = [] + for (let index = 0; index < sideModel.originModeling.outline.length; index++) { + const baseStartPoint = sideModel.originModeling.outline[index]; + + const baseEndPoint = sideModel.originModeling.outline[index + 1]; + + if (!baseEndPoint) { + break + } + const startPoint = sideModel.originModeling.outline[index].pts; + const endPoint = sideModel.originModeling.outline[index + 1].pts; + + if (baseStartPoint.buls != 0 || baseEndPoint.buls != 0) { + isRect = false + break + } + + let centerPoint = { + x: (startPoint.x + endPoint.x) / 2, + y: (startPoint.y + endPoint.y) / 2, + curve: baseStartPoint.buls, + depth: sideModel.depth, + radius: 0 + } + + + let lineLength = getDis2(startPoint, endPoint) // parseFloat(parseFloat(getDis2(startPoint, endPoint)).toFixed(3)) + if (min == 0) { + min = lineLength + } + if (max == 0) { + max = lineLength + } + min = Math.min(min, lineLength) + max = Math.max(max, lineLength) + + + let temp = { startPoint, endPoint, centerPoint, lineLength } + lines.push(temp); + if (index + 1 == sideModel.originModeling.outline.length) { + break + } + } + + let startAndEndLines = lines.filter(e => isInRange(min, e.lineLength - 0.001, e.lineLength + 0.001)); + + // 槽的起点 和终点 + if (startAndEndLines.length == 2) { + sideModel.modelStartPoint = startAndEndLines[0].centerPoint + sideModel.modelEndPoint = startAndEndLines[1].centerPoint + } + + isRect = (lines.length == 4 || lines.length == 1) && sideModel.offsetList.length == 0 + // console.log('情况123', lines, model,isRect) + sideModel.modelLength = max + sideModel.modelWidth = min + sideModel.isRect = isRect + if (isRect) { + sideModel.isTilt = checkIsTilt(lines) + } + + let directionRes = getModelDirection(block, sideModel) + + sideModel.direction = directionRes.direction; + // console.log('direction', directionRes.direction, sideModel.isRect, sideModel.isTilt); + + if (!detail.borderContour) { + console.error('initSideModel: borderContour is undefind') + return + } + const borders = detail?.borderContour?.borderFinal + const border = borders[sideModel.face] + + // 依据 造型的朝向 和 造型的轮廓数据 和 所在边的坐标 生成 轮廓数据 还有刀路数据 + if (isRect && sideModel.isTilt == false) { + detail.borderContour.borderFinal + sideModel.modelStartPoint = transformSideModelPoint(sideModel.modelStartPoint, directionRes, border, sideModel, 'modelStartPoint') + sideModel.modelEndPoint = transformSideModelPoint(sideModel.modelEndPoint, directionRes, border, sideModel, 'modelStartPoint') + } + + let newPointList: any = [] + if (Array.isArray(sideModel.originModeling.outline)) { + for (const linePoint of sideModel.originModeling.outline) { + let tempPoint = { + curve: linePoint.buls, + depth: sideModel.depth, + radius: 1 / linePoint.buls, + x: linePoint.pts.x, + y: linePoint.pts.y, + z: linePoint.pts.z, + } + let temp = transformSideModelPoint(tempPoint, directionRes, border, sideModel, 'linePoint') + + let sideModelPoint = new BlockSideModelPoint(temp) + + newPointList.push(sideModelPoint) + } + } else { + for (const i in sideModel.originModeling.outline.pts) { + let pts = sideModel.originModeling.outline.pts[i] + let bul = sideModel.originModeling.outline.buls[i] + let tempPoint = { + curve: bul, + depth: sideModel.depth, + radius: 1 / bul, + x: pts.x, + y: pts.y, + z: pts.z, + } + + let temp = transformSideModelPoint(tempPoint, directionRes, border, sideModel, 'linePoint') + + let sideModelPoint = new BlockSideModelPoint(temp) + + newPointList.push(sideModelPoint) + } + } + + sideModel.pointList = newPointList + } + } +} + +/**侧面造型数据转换 从设计端 转为生产数据 + * config: + * placeStyle 小板放置方式 +*/ +export function transFormSideModelDataToProductData(block: PlaceBlock, config) { + if(!block.blockDetail){ + console.error('transFormSideModelDataToProductData: blockDetail is undefind') + return + } + const { + placeStyle= 1 + } = config + if (block.blockDetail.modelListSide.length > 0) { + for (const sideModel of block.blockDetail.modelListSide) { + if (sideModel.isRect == true && sideModel.isTilt == false) { + // 矩形槽 要转换下起始点 + let p_start = BlockHelper.getPlacedPostionInBlock(placeStyle, block.placeWidth, block.placeLength, sideModel.modelStartPoint.x, sideModel.modelStartPoint.y, false) + let p_end = BlockHelper.getPlacedPostionInBlock(placeStyle, block.placeWidth, block.placeLength, sideModel.modelEndPoint.x, sideModel.modelEndPoint.y, false) + + sideModel.modelStartPoint.x = p_start.x + sideModel.modelStartPoint.y = p_start.y + + sideModel.modelEndPoint.x = p_end.x + sideModel.modelEndPoint.y = p_end.y + } + + // 转换造型 轮廓点阵 + for (const point of sideModel.pointList) { + let p = BlockHelper.getPlacedPostionInBlock(placeStyle, block.placeWidth, block.placeLength, point.pointX, point.pointY, false) + point.pointX = p.x + point.pointY = p.y + } + + } + } + +} + +/** + * 值 是否在范围内 + * @param value 目标值 + * @param min 最小值 比较值 + * @param max 最大值 比较值 + * @returns + */ +function isInRange(value, min, max) { + return value >= min && value <= max; +} +/** + * + * @param lines + * 注:假定2条在坐标轴上的线 与矩形造型的4条线判断是否是平行 + * + * 线的起始点 与 坐标轴 X Y 比较 判断是否倾斜 (倾斜的矩形 或者 平行四边形) + */ +// 判断是否是倾斜的 +function checkIsTilt(lines) { + // 假定的坐标轴 定义坐标点 + let p1 = { x: 0, y: 0 } + let p2 = { x: 0, y: 100 } + let p3 = { x: 100, y: 0 } + // 假定2条在坐标轴上的线 + // let tempAxiosXLine = [p1,p3] + // let tempAxiosYLine = [p1,p2] + + + + lines.forEach(line => { + // flag 是否平行的标识 true 平行 false 不平行 + let flagX = false + let flagY = false + + let startX = line.startPoint.pointX || line.startPoint.x + let startY = line.startPoint.pointY || line.startPoint.y + let endX = line.endPoint.pointX || line.endPoint.x + let endY = line.endPoint.pointY || line.endPoint.y + + // 距离X轴的距离是否相等 + // 线段的起点 距离X轴的距离 + let len1 = getDis_PointLine(p1.x, p1.y, p3.x, p3.y, startX, startY) + // 线段的终点 距离X轴的距离 + let len2 = getDis_PointLine(p1.x, p1.y, p3.x, p3.y, endX, endY) + + // 实际数据是有误差的 误差 的容差为 0.001 + if (isInRange(len1, len2 - 0.001, len2 + 0.001) || isInRange(len2, len1 - 0.001, len1 + 0.001)) { + flagX = true + } + + // 距离Y轴的距离是否相等 + + + // 线段起点距离Y轴的距离 + let len3 = getDis_PointLine(p1.x, p1.y, p2.x, p2.y, startX, startY) + // 线段终点距离Y轴的距离 + let len4 = getDis_PointLine(p1.x, p1.y, p2.x, p2.y, endX, endY) + + if (isInRange(len3, len4 - 0.001, len4 + 0.001) || isInRange(len4, len3 - 0.001, len3 + 0.001)) { + flagX = true + } + + // 只要和 X || Y 轴 平行 就不是倾斜的 + + if (flagX || flagY) { + line.isTilt = false + } else { + line.isTilt = true + } + }); + // 查找有没有 倾斜的边 + const res = lines.filter(e => e.isTilt == true).length > 0 //.findIndex(e => e.isTilt == true) == -1 + + + return res + +} +/** 转换 侧面造型的坐标点 */ +export function transformSideModelPoint(point, directionRes, border, sideModel: SideModel, flag?: string) { + const { direction, preFlag, nextFlag } = directionRes + + let newPoint: any = { x: 0, y: 0, z: 0, buls: 0, depth: 0 } + if (point == null) { + newPoint = null // { x: -1, y: -1, z: -1, buls: 0, depth: 0 } + } else { + /** + * + * 侧面造型的点坐标使用 等比三角形 解决坐标问题 + * + */ + + // 斜边比例 + let rate = point.x / border.m_Length + + + + switch (direction) { + case 0: + /**坐标转换逻辑 下造型 + * + * point 里面的 + * x 对应 实际的 X + * y 对应 实际的 Z + * 实际的y 为 border 对应的 y + */ + + // console.log('下造型',sideModel.modelId) + newPoint.x = point.x + newPoint.y = border.StartPoint.m_Y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 1: + // console.log('右造型',sideModel.modelId) + newPoint.x = border.StartPoint.m_X + newPoint.y = point.x + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 2: + // console.log('上造型',sideModel.modelId) + newPoint.x = border.StartPoint.m_X - point.x + newPoint.y = border.StartPoint.m_Y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 3: + // console.log('左造型',sideModel.modelId) + newPoint.x = border.StartPoint.m_X + newPoint.y = border.StartPoint.m_Y - point.x + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 10: + // console.log('左下造型',sideModel.modelId) + + // 底边 + var x = Math.abs(border.StartPoint.m_X) - Math.abs(border.EndPoint.m_X) + // 竖边 + var y = Math.abs(border.StartPoint.m_Y) - Math.abs(border.EndPoint.m_Y) + + + // 实际点坐标的 偏移值 + var p_x = Math.abs(x * rate) + var p_y = Math.abs(y * rate) + + var fakeStartPoint: any = null + // 二次计算的原点坐标 + + /** + * 根据 造型的坐标系的定位点 进行偏移 (边框的起点 或者终点) + * 根据preFlag (小板的轮廓的上一条边的朝向 判断 定位点事 起点还是终点) + */ + if (preFlag == 'toDown') { + fakeStartPoint = + { + x: border.StartPoint.m_X, + y: border.StartPoint.m_Y + } + } else if (preFlag == 'toLeft') { + fakeStartPoint = + { + x: border.EndPoint.m_X, + y: border.EndPoint.m_Y + } + } else { + console.log('理论上有异常 排查') + } + newPoint.x = fakeStartPoint.x + p_x + newPoint.y = fakeStartPoint.y - p_y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 11: + // console.log('右下造型',sideModel.modelId) + + + // 底边 + var x = Math.abs(border.StartPoint.m_X) - Math.abs(border.EndPoint.m_X) + // 竖边 + var y = Math.abs(border.StartPoint.m_Y) - Math.abs(border.EndPoint.m_Y) + + + // 实际点坐标的 偏移值 + var p_x = Math.abs(x * rate) + var p_y = Math.abs(y * rate) + + var fakeStartPoint: any = null + // 二次计算的原点坐标 + + /** + * 根据 造型的坐标系的定位点 进行偏移 (边框的起点 或者终点) + * 根据preFlag (小板的轮廓的上一条边的朝向 判断 定位点事 起点还是终点) + */ + if (preFlag == 'toRight') { + fakeStartPoint = + { + x: border.StartPoint.m_X, + y: border.StartPoint.m_Y + } + } else if (preFlag == 'toDown') { + fakeStartPoint = + { + x: border.EndPoint.m_X, + y: border.EndPoint.m_Y + } + } else { + console.log('理论上有异常 排查') + } + + + newPoint.x = fakeStartPoint.x + p_x + newPoint.y = fakeStartPoint.y + p_y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 12: + // console.log('右上造型',sideModel.modelId) + // 底边 + var x = Math.abs(border.StartPoint.m_X) - Math.abs(border.EndPoint.m_X) + // 竖边 + var y = Math.abs(border.StartPoint.m_Y) - Math.abs(border.EndPoint.m_Y) + + + // 实际点坐标的 偏移值 + var p_x = Math.abs(x * rate) + var p_y = Math.abs(y * rate) + + var fakeStartPoint: any = null + // 二次计算的原点坐标 + + /** + * 根据 造型的坐标系的定位点 进行偏移 (边框的起点 或者终点) + * 根据preFlag (小板的轮廓的上一条边的朝向 判断 定位点事 起点还是终点) + */ + if (preFlag == 'toUp') { + fakeStartPoint = + { + x: border.StartPoint.m_X, + y: border.StartPoint.m_Y + } + } else if (preFlag == 'toRight') { + fakeStartPoint = + { + x: border.EndPoint.m_X, + y: border.EndPoint.m_Y + } + } else { + console.log('理论上有异常 排查') + } + newPoint.x = fakeStartPoint.x - p_x + newPoint.y = fakeStartPoint.y - p_y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + case 13: + // console.log('左上造型',sideModel.modelId) + + // 底边 + var x = Math.abs(border.StartPoint.m_X) - Math.abs(border.EndPoint.m_X) + // 竖边 + var y = Math.abs(border.StartPoint.m_Y) - Math.abs(border.EndPoint.m_Y) + + + // 实际点坐标的 偏移值 + var p_x = Math.abs(x * rate) + var p_y = Math.abs(y * rate) + + var fakeStartPoint: any = null + // 二次计算的原点坐标 + + /** + * 根据 造型的坐标系的定位点 进行偏移 (边框的起点 或者终点) + * 根据preFlag (小板的轮廓的上一条边的朝向 判断 定位点事 起点还是终点) + */ + if (preFlag == 'toLeft') { + fakeStartPoint = + { + x: border.StartPoint.m_X, + y: border.StartPoint.m_Y + } + } else if (preFlag == 'toUp') { + fakeStartPoint = + { + x: border.EndPoint.m_X, + y: border.EndPoint.m_Y + } + } else { + console.log('理论上有异常 排查') + } + + + newPoint.x = fakeStartPoint.x - p_x + newPoint.y = fakeStartPoint.y - p_y + newPoint.z = point.y + newPoint.buls = point.curve + newPoint.depth = sideModel.depth + break + + default: + break; + } + } + + + return newPoint; +} +// 获取侧面造型的朝向 +export function getModelDirection(block: PlaceBlock, sideModel) { + + console.log('getModelDirection', block); + let detail = block.blockDetail + + + const borders = detail?.borderContour?.borderFinal + // if(borders==undefined){ + // return + // } + + const border = borders[sideModel.face] + + let polygon: any = [] + + borders.forEach(br => { + let temp = { + x: br.StartPoint.m_X, + y: br.StartPoint.m_Y + } + polygon.push(temp) + }); + + + // 假定的坐标轴 定义坐标点 + let p1 = { x: 0, y: 0 } + let p2 = { x: 0, y: 100 } + let p3 = { x: 100, y: 0 } + // 朝向 + let direction = -2; + + // 注: 根据上一条边和下一条的朝向判断 造型的 朝向 + // 板件轮廓 上一条边的朝向 + let preFlag = ''; + // 板件轮廓 下一条边的朝向 + let nextFlag = ''; + + + + let flagX = false + let flagY = false + + // 线段的起点 距离X轴的距离 + let len1 = getDis_PointLine(p1.x, p1.y, p3.x, p3.y, border.StartPoint.m_X, border.StartPoint.m_Y) + // 线段的终点 距离X轴的距离 + let len2 = getDis_PointLine(p1.x, p1.y, p3.x, p3.y, border.EndPoint.m_X, border.EndPoint.m_Y) + + if (len1 == len2) { + flagX = true + } + // 距离Y轴的距离是否相等 + // 线段起点距离Y轴的距离 + let len3 = getDis_PointLine(p1.x, p1.y, p2.x, p2.y, border.StartPoint.m_X, border.StartPoint.m_Y) + // 线段终点距离Y轴的距离 + let len4 = getDis_PointLine(p1.x, p1.y, p2.x, p2.y, border.EndPoint.m_X, border.EndPoint.m_Y) + if (len3 == len4) { + flagY = true + } + let xx = (border.StartPoint.m_X + border.EndPoint.m_X) / 2 + let yy = (border.StartPoint.m_Y + border.EndPoint.m_Y) / 2 + if (flagY == false && flagX == false) { + // 斜的 + direction = -1 + // 获取 上一条边 和下一条边 (忽略弧形 弧线也按照线段处理 不会影响方向的判断) + let preborderId = sideModel.face - 1 + let nextBorderId = sideModel.face + 1 + + + if (preborderId < 0) { + preborderId = borders.length - 1 + } + + if (nextBorderId > borders.length - 1) { + nextBorderId = 0 + } + + let preborder = borders[preborderId] + let nextborder = borders[nextBorderId] + + + + let preX = preborder.StartPoint.m_X - preborder.EndPoint.m_X + let preY = preborder.StartPoint.m_Y - preborder.EndPoint.m_Y + + let nextX = nextborder.StartPoint.m_X - nextborder.EndPoint.m_X + let nextY = nextborder.StartPoint.m_Y - nextborder.EndPoint.m_Y + + if (preX == 0) { + // 上下 + if (preY > 0) { + // 向下 + preFlag = 'toDown' + } else if (preY < 0) { + // 向上 + preFlag = 'toUp' + } + } + + if (preY == 0) { + // 左右 + if (preX > 0) { + // 向左 + preFlag = 'toLeft' + } else if (preX < 0) { + // 向右 + preFlag = 'toRight' + } + } + + if (nextX == 0) { + // 上下 + if (nextY > 0) { + // 向下 + nextFlag = 'toDown' + } else if (nextY < 0) { + // 向上 + nextFlag = 'toUp' + } + } + + if (nextY == 0) { + // 左右 + if (nextX > 0) { + // 向左 + nextFlag = 'toLeft' + } else if (nextX < 0) { + // 向右 + nextFlag = 'toRight' + } + } + // 理论只有下面的情况 不符合的都是异常情况 或者不存在的情况 + switch (preFlag) { + case 'toDown': + if (nextFlag == 'toLeft') { + // 造型在 右下斜面 + direction = 11 + } else if (nextFlag == 'toRight') { + // 造型在 左下斜面 + direction = 10 + } + break; + case 'toRight': + if (nextFlag == 'toUp') { + // 造型在 右下斜面 + direction = 11 + } else if (nextFlag == 'toDown') { + // 造型在 右上斜面 + direction = 12 + } + break; + case 'toUp': + if (nextFlag == 'toLeft') { + // 造型在 右上斜面 + direction = 12 + } else if (nextFlag == 'toRight') { + // 造型在 左上斜面 + direction = 13 + } + break; + case 'toLeft': + if (nextFlag == 'toUp') { + // 造型在 左下斜面 + direction = 10 + + } else if (nextFlag == 'toDown') { + // 造型在 左上斜面 + direction = 13 + } + break; + default: + break; + } + + } else if (flagX == true) { + // 上下 + + const p1 = { x: xx, y: yy - 1 } + const p2 = { x: xx, y: yy + 1 } + const isTop = isPointInPolygon(p1, polygon) + if (isTop) { + direction = 2 + } else { + const isBottom = isPointInPolygon(p2, polygon) + if (isBottom) { + direction = 0 + } + if (direction == -2) { + + } + } + + + } else if (flagY == true) { + // 左右 + const p3 = { x: xx - 1, y: yy } + const p4 = { x: xx + 1, y: yy } + const isRight = isPointInPolygon(p3, polygon) + if (isRight) { + direction = 1 + } else { + const isLeft = isPointInPolygon(p4, polygon) + if (isLeft) { + direction = 3 + } + + if (direction == -2) { + + } + } + + + } + + + const directionRes = { + direction, preFlag, nextFlag + } + + return directionRes +} +/** 判断 点 是否在轮廓内 射线法 */ +export function isPointInPolygon(point, polygon) { + const x = point.x; + const y = point.y; + let inside = false; + + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].x; + const yi = polygon[i].y; + const xj = polygon[j].x; + const yj = polygon[j].y; + + // 检查点是否位于当前边的水平线上 + const intersect = ((yi > y) != (yj > y)) && + (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside; +} + +export function createSideModelRealKnifePoints(block: PlaceBlock, sideModel: SideModel) { + let directionRes = getModelDirection(block, sideModel) + + // 确定 轮廓的中点 + let x = { min: 0, max: 0 } + let y = { min: 0, max: 0 } + let z = { min: 0, max: 0 } + sideModel.pointList.forEach((p, i) => { + if (i == 0) { + x.min = p.pointX + x.max = p.pointX + + y.min = p.pointY + y.max = p.pointY + + z.min = p.pointZ + z.max = p.pointZ + } else { + x.min = Math.min(x.min, p.pointX) + x.max = Math.max(x.max, p.pointX) + + y.min = Math.min(y.min, p.pointY) + y.max = Math.max(y.max, p.pointY) + + z.min = Math.min(z.min, p.pointZ) + z.max = Math.max(z.max, p.pointZ) + } + }); + + let centerPoint = { + x: (x.min + x.max) / 2, + y: (y.min + y.max) / 2, + z: (z.min + z.max) / 2 + } + + + + if (equal(sideModel.realKnifeRadius, sideModel.knifeRadius)) { + let realPointList: any = []; + if (directionRes.direction == 0 || directionRes.direction == 2) { + let tempPoints: any = [] + + sideModel.pointList.forEach((p) => { + tempPoints.push({ x: p.pointY, y: p.pointZ }) + }) + var newPointList = shrinkRectangle(tempPoints, centerPoint.y, centerPoint.z, sideModel.realKnifeRadius) + + realPointList = sideModel.pointList.map((point, i) => { + let temp = JSON.parse(JSON.stringify(point)) + temp.pointY = newPointList[i].x + temp.pointZ = newPointList[i].y + return temp + }) + } else if (directionRes.direction == 1 || directionRes.direction == 3) { + let tempPoints: any = [] + sideModel.pointList.forEach(p => { + tempPoints.push({ x: p.pointX, y: p.pointZ }) + + }) + var newPointList = shrinkRectangle(tempPoints, centerPoint.x, centerPoint.z, sideModel.realKnifeRadius) + realPointList = sideModel.pointList.map((point, i) => { + let temp = JSON.parse(JSON.stringify(point)) + temp.pointX = newPointList[i].x + temp.pointZ = newPointList[i].y + return temp + }) + } + + } +} + +// 刀路 向内铣 +/** + * 得到 点阵 向内缩小的 新的点阵 + * @param points 点阵 + * @param centerX 造型的中心点x + * @param centerY 造型的中心点y + * @param shrinkAmount 向内的偏移量 + * @returns + */ +function shrinkRectangle(points, centerX, centerY, shrinkAmount) { + return points.map(point => { + // 计算到中心点的偏移量 + const dx = point.x - centerX; + const dy = point.y - centerY; + + + + // 为了避免除以0的错误和保持简单性,我们可以使用下面的方法: + // 如果dx或dy的绝对值小于shrinkAmount的一半,则将其设置为0(即该点保持在中心点上或非常接近)。 + // 否则,我们按照比例减少dx和dy。 + const absDx = Math.abs(dx); + const absDy = Math.abs(dy); + const halfShrink = shrinkAmount; + const newDx = (absDx > halfShrink) ? dx - (Math.sign(dx) * halfShrink) : 0; + const newDy = (absDy > halfShrink) ? dy - (Math.sign(dy) * halfShrink) : 0; + + return { + x: centerX + newDx, + y: centerY + newDy + }; + }); +} + +// 刀路 向外铣 +/** + * 得到 点阵 向外放大的 新的点阵 + * @param points 点坐标 + * @param centerX 造型的中心点x + * @param centerY 造型的中心点y + * @param expansionAmount 向外的偏移量 + * @returns + */ +function expandRectangleCorrect(points, centerX, centerY, expansionAmount) { + const halfExpansion = expansionAmount; // 在本例中,这将是0.5 + return points.map(point => ({ + // 只加一次halfExpansion,因为我们在两个方向上都会加。 + x: centerX + (point.x - centerX) + halfExpansion, + y: centerY + (point.y - centerY) + halfExpansion + })); + +} + +/** 造型轮廓(含封边),扣除封边, 变成开料坐标 */ +export function resetModelContour(bd: PlaceBlockDetail) { + let ox = bd.offsetX + let oy = bd.offsetY + for (let m of bd.models) { + if (m.hasContour()) { + let ptsArr = m.originModeling.outline.map(e => e.pts) + for (let pt of ptsArr) { + // 23.8.5 发现矩形的挖穿轮廓坐标是不含封边的 + pt.x -= ox + pt.y -= oy + } + } + } +} +export function resetModelKnife(block: PlaceBlock, toolsHelper:ToolsHelper, isUseOffset?: boolean) { + let _isUseOffset = false + if (isUseOffset) { + _isUseOffset = isUseOffset + } + let processModelIdList: number[] = []; + // console.log(block) + for (let m_i = 0; m_i < block.blockDetail.models.length; m_i++) { + let model = block.blockDetail.models[m_i] + + if (model.isVKnifeModel()) + continue // 二维刀路 不处理 + // if (model.realKnifeRadius != 0) + // continue // 已处理 + let knife = toolsHelper.getKnifeByParams({knifeName : model.knifeName}) //sysConfig.getModelKnife(model) + if (knife == null) { + model.realKnifeRadius = -1 + } + else { + model.realKnifeRadius = knife.diameter / 2 + model.realKnifeId = knife.knifeId + // 修复问题 如果是弧线的情况下 出现 弧线生成异常 + let checkIsOk = true + for (const p of model.pointList) { + if ((p.curve != 0 && p.radius == 0) || (p.radius != 0 && p.curve == 0)) { + checkIsOk = false + break + } + } + + if (equal(model.realKnifeRadius, model.knifeRadius) && checkIsOk) { + model.realPointList = model.pointList + } + else { + model.originModeling.knifeRadius = knife.diameter / 2 + model.originModeling.thickness = model.depth + model.originModeling.brThickness = block.thickness + model.originModeling.boardContour = getOrgContour() + + let newData: any = null + if (model.originModeling && Array.isArray(model.originModeling.outline)) { + newData = JSON.parse(JSON.stringify(model.originModeling)) + let newOutLine = { pts: newData.outline.map(e => e.pts), buls: newData.outline.map(e => e.buls) } + let newHoles = newData?.holes?.map(x => { return { pts: x.map(e => e.pts), buls: x.map(e => e.buls) } }) + newData.outline = newOutLine + newData.holes = newHoles || [] + } else { + newData = model.originModeling + } + + let custborders: any = [] + + try { + if (model.originModeling.outline) { + custborders = Production.GetChaiDanFeedingPath(newData, 0) + } + } catch (error) { + console.error(error, block.blockNo, model.modelId, model); + } + let offx = _isUseOffset ? block.offsetX() : 0// block.OffsetX; + let offy = _isUseOffset ? block.offsetY() : 0// block.OffsetY; + let ps1: BlockModelPoint[] = [] + if (custborders.length > 0) { + let ct = custborders[0] + + if (processModelIdList.includes(model.modelId)) { + ct = custborders[model.lineId - 1] + } else { + processModelIdList.push(model.modelId) + } + if (ct) { + for (let i = 0; i < ct.buls.length; i++) { + var r = 0 + if (ct.buls[i] != 0) { + let j = i + 1 + if (j == ct.buls.length) + j = 0 + r = CADExt.getR(ct.pts[i].x, ct.pts[i].y, ct.pts[j].x, ct.pts[j].y, ct.buls[i]) + } + + ps1.push(new BlockModelPoint({ pointX: ct.pts[i].x - offx, pointY: ct.pts[i].y - offy, curve: ct.buls[i], radius: r, depth: model.depth })) + } + } + } + + if (ps1.length > 0) { + model.realPointList = ps1 + } + else { + model.realKnifeId = -1 + model.realKnifeRadius = -1 + model.realPointList = [] + } + } + } + } + /** 获取轮廓 */ + function getOrgContour() { + if (!block.blockDetail) { + console.error('resetModelKnife=>getOrgContour: blockDetail is undefind') + return + } + if (!block.blockDetail.orgContourData) { + let pts: any = [] + let buls: any = [] + if (!block.isUnRegular) { + pts.push({ x: 0, y: 0 }) + pts.push({ x: block.cutWidth, y: 0 }) + pts.push({ x: block.cutWidth, y: block.cutLength }) + pts.push({ x: 0, y: block.cutLength }) + buls.push(0) + buls.push(0) + buls.push(0) + buls.push(0) + } + else // 矩形板 + { + for (let p of block.blockDetail.points) { + pts.push({ x: p.pointX, y: p.pointY }) + buls.push(p.curve) + } + } + + block.blockDetail.orgContourData = { pts, buls } + } + return block.blockDetail.orgContourData + } +} + +export async function resetSideModelKnife(block: PlaceBlock, tools: ToolsHelper) { + if (!block.blockDetail) { + console.error('resetModelKnife=>getOrgContour: blockDetail is undefind') + return + } + + for (let model of block.blockDetail.modelListSide) { + + + let knife = model.knife || tools.getKnifeByParams({ knifeName: model.knifeName }) + if (knife == null) { + model.realKnifeRadius = -1 + } + else { + model.knife = knife + model.realKnifeRadius = knife.diameter / 2 + model.realKnifeId = knife.knifeId + + // if(model.pointList.length ==0){ + // await initSideModel(block) + // } + + if (equal(model.realKnifeRadius, model.knifeRadius)) { + model.realPointList = model.pointList + } + else { + + model.originModeling.knifeRadius = knife.diameter / 2 + model.originModeling.thickness = model.depth + model.originModeling.brThickness = block.thickness + model.originModeling.boardContour = getOrgContour() + console.log('侧面造型生成新刀路:', model.modelId, model) + + let newData: any = null + + if (model.originModeling && Array.isArray(model.originModeling.outline)) { + newData = JSON.parse(JSON.stringify(model.originModeling)) + let newOutLine = { pts: newData.outline.map(e => e.pts), buls: newData.outline.map(e => e.buls) } + let newHoles = newData?.holes?.map(x => { return { pts: x.map(e => e.pts), buls: x.map(e => e.buls) } }) + newData.outline = newOutLine + newData.holes = newHoles || [] + } else { + newData = model.originModeling + } + + + + let custborders = model.originModeling.outline ? Production.GetChaiDanFeedingPath(newData) : [] + let offx = 0// block.OffsetX; + let offy = 0// block.OffsetY; + let ps1: BlockSideModelPoint[] = [] + if (custborders.length > 0) { + if (custborders.length == 1) { + let ct = custborders[0] + model.lineId = 1 + + for (let i = 0; i < ct.buls.length; i++) { + let r = 0 + if (ct.buls[i] != 0) { + let j = i + 1 + if (j == ct.buls.length) + j = 0 + r = CADExt.getR(ct.pts[i].x, ct.pts[i].y, ct.pts[j].x, ct.pts[j].y, ct.buls[i]) + } + ps1.push(new BlockSideModelPoint({ pointX: ct.pts[i].x - offx, pointY: ct.pts[i].y - offy, pointZ: 0, curve: ct.buls[i], radius: r, depth: model.depth })) + } + + // 要处理下坐标 + if (ps1.length > 0) { + let directionRes = getModelDirection(block, model) + const borders = block.blockDetail?.borderContour?.borderFinal + if (!borders) { + console.error('resetModelKnife=>getOrgContour: blockDetail is undefind') + return + } + const border = borders[model.face] + let newRealPointList: BlockSideModelPoint[] = [] + for (const linePoint of ps1) { + let tempPoint = { + curve: linePoint.buls, + depth: model.depth, + radius: 1 / linePoint.buls, + x: linePoint.pointX || 0, + y: linePoint.pointY || 0, + z: linePoint.pointZ || 0, + } + let temp = transformSideModelPoint(tempPoint, directionRes, border, model, 'linePoint') + let sideModelPoint = new BlockSideModelPoint(temp) + newRealPointList.push(sideModelPoint); + } + + model.realPointList = newRealPointList + } + else { + model.realKnifeId = -1 + model.realKnifeRadius = -1 + model.realPointList = [] + } + } else { + for (const ct_key in custborders) { + let tempModel = new SideModel(model) + ps1 = [] + let ct = custborders[ct_key] + tempModel.lineId = parseFloat(ct_key) + 1 + + for (let i = 0; i < ct.buls.length; i++) { + let r = 0 + if (ct.buls[i] != 0) { + let j = i + 1 + if (j == ct.buls.length) + j = 0 + r = CADExt.getR(ct.pts[i].x, ct.pts[i].y, ct.pts[j].x, ct.pts[j].y, ct.buls[i]) + } + ps1.push(new BlockSideModelPoint({ pointX: ct.pts[i].x - offx, pointY: ct.pts[i].y - offy, pointZ: 0, curve: ct.buls[i], radius: r, depth: tempModel.depth })) + } + + if (ps1.length > 0) { + let directionRes = getModelDirection(block, tempModel) + const borders = block.blockDetail?.borderContour?.borderFinal + const border = borders[tempModel.face] + let newRealPointList: BlockSideModelPoint[] = [] + for (const linePoint of ps1) { + let tempPoint = { + curve: linePoint.buls, + depth: tempModel.depth, + radius: 1 / linePoint.buls, + x: linePoint.pointX || 0, + y: linePoint.pointY || 0, + z: linePoint.pointZ || 0, + } + let temp = transformSideModelPoint(tempPoint, directionRes, border, tempModel, 'linePoint') + let sideModelPoint = new BlockSideModelPoint(temp) + newRealPointList.push(sideModelPoint); + } + + tempModel.realPointList = newRealPointList + } + else { + tempModel.realKnifeId = -1 + tempModel.realKnifeRadius = -1 + tempModel.realPointList = [] + } + + // 避免拆分的刀路造型 重复插入 + if (block.blockDetail.modelListSide.findIndex(e => e.modelId == tempModel.modelId && e.lineId == tempModel.lineId) == -1) { + block.blockDetail.modelListSide.push(tempModel) + } else { + model = tempModel + } + } + } + } + + + + + } + + } + } + /** 获取轮廓 */ + function getOrgContour() { + if (!block.blockDetail) { + console.error('resetModelKnife=>getOrgContour: blockDetail is undefind') + return + } + if (!block.blockDetail.orgContourData) { + let pts: any = [] + let buls: any = [] + if (!block.isUnRegular) { + pts.push({ x: 0, y: 0 }) + pts.push({ x: block.cutWidth, y: 0 }) + pts.push({ x: block.cutWidth, y: block.cutLength }) + pts.push({ x: 0, y: block.cutLength }) + buls.push(0) + buls.push(0) + buls.push(0) + buls.push(0) + } + else // 矩形板 + { + for (let p of block.blockDetail.points) { + pts.push({ x: p.pointX, y: p.pointY }) + buls.push(p.curve) + } + } + + block.blockDetail.orgContourData = { pts, buls } + } + return block.blockDetail.orgContourData + } +} + + + + + + +/** 二维刀路初始化 */ +export function init2VModel(blockDetail: PlaceBlockDetail, tools: ToolsHelper, isCNC = false) { + for (let model of blockDetail.models) { + if (!model.isVKnifeModel()) + continue + let vModels: any[] = [] + model.VLines = vModels + let isFaceB = model.face == FaceType.BACK + if (model.pointList.length < 1) + continue + let ps = model.pointList.map((t) => { return { x: t.pointX, y: t.pointY, bul: t.curve } }) + let pl = PolylineHelper.create(ps) + if (model.VLines?.length > 0) + return // 已经分析了 + model.VLines = [] + for (let os of model.offsetList) { + // 根据刀名称找刀 + let knife1 = isCNC ? null : tools.getKnifeByParams({ knifeName: 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 + } +} + +/** 异形板 封边值 */ +export function resetBorderValue(block: PlaceBlock) { + if (!block.blockDetail) { + console.error('resetBorderValue: blockDetail is undefind') + return + } + if (block.isUnRegular) { + block.cutWidth = block.blockDetail.cutWidth + block.cutLength = block.blockDetail.cutLength + } + + block.blockDetail.cutWidth = block.cutWidth + block.blockDetail.cutLength = block.cutLength + + if (block.isUnRegular == false) + return + + // 计算异形板 的上下左右 封边值 + block.sealLeft = getSealEdge('LEFT') + block.sealRight = getSealEdge('RIGHT') + block.sealTop = getSealEdge('TOP') + block.sealBottom = getSealEdge('BOTTOM') + + function getSealEdge(sealEdge) { + let closetP + let dis = 1000000 + for (let i = 0; i < block.orgPoints.length; i++) { + let p0 = block.orgPoints[i] + let p1 = i == block.orgPoints.length - 1 ? block.orgPoints[0] : block.orgPoints[i + 1] + + if (sealEdge == 'LEFT') { + if (equal(p0.pointX, 0) && equal(p1.pointX, 0)) + return p0.sealSize + let dis_t = (p0.pointX + p1.pointX) * 0.5 + if (dis_t < dis) { + closetP = p0 + dis = dis_t + } + } + if (sealEdge == 'RIGHT') { + if (equal(p0.pointX, block.width) && equal(p1.pointX, block.width)) + return p0.sealSize + let dis_t = (p0.pointX + p1.pointX) * 0.5 + if (block.width - dis_t < dis) { + closetP = p0 + dis = block.width - dis_t + } + } + if (sealEdge == 'TOP') { + if (equal(p0.pointY, block.length) && equal(p1.pointY, block.length)) + return p0.sealSize + let dis_t = (p0.pointY + p1.pointY) * 0.5 + if (block.length - dis_t < dis) { + closetP = p0 + dis = dis_t + } + } + if (sealEdge == 'BOTTOM') { + if (equal(p0.pointY, 0) && equal(p1.pointY, 0)) + return p0.sealSize + let dis_t = (p0.pointY + p1.pointY) * 0.5 + if (dis_t < dis) { + closetP = p0 + dis = dis_t + } + } + } + // 找最接近边的点 + return closetP ? closetP.sealSize : 0 + } +} + + + +/** + * 设置小板是否反面排版优化 + * @param block 小板 + * @param processMode 加工模式(0开料机(雕刻机)加工 1开料机CNC组合 2定制加工) + */ +export function setFaceToPlace(block: PlaceBlock, config) { + // let processMode = 0; + // if(sys.isCutProcess) processMode = 0; + // if(sys.isCutAndCNCProcess) processMode = 1; + // if(sys.isCustomized) processMode = 2; + const { + processMode = 0 + } = config + block.isTurnFaceToPlace = !getDoFace(block, processMode) +} + + diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts new file mode 100644 index 0000000..e127faf --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts @@ -0,0 +1,365 @@ + +import { BlockHelper } from './BlockHelper.js' +import { BlockSizePlus } from './BlockSizePlus.js' +import { resetPlaceBoard } from './PlaceBase.js' +import { NcCustomized } from '@/imes/biz/NcCustomized.js' +import { FaceType, HoleType, HoleArrange, PlaceBlock, PlaceMaterial, PlaceBoard } from '../../confClass.js' + +/** 计算大板中所有的小板的正反面孔造型数量,让正面加工时间接近 比例 30% */ +export async function TurnOverWithDoneRate(pm: PlaceMaterial, cncData: boolean, doneRate: number, sp_h: number, sp_m: number) { + let i = 0 + console.time(`${pm.fullName} full use time`) + for (let pb of pm.boardList) { + // if (pb.IsOddmengt) continue; + i++ + // console.log(`${pm.FullName} bid=${i} 开始计算`); + await TurnOverWithDoneRate_pb(pm, pb, cncData, doneRate, sp_h, sp_m, i) + } + console.timeEnd(`${pm.fullName} full use time`) +} + +/** 计算大板中所有的小板的正反面孔造型数量,让正面加工时间接近 比例 30% */ +export async function TurnOverWithDoneRate_pb(pm: PlaceMaterial, pb: PlaceBoard, useCncData: boolean, doneRate: number, sp_h: number, sp2: number, i) { + // 孔 0.5秒/个 + // 造型, 16秒/米 , 0.016秒/mm + + let sp_m = sp2 * 0.001 + let rate = doneRate * 0.01 + + // 一. 先求出大板所有小板,当前正反面加工时间,是否可翻转 + let tm: any = [] // + + let t1_z = 0 // 不可翻 正面加工时间 合计 + let t1_f = 0 // 不可翻 反面加工时间 合计 + let t2_z = 0 // 可翻 正面加工时间 合计 + let t2_f = 0 // 可翻 反面加工时间 合计 + + let t_full = 0 // 所有加工时间 合计 + let t_throngh = 0// 挖穿的造型的时间, + + for (let block of pb.blockList) { + let tc = 0 // 挖穿; + let tz = 0 // 当前加工面 时间 + let tf = 0 // 反面 时间 + + let holes = block.holeListFaceA + let models = block.modelListFaceA + if (useCncData) // 使用cnc 数据 + { + holes = block.isTurnOver ? block.holeListOrgFaceB.filter(t => t.isCutting == false) : block.holeListOrgFaceA.filter(t => t.isCutting == false) + models = block.isTurnOver ? block.modelListOrgFaceB.filter(t => t.isCutting == false) : block.modelListOrgFaceA.filter(t => t.isCutting == false) + } + tz += holes.length * sp_h // 正面孔时间 + for (let model of models) { + let len = 0 + for (let i = 0; i < model.pointList.length - 1; i++) { + let p0 = model.pointList[i] + let p1 = model.pointList[i + 1] + if (p0 == null || p1 == null) + continue + len += Math.sqrt((p0.pointX - p1.pointX) ** 2 + (p0.pointY - p1.pointY) ** 2) + } + if (model.depth >= block.thickness - 0.001) { + tc += len * sp_m + } + else { + tz += len * sp_m + } + } + + let holes2 = block.holeListFaceB + let models2 = block.modelListFaceB + if (useCncData) // 使用cnc 数据 + { + holes2 = block.isTurnOver ? block.holeListOrgFaceA.filter(t => t.isCutting == false) : block.holeListOrgFaceB.filter(t => t.isCutting == false) + models2 = block.isTurnOver ? block.modelListOrgFaceB.filter(t => t.isCutting == false) : block.modelListOrgFaceB.filter(t => t.isCutting == false) + } + tf += holes2.length * sp_h + for (let model of models2) { + let len = 0 + for (let i = 0; i < model.pointList.length - 1; i++) { + let p0 = model.pointList[i] + let p1 = model.pointList[i + 1] + if (p0 == null || p1 == null) + continue + len += Math.sqrt((p0.pointX - p1.pointX) ** 2 + (p0.pointY - p1.pointY) ** 2) + } + tf += len * sp_m + } + + if (tz + tc == 0 && tf == 0) + continue // 没有加工 + + let canTurn = true + if (block.isUnRegular) + canTurn = false // 异形不能翻 + if (canTurn && block.placeHole != 2) + canTurn = false // 非可翻转的 + if (canTurn && hasChildBlock(block, pb.blockList)) + canTurn = false// 里面有小板,不能翻 + + t_throngh += tc + t_full += (tc + tz + tf) + + if (canTurn == false) // 不能翻 + { + t1_z += tz + t1_f += tf + } + else // 可以翻 , + { + if (Math.abs(tz - tf) > sp_h) // 翻面效果很差, 时间小于一个孔 ,翻不翻无所谓, + { + tm.push({ bno: block.blockNo, z: tz, f: tf, dis: tz - tf, child: [] }) + } + t2_z += tz + t2_f += tf + } + } + + // 二. 计算 不可翻的小板 正面加工 与理论值 相差多少 + let t2_L = t_full * rate - t1_z - t_throngh // 剩下加工正面的时间 理论值 ( 理论值- 当前正面加工时间,就是剩下可翻的板 正面加工时间 ) + let tz: any = t1_z + t_throngh + t2_z // 当前正面时间 + let t2_dis = t2_z - t2_L // 理论值与 当前剩下正面加工时间的差值 ,应该要节省的时间总 + + // 正面加工时间 比例 接近预定于的比例 。 则不再计算 + if (Math.abs(100 * t2_dis / t_full) < 3) + return { tz, fz: t_full - tz, full: t_full } + + // 三.接下来的问题就是在 tm 中 找出 翻面加工省时 合计 最接近 t2_dis 的集合出来. + let best_list: any = [] // 最优解 需要翻面的 + let best_v = t2_dis // 差值最大 默认当前 + + // 四.将tm 转换成 最多16个可翻面 的数组 + tm.sort((a, b) => a.dis - b.dis) // 按小到大排序 + + // 精简 tm //将tm中 翻面效果 相反的 且大的 剔除掉 + if (tm.length > 16) { + if (t2_dis > 0) { + for (let i = tm.length - 1; i >= 0; i--) { + if (tm[i].dis < -t2_dis) { + tm.splice(i, 1) + } + } + } + else { + for (let i = tm.length - 1; i >= 0; i--) { + if (tm[i].dis > -t2_dis) { + tm.splice(i, 1) + } + } + } + } + + if (tm.length == 0) + return { tz, fz: t_full - tz, full: t_full } + + let blocks: any = [] + for (let b of pb.blockList) { + blocks[b.blockNo] = b + } + + // 可翻转小板集合,精简 + tm = smallerBlocks(tm) + + let rt = await getBestTurnBlocks(pm, i, tm, best_v, t_full) + best_list = rt.result + best_v = rt.v + + // 四. 将最优解中的小板 翻面 + + for (let m of best_list) { + tz -= m.dis + let bs: any = [] + + let block: any = blocks[m.bno] + bs.push(block) + for (let no of m.child) { + bs.push(blocks[no]) + } + for (let block of bs) { + let orgStyle = block.placeStyle + let newStyle = BlockHelper.getTurnedPlaceStyle(orgStyle, 0) + + let orgPlaceX = block.placeX - block.placeOffX + let orgPlaceY = block.placeY - block.placeOffY + + let offset = BlockSizePlus.getOffDis(block, newStyle) + let newPlaceX = orgPlaceX + offset.x + let newPlaceY = orgPlaceY + offset.y + + block.placeX = newPlaceX + block.placeY = newPlaceY + block.placeOffX = offset.x + block.placeOffY = offset.y + // 修改小板的放置方式 + BlockHelper.resetPlaceStyle(block, newStyle) + } + } + + // 重设pb + if (best_list.length > 0) + resetPlaceBoard(pm, pb) + + return { tz, fz: t_full - tz, full: t_full } +} + + +/** 获取加工面:processMode: 0开料机加工 1开料机CNC组合加工 2定制加工 */ +export function 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 getHoleFaceMore(block) + } + + // 造型单面 作为开料面 + if (block.modelCountFront() > 0 && block.modelCountBack() == 0) + return true + if (block.modelCountFront() == 0 && block.modelCountBack() > 0) + return false + return true +} + +/** 大孔/孔多作为正面 */ +function getHoleFaceMore(block: PlaceBlock): boolean { + if (!block.blockDetail) { + console.log('getHoleFaceMore: blockDetail is undefind', block) + return true + } + // 优先考虑 大孔面 + let bigHole = block.blockDetail.holes.find(t => t.holeType == HoleType.BIG_HOLE) + if (bigHole) + return bigHole.face == FaceType.FRONT + // 多孔面 + if (block.blockDetail.holeListFaceA.length > block.blockDetail.holeListFaceB.length) + return true + if (block.blockDetail.holeListFaceA.length < block.blockDetail.holeListFaceB.length) + return false + return true +} + +// 大孔/孔多 作为正面 +function getHoleFace_more(block: PlaceBlock): boolean { + // 优先考虑 大孔面 + let bigHole = block.holes().find(t => t.holeType == HoleType.BIG_HOLE) + if (bigHole) + return bigHole.face == FaceType.FRONT + // 多孔面 + if (block.holeCountFront > block.holeCountBack) + return true + if (block.holeCountFront < block.holeCountBack) + return false + return true +} +// 非大孔/孔少 作为正面 +function getHoleFace_less(block: PlaceBlock): boolean { + // 优先考虑 大孔面 + let bigHole = block.holes().find(t => t.holeType == HoleType.BIG_HOLE) + if (bigHole) + return bigHole.face != FaceType.FRONT + // 少孔面 + if (block.holeCountFront > block.holeCountBack) + return false + if (block.holeCountFront < block.holeCountBack) + return true + return true +} + +// 可翻转小板集合,精简 +function smallerBlocks(tm: any[]): any[] { + if (tm.length <= 30) + return tm + let time = 1 + + if (tm.length > 40) + time = 2 + if (tm.length > 60) + time = 3 + if (tm.length > 80) + time = 4 + if (tm.length > 100) + time = 5 + if (tm.length > 120) + time = 10 + + let newtm: any = [] + let newLength = Math.ceil(tm.length / time) + for (let t = 0; t < newLength; t++) { + let block0 = tm[t * time] + block0.child = [] // bno: block.BlockNo, z: t1, f: t2, dis: t1 - t2 + for (let i = 1; i < time; i++) { + let j = t * time + i + if (j >= tm.length) + break + let blockNext = tm[j] + block0.child.push(blockNext.bno) + block0.z += blockNext.z + block0.f += blockNext.f + block0.dis += blockNext.dis + } + newtm.push(block0) + } + return newtm +} + +async function getBestTurnBlocks(pm, pid, tm: any[], best_v, t_full) { + let result = [] + let result_v = best_v + let disRate = 111111 + + return new Promise((resolve, reject) => { + let num1 = 1 + let num2 = 2 ** tm.length - 1 + + let isOK = false + + // const worker = new Worker(new URL('./FaceDoTimeWorker', import.meta.url)) + // worker.onmessage = (res) => + // { + // let tmp = res.data.tmp + // let v = res.data.v + // let rate = res.data.rate + // result = tmp + // result_v = v + // disRate = rate + // if (res.data.ok == 1) + // { + // worker.terminate() // 关闭 + // resolve({ result, v: result_v }) + // isOK = true + // } + // } + // worker.postMessage({ num1, num2, blocks: tm, disTime: best_v, fullTime: t_full }) + // Sleep(3000).then(() => + // { + // if (isOK) + // return + // resolve({ result, v: result_v }) + // // console.log(`${pm.FullName} bid=${pid} ${tm.length} 超时3秒`); + // }) + }) +} + +/** 判断block 里头有没有小板 */ +function hasChildBlock(block: PlaceBlock, blocks: PlaceBlock[]): boolean { + for (let b of blocks) { + if (b.blockNo == block.blockNo) + continue + + if (b.placeX > block.placeX && b.placeX + b.placeWidth < block.placeX + block.placeWidth && b.placeY > block.placeY && b.placeY + b.placeLength < block.placeY + block.placeLength) + return true + } + return false +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts new file mode 100644 index 0000000..54119b4 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts @@ -0,0 +1,1300 @@ +import { ArrayExt } from '../../common/base/ArrayExt.js' +import type { Arc2d, Curve2d } from '../../common/base/CAD.js' +import { CADExt, Line2d } from '../../common/base/CAD.js' +import { + BlockRegion, + FaceType, HoleType, HoleArrange, PlaceStyle, TextureType, BlockBorderPoint, BlockHole, + BlockModel, BlockModelPoint, PlacePositionClass, + PlaceBlock, + PlaceMaterial, + PlaceBoard +} from '../../confClass.js' + +import { equal } from '../../common/base/MathComm.js' +import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' +import { Point } from '../../common/Vector2.js' +import { BlockPlus } from './BlockPlus.js' +import { BlockSizePlus } from './BlockSizePlus.js' +import { NcAdvanceHelper } from '@/imes/biz/NcAdvance.js' // './NcAdvance.js' +import { getPlaceBoard, resetPlaceBoard, resetPlaceMaterial } from './PlaceBase' +import { resetModelKnife } from './BlockDataPlus.js' + + +/** 小板业务类 */ +export class BlockHelper { + // static testBlockNo = ''; //测试板号 + + + + /** 初始化小板明细 */ + static setBlockDetail(block: PlaceBlock) { + if (block.blockDetail) + return // 已存在 + } + + // 初始化block ,边框,内部边框,偏移后等---------------------------------------- + /** 初始化 block (初始化明细,重设排版) */ + + private static equal(a, b, off = 0.001): boolean { + return Math.abs(a - b) < off + } + + // 排版相关:小板翻转,移动--------------------------------------------------- + + /** 翻转小板,翻转动作opType: 0翻面 1右转 2后转 3左转 */ + static turnBlock(block: PlaceBlock, placeMaterial: PlaceMaterial, opType: number) { + let orgStyle = block.placeStyle + let newStyle = BlockHelper.getTurnedPlaceStyle(orgStyle, opType) + + let orgPlaceX = block.placeX - block.placeOffX + let orgPlaceY = block.placeY - block.placeOffY + + let offset = BlockSizePlus.getOffDis(block, newStyle) + let newPlaceX = orgPlaceX + offset.x + let newPlaceY = orgPlaceY + offset.y + block.placeOffX = offset.x + block.placeOffY = offset.y + // 修改小板的放置方式 + console.log('修改小板的放置方式', block.blockNo, block, newStyle) + // BlockHelper.resetPlaceStyle(block, newStyle) + PlacePositionClass.resetPlaceStyle(block, newStyle); + this.resetDoFace_HoleModel(block, false) + this.restOther(block) + // 重置放置,检查冲突 + BlockHelper.replaceBlock(placeMaterial, block, block.boardId, new Point(newPlaceX, newPlaceY)) + } + + /** 获得翻转后的新放置方式, 翻转动作opType: 0翻面 1右转 2后转 3左转 */ + static getTurnedPlaceStyle(orgStyle: PlaceStyle, opType: number): PlaceStyle { + + let newStyle: number = orgStyle + if (opType == 0) // 翻面 + { + switch (orgStyle) { + case PlaceStyle.FRONT: // 正面 + newStyle = PlaceStyle.BACK // 反面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + newStyle = PlaceStyle.BACK_TURN_LEFT // 反面左转 + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + newStyle = PlaceStyle.BACK_TURN_BACK // 反面后转 + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + newStyle = PlaceStyle.BACK_TURN_RIGHT // 反面右转 + break + case PlaceStyle.BACK: // 反面 + newStyle = PlaceStyle.FRONT // 正面 + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + newStyle = PlaceStyle.FRONT_TURN_LEFT // 正面左转 + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + newStyle = PlaceStyle.FRONT_TURN_BACK // 正面后转 + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + newStyle = PlaceStyle.FRONT_TURN_RIGHT // 正面右转 + break + default: + break + } + } + else if (opType == 1) // 右转 + { + newStyle = orgStyle + 1 + if (newStyle == 4) + newStyle = 0 + if (newStyle == 8) + newStyle = 4 + } + else if (opType == 2) // 后转 + { + newStyle = orgStyle + 2 + if (orgStyle < 4 && newStyle >= 4) + newStyle = newStyle - 4 + if (orgStyle >= 4 && newStyle >= 8) + newStyle = newStyle - 4 + } + else if (opType == 3) // 左转 + { + newStyle = orgStyle - 1 + if (newStyle == 3) + newStyle = 7 + if (newStyle == -1) + newStyle = 3 + } + return newStyle + } + + /** 板放置后,重置placeWidth, placeLength, 封边, 正反面, 面孔, 造型等 */ + static resetPlaceStyle(block: PlaceBlock, newStyle: PlaceStyle) { + block.placeStyle = newStyle + + // tryFix + let _width = block.cutWidth + let _lenth = block.cutLength + if (block.width > block.length) { + block.cutWidth = Math.max(_width, _lenth) + block.cutLength = Math.min(_width, _lenth) + } else { + block.cutWidth = Math.min(_width, _lenth) + block.cutLength = Math.max(_width, _lenth) + + } + + switch (newStyle) { + case PlaceStyle.FRONT: // 正面 + block.placeWidth = block.cutWidth + block.placeLength = block.cutLength + block.placeSealLeft = block.sealLeft + block.placeSealRight = block.sealRight + block.placeSealTop = block.sealTop + block.placeSealBottom = block.sealBottom + block.holeCountSideLeft = block.holeCountLeft() + block.holeCountSideRight = block.holeCountRight() + block.holeCountSideTop = block.holeCountTop() + block.holeCountSideBottom = block.holeCountBottom() + block.placeDirection = '→' + block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↓' + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + block.placeWidth = block.cutLength + block.placeLength = block.cutWidth + block.placeSealLeft = block.sealBottom + block.placeSealRight = block.sealTop + block.placeSealTop = block.sealLeft + block.placeSealBottom = block.sealRight + block.holeCountSideLeft = block.holeCountBottom() + block.holeCountSideRight = block.holeCountTop() + block.holeCountSideTop = block.holeCountLeft() + block.holeCountSideBottom = block.holeCountRight() + block.placeDirection = '↓' + block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '←' + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + block.placeWidth = block.cutWidth + block.placeLength = block.cutLength + block.placeSealLeft = block.sealRight + block.placeSealRight = block.sealLeft + block.placeSealTop = block.sealBottom + block.placeSealBottom = block.sealTop + block.holeCountSideLeft = block.holeCountRight() + block.holeCountSideRight = block.holeCountLeft() + block.holeCountSideTop = block.holeCountBottom() + block.holeCountSideBottom = block.holeCountTop() + block.placeDirection = '←' + block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↑' + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + block.placeWidth = block.cutLength + block.placeLength = block.cutWidth + block.placeSealLeft = block.sealTop + block.placeSealRight = block.sealBottom + block.placeSealTop = block.sealRight + block.placeSealBottom = block.sealLeft + block.holeCountSideLeft = block.holeCountTop() + block.holeCountSideRight = block.holeCountBottom() + block.holeCountSideTop = block.holeCountRight() + block.holeCountSideBottom = block.holeCountLeft() + block.placeDirection = '↑' + block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '→' + break + case PlaceStyle.BACK: // 反面 + block.placeWidth = block.cutWidth + block.placeLength = block.cutLength + block.placeSealLeft = block.sealRight + block.placeSealRight = block.sealLeft + block.placeSealTop = block.sealTop + block.placeSealBottom = block.sealBottom + block.holeCountSideLeft = block.holeCountRight() + block.holeCountSideRight = block.holeCountLeft() + block.holeCountSideTop = block.holeCountTop() + block.holeCountSideBottom = block.holeCountBottom() + block.placeDirection = '→' + block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↑' + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + block.placeWidth = block.cutLength + block.placeLength = block.cutWidth + block.placeSealLeft = block.sealBottom + block.placeSealRight = block.sealTop + block.placeSealTop = block.sealRight + block.placeSealBottom = block.sealLeft + block.holeCountSideLeft = block.holeCountBottom() + block.holeCountSideRight = block.holeCountTop() + block.holeCountSideTop = block.holeCountRight() + block.holeCountSideBottom = block.holeCountLeft() + block.placeDirection = '↓' + block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '→' + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + block.placeWidth = block.cutWidth + block.placeLength = block.cutLength + block.placeSealLeft = block.sealLeft + block.placeSealRight = block.sealRight + block.placeSealTop = block.sealTop + block.placeSealBottom = block.sealBottom + block.holeCountSideLeft = block.holeCountLeft() + block.holeCountSideRight = block.holeCountRight() + block.holeCountSideTop = block.holeCountTop() + block.holeCountSideBottom = block.holeCountBottom() + block.placeDirection = '←' + block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↓' + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + block.placeWidth = block.cutLength + block.placeLength = block.cutWidth + block.placeSealLeft = block.sealTop + block.placeSealRight = block.sealBottom + block.placeSealTop = block.sealLeft + block.placeSealBottom = block.sealRight + block.holeCountSideLeft = block.holeCountTop() + block.holeCountSideRight = block.holeCountBottom() + block.holeCountSideTop = block.holeCountLeft() + block.holeCountSideBottom = block.holeCountRight() + block.placeDirection = '↑' + block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '←' + break + default: + break + } + this.resetDoFace_HoleModel(block) + + this.restOther(block) + } + + /** 获得小板放置方向标识 */ + + static getPlaceDirection(block: PlaceBlock): string { + return block.placeDirection + } + + /** 选择最近的板,弹出定位方向盘,选择定位位置 */ + + static replaceBlockWidthDirect(pm: PlaceMaterial, block: PlaceBlock, newBoardId: number, dragingBlockPoint: BlockBorderPoint, referBlockPoint: BlockBorderPoint, direction: number) { + let gap = pm.diameter + pm.cutKnifeGap + // '↖', '↑', '↗', '←', 'X', '→', '↙', '↓', '↘' + // 参照点 现在位置 + let rPointX = referBlockPoint.placeX + let rPointY = referBlockPoint.placeY + if (direction == 0) // '↖' + { + rPointX -= gap + rPointY -= gap + } + if (direction == 1) // '↑' + { + rPointX -= gap + } + if (direction == 2) // '↗' + { + rPointX -= gap + rPointY += gap + } + if (direction == 3) // '←' + { + rPointY -= gap + } + if (direction == 4) // '←' + { + + } + if (direction == 5) // '→' + { + rPointY += gap + } + if (direction == 6) // '↙' + { + rPointX += gap + rPointY -= gap + } + if (direction == 7) // '↓' + { + rPointX += gap + } + if (direction == 8) // '↘' + { + rPointX += gap + rPointY += gap + } + // 减去拖拉点自身与基点的距离 + rPointX = rPointX - dragingBlockPoint.x + rPointY = rPointY - dragingBlockPoint.y + + BlockHelper.replaceBlock(pm, block, newBoardId, new Point(rPointX, rPointY)) + } + + /** 移动小板 自由移动 */ + static replaceBlock(pm: PlaceMaterial, block: PlaceBlock, newBoardId: number, p: Point) { + let oldBoardId = block.boardId + let isChangeBoard = block.boardId != newBoardId + if (oldBoardId > pm.boardList.length) { + oldBoardId = 1 + } + if (newBoardId > pm.boardList.length) { + newBoardId = 1 + } + let pb_org = getPlaceBoard(pm, oldBoardId) + let pb_new = getPlaceBoard(pm, newBoardId) + + pb_org.isCreateRemainSpace = true + pb_new.isCreateRemainSpace = true + + let orgPlaceX = block.placeX + let orgPlaceY = block.placeY + + let list = pb_new.blockList + let maxPlaceId = ArrayExt.max(list, t => t.placeId, t => true, 0) + 1 + let newPlaceId = maxPlaceId++ + + + // 设置block + block.boardId = newBoardId + block.placeId = newPlaceId + block.placeX = p.x + block.placeY = p.y + block.isAutoPlaced = false + if (isChangeBoard) { + ArrayExt.remove(pb_org.blockList, block) + + pb_new.blockList.push(block) + } + + if (isChangeBoard) { + pb_org.blockList = pb_org.blockList.filter(b => b.blockId != block.blockId) + for (let board of pm.boardList) { + if (board.boardId == oldBoardId) { + board = pb_org + continue + } + } + // pm.boardList[oldBoardId] = pb_org + checkOverlapInBoard(pm, pb_org) + resetPlaceBoard(pm, pb_org) + + } + + checkOverlapInBoard(pm, pb_new) + resetPlaceBoard(pm, pb_new) + + resetPlaceMaterial(pm) + } + + /** 开始拖动时,获得板上的停靠点 */ + static getDragingPointInBlock(block: PlaceBlock, mousePos: Point): BlockBorderPoint { + let dis = Number.MAX_VALUE + let point: Point + let index = -1 + + // 基于小板排版基点的 鼠标坐标 + let rx = mousePos.x - block.placeX + let ry = mousePos.y - block.placeY + + // 算出 最近的拖拉点 + let curves = BlockPlus.getBorder_moving(block) + for (let i = 0; i < curves.length; i++) { + let line = curves[i] + let d = Math.pow(line.StartPoint.m_X + block.placeX - mousePos.x, 2) + Math.pow(line.StartPoint.m_Y + block.placeY - mousePos.y, 2); + if (d < dis) { + point = new Point(line.StartPoint.m_X, line.StartPoint.m_Y) + dis = d + index = i + } + } + + // 存在板外 的造型或2v刀路 + if (BlockPlus.hasModelOutBlock(block)) { + let pl = BlockPlus.borderToPolyline(curves, true) + let x1 = pl.BoundingBox.min.x + let y1 = pl.BoundingBox.min.y + let x2 = pl.BoundingBox.max.x + let y2 = pl.BoundingBox.max.y + + if (point.x < 0) + point.x = x1 + if (point.x > block.placeWidth) + point.x = x2 + if (point.y < 0) + point.y = y1 + if (point.y > block.placeLength) + point.y = y2 + // //拖拉死角,用偏移后的 + // if(point.X < 0 && point.Y < 0) //左下角 + // { + // if(Math.abs(point.X -x1 ) < 40 ) point.X = x1; + // if(Math.abs(point.Y -y1 ) < 40 ) point.Y = y1; + // } + // else if(point.X>block.PlaceWidth && point.Y < 0) //右下角 + // { + // if(Math.abs(point.X -x2 ) < 40 ) point.X = x2; + // if(Math.abs(point.Y -y1 ) < 40 ) point.Y = y1; + // } + // else if(point.X>block.PlaceWidth && point.Y > block.PlaceLength ) + // { + // if(Math.abs(point.X -x2 ) < 40 ) point.X = x2; + // if(Math.abs(point.Y -y2 ) < 40 ) point.Y = y2; + // } + // else if(point.X<0 && point.Y > block.PlaceLength) + // { + // if(Math.abs(point.X -x1 ) < 40 ) point.X = x1; + // if(Math.abs(point.Y -y2 ) < 40 ) point.Y = y2; + // } + } + + // 找出 距 小板 + dis = Number.MAX_VALUE + let point_base: Point + let curves_cut = BlockPlus.getBorder_sameKnife(block) + for (let i = 0; i < curves_cut.length; i++) { + let line = curves_cut[i] + let d = Math.pow(line.StartPoint.m_X + block.placeX - mousePos.x, 2) + Math.pow(line.StartPoint.m_Y + block.placeY - mousePos.y, 2); + if (d < dis) { + point_base = new Point(line.StartPoint.m_X, line.StartPoint.m_Y) + dis = d + index = i + } + } + + let apexId = BlockHelper.getApexAngleNumFromBlock(block, point) + + return new BlockBorderPoint(block, point.x, point.y, index, apexId, point_base.x, point_base.y) + } + + /** 拖动时,寻找停靠点最近的板和参照点 */ + static getClosestBlock(pm: PlaceMaterial, pb: PlaceBoard, block: PlaceBlock, p: Point): BlockBorderPoint { + let bestDis = Number.MAX_VALUE + let closestBlock: PlaceBlock // 最接近的小板 + let curveIndex = 0 + let x = 0; let y = 0 // 最接近小板的接触点 坐标(现对于小板的位置) + + // 1先判断是否把停靠点拉到大板的四个点 + let border = pm.cutBorder - pm.diameter / 2 - pm.cutKnifeGap / 2 // 因为移动小板的轮廓 是包括缝隙的一半 ,所以这边要抵消掉。 + if (p.x < border + 50 && p.y < border + 50) return new BlockBorderPoint(null, border, border, -1, -1); + if (p.x > pb.width - 50 && p.y < border + 50) return new BlockBorderPoint(null, pb.width - border, border, -1, -1); + if (p.x > pb.width - 50 && p.y > pb.length - 50) return new BlockBorderPoint(null, pb.width - border, pb.length - border, -1, -1); + if (p.x < border + 50 && p.y > pb.length - 50) return new BlockBorderPoint(null, border, pb.length - border, -1, -1); + + let innerBlocks = BlockHelper.getInnerBlock(block, pb.blockList) // 内部的小板 + // 2找最靠近的板顶点(包括内部被造型挖掉的顶点) + for (let b of pb.blockList) { + if (b == block) + continue + // 这个板在block 内部,不参与对比 + if (innerBlocks.includes(b)) + continue + let border = BlockPlus.getBorder_moving(b) + let borders_inner = BlockPlus.getInnerPlaceBorders(b) + // 在外框+内部内框内找到点 + let borders = [] + borders.push(border) + for (const b of borders_inner) { borders.push(b) } + for (const curves of borders) { + for (let i = 0; i < curves.length; i++) { + let line = curves[i] + let dis = Math.pow(b.placeX + line.StartPoint.m_X - p.x, 2) + Math.pow(b.placeY + line.StartPoint.m_Y - p.y, 2); + if (dis < 5000 && dis < bestDis) { + bestDis = dis + x = line.StartPoint.m_X + y = line.StartPoint.m_Y + curveIndex = i + closestBlock = b + } + } + } + } + if (closestBlock != null) { + const point = new Point(x, y) + let apexId = BlockHelper.getApexAngleNumFromBlock(closestBlock, point) + // 考虑 closestBlock 有微小突出造型,加以偏移 + const curves = BlockPlus.getBorder_moving(closestBlock) + // 存在板外 的造型或2v刀路 + if (BlockPlus.hasModelOutBlock(closestBlock)) { + let pl = BlockPlus.borderToPolyline(curves, true) + let x1 = pl.BoundingBox.min.x + let y1 = pl.BoundingBox.min.y + let x2 = pl.BoundingBox.max.x + let y2 = pl.BoundingBox.max.y + + if (point.x < 0) + point.x = x1 + if (point.x > closestBlock.placeWidth) + point.x = x2 + if (point.y < 0) + point.y = y1 + if (point.y > closestBlock.placeLength) + point.y = y2 + } + return new BlockBorderPoint(closestBlock, point.x, point.y, curveIndex, apexId) + } + + // 3找不到最靠近的板 看看是否靠近大板四边 + if (p.x < border + 50) + return new BlockBorderPoint(null, border, p.y, -1, -1) + if (p.x > pb.width - 50) + return new BlockBorderPoint(null, pb.width - border, p.y, -1, -1) + if (p.y < border + 50) + return new BlockBorderPoint(null, p.x, border, -1, -1) + if (p.y > pb.length - 50) + return new BlockBorderPoint(null, p.x, pb.length - border, -1, -1) + + // 没找到附近可参考的板 + return null + } + + /** 选择最近的板,来定位被移动小板的位置 */ + static replaceBlockRefClosest(pm: PlaceMaterial, block: PlaceBlock, newBoardId: number, dragingBlockPoint: BlockBorderPoint, referBlockPoint: BlockBorderPoint, dragedDistance: Point) { + let gap = pm.diameter + pm.cutKnifeGap + + // 拖拉点现在位置 + let dragedPointX = dragingBlockPoint.placeX + dragedDistance.x + let dragedPointY = dragingBlockPoint.placeY + dragedDistance.y + + // 板的当前外扩 + let blockoff = BlockSizePlus.getOffDis(block) + + let newP: Point + let nx = 0 + let ny = 0 + // 参照点 为大板的四个顶点 + if (referBlockPoint.block == null) { + nx = referBlockPoint.x + ny = referBlockPoint.y + // block 定位坐标 < 拖拉点 偏移> + nx = nx - dragingBlockPoint.x + ny = ny - dragingBlockPoint.y + } + else { + // 实际停靠点 坐标 + nx = referBlockPoint.block.placeX + referBlockPoint.x + ny = referBlockPoint.block.placeY + referBlockPoint.y + // block 定位坐标 < 拖拉点 偏移> + nx = nx - dragingBlockPoint.x + ny = ny - dragingBlockPoint.y + } + + // 默认按P定位 + BlockHelper.replaceBlock(pm, block, newBoardId, new Point(nx, ny)) + } + + /** 获得放置在block内部的板列表 */ + static getInnerBlock(block: PlaceBlock, list: PlaceBlock[]): PlaceBlock[] { + let innerList: PlaceBlock[] = [] + // 如果没有内部空间直接范围 + let borders = BlockPlus.getBorders_inner(block) + if (!(borders && borders.length > 0)) + return innerList + + for (let b of list) { + if (b == block) + continue + let p1 = new Point(b.placeX, b.placeY) + let p2 = new Point(b.placeX + b.placeWidth, b.placeY + b.placeLength) + + if (BlockHelper.isPointInInnerSpace(p1, block) && BlockHelper.isPointInInnerSpace(p2, block)) { + innerList.push(b) + continue + } + } + return innerList + } + + + static findIdx(arr, target) { + return arr.findIndex(c => c.blockId == target.blockId) + } + /** 删除小板 */ + static removeBlock(order: PlaceOrder, pm: PlaceMaterial, pb: PlaceBoard, block: PlaceBlock) { + console.log('移除前', order.blockList, pm.blockList, pb.blockList, 'block', block); + if (block.isAdditionalBlock == false && block.isRemainBlock == false && [0, 1, 2].includes(block.type)) + throw new Error('该板不能删除') + console.log('移除前', order.blockList, pm.blockList, pb.blockList, 'block', block); + // ArrayExt.remove(order.blockList, block); + // ArrayExt.remove(pm.blockList, block); + // ArrayExt.remove(pb.blockList, block); + + order.blockList.splice(this.findIdx(order.blockList, block), 1); + pm.blockList.splice(this.findIdx(pm.blockList, block), 1); + pb.blockList.splice(this.findIdx(pb.blockList, block), 1); + console.log('移除后', order.blockList, pm.blockList, pb.blockList, 'block', block); + + //检查干涉 + checkOverlapInBoard(pm, pb); + //重设汇总 + resetPlaceBoard(pm, pb); + resetPlaceMaterial(pm); + } + + /** 订单中添加余料板 */ + static createRemainBlock(order: PlaceOrder, pm: PlaceMaterial, pb: PlaceBoard, binfo: BlockPlaceInfo) { + let block = new PlaceBlock() + let [info] = binfo.abInfo + block.isRemainBlock = true + block.blockName = '余料板' + block.width = info.width + block.length = info.length + block.area = info.width * info.length * 0.000001 + block.sealLeft = 0 + block.sealRight = 0 + block.sealTop = 0 + block.sealBottom = 0 + block.cutWidth = info.width + block.cutLength = info.length + block.cutArea = block.area + block.texture = info.texture + block.placeHole = 2 + block.thickness = pm.thickness + + block.goodsId = pm.goodsId + block.blockNo = binfo.bo + block.blockId = Number.parseInt(binfo.bo) + block.orderId = 0 + + + if (binfo.points && binfo.points.length > 0) { + let bdetail = new PlaceBlockDetail() + bdetail.points = binfo.points + bdetail.orgPoints = JSON.parse(JSON.stringify(binfo.points)) + bdetail.isUnRegular = true + bdetail.cutWidth = block.width + bdetail.cutLength = block.length + order.blockDetailList.push(bdetail) + block.blockDetail = bdetail + } + let saleorder = order.orderList.find(t => t.orderId == 0) + + if (saleorder == null) { + saleorder = new Order() + order.orderList.push(saleorder) + } + block.placeOrder = order + block.order = saleorder + block.placeMaterial = pm + + order.blockList.push(block) + pm.blockList.push(block) + if (pb) + pb.blockList.push(block) + return block + } + + /** 获得排序ID的位置 */ + + static getCutOrderPoint(block: PlaceBlock): Point { + // return this.GetPlaceXYInBoard(block, block.SaleBlockDetail.notePosX, block.SaleBlockDetail.notePosY); + return new Point(block.placeX + block.labelPosX, block.placeY + block.labelPosY) + } + + + static getBackPlaceStyle(placeStype: PlaceStyle): PlaceStyle { + console.log('getBackPlaceStyle') + // 正面=0,正面右转=1,正面后转=2,正面左转=3,反面=4,反面右转=5,反面后转=6,反面左转=7 + if (placeStype == PlaceStyle.FRONT) + return PlaceStyle.BACK + if (placeStype == PlaceStyle.FRONT_TURN_RIGHT) + return PlaceStyle.BACK_TURN_LEFT + if (placeStype == PlaceStyle.FRONT_TURN_BACK) + return PlaceStyle.BACK_TURN_BACK + if (placeStype == PlaceStyle.FRONT_TURN_LEFT) + return PlaceStyle.BACK_TURN_RIGHT + if (placeStype == PlaceStyle.BACK) + return PlaceStyle.FRONT + if (placeStype == PlaceStyle.BACK_TURN_LEFT) + return PlaceStyle.FRONT_TURN_RIGHT + if (placeStype == PlaceStyle.BACK_TURN_BACK) + return PlaceStyle.FRONT_TURN_BACK + if (placeStype == PlaceStyle.BACK_TURN_RIGHT) + return PlaceStyle.FRONT_TURN_LEFT + return placeStype + } + + // 开料面排钻, 造型, 是否双面开料---------------------------------------------- + + /** 重设开料正方面孔造型列表 */ + /** + * + * @param block + * @param config + * config.processMode 加工模式(0开料机(雕刻机)加工 1开料机CNC组合 2定制加工) + * config.isCutProcess 开料机(雕刻机)加工 + */ + static resetDoFace_HoleModel(block: PlaceBlock, config) { + // //开料机加工模式: 0全加工 1不加工(特殊板除外) 2加工多的一面(特殊版都加工) 3加工少的一面(特殊版都加工) 4编程模式 5高级模式 + + // let processMode = PlaceStore.sysConfig.processMode; + + const { + processMode = 0, + isCutProcess = true, + isCutAndCNCProcess = false, + isCustomized = false + } = config + if (!block.blockDetail) { + console.log('resetDoFace_HoleModel:block.blockDetail is undefind, please chck') + return + } + let isTurnOver = block.isTurnOver() + + // 模式0: 开料机全部处理 + + if (isCutProcess) { + if (!block.blockDetail) { + console.error('') + } + // 正面 排钻 + let faceHoles = isTurnOver ? block.blockDetail.holeListFaceB : block.blockDetail.holeListFaceA + block.holeListFaceA = faceHoles.concat(block.blockDetail.holeListThrough) + // 反面 排钻 + block.holeListFaceB = isTurnOver ? block.blockDetail.holeListFaceA : block.blockDetail.holeListFaceB + + // 正面 造型 + let faceModels = isTurnOver ? block.blockDetail.modelListFaceB : block.blockDetail.modelListFaceA + block.modelListFaceA = faceModels.concat(block.blockDetail.modelListThrough) + // 反面造型 + block.modelListFaceB = isTurnOver ? block.blockDetail.modelListFaceA : block.blockDetail.modelListFaceB + } + else if (isCutAndCNCProcess) { + NcAdvanceHelper.changeDoFace(block); + + + let orgMA = block.blockDetail.modelListFaceA.filter(t => t.isCutting); + let orgMB = block.blockDetail.modelListFaceB.filter(t => t.isCutting); + let orgMT = block.blockDetail.modelListThrough.filter(t => t.isCutting); + let orgHA = block.blockDetail.holeListFaceA.filter(t => t.isCutting); + let orgHB = block.blockDetail.holeListFaceB.filter(t => t.isCutting); + let orgHT = block.blockDetail.holeListThrough.filter(t => t.isCutting); + + + if (isTurnOver) { + block.modelListFaceA = orgMB.concat(orgMT); + block.modelListFaceB = orgMA; + block.holeListFaceA = orgHB.concat(orgHT); + block.holeListFaceB = orgHA; + } + else { + block.modelListFaceA = orgMA.concat(orgMT); + block.modelListFaceB = orgMB; + block.holeListFaceA = orgHA.concat(orgHT);; + block.holeListFaceB = orgHB; + } + } + else if (isCustomized) { + //正面 排钻 + let faceHoles = isTurnOver ? block.blockDetail.holeListFaceB : block.blockDetail.holeListFaceA; + block.holeListFaceA = faceHoles.concat(block.blockDetail.holeListThrough); + block.holeListFaceA = block.holeListFaceA.filter(t => t.isCutting); + //反面 排钻 + block.holeListFaceB = isTurnOver ? block.blockDetail.holeListFaceA : block.blockDetail.holeListFaceB; + block.holeListFaceB = block.holeListFaceB.filter(t => t.isCutting); + //正面 造型 + let faceModels = isTurnOver ? block.blockDetail.modelListFaceB : block.blockDetail.modelListFaceA; + block.modelListFaceA = faceModels.concat(block.blockDetail.modelListThrough); + block.modelListFaceA = block.modelListFaceA.filter(t => t.isCutting); + //反面造型 + block.modelListFaceB = isTurnOver ? block.blockDetail.modelListFaceA : block.blockDetail.modelListFaceB; + block.modelListFaceB = block.modelListFaceB.filter(t => t.isCutting); + } + + + //重设 isDO + block.blockDetail.holes.forEach(t => t.isCutting = false); + block.blockDetail.models.forEach(t => t.isCutting = false); + block.holeListFaceA.forEach(t => t.isCutting = true); + block.holeListFaceB.forEach(t => t.isCutting = true); + block.modelListFaceA.forEach(t => t.isCutting = true); + block.modelListFaceB.forEach(t => t.isCutting = true); + //修改了默认加工,需要再对造型换刀处理 + resetModelKnife(block, config); + + + //B面是否需要加工 + block.isCutOtherFace = block.blockDetail.holeListFaceB.length + block.blockDetail.modelListFaceB.length > 0; + //需翻面处理(包括开料机,cnc) + block.isCutTurnOver = block.isCutOtherFace; + + //反面需处理(包括开料机,cnc) by origin + // if (!block.IsTurnOver) + // { + // block.NeedTurnFaceToDo = block.BackHoleCount + block.BackModelCount + block.HoleCount_Side > 0; + // } + + //获取贴标位置 + let p = this.getPlaceXYInBlock(block, block.blockDetail.labelPosX, block.blockDetail.labelPosY, false, false); + block.labelPosX = p.x; + block.labelPosY = p.y; + //设置 cnc 处理面 + this.resetCncIsDoFace(block); + } + /** 重设其他 */ + static restOther(block) { + //B面是否需要加工 + block.isCutOtherFace = block.blockDetail.holeListFaceB.length + block.blockDetail.modelListFaceB.length > 0; + //需翻面处理(包括开料机,cnc) + block.isCutTurnOver = block.isCutOtherFace; + + //获取贴标位置 + let p = this.getPlaceXYInBlock(block, block.blockDetail.labelPosX, block.blockDetail.labelPosY, false, false); + block.labelPosX = p.x; + block.labelPosY = p.y; + //设置 cnc 处理面 + this.resetCncIsDoFace(block); + } + + /** 开料面排钻列表 */ + static getHoles_DoFace(block: PlaceBlock, isBackFace: boolean): BlockHole[] { + if (!block.blockDetail) { + console.log('getHoles_DoFace:blockDetail is undefind!') + return [] + } + let holesArr = isBackFace ? block.blockDetail?.holes.filter(e => e.face == 1) : block.blockDetail?.holes.filter(e => e.face == 0) + // 给孔 加上所属的板编号 + holesArr.map(e => e.blockNo = block.blockNo) + return holesArr + } + + /** 获取开料大板内所有的排钻 未排序*/ + static async GetHoles_BoardAllBlocksByDoFace(pb: PlaceBoard) { + let allHoles: any[] = [] + const backPlaceStyleArr = [PlaceStyle.BACK, PlaceStyle.BACK_TURN_BACK, PlaceStyle.BACK_TURN_LEFT, PlaceStyle.BACK_TURN_RIGHT] + for (const block of pb.blockList) { + + let holes = await this.getHoles_DoFace(block, backPlaceStyleArr.includes(block.placeStyle)) + allHoles = allHoles.concat(holes) + } + + return allHoles + } + + + /** 开料面造型列表 */ + static GetModels_DoFace(block: PlaceBlock, isBackFace: boolean): BlockModel[] { + if (!block.blockDetail) { + console.log('getHoles_DoFace:blockDetail is undefind!') + return [] + } + let modelsArr = isBackFace ? block.blockDetail?.models.filter(e => e.face == 1) : block.blockDetail?.models.filter(e => e.face == 0) + // 给造型 加上所属的板编号 + modelsArr.map(e => e.blockNo = block.blockNo) + return modelsArr + } + + static async GetModels_BoardAllBlocksByDoFace(pb: PlaceBoard) { + let allModels: any[] = [] + const backPlaceStyleArr = [PlaceStyle.BACK, PlaceStyle.BACK_TURN_BACK, PlaceStyle.BACK_TURN_LEFT, PlaceStyle.BACK_TURN_RIGHT] + for (const block of pb.blockList) { + + let models = await this.GetModels_DoFace(block, backPlaceStyleArr.includes(block.placeStyle)) + allModels = allModels.concat(models) + } + return allModels + } + + // 判断造型是否打穿且矩形 + static checkModelIsThroughRectange(block: PlaceBlock, m: BlockModel): boolean { + if (m.depth < block.thickness - 0.01) + return false + if (m.pointList.length != 5) + return false + let p0 = m.pointList[0] + let p1 = m.pointList[1] + let p2 = m.pointList[2] + let p3 = m.pointList[3] + let p4 = m.pointList[4] + + // 有曲线,不算 + if (p0.curve != 0 || p1.curve != 0 || p2.curve != 0 || p3.curve != 0 || p4.curve != 0) + return false + + // 不是闭合的,不算矩形 + if (!equal(p0.pointX, p4.pointX) || !equal(p0.pointY, p4.pointY)) + return false + + // 有边都斜线 ,就不算矩形 + if (!equal(p0.pointX, p1.pointX) && !equal(p0.pointY, p1.pointY)) + return false + if (!equal(p1.pointX, p2.pointX) && !equal(p1.pointY, p2.pointY)) + return false + if (!equal(p2.pointX, p3.pointX) && !equal(p2.pointY, p3.pointY)) + return false + if (!equal(p3.pointX, p4.pointX) && !equal(p3.pointY, p4.pointY)) + return false + + // 对角线 不想等,不算 + let d1 = (p0.pointX - p2.pointX) * (p0.pointX - p2.pointX) + (p0.pointY - p2.pointY) * (p0.pointY - p2.pointY) + let d2 = (p1.pointX - p3.pointX) * (p1.pointX - p3.pointX) + (p1.pointY - p3.pointY) * (p1.pointY - p3.pointY) + if (Math.abs(d1 - d2) > 0.5) + return false + + return true + } + + // 判断孔是打穿的面孔 + static isHoleThrough(block: PlaceBlock, hole: BlockHole): boolean { + if (hole.face == FaceType.SIDE) + return false + if (hole.holeType == HoleType.THROUGH_HOLE) + return true + if (hole.depth >= block.thickness) + return true + return false + } + + // 翻面标签开料面 是否需处理<标签显示>-------------------------------------------- + /** 计算 cnc 正面,翻面是否应该处理 */ + static resetCncIsDoFace(block: PlaceBlock) { + // remove by yonnie + // //开料机加工模式: 0全加工 1不加工(特殊板除外) 2加工多的一面(特殊版都加工) 3加工少的一面(特殊版都加工) 4编程模式 5高级模式 + // let roleNum = PlaceStore.sysConfig.IsPriorFacing_RoleNum; + + // 模式1: 开料机默认全部排钻造型 (正反面全部) + if (PlaceStore.sysConfig.isCutProcess) { + block.isCncLabelA = false + block.isCncLabelB = false + block.isCncLabelTurnOver = block.holeCountFaceB + block.modelCountFaceB > 0 + return + } + + // 其他模式 + block.isCncLabelA = false + block.isCncLabelB = false + // 侧孔 + block.isCncLabelTurnOver = block.holeCountSide + (block.isTurnOver ? block.holeCountFront + block.modelCountFront : block.holeCountBack + block.modelCountBack) > 0 + return + } + + // 开料时下刀点,下刀路径相关-------------------------------------------------- + + /** 获得下刀点在边框线上的坐标 */ + static getCutPoint(block: PlaceBlock): Point { + let curves = BlockPlus.getBorder(block) + let pnum = block.cutPointId + while (pnum < 0) { + pnum += curves.length + } + while (pnum >= curves.length) { + pnum -= curves.length + } + return new Point(curves[pnum].StartPoint.m_X, curves[pnum].StartPoint.m_Y) + } + + /** 获得下刀点在大板上的显示红点坐标 */ + static getCutPointXYInBoard(block: PlaceBlock): Point { + let p = this.getCutPoint(block) + let x = p.x + let y = p.y + let offset = 4 // 靠近板心 距离 + x = (x < block.placeWidth * 0.5) ? x + offset : x - offset + y = (y < block.placeLength * 0.5) ? y + offset : y - offset + return new Point(block.placeX + x, block.placeY + y) + } + + /** 获得下刀线,下刀点前一段线 */ + static getCutLine_Prev(block: PlaceBlock, borders: Curve2d[], offValue: number, prevLength = 10): Curve2d { + let p = this.getCutPoint(block) + let minDis = Number.MAX_VALUE + let index = 0 + for (let i = 0; i < borders.length; i++) { + let x0 = borders[i].StartPoint.m_X - p.x + let y0 = borders[i].StartPoint.m_Y - p.y + let dis = x0 * x0 + y0 * y0 + if (dis < minDis) { + minDis = dis + index = i + } + } + let prevID = index - 1 + if (prevID < 0) + prevID = prevID + borders.length + let prevCurve = borders[prevID] + if (prevCurve instanceof Line2d) { + if (prevCurve.m_Length < prevLength) { + let newID = index - 2 + if (newID < 0) + newID = newID + borders.length + let newC = borders[newID] + if (newC instanceof Line2d) { + if (newC.m_Length > prevLength) + return newC + } + } + return prevCurve + } + + let arc = prevCurve as Arc2d + if (Math.abs(arc.m_Radius - offValue) < 0.2) { + // 这是倒角,返回 再前一段作为下刀线 + let prevID = index - 2 + if (prevID < 0) + prevID = prevID + borders.length + return borders[prevID] + } + else { + // 不是倒角,鬼知道之前是什么东西,就返回这条了. + return arc + } + } + + // 获得下一条线段 + static getCutLine_Next(borders: Curve2d[], line: Curve2d): Curve2d { + let index = borders.indexOf(line) + index++ + if (index == borders.length) { + index = 0 + } + return borders[index] + } + + // 坐标相关 ,判断点在板内, 转换坐标等-------------------------------------------- + + /** 获得点在小板的顶角序号 左下角0,右下角1,右上角2,左上角3 不是顶角-1 */ + static getApexAngleNumFromBlock(block: PlaceBlock, p: Point) { + let areaID = -1 + if (equal(p.x, 0) && equal(p.y, 0)) + return 0 + if (equal(p.x, block.placeWidth) && equal(p.y, 0)) + return 1 + if (equal(p.x, block.placeWidth) && equal(p.y, block.placeLength)) + return 2 + if (equal(p.x, 0) && equal(p.y, block.placeLength)) + return 3 + return -1 + } + + // 判断点是否在板内,isOffset 是否外扩 刀直径 + static isPointInBlock(p: Point, block: PlaceBlock, isOffset: boolean = false): boolean { + let offValue = isOffset ? block.blockDetail.offsetKnifeRadius : 0 + // 如果 点 直接在异形的所在的矩形外,表示不在异形内,直接返回false + if (p.x < block.placeX - offValue) + return false + if (p.x > block.placeX + block.placeWidth + offValue) + return false + if (p.y < block.placeY - offValue) + return false + if (p.y > block.placeY + block.placeLength + offValue) + return false + + let x = p.x - block.placeX + let y = p.y - block.placeY + let bpl = isOffset ? BlockPlus.getOrgPloylines(block) : BlockPlus.getCutPloylines(block) + if (!bpl) + return false + + let isIn = PolylineHelper.isPointInPolyline(bpl.pl, x, y) + if (isIn == false) + return false + for (let mpl of bpl.pls_inner) { + let isInModel = PolylineHelper.isPointInPolyline(mpl, x, y) + if (isInModel) + return false // 如果在造型洞里头 + } + + return true + } + + // 判断点是否在板内空间里头(造型挖空的) + static isPointInInnerSpace(p: Point, block: PlaceBlock) { + // 判断是否在内部空间里头,则返回false; + let borders_inner = BlockPlus.getCutLines_inner(block) + if (borders_inner && borders_inner.length > 0) { + for (const border of borders_inner) { + // 内部空间 有四个边 + let xs = border.map((t) => { return t.StartPoint.m_X }) + let ys = border.map((t) => { return t.StartPoint.m_X }) + + let x0 = Math.min.apply(null, xs) + let x1 = Math.max.apply(null, xs) + let y0 = Math.min.apply(null, ys) + let y1 = Math.max.apply(null, ys) + if (p.x >= block.placeX + x0 && p.x <= block.placeX + x1 && p.y >= block.placeY + y0 && p.y <= block.placeY + y1) + return true + } + } + return false + } + + /** 获得原坐标在放置后的新坐标 */ + static getPlaceXYInBlock(block: PlaceBlock, x: number, y: number, isSealed: boolean, isFaceB = false, preCutValueOff = null): Point { + let bwidth = block.cutWidth + let blength = block.cutLength + let x0 = x + let y0 = y + if (isSealed) // 已封边 + { + bwidth = block.width + blength = block.length + x0 += block.offsetX + y0 += block.offsetY + } + if (!isSealed && preCutValueOff) // 没封边,且有封边 + { + bwidth += preCutValueOff.w + blength += preCutValueOff.l + x0 += preCutValueOff.x + y0 += preCutValueOff.y + } + + let _point: any = BlockHelper.getPlacedPostionInBlock(block.placeStyle, bwidth, blength, x0, y0, isFaceB) + + return _point + } + + /** 获得原板内坐标在大板中的坐标 */ + static getPlaceXYInBoard(block: PlaceBlock, x: number, y: number, isSealed = false): Point { + // 正面 + let pos0 = BlockHelper.getPlaceXYInBlock(block, x, y, isSealed, false) + pos0.x += block.placeX + pos0.y += block.placeY + return pos0 + } + + /** 获得翻转后的新坐标 */ + static getPlacedPostionInBlock(placeStyle: PlaceStyle, bwidth: number, blength: number, x0: number, y0: number, isFaceB: boolean): Point { + let posX = x0 + let posY = y0 + let placeWidth = bwidth + switch (placeStyle) { + case PlaceStyle.FRONT: + break + case PlaceStyle.FRONT_TURN_RIGHT: + posX = y0 + posY = bwidth - x0 + placeWidth = blength + break + case PlaceStyle.FRONT_TURN_BACK: + posX = bwidth - x0 + posY = blength - y0 + break + case PlaceStyle.FRONT_TURN_LEFT: + posX = blength - y0 + posY = x0 + placeWidth = blength + break + case PlaceStyle.BACK: + posX = bwidth - x0 + posY = y0 + break + case PlaceStyle.BACK_TURN_RIGHT: + posX = y0 + posY = x0 + placeWidth = blength + break + case PlaceStyle.BACK_TURN_BACK: + posX = x0 + posY = blength - y0 + break + case PlaceStyle.BACK_TURN_LEFT: + posX = blength - y0 + posY = bwidth - x0 + placeWidth = blength + break + default: + break + } + if (isFaceB) { + posX = placeWidth - posX + } + return new Point(posX, posY) + } + + /** 获得翻转前 小板内原坐标 posX,posY 放置后的坐标 */ + static getOrginalPositionInBlock(block: PlaceBlock, placeStyle: PlaceStyle, posX: number, posY: number): Point { + let x0 = posX + let y0 = posY + let bwidth = block.cutWidth + let blength = block.cutLength + switch (placeStyle) { + case PlaceStyle.FRONT: + break + case PlaceStyle.FRONT_TURN_RIGHT: + y0 = posX + x0 = bwidth - posY + break + case PlaceStyle.FRONT_TURN_BACK: + x0 = bwidth - posX + y0 = blength - posY + break + case PlaceStyle.FRONT_TURN_LEFT: + y0 = blength - posX + x0 = posY + break + case PlaceStyle.BACK: + x0 = bwidth - posX + y0 = posY + break + case PlaceStyle.BACK_TURN_RIGHT: + y0 = posX + x0 = posY + break + case PlaceStyle.BACK_TURN_BACK: + x0 = posX + y0 = blength - posY + break + case PlaceStyle.BACK_TURN_LEFT: + y0 = blength - posX + x0 = bwidth - posY + break + default: + break + } + return new Point(x0, y0) + } + + /** 获得离点距离最近的边框序号 faceId */ + static getClosestSideFaceID(bDetail: PlaceBlockDetail, x3: number, y3: number): number { + let index = 0 + let minDis = 99999 + for (let i = 0; i < bDetail.points.length; i++) { + let j = i + 1 + if (i == bDetail.points.length - 1) + j = 0 + let x1 = bDetail.points[i].pointX + let y1 = bDetail.points[i].pointY + let x2 = bDetail.points[j].pointX + let y2 = bDetail.points[j].pointY + + let dis = CADExt.getDisOff(x1, y1, x2, y2, x3, y3) + if (dis < minDis) { + minDis = dis + index = i + } + } + return index + } + + /** 获得造型 反向点阵 */ + static getModelPoints(model: BlockModel, isTurn: boolean): BlockModelPoint[] { + let orgPoints = model.pointList + if (isTurn == false) + return orgPoints + return BlockHelper.getTurnedModelPoints(orgPoints) + } + + static getTurnedModelPoints(orgPoints: BlockModelPoint[]) // 造型翻转 + { + let newPoints = [] + for (let i = orgPoints.length - 1; i >= 0; i--) { + let prev = i > 0 ? i - 1 : orgPoints.length - 1 + let newPoint = orgPoints[i].copy() + newPoint.curve = -orgPoints[prev].curve + newPoint.radius = orgPoints[prev].radius + newPoints.push(newPoint) + } + return newPoints + } + + static getTurnPoints(orgPoints): any[] { + let newPoints = [] + for (let i = orgPoints.length - 1; i >= 0; i--) { + let p = orgPoints[i] + let p_prev = i > 0 ? orgPoints[i - 1] : orgPoints[orgPoints.length - 1] + + let newP = { x: p.x, y: p.y, z: p.z, bul: p_prev.bul, r: p_prev.r } + newPoints.push(newP) + } + return newPoints + } + + static getRadius(x1: number, y1: number, x2: number, y2: number, bul: number): number { + let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2 + return 0.5 * d * (1 + bul ** 2) / bul + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts new file mode 100644 index 0000000..a7f64f9 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts @@ -0,0 +1,300 @@ +import { isTargetCurInOrOnSourceCur } from 'cadapi' +import type { Polyline } from 'cadapi' +import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' +import { PlaceBlock,PlaceBoard,PlaceMaterial } from '../../confClass.js' +import { BlockPlus } from './BlockPlus.js' + + +// 小板干涉判断等算法 +/** 判断小板干涉情况 */ +export function checkOverlapInBoard(pm: PlaceMaterial, pb: PlaceBoard,config:any) { + if (pb.blockList.length == 0) + return + const blocks = pb.blockList.slice(0).sort((a, b) => a.cutOrder - b.cutOrder) + let { + overlapGap= 0.05 + } = config + // 初始化 + for (const t of blocks) { + t.isOverlap = false + t.isOutBoard = false + t.pl_border = null // 开料后轮廓(待预铣,同刀辅助 ,不含刀半径) ,内缩一个干涉容差/2 + t.pl_cutBorder = null // 走刀轮廓 + t.pl_modelSpaces = null + t.pl_modelsOut = null + } + + const r = pm.diameter / 2 + // 干涉容差 + let _overlapGap = overlapGap + 0.002 + + if (_overlapGap < 0) + _overlapGap = 0 + + const borderOff = pm.cutBorder - _overlapGap / 2 - 0.001 + // console.log('大板的尺寸 -- borderOff', pm.cutBorder, overlapGap / 2 - 0.001) + const minx = borderOff + const maxX = pb.width - borderOff + const miny = borderOff + const maxY = pb.length - borderOff + + // 余料异形大板 的轮廓 + let boardBorders + + if (pb.isAdnormal()) { + if (pb.polyline == null) { + pb.polyline = PolylineHelper.create(pb.points, true) + } + if (_overlapGap > 0) // 有干涉容差, 变大 + { + boardBorders = pb.polyline.GetOffsetCurves(_overlapGap)[0] + } + else { + boardBorders = pb.polyline + } + } + + for (let i = 0; i < blocks.length; i++) { + const block1 = blocks[i] + // 1.是否超出大板外 + if (checkOutOfBoard(block1, boardBorders, minx, maxX, miny, maxY, _overlapGap)) { + console.log('超出大板外', block1.blockNo) + block1.isOverlap = true + } + // 2.检查板 铣刀路径,是否与前面板的铣刀路径 相干涉 + for (let p = 0; p < i; p++) { + const block2 = blocks[p] + + const isOverlap = isOverlap_block(block1, block2, r, _overlapGap) + if (isOverlap) { + console.log('铣刀路径问题 检查板 铣刀路径,与前面板的铣刀路径 相干涉', block1.blockNo, block1.blockId, block2.blockNo, block2.blockId) + block1.isOverlap = true + block2.isOverlap = true + } + } + } + + // 初始化 + for (const t of blocks) { + t.pl_border = null // 开料后轮廓(待预铣,同刀辅助 ,不含刀半径) ,内缩一个干涉容差/2 + t.pl_cutBorder = null // 走刀轮廓 + t.pl_modelSpaces = null // 内部轮廓 + t.pl_modelsOut = null // 造型外扩 轮廓 + } +} + +/** 判断板是否超出大板外 p0:大板可用最低点,p1:大板可用最高点 */ +function checkOutOfBoard(b: PlaceBlock, pl_board: Polyline, minx, maxx, miny, maxy, overlapGap): boolean { + // 判断小板的开料轮廓 + const pl_block = getBorder(b, overlapGap) + + // 异形的余料大板 + if (pl_board) { + // 小板面积比大板大 , 干涉 + if (pl_board.Area < pl_block.Area) { + console.log('小板面积比大板大 , 干涉') + return true + } + + // 盒子 不干涉,小板在外面 + if (pl_board.BoundingBox.intersectsBox(pl_block.BoundingBox) == false) { + console.log('盒子 不干涉,小板在外面') + return true + } + // 大盒子,没有包含 小盒子, 肯定在大板外 , + if (pl_board.BoundingBox.containsBox(pl_block.BoundingBox) == false) { + console.log('大盒子,没有包含 小盒子, 肯定在大板外 ,') + return true + } + // 轮廓有干涉 + if (pl_block.IntersectWith(pl_board, 0).length > 0) { + console.log('轮廓有干涉') + return true + } + + // 大轮廓 包含 小轮廓 , + if (isTargetCurInOrOnSourceCur(pl_board, pl_block) == false) { + console.log('大轮廓 包含 小轮廓 ') + return true + } + + return false + } + else // 矩形板 + { + // console.log('打印所有的矩形板=>', b.blockNo, pl_block) + if (pl_block.BoundingBox.min.x < minx) + { + console.log('矩形板 小板最小X 小于限定的 minx ') + return true + } + if (pl_block.BoundingBox.min.y < miny) + { + console.log('矩形板 小板最小y 小于限定的 miny ', b.blockNo, pl_block.BoundingBox.min.y, miny) + return true + } + if (pl_block.BoundingBox.max.x > maxx) + { + console.log('矩形板 小板最大x 小于限定的 maxx ') + return true + } + if (pl_block.BoundingBox.max.y > maxy) + { + console.log('矩形板 小板最大y 大于限定的 maxy ') + return true + } + return false + } +} + +/** 判断两板是否重叠 */ +function isOverlap_block(block1: PlaceBlock, block2: PlaceBlock, r: number, overlapGap: number): boolean { + if (block1.boardId != block2.boardId) + { + console.log('板材都不一样',block1.boardId ,block2.boardId); + return false // 两板不在同一个大板上 + } + + // 判断 b1,b2 开料是否干涉, <包括 造型洞内> + if (isOverLap_border(block1, block2, r, overlapGap)) { + return true + } + + // 判断b1造型走刀 干涉到 b2。 + if (isOverlap_model(block1, block2, r, overlapGap)) { + return true + } + + // 判断b2造型走刀 干涉到 b1。 + if (isOverlap_model(block2, block1, r, overlapGap)) { + return true + } + + return false +} + +/** 开料轮廓是否干涉 */ +function isOverLap_border(block1: PlaceBlock, block2: PlaceBlock, r: number, overlapGap: number): boolean { + let b1 = block1 + let b2 = block2 + + let pl_b1 = getCutBorder(b1, r, overlapGap) + let pl_b2 = getCutBorder(b2, r, overlapGap) + + // 盒子没有交集 ,不干涉 + if (pl_b1.BoundingBox.intersectsBox(pl_b2.BoundingBox) == false) + return false + // 盒子有交集 ,有可能干涉 + + // 走刀轮廓有干涉点 , 肯定干涉了 + if (pl_b1.IntersectWith(pl_b2, 0).length > 0) { + // console.log('走刀轮廓有干涉点 , 肯定干涉了') + return true + } + // 强制 b1 比 b2 大 + if (pl_b1.Area < pl_b2.Area) { + [b1, b2] = [b2, b1]; + [pl_b1, pl_b2] = [pl_b2, pl_b1] + } + + // b1 ,包含 b2 + if (isTargetCurInOrOnSourceCur(pl_b1, pl_b2)) { + // b1 的挖穿空间 + const pls_inner1 = getModelSpaces(b1, r, overlapGap) + // b2 的开料轮廓 + const pl_border2 = getBorder(b2, overlapGap) + for (const pl_1 of pls_inner1) { + // 挖穿造型 包含 b2 , 则不干涉 + if (pl_1.Area > pl_border2.Area && isTargetCurInOrOnSourceCur(pl_1, pl_border2)) + return false + } + + return true + } + + return false +} + +/** 判断block1 的造型走刀 是否干涉到 block2 */ +function isOverlap_model(block1: PlaceBlock, block2: PlaceBlock, r: number, overlapGap: number): boolean { + // 外扩造型线 + const pls_1 = getModelsOuter(block1, r, overlapGap) + + const pl_2 = getCutBorder(block2, r, overlapGap) + + for (const pl of pls_1) { + if (pl.IntersectWith(pl_2, 0).length > 0) + return true + } + return false +} + +/** 开料轮廓(待预铣,同刀辅助 ,不含刀半径) ,内缩一个干涉容差/2 */ +function getBorder(b: PlaceBlock, overlapGap: number): Polyline { + let pl_border = b.pl_border + + // if (pl_border == null) { + const borders = BlockPlus.getBorder_sameKnife(b) + let pl = BlockPlus.borderToPolyline(borders) + pl = pl.GetOffsetCurves(-overlapGap / 2)[0] + + pl_border = PolylineHelper.moveTo(pl, b.placeX, b.placeY) + + // b.pl_border = pl_border + // } + return pl_border +} + +/** 走刀轮廓(待预铣,同刀辅助 , 刀半径) ,内缩一个干涉容差/2 */ +function getCutBorder(b: PlaceBlock, r: number, overlapGap: number): Polyline { + let pl_cutBorder = b.pl_cutBorder + if (pl_cutBorder == null) { + const border = BlockPlus.getCutLines_sameKnife(b) + pl_cutBorder = BlockPlus.borderToPolyline(border) + pl_cutBorder = pl_cutBorder.GetOffsetCurves(-overlapGap / 2)[0] + pl_cutBorder = PolylineHelper.moveTo(pl_cutBorder, b.placeX, b.placeY) + b.pl_cutBorder = pl_cutBorder + } + return pl_cutBorder +} + +/** 挖穿造型空间<扣除:刀直径 - 容差/2> */ +function getModelSpaces(b: PlaceBlock, r: number, overlapGap: number): Polyline[] { + let pl_models = b.pl_modelSpaces + if (pl_models == null) { + let spaces = BlockPlus.getBorders_inner(b) + let rs = BlockPlus.getBorders_inner_r(b) + + pl_models = [] + if (spaces.length > 0 && spaces.length == rs.length) { + for (let i = 0; i < spaces.length; i++) { + let pl = BlockPlus.borderToPolyline(spaces[i]) + // 内侧 刀直径 < 造型刀,开料刀 最大值 > + const off = Math.max(r, rs[i]) * 2 - overlapGap / 2 + pl = pl.GetOffsetCurves(-off)[0] + if (pl == null) + continue + pl = PolylineHelper.moveTo(pl, b.placeX, b.placeY) + pl_models.push(pl) + } + } + b.pl_modelSpaces = pl_models + } + return pl_models +} + +/** 外扩造型 多段线 */ +function getModelsOuter(b: PlaceBlock, r: number, overlapGap: number): Polyline[] { + let pl_models = b.pl_modelsOut + if (pl_models == null) { + pl_models = [] + const pls_org = BlockPlus.getOutModelBorders(b) + for (const pl of pls_org) { + const npl = PolylineHelper.moveTo(pl, b.placeX, b.placeY) + + pl_models.push(npl) + } + b.pl_modelsOut = pl_models + } + return pl_models +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts new file mode 100644 index 0000000..5ab6b56 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts @@ -0,0 +1,2395 @@ +import { Circle, Contour, ConverToPolylineAndSplitArc, Shape, ShapeManager } from 'cadapi' +import type { Polyline } from 'cadapi' +import { Vector3 } from 'three' +import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' +import type { Curve2d } from '../../common/base/CAD.js' +import { Arc2d, Line2d, Point2d } from '../../common/base/CAD.js' +import { PlaceStyle, SizeExpand, PlaceBlock, PlaceBlockDetail, PlaceBorderContour, PlaceSpace, PlaceMaterial } from '../../confClass.js' +import { equal, getDis, getDis2, getDis_PointLine, getRect } from '../../common/base/MathComm.js' +import { getPlacePosition } from '../../common/base/PlaceStyleHelper' +import { SpacePlus } from './SpacePlus' + +/**板件计算工具类 小板轮廓,走刀路径等 */ +export class BlockPlus { + + /** 小板初始化:1各种轮廓|走刀路径|,2尺寸扩展后的轮廓与走刀路径 + * 配置入参 + * isRectBlockChamfer = false, // 矩形板件倒角开料 + isUnRegularBlockChamfer = false, // 异形板件倒角开料 + preMillingSize = 0, // 预铣尺寸 + */ + static initBlock(block: PlaceBlock, placeMaterial: PlaceMaterial, params: any) { + if(block.blockDetail == null) { + console.error('BlockPlus.initBlock: block.blockDetail is undefined', block) + return + } + if (block.blockDetail.borderContour) + return // 已初始化 + + const { + isRectBlockChamfer = false, // 矩形板件倒角开料 + isUnRegularBlockChamfer = false, // 异形板件倒角开料 + preMillingSize = 0, // 预铣尺寸 + helpCutStyle = 0, // 辅助开料方式(0同刀辅助开料 1小刀辅助开料) useSameKnifeToHelpCutStyle + helpCutGap = 2, // 同刀辅助开料缝隙 + } = params + + if (!block.blockDetail) { return } + const pm = 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 = isRectBlockChamfer + } + else // 异形板 + { + // 异形板倒角 + bd.isOffsetRounding = 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() + + if (preMillingSize > 0.0001) { + const rt = this.creatOrgBorderWithPrevCut(block, border_org, preMillingSize) + bd.borderContour.borderPreMilling = rt?.newBorders || [] + bd.preMillingExpandSize = rt?.newSizeExpand || new SizeExpand() + // 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 = helpCutStyle || 0 + let useSameKnifeToHelpCutGap = helpCutGap || 0 + const isSameKnifeToCut = isUseSameKnifeToHelpCut && useSameKnifeToHelpCutGap > 0 + if (isSameKnifeToCut == false || bd.isUseSameKnifeToHelpCut == false) { + // 未启动同刀辅助, 或 该小板 不需要同刀辅助 + bd.useSameKnifeToHelpCutGap = 0 + bd.sameKnifeHelpExpandSize = new SizeExpand() + } + else { + const gap = useSameKnifeToHelpCutGap + bd.useSameKnifeToHelpCutGap = gap + bd.sameKnifeHelpExpandSize = new SizeExpand({ left: gap, right: gap, under: gap, upper: gap }) + } + // 2V刀路,预洗,同刀辅助开料,出板造型刀 + this.resetBlock(block, pm) + } + + /** 重设小板 ,因为板材重新设置 预铣值 或 板外下刀 */ + static resetBlock(block: PlaceBlock, placeMaterial: PlaceMaterial) { + if (!block.blockDetail) { + console.error('BlockPlus.resetBlock: block.blockDetail is undefined', block) + return + } + const pm = placeMaterial + const bd = block.blockDetail + + const preMillingSize = pm.preMillingSize + // startDebug(block); + if (!bd.borderContour) { + console.error('BlockPlus.resetBlock: bd.borderContour is undefined', block) + return + } + + // 开料刀 修改了,内部空间发生变化 + if (!equal(bd.offsetKnifeRadius, pm.diameter / 2)) { + bd.offsetKnifeRadius = pm.diameter / 2 + bd.borderContour.cutKnifeChanged() + + // 4. 板内走刀路径,板内空间 + const innerGroup = this.analyeInners(bd, pm.diameter / 2, pm.cutKnifeGap) + + bd.borderContour.borderModelThrough = innerGroup.borders_inner_org + bd.borderContour.cutLinesModelThrough = innerGroup.borders_inner_cut + bd.borderContour.borderInnerPlace = innerGroup.borders_inner_place + bd.borderContour.blockInnerSpace = innerGroup.spaces + } + + // 因为预铣, 同刀辅助会影响到开料走刀等轮廓,所以要将受影响的轮廓清空,需要的时候再生成 + // 预铣 + if (!equal(bd.preMillingSize, pm.preMillingSize)) { + bd.preMillingSize = preMillingSize + bd.borderContour.preValueChanged() + } + // 同刀辅助 + let newTDFZ = 0 + if (pm.isHelpCut && pm.helpCutGap > 0 && block.blockDetail.isUseSameKnifeToHelpCut) { + newTDFZ = pm.helpCutGap + block.blockDetail.sameKnifeHelpExpandSize = new SizeExpand({ left: newTDFZ, right: newTDFZ, upper: newTDFZ, under: newTDFZ }) + } + else { + newTDFZ = 0 + block.blockDetail.sameKnifeHelpExpandSize = new SizeExpand() + } + + if (!equal(newTDFZ, bd.useSameKnifeToHelpCutGap)) { + bd.useSameKnifeToHelpCutGap = newTDFZ + bd.borderContour.sameKnifeGapChanged() + } + + const cutRadius = (pm.diameter + pm.cutKnifeGap) / 2 + + // 预铣 + let off1 = block.blockDetail.preMillingExpandSize || new SizeExpand() + // 造型外扩 + const off2 = block.blockDetail.modelExpandSize || new SizeExpand() + // 2v 板外下刀 + const off3 = block.blockDetail.vKnifeModelExpandSize || new SizeExpand() + // 同刀辅助 + let off4 = block.blockDetail?.sameKnifeHelpExpandSize || new SizeExpand() + + // 不启用 预铣 + // if (preMillingSize < 0.0001) + // off1 = new SizeExpand() + if (newTDFZ < 0.001) + off4 = new SizeExpand() + + // 预铣+同刀辅助+, 造型外扩,2v刀路 应该取最大值 + const off = new SizeExpand() + off.left = Math.max(off1.left + off4.left + cutRadius, off2.left, off3.left) + off.right = Math.max(off1.right + off4.right + cutRadius, off2.right, off3.right) + off.bottom = Math.max(off1.bottom + off4.bottom + cutRadius, off2.bottom, off3.bottom) + off.top = Math.max(off1.top + off4.top + cutRadius, off2.top, off3.top) + + off.setSize() + bd.currentSizeExpand = off + } + + /** 小板是否有超出造型或二维刀路 */ + static hasModelOutBlock(block: PlaceBlock): boolean { + if (block.blockDetail == null) + return false + if (block.blockDetail.borderContour == null) + return false + + if (block.blockDetail.borderContour.polylinesOutModel == null || block.blockDetail.borderContour.polylines2vModel == null) + return false + + return block.blockDetail.borderContour.polylinesOutModel.length + block.blockDetail.borderContour.polylines2vModel.length > 0 + } + + /** 获得 成品轮廓 isPlaced=false为不旋转的 */ + static getFinalBorder(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getOrgBorderNew: bu is undefined', block) + return [] + } + return bu.borderFinal + } + + /** 获得 开料轮廓-不含预铣 isPlaced=false为不旋转的 */ + static getOrgBorder(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getOrgBorder: bu is undefined', block) + return [] + } + return bu.borderOrg + } + + /** 获得 开料轮廓-不含预铣 isPlaced=false为不旋转的 */ + static getOrgBorderNew(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + const bu = this.getPlacedUnitNew(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getOrgBorderNew: bu is undefined', block) + return [] + } + return bu.borderOrg + } + /** 获得当前板 轮廓多段线 */ + static getOrgPolyline(block: PlaceBlock): Polyline | null { + if (block.blockDetail == null) { + console.error('BlockPlus.getOrgPolyline: block.blockDetail is undefined', block) + return null + } + const bd = block.blockDetail.borderContour + if (bd == null) { + console.error('BlockPlus.getOrgPolyline: bd is undefined', block) + return null + } + if (!bd.polylineOrg) { + const border = bd.borderOrg + const pl = PolylineHelper.createByCurve2d(border) + bd.polylineOrg = pl + } + return bd.polylineOrg + } + + /** 获得 开料轮廓 _有预铣含预铣 */ + static getBorder(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + if (block.blockDetail == null) { + console.error('BlockPlus.getBorder: block.blockDetail is undefined', block) + return [] + } + + let preSize = block.blockDetail.preMillingSize + if (preSize < 0.0001) + preSize = 0 + + // 初始 + const baseContour = block.blockDetail.borderContour + if (baseContour == null) { + console.error('BlockPlus.getBorder: baseContour is undefined', block) + return [] + } + if (!baseContour.border ||(Array.isArray(baseContour.border) && baseContour.border.length ==0)) // 生成 预铣轮廓 + { + const border = preSize == 0 ? baseContour.borderOrg : baseContour.borderPreMilling + baseContour.border = border + } + + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + if (!pbu.border) { + pbu.border = this.transformBorder(baseContour.border, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.border + } + + /** 获得 开料轮廓—有预铣带预铣-有同刀辅助带同刀辅助 */ + static getBorder_sameKnife(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + if (block.blockDetail == null) { + console.error('BlockPlus.getBorder_sameKnife: block.blockDetail is undefined', block) + return [] + } + // 如果没有同刀辅助,直接返回开料轮廓 + let helpCutGap = block.blockDetail.useSameKnifeToHelpCutGap + + if (helpCutGap < 0.001) + helpCutGap = 0 + + // 初始 + const baseContour = block.blockDetail.borderContour + if (baseContour == null) { + console.error('BlockPlus.getBorder_sameKnife: baseContour is undefined', block) + return [] + } + if (!baseContour.borderSameKnifeHelpCut) { + const borders = this.getBorder(block, false) + if (helpCutGap == 0) // 无同刀辅助 + { + baseContour.borderSameKnifeHelpCut = borders + } + else { + let rounding = block.blockDetail.isOffsetRounding + if (block.isUnRegular) + rounding = true // 异形板 必须倒角 + baseContour.borderSameKnifeHelpCut = this.offsetCurves(borders, helpCutGap, rounding) // 必须倒角,否则 尖角超出 开料范围 + } + } + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + if (!pbu.borderSameKnifeHelpCut) { + pbu.borderSameKnifeHelpCut = this.transformBorder(baseContour.borderSameKnifeHelpCut, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.borderSameKnifeHelpCut + } + + /** 获得走刀路径(含预铣) isPlaced = false为不旋转 */ + /** + * + * @param block 小板 + * @param placeMaterial 开料大板 + * @param isPlaced 是否旋转 + * @param placeStyle 旋转后的放置方式 + * @param config 配置 + { + isRectBlockChamfer: false, // 矩形板件倒角开料 + isUnRegularBlockChamfer: false, // 异形板件倒角开料 + } + * @returns + */ + static getCutLines(block: PlaceBlock, placeMaterial: PlaceMaterial, isPlaced = true, placeStyle: PlaceStyle | undefined, config: any): Curve2d[] { + // 走刀路径由开料轮廓(含预铣) border_plus 偏移一个刀半径而成 + const { + isRectBlockChamfer = false, // 矩形板件倒角开料 + isUnRegularBlockChamfer = false, // 异形板件倒角开料 + + } = config + if (!block.blockDetail) { + console.error('BlockPlus.getCutLines: block.blockDetail is undefined', block) + return [] + } + if (!block.blockDetail.borderContour) { + console.error('BlockPlus.getCutLines: block.blockDetail.borderContour is undefined', block) + return [] + } + // 初始 + const baseContour = block.blockDetail.borderContour + if (!baseContour.cutLines) { + const borders = this.getBorder(block, false) + // 偏移值 + const offvalue = placeMaterial.diameter / 2 // 开料刀的一半 + // 是否倒角 + let isOffsetRounding = false // block.SaleBlockDetail.IsOffsetRounding; + if (block.isUnRegular == false) // 矩形板,设置不倒角 + { + isOffsetRounding = isRectBlockChamfer + } + else // 异形板 + { + // 异形倒角 + isOffsetRounding = isUnRegularBlockChamfer + } + baseContour.cutLines = this.offsetCurves(borders, offvalue, isOffsetRounding) + } + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + if (!pbu.cutLines) { + pbu.cutLines = this.transformBorder(baseContour.cutLines, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.cutLines + } + /** + * 获得 同刀辅助 的走刀路径 + * @param block 小板 + * @param isPlaced 是否翻转 + * @param placeStyle 翻转后的放置方式 + * @param config 配置 + * { + isRectBlockChamfer: false, // 矩形板件倒角开料 + isUnRegularBlockChamfer: false, // 异形板件倒角 + } = config || {} + * @returns + */ + static getCutLines_sameKnife(block: PlaceBlock, placeMaterial: PlaceMaterial, isPlaced = true, placeStyle?: PlaceStyle, config?: any): Curve2d[] { + // 同刀辅助 走刀路径 由 开料轮廓 _含预铣_同刀辅助 偏移一个刀半径 厚度 而成 + const { + isRectBlockChamfer = false, // 矩形板件倒角开料 + isUnRegularBlockChamfer = false, // 异形板件倒角开料 + + } = config || {} + if (!block.blockDetail) { + console.error('BlockPlus.getCutLines_sameKnife: block.blockDetail is undefined', block) + return [] + } + // 不用同刀辅助 + const helpCutGap = block.blockDetail.useSameKnifeToHelpCutGap + if (helpCutGap < 0.001) { + return this.getCutLines(block, placeMaterial, isPlaced, placeStyle, config) + } + + const baseContour = block.blockDetail.borderContour + if (baseContour == null) { + console.error('BlockPlus.getCutLines_sameKnife: baseContour is undefined', block) + return [] + } + if (!baseContour.cutLinesSameKnifeHelpCut) { + const borders = this.getBorder_sameKnife(block, false) + // 偏移值 + const kr = placeMaterial.diameter / 2 // 开料刀的一半 + // 同刀辅助 不倒角 , + // 是否倒角 异形板必须倒角 + let isOffsetRounding = false // block.SaleBlockDetail.IsOffsetRounding; + if (block.isUnRegular == false) // 矩形板,设置不倒角 + { + isOffsetRounding = isRectBlockChamfer + } + else // 异形板 + { + isOffsetRounding = isUnRegularBlockChamfer + } + baseContour.cutLinesSameKnifeHelpCut = this.offsetCurves(borders, kr, isOffsetRounding) + } + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + if (!pbu.cutLinesSameKnifeHelpCut) { + pbu.cutLinesSameKnifeHelpCut = this.transformBorder(baseContour.cutLinesSameKnifeHelpCut, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.cutLinesSameKnifeHelpCut + } + + /** + * 获得 手动排版路径 包括板外下刀等轮廓 + * 由开料轮廓 偏移 ( 预铣 ,同刀辅助, 开料刀半径 , 缝隙/2 ) + * 再与 二维刀路,板外小刀,造型外扩 合并起来的 + */ + static getBorder_moving(block: PlaceBlock, placeMaterial: PlaceMaterial, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + if (!block.blockDetail || !block.blockDetail.borderContour) { + console.error('BlockPlus.getBorder_moving: block.blockDetail or block.blockDetail.borderContour is undefined', block) + return [] + } + this.theBlock = block + const bd = block.blockDetail + + // 初始 + const baseContour = bd.borderContour + if (baseContour == null) { + console.error('BlockPlus.getBorder_moving: baseContour is undefined', block) + return [] + } + if (!baseContour.borderMoving) { + // 1.开料轮廓<带预铣> + const borders = this.getBorder(block, false) + // 偏移值 = 同刀辅助 + 刀半径 + 缝隙 /2 + const cutGap = block.blockDetail.useSameKnifeToHelpCutGap + (placeMaterial.diameter + placeMaterial.cutKnifeGap) / 2 + // 是否倒角(如果倒角,手动排版,拖拉时,停靠点不便计算,所以设置成不倒角,然后截断> + const isOffsetRounding = false + + // 原轮廓 + let border_moving = this.offsetCurves(borders, cutGap, isOffsetRounding) + const pl = this.borderToPolyline(border_moving) + + // 造型的外扩,2v刀路的 + const pls_m = baseContour.polylinesOutModel.concat(baseContour.polylines2vModel) + // 合并 + const rpl = this.unionPolylines(pl, pls_m) + // + border_moving = this.polylineToBorder(rpl) + + baseContour.borderMoving = border_moving + } + + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + return pbu.borderMoving + } + static getBorder_moving_new(block: PlaceBlock, pb: PlaceMaterial, isPlaced = true, placeStyle: PlaceStyle): Curve2d[] { + if (block.blockDetail == null) { + console.error('BlockPlus.getBorder_moving_new: block.blockDetail is undefined', block) + return [] + } + this.theBlock = block + const bd = block.blockDetail + + // 初始 + const baseContour = bd.borderContour + if (!baseContour) { + console.error('BlockPlus.getBorder_moving_new: baseContour is undefined', block) + return [] + } + if (!baseContour.borderMoving) { + // 1.开料轮廓<带预铣> + const borders = this.getBorder(block, false) + // 偏移值 = 同刀辅助 + 刀半径 + 缝隙 /2 + const cutGap = block.blockDetail.useSameKnifeToHelpCutGap + (pb.diameter + pb.cutKnifeGap) / 2 + // 是否倒角(如果倒角,手动排版,拖拉时,停靠点不便计算,所以设置成不倒角,然后截断> + const isOffsetRounding = false + + // 原轮廓 + let border_moving = this.offsetCurves(borders, cutGap, isOffsetRounding) + const pl = this.borderToPolyline(border_moving) + + // 造型的外扩,2v刀路的 + const pls_m = baseContour.polylinesOutModel.concat(baseContour.polylines2vModel) + // 合并 + const rpl = this.unionPolylines(pl, pls_m) + // + border_moving = this.polylineToBorder(rpl) + + baseContour.borderMoving = border_moving + } + + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getBorder_moving_new: pbu is undefined', block) + return [] + } + if (!pbu.borderMoving) { + pbu.borderMoving = this.transformBorder(baseContour.borderMoving, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.borderMoving + } + + static getOutModelPloyline(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Polyline[] { + if (!block.blockDetail || !block.blockDetail.borderContour) { + console.error('BlockPlus.getOutModelPloyline: block.blockDetail or block.blockDetail.borderContour is undefined', block) + return [] + } + const bd = block.blockDetail + // 初始 + const baseContour = bd.borderContour + + if (baseContour == null) { + console.error('BlockPlus.getOutModelPloyline: baseContour is undefined', block) + return [] + } + const pls_m = baseContour.polylinesOutModel.concat(baseContour.polylines2vModel) + + if (placeStyle == PlaceStyle.FRONT) + return pls_m + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + console.error('BlockPlus.getOutModelPloyline: pbu is undefined', block) + return [] + } + let newPls: Polyline[] = [] + for (let pl of pls_m) { + let newpl = this.transformPolyline(pl, pbu.placeStyle, block.cutWidth, block.cutLength) + newPls.push(newpl) + } + return newPls + } + + static theBlock + /** 获取轮廓用于王者优化,预铣 + 同刀辅助 ,造型外扩 + 2维刀路外扩 */ + static getBorder_WZYH(block: PlaceBlock, placeMaterial: PlaceMaterial, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[] { + this.theBlock = block + const bd = block.blockDetail + if (!bd || !bd.borderContour) { + console.error('BlockPlus.getBorder_WZYH: block.blockDetail or block.blockDetail.borderContour is undefined', block) + return [] + } + const baseContour = bd.borderContour + // 生成 同刀辅助 路径 <原方向》 + if (baseContour.borderKingOptimize == null) { + // 开料轮廓 <同刀辅助> + const borders = this.getBorder_sameKnife(block, false) + + // 偏移值= 同刀辅助 + 刀半径 + + 缝隙 /2 + const cutGap = (placeMaterial.diameter + placeMaterial.diameter) / 2 + // 是否倒角 < 如果倒角,手动排版,拖拉时,停靠点 不好计算,所以设置成不倒角,然后截断>, + const isOffsetRounding = bd.isOffsetRounding + + // 走刀路径 + const borders2 = this.offsetCurves(borders, cutGap, isOffsetRounding) + const pl = this.borderToPolyline(borders2) + // 造型的外扩,2v刀路的 + const pls_m = baseContour.polylinesOutModel.concat(baseContour.polylines2vModel) + // 合并 + const rpl = this.unionPolylines(pl, pls_m) + + baseContour.borderKingOptimize = this.polylineToBorder(rpl) + } + + // 放置后 + const pbu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (pbu == null) { + return [] + } + if (!pbu.borderKingOptimize) { + pbu.borderKingOptimize = this.transformBorder(baseContour.borderKingOptimize, pbu.placeStyle, block.cutWidth, block.cutLength) + } + return pbu.borderKingOptimize + } + + /** 获得 内部轮廓 <挖穿造型> */ + static getBorders_inner(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[][] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getBorders_inner: 获取板的内部轮廓失败', block) + return [] + } + // 如果没有内部轮廓 + if (bu.borderModelThrough == null) { + const borders = this.getBorders_inner(block, false) + const borders_off: Curve2d[][] = [] + for (const border of borders) { + const newBorder = this.transformBorder(border, bu.placeStyle, block.cutWidth, block.cutLength) + borders_off.push(newBorder) + } + bu.borderModelThrough = borders_off + } + return bu.borderModelThrough + } + /** 获得 内部轮廓 <挖穿造型> */ + static getBorders_innerNew(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[][] { + const bu = this.getPlacedUnitNew(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getBorders_innerNew: 获取板的内部轮廓失败', block) + return [] + } + if (bu.borderModelThrough == null) { + const borders = this.getBorders_innerNew(block, false) + const borders_off: Curve2d[][] = [] + for (const border of borders) { + const newBorder = this.transformBorderNew(border, bu.placeStyle, block.cutWidth, block.cutLength) + borders_off.push(newBorder) + } + bu.borderModelThrough = borders_off + } + return bu.borderModelThrough + } + /** 获得内部轮廓 <挖穿造型> 半径 */ + static getBorders_inner_r(block: PlaceBlock) { + if (block.blockDetail == null || block.blockDetail.borderContour == null) { + console.error('BlockPlus.getBorders_inner_r: block.blockDetail or block.blockDetail.borderContour is undefined', block) + return [] + } + return block.blockDetail.borderContour.borderModelThroughR + } + + /** 获得 内部走刀轮廓 <挖穿造型> */ + static getCutLines_inner(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[][] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getCutLines_inner: 获取板的内部走刀轮廓失败', block) + return [] + } + if (bu.cutLinesModelThrough == null) { + const borders = this.getCutLines_inner(block, false) + const borders_off: Curve2d[][] = [] + for (const border of borders) { + const newBorder = this.transformBorder(border, bu.placeStyle, block.cutWidth, block.cutLength) + borders_off.push(newBorder) + } + bu.cutLinesModelThrough = borders_off + } + return bu.cutLinesModelThrough + } + + /** 获得 内部排版轮廓 <挖穿造型> */ + static getInnerPlaceBorders(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[][] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getInnerPlaceBorders: 获取板的内部排版轮廓失败', block) + return [] + } + if (bu.borderInnerPlace == null) { + const borders = this.getInnerPlaceBorders(block, false) + const borders_off: Curve2d[][] = [] + for (const border of borders) { + const newBorder = this.transformBorder(border, bu.placeStyle, block.cutWidth, block.cutLength) + borders_off.push(newBorder) + } + bu.borderInnerPlace = borders_off + } + return bu.borderInnerPlace + } + + /** 获得 板内空间 《挖穿孔洞 + 缺角》 */ + static getSpaces(block: PlaceBlock, pm: PlaceMaterial): PlaceSpace[] { + if (block.blockDetail == null || block.blockDetail.borderContour == null) { + console.error('BlockPlus.getSpaces: block.blockDetail or block.blockDetail.borderContour is undefined', block) + return [] + } + const bu = block.blockDetail.borderContour + if (!bu.blockOuterSpace) + bu.blockOuterSpace = this.createOutSpaces(block, pm) // 缺角空间不存在,创建 + // 挖穿孔洞 + 缺角 + return bu.blockInnerSpace.concat(bu.blockOuterSpace) + } + + static getInnerSpaces(block: PlaceBlock): PlaceSpace[] { + const bu = this.getPlacedUnit(block, false) + // 挖穿孔洞 + return bu ? bu.blockInnerSpace : [] + } + + static getModelHole(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle): Curve2d[][] { + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getModelHole: 获取板的轮廓集失败', block) + return [] + } + if (!bu.borderInnerPlace) { + if (block.blockDetail == undefined) { + console.error('BlockPlus.getModelHole: block.blockDetail is undefined', block) + return [] + } + if (block.blockDetail.borderContour == undefined) { + console.error('BlockPlus.getModelHole: block.blockDetail.borderContour is undefined', block) + return [] + } + + const org_list = block.blockDetail.borderContour.borderInnerPlace + if (!org_list || org_list.length == 0) + return [] + const liset: Curve2d[][] = [] + for (const cur of org_list) { + const newCur = this.transformBorder(cur, bu.placeStyle, block.cutWidth, block.cutLength) + if (newCur) + liset.push(newCur) + } + bu.borderInnerPlace = liset + } + return bu.borderInnerPlace + } + + /** 获得 板的轮廓多段线(包括内部挖穿) */ + static getOrgPloylines(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle) { + const borderPlus = this.getBorder(block, isPlaced, placeStyle) + const innerBorders = this.getBorders_inner(block, isPlaced, placeStyle) + + const pl = this.borderToPolyline(borderPlus) + const pls_inner = new Array() + for (const border of innerBorders) { + const npl = this.borderToPolyline(border) + pls_inner.push(npl) + } + return { pl, pls_inner } + } + + /** 获得 板的开料多段线(包括内部挖穿) */ + static getCutPloylines(block: PlaceBlock, pm: PlaceMaterial, isPlaced = true, placeStyle?: PlaceStyle) { + const borderPlus = this.getCutLines_sameKnife(block, pm, isPlaced, placeStyle) + const innerBorders = this.getCutLines_inner(block, isPlaced, placeStyle) + + const pl = this.borderToPolyline(borderPlus) + const pls_inner = new Array() + for (const border of innerBorders) { + const npl = this.borderToPolyline(border) + pls_inner.push(npl) + } + return { pl, pls_inner } + } + + /** 获得小板 板外造型轮廓 */ + static getOutModelBorders(block: PlaceBlock, isPlaced = true, placeStyle?: PlaceStyle) { + // b1.SaleBlockDetail.borderUnit.polylines_outModel + // b1.SaleBlockDetail.borderUnit.polylines_2vModel + const bu = this.getPlacedUnit(block, isPlaced, placeStyle) + if (bu == null) { + console.error('BlockPlus.getOutModelBorders: 获取板的轮廓集失败', block) + return [] + } + if (block.blockDetail == undefined) { + console.error('BlockPlus.getOutModelBorders: block.blockDetail is undefined', block) + return [] + } + if (block.blockDetail.borderContour == undefined) { + console.error('BlockPlus.getOutModelBorders: block.blockDetail.borderContour is undefined', block) + return [] + } + if (bu.polylinesOutModel == null) { + const polylines_outModel = block.blockDetail.borderContour.polylinesOutModel + const new_outModels: Polyline[] = [] + for (const border of polylines_outModel) { + const newBorder = this.transformPolyline(border, bu.placeStyle, block.cutWidth, block.cutLength) + new_outModels.push(newBorder) + } + bu.polylinesOutModel = new_outModels + } + if (bu.polylines2vModel == null) { + const polylines_2vModel = block.blockDetail.borderContour.polylines2vModel + const new_outModels: Polyline[] = [] + for (const border of polylines_2vModel) { + const newBorder = this.transformPolyline(border, bu.placeStyle, block.cutWidth, block.cutLength) + new_outModels.push(newBorder) + } + bu.polylines2vModel = new_outModels + } + return bu.polylinesOutModel.concat(bu.polylines2vModel) + } + + /** 获得小板外形轮廓 */ + static getOrgContour(block: PlaceBlock): any { + if (!block.blockDetail) { + console.error('BlockPlus.getOrgContour: block.blockDetail is undefined', block) + return null + } + + if (!block.blockDetail.orgContourData) { + const pts: any = [] + const buls: any = [] + if (!block.isUnRegular) { + pts.push({ x: 0, y: 0 }) + pts.push({ x: block.width, y: 0 }) + pts.push({ x: block.width, y: block.length }) + pts.push({ x: 0, y: block.length }) + buls.push(0) + buls.push(0) + buls.push(0) + buls.push(0) + } + else // 矩形板 + { + for (const p of block.blockDetail.orgPoints) { + pts.push({ x: p.pointX, y: p.pointY }) + buls.push(p.curve) + } + } + + block.blockDetail.orgContourData = { pts, buls } + } + return block.blockDetail.orgContourData + } + + /** 获得板的 轮廓集 isPlaced=false为不旋转的 */ + private static getPlacedUnit(block: PlaceBlock, isPlaced = true, style?: PlaceStyle): PlaceBorderContour | null { + if (!block.blockDetail) { + console.error('BlockPlus.getPlacedUnit: block.blockDetail is undefined', block) + return null + } + if (block.blockDetail.borderContour == undefined) { + console.error('BlockPlus.getPlacedUnit: block.blockDetail.borderContour is undefined', block) + return null + } + const baseContour = block.blockDetail.borderContour + if (isPlaced == false) + style = PlaceStyle.FRONT + if (isPlaced == true) { + if (style == undefined || style == null) + style = block.placeStyle + } + + if (style == undefined) { + style = PlaceStyle.FRONT + } + + if (style == PlaceStyle.FRONT) + return baseContour + + let placeContour = baseContour.placeContours.find(t => t.placeStyle == style) + if (placeContour == null) { + const border_final = this.transformBorder(baseContour.borderFinal, style, block.width, block.length) + const border_org = this.transformBorder(baseContour.borderOrg, style, block.cutWidth, block.cutLength) + placeContour = new PlaceBorderContour(style, border_final, border_org) + baseContour.placeContours.push(placeContour) + } + return placeContour + } + /** + * 获得板的 轮廓集 isPlaced=false为不旋转的 + * @param block + * @param isPlaced + * @param style + * @returns + */ + private static getPlacedUnitNew(block: PlaceBlock, isPlaced = true, style?: PlaceStyle): PlaceBorderContour | null { + + if (!block.blockDetail) { + console.error('BlockPlus.getPlacedUnitNew: block.blockDetail is undefined', block) + return null + } + if (block.blockDetail.borderContour == undefined) { + console.error('BlockPlus.getPlacedUnitNew: block.blockDetail.borderContour is undefined', block) + return null + } + const baseContour = block.blockDetail.borderContour + + + if (isPlaced == false) + style = PlaceStyle.FRONT + if (isPlaced == true) { + if (style == undefined || style == null) + style = block.placeStyle + } + + if (style == undefined) { + style = PlaceStyle.FRONT + } + + if (style == PlaceStyle.FRONT) + return baseContour + + let placeContour = baseContour.placeContours.find(t => t.placeStyle == style) + if (placeContour == null) { + const border_final = this.transformBorderNew(baseContour.borderFinal, style, block.width, block.length) + const border_org = this.transformBorderNew(baseContour.borderOrg, style, block.cutWidth, block.cutLength) + placeContour = new PlaceBorderContour(style, border_final, border_org) + baseContour.placeContours.push(placeContour) + } + return placeContour + } + + + /** 创建 小板成品轮廓 */ + private static createFinalBorder(bd: PlaceBlockDetail, block: PlaceBlock): Curve2d[] { + const orgPoints = bd.orgPoints + const orgBorderCurveList = new Array() + 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() + + const line1 = Line2d.New(w, 0, w, l) + line1.tagData = block.sealRight + line1.tagData2 = block.holeCountRight() + + const line2 = Line2d.New(w, l, 0, l) + line2.tagData = block.sealTop + line2.tagData2 = block.holeCountTop() + + const line3 = Line2d.New(0, l, 0, 0) + line3.tagData = block.sealLeft + line3.tagData2 = block.holeCountLeft() + + orgBorderCurveList.push(line0) + orgBorderCurveList.push(line1) + orgBorderCurveList.push(line2) + orgBorderCurveList.push(line3) + } + return orgBorderCurveList + } + + /** 创建 开料轮廓 */ + private static createOrgBorder(bd: PlaceBlockDetail): Curve2d[] { + const borders = new Array() + + 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 + } + + /** 创建预铣轮廓 */ + private static creatOrgBorderWithPrevCut(block: PlaceBlock, border: Curve2d[], preSize: number) { + // 矩形板 + const newBorders: Curve2d[] = [] + const newSizeExpand = new SizeExpand() + if (!block.isUnRegular) { + let x1 = 0 + let y1 = 0 + let x2 = block.cutWidth + let y2 = block.cutLength + + if (block.isUnRegular == false) // 矩形板 + { + if (block.sealLeft > 0.001) { + x1 -= preSize + newSizeExpand.left = preSize + } + if (block.sealRight > 0.001) { + x2 += preSize + newSizeExpand.right = preSize + } + if (block.sealBottom > 0.001) { + y1 -= preSize + newSizeExpand.bottom = preSize + } + if (block.sealTop > 0.001) { + y2 += preSize + newSizeExpand.top = preSize + } + } + newBorders.push(Line2d.New(x1, y1, x2, y1)) + newBorders.push(Line2d.New(x2, y1, x2, y2)) + newBorders.push(Line2d.New(x2, y2, x1, y2)) + newBorders.push(Line2d.New(x1, y2, x1, y1)) + newSizeExpand.setSize() + return { newBorders, newSizeExpand } + } + + // 二。异形板 + // 2.1 先分析 每条原始边 是否预铣 + if (!block.blockDetail) { + console.error("创建预铣轮廓时,block.blockDetail 为空", block); + return; + } + const bd = block.blockDetail + for (let i = 0; i < bd.orgPoints.length; i++) { + let s = i - 1 + let j = i + 1 + if (s == -1) + s = bd.orgPoints.length - 1 + if (j == bd.orgPoints.length) + j = 0 + + const p0 = bd.orgPoints[s] + const p1 = bd.orgPoints[i] + const p2 = bd.orgPoints[j] + + p1.isPreCutRequired = checkYX(p0, p1, p2, block.width, block.length) + } + + // 2.1.5 同一边,如果有缺角,则都不能预铣 + for (let i = 0; i < bd.orgPoints.length - 1; i++) { + const j = (i + 1) % bd.orgPoints.length + const pi = bd.orgPoints[i] + const pj = bd.orgPoints[j] + + // if(pi.needPreCut==false) continue; + // 垂直或平行 + let isVertical = equal(pi.pointX, pj.pointX) + if (isVertical == false) { + if (equal(pi.pointY, pj.pointY)) { + isVertical = false + } + else { + continue // 斜线 + } + } + + for (let m = j; m < bd.orgPoints.length; m++) { + const n = (m + 1) % bd.orgPoints.length + + const pm = bd.orgPoints[m] + const pn = bd.orgPoints[n] + + // 垂直 ,同边 有缝隙,多段线 ,不能预铣 + if (isVertical && equal(pi.pointX, pm.pointX) && equal(pi.pointX, pn.pointX)) { + pi.isPreCutRequired = false + pm.isPreCutRequired = false + } + // 水平 ,同边 有缝隙,多段线 ,不能预铣 + if (isVertical == false && equal(pi.pointY, pm.pointY) && equal(pi.pointY, pn.pointY)) { + pi.isPreCutRequired = false + pm.isPreCutRequired = false + } + } + } + + // 2.2 生成预铣轮廓 + for (let i = 0; i < border.length; i++) { + const cur = border[i] + let newCur: Curve2d + if (cur instanceof Arc2d) { + newCur = new Arc2d(cur.StartPoint, cur.EndPoint, cur.Bul) + } + else { + newCur = new Line2d(cur.StartPoint, cur.EndPoint) + } + + if ((newCur instanceof Line2d) && newCur.m_Length < 0.001) + continue + // 曲线肯定不能偏移, + // 直线的话,需要跟 板原始轮廓边 比对确定是否需要偏移 + const curNeedOff = preSize > 0 && needPrev(cur) + if (curNeedOff) { + // 偏移 + const line = cur as Line2d + newCur = line.Offset(preSize) + } + + // 判断前一条边的终点是否 != 当前边的起点,需要加一条连接线 + if (newBorders.length > 0) { + const pLast = newBorders[newBorders.length - 1] + if (equal2Point(pLast.EndPoint, newCur.StartPoint) == false) { + let hasJD = false + if ((pLast instanceof Line2d) && (newCur instanceof Line2d)) { + const p_jds: Point2d[] = [] + newCur.IntersectWith(pLast, p_jds) + if (p_jds.length == 1) { + pLast.EndPoint = new Point2d(p_jds[0].m_X, p_jds[0].m_Y) + newCur.StartPoint = new Point2d(p_jds[0].m_X, p_jds[0].m_Y) + hasJD = true + } + } + if (hasJD == false) // 直接连接 + { + const linkLine = new Line2d(pLast.EndPoint, newCur.StartPoint) + newBorders.push(linkLine) + } + } + } + // 插入新的边 + newBorders.push(newCur) + } + + // 2.3 闭合 最后一边 ,是否与 第一条边 连接 + const firstB = newBorders[0] + const lastB = newBorders[newBorders.length - 1] + if (equal2Point(lastB.EndPoint, firstB.StartPoint) == false) { + // 如果前后都是直线则求交点, 直接延长直线 + let hasJD = false + if ((firstB instanceof Line2d) && (lastB instanceof Line2d)) { + const p_jds: Point2d[] = [] + lastB.IntersectWith(firstB, p_jds) + if (p_jds.length == 1) { + lastB.EndPoint = new Point2d(p_jds[0].m_X, p_jds[0].m_Y) + firstB.StartPoint = new Point2d(p_jds[0].m_X, p_jds[0].m_Y) + hasJD = true + } + } + if (hasJD == false) // 直接连接 + { + const linkLine = new Line2d(lastB.EndPoint, firstB.StartPoint) + newBorders.push(linkLine) + } + } + + // 2.4 生成外扩 + let xz = 0; let xy = block.cutWidth; let yx = 0; let ys = block.cutLength + for (const cur of newBorders) { + const p = cur.StartPoint + if (p.m_X < xz) + xz = p.m_X + if (p.m_X > xy) + xy = p.m_X + if (p.m_Y < yx) + yx = p.m_Y + if (p.m_Y > ys) + ys = p.m_Y + } + newSizeExpand.left = -xz + newSizeExpand.right = xy - block.cutWidth + newSizeExpand.bottom = -yx + newSizeExpand.top = ys - block.cutLength + newSizeExpand.setSize() + + return { newBorders, newSizeExpand } + + // 判断外轮廓点p1是否需要预铣 + function checkYX(p0, p1, p2, width, length) { + if (p1.curve != 0) + return false // 本身是圆弧 + if (p1.sealSize < 0.001) + return false// 本身不封边 + if (p0.curve != 0) + return false // 前一段是圆弧 + if (p2.curve != 0) + return false// 后一段是圆弧 + // p1.p2 只要有一点在板内,就不行 + const isIn1 = (p1.pointX > 0.001 && p1.pointX < width - 0.001 && p1.pointY > 0.001 && p1.pointY < length - 0.001) + if (isIn1) + return false + const isIn2 = (p2.pointX > 0.001 && p2.pointX < width - 0.001 && p2.pointY > 0.001 && p2.pointY < length - 0.001) + if (isIn2) + return false + return true // 需要预洗 + } + + // 判断边是否预铣 + function needPrev(line: Curve2d) { + if (!block.blockDetail) { + console.error("判断边是否预铣时,block.blockDetail 为空", block); + return false; + } + if (!(line instanceof Line2d)) + return false + + if (block.isUnRegular == false) // 矩形 + { + if (line.StartPoint.m_Y == 0 && line.EndPoint.m_Y == 0) + return block.sealBottom > 0 + if (line.StartPoint.m_X == block.cutWidth && line.EndPoint.m_X == block.cutWidth) + return block.sealRight > 0 + if (line.StartPoint.m_Y == block.cutLength && line.EndPoint.m_Y == block.cutLength) + return block.sealTop > 0 + if (line.StartPoint.m_X == 0 && line.EndPoint.m_X == 0) + return block.sealLeft > 0 + return false + } + + const offx = block.blockDetail.offsetX + const offy = block.blockDetail.offsetY + + const x1 = line.StartPoint.m_X + const y1 = line.StartPoint.m_Y + const x2 = line.EndPoint.m_X + const y2 = line.EndPoint.m_Y + const tag0 = (x2 - x1) / getDis(x1, y1, x2, y2) + + // 找出原始轮廓中 对应的边 , 是否有 预洗信息 + for (let i = 0; i < block.orgPoints.length; i++) { + let j = i + 1 + if (j == block.orgPoints.length) + j = 0 + if (block.orgPoints[i].isPreCutRequired == false) + continue + const tag1 = (block.orgPoints[j].pointX - block.orgPoints[i].pointX) / getDis(block.orgPoints[i].pointX, block.orgPoints[i].pointY, block.orgPoints[j].pointX, block.orgPoints[j].pointY) + if (equal(tag0, tag1, 0.1) == false) + continue // 不平行 + + const dis = getDis_PointLine(x1, y1, x2, y2, block.orgPoints[i].pointX - offx, block.orgPoints[i].pointY - offy) + if (dis < block.orgPoints[i].sealSize * 2 + 3) + return true + } + + return false + } + + function equal2Point(p1, p2, dis = 0.001) { + const x1 = p1.m_X + const y1 = p1.m_Y + const x2 = p2.m_X + const y2 = p2.m_Y + const len1 = (x1 - x2) * (x1 - x2) + const len2 = (y1 - y2) * (y1 - y2) + const len = Math.sqrt(len2 + len1) + return len < dis + } + } + + /** 创建 挖穿造型,走刀路径,板内空间 */ + private static analyeInners(bd: PlaceBlockDetail, cutR, cutGap = 1) { + /** 挖穿轮廓 */ + const borders_inner_org: Curve2d[][] = [] + /** 挖穿轮廓 刀半径 */ + const borders_inner_r: number[] = [] + /** 挖穿走刀路径 */ + const borders_inner_cut: Curve2d[][] = [] + /** 挖穿排版轮廓 <加缝隙> */ + const borders_inner_place: Curve2d[][] = [] + /** 排版空间 */ + const spaces: PlaceSpace[] = [] + for (const m of bd.models) { + if (m.depth < bd.thickness - 0.01) + continue // 非挖穿 + if (m.isVKnifeModel()) + continue // 挖穿的二维刀路造型 不计算. + if (m.isCutting == false) + continue // 不加工 + + // 挖穿轮廓 <切割后的>,顺时针的 + let baseBorder + if (m.hasContour()) // 有轮廓数据 + { + let ptsArr = m.originModeling.outline.map(e => e.pts) + const pts = ptsArr.map((t) => { return { x: t.x, y: t.y } }) + for (let i = 0; i < pts.length; i++) { + pts[i].bul = m.originModeling.outline[i].buls + } + baseBorder = this.createBorderByPts(pts) + } + else // 没有轮廓数据,用走刀路径 偏移 + { + const pts = m.pointList.map((t) => { return { x: t.pointX, y: t.pointY, bul: t.curve } }) + baseBorder = this.createBorderByPts(pts) + baseBorder = this.offsetCurves(baseBorder, m.knifeRadius, false) + } + + // 铣刀路径 + const border_cut = this.offsetCurves(baseBorder, -m.realKnifeRadius, false) + + // 排版轮廓 + const placeGap = Math.max(m.realKnifeRadius, cutR) * 2 - cutR + cutGap / 2 // 取造型刀,开料刀 最大的 + const border_place = this.offsetCurves(baseBorder, -placeGap, false) + + borders_inner_org.push(baseBorder) + borders_inner_r.push(m.realKnifeRadius) + borders_inner_cut.push(border_cut) + borders_inner_place.push(border_place) + + // 内部空间 + let createdSpaces = false + if (m.hasContour()) // 简单形状,直接生成矩形,圆形 + { + // 是否矩形,圆形 + const rect = isRect(m.originModeling.outline) || isCircle(m.originModeling.outline) + if (rect) { + const gap = placeGap + rect.x += gap + rect.y += gap + rect.w -= gap * 2 + rect.l -= gap * 2 + if (rect.w > 100 && rect.l > 100) { + const space = new PlaceSpace(rect.x, rect.y, rect.w, rect.l) + space.isBlockInnerSpace = true + spaces.push(space) + } + createdSpaces = true + } + } + + if (createdSpaces == false) // 复杂形状 + { + const tmpSpaces = SpacePlus.borderToSpace(border_place) + if (tmpSpaces) { + tmpSpaces.forEach(t => t.isBlockInnerSpace = true) + tmpSpaces.forEach(t => spaces.push(t)) + } + } + } + return { borders_inner_org, borders_inner_r, borders_inner_cut, borders_inner_place, spaces } + + function isRect(outline: any): any { + const pts = outline.map(e => e.pts) //outline.pts + const buls = outline.map(e => e.buls) // outline.buls + + if (buls.length != 5) + return null + if (buls.some(t => t != 0)) + return null + + if (pts.length != 5) + return null + if (pts[4].x != pts[0].x || pts[4].y != pts[0].y) + return null + + return getRect(pts[0], pts[1], pts[2], pts[3]) + } + function isCircle(outline: any): any { + // const pts = outline.pts + // const buls = outline.buls + const pts = outline.map(e => e.pts) //outline.pts + const buls = outline.map(e => e.buls) // outline.buls + + + if (buls.length != 3) + return null + if (equal(buls[0], 1) && equal(buls[1], 1) && equal(buls[2], 0)) { + const p0 = Array.isArray(outline) ? outline[0].pts : outline?.pts[0] + const p1 = Array.isArray(outline) ? outline[1].pts : outline?.pts[1] + let x0 = Math.min(p0.x, p1.x) + let x1 = Math.max(p0.x, p1.x) + let y0 = Math.min(p0.y, p1.y) + let y1 = Math.max(p0.y, p1.y) + + let r + if (equal(x0, x1)) { + r = (y1 - y0) / 2 + x0 -= r + x1 += r + } + else { + r = (x1 - x0) / 2 + y0 -= r + y1 += r + } + const t = r / Math.SQRT2 + const dis = r - t + return { x: x0 + dis, y: y0 + dis, w: t * 2, l: t * 2 } + } + } + } + + /** 创建 缺角空间 */ + private static createOutSpaces(block: PlaceBlock, placeMaterial: PlaceMaterial): PlaceSpace[] { + if (block.blockDetail == undefined) { + console.error("创建缺角空间时,block.blockDetail 为空", block); + return []; + } + if (block.blockDetail.borderContour == null) { + console.error("创建缺角空间时,block.blockDetail.borderContour 为空", block); + return []; + } + if (block.isUnRegular == false && (block.blockDetail.borderContour.polylines2vModel.length + block.blockDetail.borderContour.polylinesOutModel.length == 0)) + return [] // 矩形 + const pm = placeMaterial + + let border = this.getBorder_WZYH(block, placeMaterial, false) // 获得全偏移的轮廓 + const pl = this.borderToPolyline(border, true) + + /** 空间 */ + let spaces: PlaceSpace[] = [] + const reg = { z: 0, x: 0, y: 0, s: 0, w: 0, l: 0 } // 外围空间 左右上下 ,宽长 + reg.z = pl.BoundingBox.min.x + reg.x = pl.BoundingBox.min.y + reg.y = pl.BoundingBox.max.x + reg.s = pl.BoundingBox.max.y + reg.w = -reg.z + reg.y + reg.l = -reg.x + reg.s + + const checkSize = new SizeExpand() // 空间验证 + checkSize.left = -block.sizeExpand()?.left - (pm.diameter + pm.cutKnifeGap) / 2 + checkSize.right = block.cutWidth + block.sizeExpand()?.right + (pm.diameter + pm.cutKnifeGap) / 2 + checkSize.bottom = -block.sizeExpand()?.bottom - (pm.diameter + pm.cutKnifeGap) / 2 + checkSize.top = block.cutLength + block.sizeExpand()?.top + (pm.diameter + pm.cutKnifeGap) / 2 + + const offDis = pm.preMillingSize + 0.01 // 点判断是否在边缘 误差 一个预铣值 + + border = border.slice(0) + // 确保 第一条 起点在边缘 + let count = 0 // 移动不能大于一个循环,避免死循环 + while (count < border.length) { + const line0 = border[0] + const isOnBorder = onBorder(line0.StartPoint) + if (isOnBorder) + break + // 如果起点在板内,移动到最后面 + border.shift() + border.push(line0) + count++ + } + SpacePlus.setSpaceStyle(block) + let lines: Curve2d[] = [] // + for (let i = 0; i < border.length; i++) { + const line = border[i] + const spOnBorder = onBorder(line.StartPoint) + const epOnBorder = onBorder(line.EndPoint) + const isxl = isXL(line) + if (spOnBorder && (!epOnBorder || isxl)) // 起始线 + { + lines = [] + lines.push(line) + } + if (!spOnBorder && !epOnBorder) // 板内线 + { + if (lines && lines.length > 0 && lines[lines.length - 1] != line) + lines.push(line) + } + if (epOnBorder && (!spOnBorder || isxl)) // 结束线,开始分析空间 + { + if (lines && lines.length > 0) { + if (lines[lines.length - 1] != line) + lines.push(line) + const newspaces = createSpace(lines) + if (newspaces && newspaces.length > 0) + spaces = spaces.concat(newspaces) + lines = [] + } + } + } + const newSpaces: PlaceSpace[] = [] + // 空间验证 + for (const space of spaces) { + let x1 = space.x + let y1 = space.y + let x2 = space.topX() + let y2 = space.topY() + + if (x1 < checkSize.left) + x1 = checkSize.left + if (y1 < checkSize.bottom) + y1 = checkSize.bottom + if (x2 > checkSize.right) + x2 = checkSize.right + if (y2 > checkSize.top) + y2 = checkSize.top + + const newspace = PlaceSpace.create(x1, y1, x2, y2) + newSpaces.push(newspace) + } + + return newSpaces + + /** 缺角的开始? 起点在边缘,终点在板内的 或 斜线 (终点在另外一边) */ + function isBegin(curve: Curve2d) { return onBorder(curve.StartPoint) && (!onBorder(curve.EndPoint) || isXL(curve)) } + /** 缺角的结束? 终点在边缘的 起点在板内 或 (起点点在另外一边) */ + function isEnd(curve: Curve2d) { return onBorder(curve.EndPoint) && (!onBorder(curve.StartPoint) || isXL(curve)) } + /** 在板内 */ + function inBorder(curve: Curve2d) { return !onBorder(curve.StartPoint) && !onBorder(curve.EndPoint) } + + /** 点是否在边缘 */ + function onBorder(p: Point2d) { return equal(p.m_X, reg.z, offDis) || equal(p.m_X, reg.y, offDis) || equal(p.m_Y, reg.s, offDis) || equal(p.m_Y, reg.x, offDis) } + /** 斜线 */ + function isXL(line: Curve2d) { return !equal(line.StartPoint.m_X, line.EndPoint.m_X) && !equal(line.StartPoint.m_Y, line.EndPoint.m_Y) } + /** 线是边缘 */ + function isBorderLine(line: Line2d) { return !isXL(line) && onBorder(line.StartPoint) && onBorder(line.EndPoint) } + /** 分析空间 */ + function createSpace(curves: Curve2d[]): PlaceSpace[] { + if (curves.length == 0) + return [] + + // 1.补边,闭合轮廓 + const sp = curves[0].StartPoint + const ep = curves[curves.length - 1].EndPoint + const pushPts: Point2d[] = [] + pushPts.push(ep) + const sb = getEdge(sp) // 起点的边 + const eb = getEdge(ep) // 终点的边 + if (sb == -1 || eb == -1) + return []// 不在边上 + // 在同一边 的缺角 + if (sb == eb) { + if (equal(sp.m_X, ep.m_X, offDis) && sp.m_X != ep.m_X) // 消除误差 + { + let x = Math.max(sp.m_X, ep.m_X) + if (sp.m_X > block.width / 2) + x = Math.min(sp.m_X, ep.m_X) + sp.m_X = x + ep.m_X = x + } + if (equal(sp.m_Y, ep.m_Y, offDis) && sp.m_Y != ep.m_Y) // 消除误差 + { + let y = Math.max(sp.m_Y, ep.m_Y) + if (sp.m_Y > block.length / 2) + y = Math.min(sp.m_Y, ep.m_Y) + sp.m_Y = y + ep.m_Y = y + } + } + // 右下角 + else if (sb == 0 && eb == 1) { + const cp = new Point2d(ep.m_X, sp.m_Y) + pushPts.push(cp) + } + // 右上角 + else if (sb == 1 && eb == 2) { + const cp = new Point2d(sp.m_X, ep.m_Y) + pushPts.push(cp) + } + // 左上角 + else if (sb == 2 && eb == 3) { + const cp = new Point2d(ep.m_X, sp.m_Y) + pushPts.push(cp) + } + // 左下角 + else if (sb == 3 && eb == 0) { + const cp = new Point2d(sp.m_X, ep.m_Y) + pushPts.push(cp) + } + // 起点下边 -> 终点上边 //因为倒角 + else if (sb == 0 && eb == 2) { + const cp1 = new Point2d(reg.y, ep.m_Y) + const cp2 = new Point2d(reg.y, sp.m_Y) + pushPts.push(cp1) + pushPts.push(cp2) + } + // 起点右边 -> 终点左边 + else if (sb == 1 && eb == 3) { + const cp1 = new Point2d(ep.m_X, reg.s) + const cp2 = new Point2d(sp.m_X, reg.s) + pushPts.push(cp1) + pushPts.push(cp2) + } + // 起点上边 -> 终点下边 + else if (sb == 2 && eb == 0) { + const cp1 = new Point2d(reg.z, ep.m_Y) + const cp2 = new Point2d(reg.z, sp.m_Y) + pushPts.push(cp1) + pushPts.push(cp2) + } + // 起点左边 -> 终点右边 + else if (sb == 3 && eb == 1) { + const cp1 = new Point2d(ep.m_X, reg.x) + const cp2 = new Point2d(sp.m_X, reg.x) + pushPts.push(cp1) + pushPts.push(cp2) + } + // 起点下边 -> 终点左边 + else if (sb == 0 && eb == 3) { + const cp1 = new Point2d(reg.z, reg.s) + const cp2 = new Point2d(reg.y, reg.s) + const cp3 = new Point2d(reg.y, reg.x) + pushPts.push(cp1) + pushPts.push(cp2) + pushPts.push(cp3) + } + // 起点右边 -> 终点下边 + else if (sb == 1 && eb == 0) { + const cp1 = new Point2d(reg.z, reg.x) + const cp2 = new Point2d(reg.z, reg.s) + const cp3 = new Point2d(reg.y, reg.s) + pushPts.push(cp1) + pushPts.push(cp2) + pushPts.push(cp3) + } + // 起点上 -> 终点右边 + else if (sb == 2 && eb == 1) { + const cp1 = new Point2d(reg.y, reg.x) + const cp2 = new Point2d(reg.z, reg.x) + const cp3 = new Point2d(reg.z, reg.s) + pushPts.push(cp1) + pushPts.push(cp2) + pushPts.push(cp3) + } + // 起点左 -> 终点上边 + else if (sb == 3 && eb == 2) { + const cp1 = new Point2d(reg.y, reg.s) + const cp2 = new Point2d(reg.y, reg.x) + const cp3 = new Point2d(reg.z, reg.x) + pushPts.push(cp1) + pushPts.push(cp2) + pushPts.push(cp3) + } + else // 判断不了 缺口 的起点终点 范围 + { + return [] + } + pushPts.push(sp) + for (let i = 0; i < pushPts.length - 1; i++) // 闭合缺口 + { + const newLine = new Line2d(pushPts[i], pushPts[i + 1]) + curves.push(newLine) + } + + // 3 目前轮廓顺时针,必须转成 逆时针 + curves = SpacePlus.reverseCurves(curves) + + // 4. 分析空间 analyeSpace + const tmpSpaces = SpacePlus.borderToSpace(curves) || [] + + return tmpSpaces + } + + /** 获得点所在的边 getB */ + function getEdge(sp) { + if (equal(sp.m_Y, reg.x, offDis)) + return 0 + if (equal(sp.m_X, reg.y, offDis)) + return 1 + if (equal(sp.m_Y, reg.s, offDis)) + return 2 + if (equal(sp.m_X, reg.z, offDis)) + return 3 + return -1 + } + + /** 设置方向 */ + function setDirect(line: Line2d) { + let fx = -1 // 向右 0,向上 1,向左 2,向下 3 ,右上 4,左上5,左下6,右下 7 + if (line.EndPoint.m_X > line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) { + fx = 0 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) { + fx = 2 + } + else if (line.EndPoint.m_Y > line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) { + fx = 1 + } + else if (line.EndPoint.m_Y < line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) { + fx = 3 + } + else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) { + fx = 4 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) { + fx = 5 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) { + fx = 6 + } + else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) { + fx = 7 + } + line.fx = fx + return fx > -1 + } + } + + /** 创建 普通造型外扩的 多段线 */ + public static createPolyline_model(block: PlaceBlock) { + if (!block.blockDetail) { + console.warn("PlaceBlock没有blockDetail,无法创建造型轮廓") + return { pls_model: [], sizeout: new SizeExpand() } + } + const pl_block = this.getOrgPolyline(block) + const pls_model: Polyline[] = [] + for (const model of block.blockDetail.models) { + if (model.originModeling == undefined) { + continue + } + let _pts: any[] = [] + let _buls: any[] = [] + if (Array.isArray(model.originModeling.outline)) { + _pts = model.originModeling.outline.map(e => e.pts) + _buls = model.originModeling.outline.map(e => e.buls) + } else { + _pts = model.originModeling.outline.pts + _buls = model.originModeling.outline.buls + } + + + + + if (model.isCutting == false) + continue + if (model.isVKnifeModel()) + continue + if (model.depth > block.thickness - 0.01) + continue + if (model.pointList.length < 2) + continue + if (model.hasContour() == false) + continue + if (model.originModeling == null) + continue + if (model.originModeling.outline == null) + continue + if (!Array.isArray(_pts)) + continue + if (_pts.length < 1) + continue + if (model.knifeRadius < 0.001) + continue + const pl_model = PolylineHelper.createByPts(_pts, _buls, true) + const pl_off = pl_model.GetOffsetCurves(model.knifeRadius)[0] + if (pl_off == null) + continue + if (pl_block.IntersectWith(pl_off, 0).length == 0) + continue // 造型轮廓偏移一个刀半径 ,没有与板轮廓相交 + + // 有可能超出板外 + const pts: any[] = [] + const p0 = model.pointList[0] + let isClose = false + for (let i = 0; i < model.pointList.length; i++) { + const p = model.pointList[i] + + if (i == 0) { + pts.push({ x: p.pointX, y: p.pointY, bul: p.curve }) + continue + } + const pp = pts[pts.length - 1] + if (getDis2(pp, p) < 0.1) + pts.pop() // 与前一点,接近去掉前一点,避免重合 + pts.push({ x: p.pointX, y: p.pointY, bul: p.curve }) + if (i == model.pointList.length - 1) + break // 完成了 + isClose = getDis2(p0, p) < 0.1 + if (isClose) // 闭环且后面的点都在环内,那后面的点就不需要考虑了 + { + const pl = PolylineHelper.create(pts, true) + let isAllIn = true + for (let j = i + 1; j < model.pointList.length; j++) { + const pn = model.pointList[j] + isAllIn = pl.PtInCurve(new Vector3(pn.pointX, pn.pointY, 0)) + if (isAllIn == false) + break + } + if (isAllIn) + break + } + } + + try { + this.createModelPLS(pl_block, pls_model, pts, model.knifeRadius) + } + catch { + continue + } + } + + // 计算外扩偏移 + let xz = 0; let xy = block.cutWidth; let yx = 0; let ys = block.cutLength + for (const pl of pls_model) { + if (pl.BoundingBox.min.x < xz) + xz = pl.BoundingBox.min.x + if (pl.BoundingBox.min.y < yx) + yx = pl.BoundingBox.min.y + if (pl.BoundingBox.max.x > xy) + xy = pl.BoundingBox.max.x + if (pl.BoundingBox.max.y > ys) + ys = pl.BoundingBox.max.y + } + + const sizeout_model = new SizeExpand() + if (xz < 0) + sizeout_model.left = -xz + if (xy > block.cutWidth) + sizeout_model.right = xy - block.cutWidth + if (yx < 0) + sizeout_model.bottom = -yx + if (ys > block.cutLength) + sizeout_model.top = ys - block.cutLength + sizeout_model.setSize() + + return { pls_model, sizeout: sizeout_model } + } + + /** 创建 2v板外下刀的 多段线 */ + private static createPolyline_2vModel(block: PlaceBlock) { + if (!block.blockDetail) { + console.warn("PlaceBlock没有blockDetail,无法创建造型轮廓") + return { pls_model: [], sizeout: new SizeExpand() } + } + const pls_2v: Polyline[] = [] + for (const model of block.blockDetail.models) { + + if (model.isVKnifeModel() == false) + continue + if (model.isCutting == false) + continue + // if(model.depth > block.thickness - 0.01) continue; + const pl_block = this.getOrgPolyline(block) + for (const vline of model.VLines) { // knifeRadius 刀半径, depth深度, points{x,y,z,bul,r} + const r = vline.knifeRadius + if (r < 0.001) + continue + const pts = vline.points.map((t) => { return { x: t.x, y: t.y, bul: t.bul } }) + try { + this.createModelPLS(pl_block, pls_2v, pts, r, true) + } + catch { + continue + } + } + } + + // 计算外扩偏移 + let xz = 0; let xy = block.cutWidth; let yx = 0; let ys = block.cutLength + for (const pl of pls_2v) { + if (pl.BoundingBox.min.x < xz) + xz = pl.BoundingBox.min.x + if (pl.BoundingBox.min.y < yx) + yx = pl.BoundingBox.min.y + if (pl.BoundingBox.max.x > xy) + xy = pl.BoundingBox.max.x + if (pl.BoundingBox.max.y > ys) + ys = pl.BoundingBox.max.y + } + + const sizeout_2v = new SizeExpand() + if (xz < 0) + sizeout_2v.left = -xz + if (xy > block.cutWidth) + sizeout_2v.right = xy - block.cutWidth + if (yx < 0) + sizeout_2v.bottom = -yx + if (ys > block.cutLength) + sizeout_2v.top = ys - block.cutLength + sizeout_2v.setSize() + + return { pls_2v, sizeout_2v } + } + + /** + * 创建超出板外的造型轮廓 + * + */ + private static createModelPLS(pl_block: Polyline, pls_model: Polyline[], points: any[], r: number, is2V = false) { + const lines: any = [] + + // 23.10.9 发现普通造型还是有自交的线段,强制 分段处理 + for (let i = 0; i < points.length - 1; i++) { + const j = i + 1 + const p1 = { x: points[i].x, y: points[i].y, bul: points[i].bul } + const p2 = { x: points[j].x, y: points[j].y, bul: 0 } + lines.push([p1, p2]) + } + // 对 每段line 进行 偏移多半径,获取闭合多段线 + const pls: Polyline[] = [] + for (const pts of lines) { + // 1. pts 不闭合,偏移0.5 + // 原路回来,组合成闭合线 + const l = pts.length - 1 + pts[l].bul = 0 // 确保最后一点 是直线点 + const isclose = equal(pts[0].x, pts[l].x) && equal(pts[0].y, pts[l].y) + if (isclose == false) // + { + const pl_0 = PolylineHelper.create(pts, false) + const pl_1s = pl_0.GetOffsetCurves(0.5) + let pl_1 = pl_1s[0] + if (pl_1 == null) + continue + pl_1 = pl_1.Reverse() + for (const p of pl_1.LineData) { + pts.push({ x: p.pt.x, y: p.pt.y, bul: p.bul }) + } + } + + // 2 偏移刀r后的 轮廓 + let pl_vline = PolylineHelper.create(pts, true) + if (pl_vline.IsClockWise) + pl_vline = pl_vline.Reverse() + const pl_off = pl_vline.GetOffsetCurves(r)[0] + if (pl_off == null) + continue + // if(pl_off.CloseMark == false) ; + pls.push(pl_off) + } + + if (pls.length == 0) + return + // 对 多段线进行 合并,获得最后 造型轮廓 + let pl: Polyline | undefined = pls.shift() + if (pl == undefined || pl == null) { + console.warn("创建造型轮廓失败,pls数组为空") + return + } + if (pls.length > 0) { + pl = this.unionPolylines(pl, pls) + } + + // 3 造型轮廓 与 板轮廓干涉 + if (pl_block.IntersectWith(pl, 0).length > 0) { + pls_model.push(pl) + } + + // 4 2V 轮廓 可能包含了 板轮廓 + if (pl.BoundingBox.min.x <= pl_block.BoundingBox.min.x + && pl.BoundingBox.min.y <= pl_block.BoundingBox.min.y + && pl.BoundingBox.max.x >= pl_block.BoundingBox.max.x + && pl.BoundingBox.max.y >= pl_block.BoundingBox.max.y) { + pls_model.push(pl) + } + } + + /** 创建 点阵pts ,在 板边缘 干涉到的 区域 */ + private static createInterPLS(pl_block: Polyline, pts: any[], r: number, pls_model: Polyline[]) { + const pl_vline = PolylineHelper.create(pts) + let curves + curves = pl_vline.Explode() // 分解成 一段 一段的 + + const outlines: any = [] // 当前在板外的线段 + for (let i = 0; i < curves.length;) // 判断 有没有交集 + { + const curve = curves[i] + const pos = getRelation(pl_block, curve, r) + + if (pos[0] == -1) { + if (outlines.length > 0) + createPL_outLines(outlines, pls_model, r) + } + else if (pos[0] == 0 || pos[0] == 1) // 板外,或交接 + { + outlines.push(curve) + } + else // 多个连接点 。,要拆分 多段,处理 + { + const childs = curve.GetSplitCurves(0.2) + curves.splice(i, 1) + for (let j = childs.length - 1; j >= 0; j--) { + const child = childs[j] + curves.splice(i, 0, child) + } + continue + } + i++ + } + + // 最后一条线段可能是板外,那就要补 板外线段生成 + if (outlines.length > 0) + createPL_outLines(outlines, pls_model, r) + + /** 判断 line 与 polyline 的关系 -1 板内,0 板外 ,> 0 相交点数 */ + function getRelation(pl_block, curve, r) { + const xjd = pl_block.IntersectWith(curve, 0) + if (xjd.length > 0) + return [xjd.length, xjd] // 相交点 + + // line 的 4 个点 + const box = curve.BoundingBox + const x0 = box.min.x - r + const y0 = box.min.y - r + const x1 = box.max.x + r + const y1 = box.max.y + r + + const p0 = pl_block.PtInCurve(new Vector3(x0, y0)) + const p1 = pl_block.PtInCurve(new Vector3(x1, y0)) + const p2 = pl_block.PtInCurve(new Vector3(x1, y1)) + const p3 = pl_block.PtInCurve(new Vector3(x0, y1)) + + if (p0 && p1 && p2 && p3) + return [-1, []] // 板内 + + if (!p0 && !p1 && !p2 && !p3) + return [0, []] // 板外 + return [0, []] + } + + function createPL(x0, y0, x1, y1) { + const pts: any[] = [] + pts.push({ x: x0, y: y0 }) + pts.push({ x: x1, y: y0 }) + pts.push({ x: x1, y: y1 }) + pts.push({ x: x0, y: y1 }) + const pl_line = PolylineHelper.create(pts, true) + return pl_line + } + + function createPL_outLines(outlines, pls_model, r) { + let xz = 28000; let xy = -100000; let yx = 2900000; let ys = -100000 + for (const curve of outlines) { + const box = curve.BoundingBox + const x0 = box.min.x - r - 1 + const y0 = box.min.y - r - 1 + const x1 = box.max.x + r + 1 + const y1 = box.max.y + r + 1 + if (x0 < xz) + xz = x0 + if (x1 > xy) + xy = x1 + if (y0 < yx) + yx = y0 + if (y1 > ys) + ys = y1 + } + const pl_line = createPL(xz, yx, xy, ys) + pls_model.push(pl_line) + outlines.splice(0) // 清空,可能下面 还有 板外的线段 + } + } + + /** 创建 轮廓 pts : {x,y,bul}[] */ + static createBorderByPts(pts: any[]): Curve2d[] { + const borders: Curve2d[] = [] + for (let i = 0; i < pts.length; i++) { + let j = i + 1 + if (j == pts.length) + j = 0 + const pi = pts[i] + const pj = pts[j] + if (pi.bul == 0) { + const line = new Line2d(new Point2d(pi.x, pi.y), new Point2d(pj.x, pj.y)) + borders.push(line) + } + else { + const arc = new Arc2d(new Point2d(pi.x, pi.y), new Point2d(pj.x, pj.y), pi.bul) + borders.push(arc) + } + } + return borders + } + + /** 翻转 多线段 */ + static transformBorder(borders: Curve2d[], placeStyle: PlaceStyle, width: number, length: number, blockNo?: any): Curve2d[] { + // console.log('翻转 多线段') + const newBorders: Curve2d[] = [] + for (let i = 0; i < borders.length; i++) { + const border = borders[i] + const np1 = getPlacePosition(border.StartPoint.m_X, border.StartPoint.m_Y, width, length, placeStyle) + const np2 = getPlacePosition(border.EndPoint.m_X, border.EndPoint.m_Y, width, length, placeStyle) + let p1 = new Point2d(np1.x, np1.y) + let p2 = new Point2d(np2.x, np2.y) + if (placeStyle >= 4) + [p1, p2] = [p2, p1] + let newBorder + if (border instanceof Arc2d) { + newBorder = new Arc2d(p1, p2, border.Bul) + } + else { + newBorder = new Line2d(p1, p2) + } + if (border.tagData != undefined || border.tagData != null) { + newBorder.tagData = border.tagData + newBorder.tagData2 = border.tagData2 + } + newBorders.push(newBorder) + } + if (placeStyle >= 4) + newBorders.reverse() + return newBorders + } + + /** 翻转 多线段 */ + static transformBorderNew(borders: Curve2d[], placeStyle: PlaceStyle, width: number, length: number): Curve2d[] { + + const newBorders: Curve2d[] = [] + for (let i = 0; i < borders.length; i++) { + const border = borders[i] + const np1 = getPlacePosition(border.StartPoint.m_X, border.StartPoint.m_Y, width, length, placeStyle) + const np2 = getPlacePosition(border.EndPoint.m_X, border.EndPoint.m_Y, width, length, placeStyle) + let p1 = new Point2d(np1.x, np1.y) + let p2 = new Point2d(np2.x, np2.y) + if (placeStyle >= 4) + [p1, p2] = [p2, p1] + let newBorder + if (border instanceof Arc2d) { + newBorder = new Arc2d(p1, p2, border.Bul) + } + else { + newBorder = new Line2d(p1, p2) + } + if (border.tagData != undefined || border.tagData != null) { + newBorder.tagData = border.tagData + newBorder.tagData2 = border.tagData2 + } + newBorders.push(newBorder) + } + if (placeStyle >= 4) + newBorders.reverse() + + return newBorders + } + + /** 翻转 多段线 */ + static transformPolyline(pl: Polyline, placeStyle: PlaceStyle, width: number, length: number): Polyline { + const bx = pl.Position.x + const by = pl.Position.y + let pts = pl.LineData.map((t) => { return { x: t.pt.x, y: t.pt.y, bul: t.bul } }) + pts.forEach((t) => { t.x += bx; t.y += by }) + + // 翻面,翻转点阵 + if (placeStyle > 3) { + const newPts: any[] = [] + for (let j = pts.length - 1; j >= 0; j--) { + let i = j - 1 + if (i < 0) + i = pts.length - 1 + const x = pts[j].x + const y = pts[j].y + const bul = pts[i].bul + newPts.push({ x, y, bul }) + } + pts = newPts + } + + // 转换坐标 + for (let i = 0; i < pts.length; i++) { + const p = pts[i] + const np = getPlacePosition(p.x, p.y, width, length, placeStyle) + p.x = np.x + p.y = np.y + } + + const npl = PolylineHelper.create(pts) + npl.CloseMark = true + return npl + } + + /** 偏移 多段线 */ + static offsetCurves(borders: Curve2d[], offvalue: number, isOffsetRounding: boolean): Curve2d[] { + if (equal(offvalue, 0)) + return borders + const pts: any[] = [] + for (const cur of borders) { + const x = cur.StartPoint.m_X + const y = cur.StartPoint.m_Y + const bul = (cur instanceof Arc2d) ? cur.Bul : 0 + pts.push({ x, y, bul }) + } + if (borders.length != 0) { + pts.push({ x: borders[0].StartPoint.m_X, y: borders[0].StartPoint.m_Y, bul: 0 }) + } + + const pl = PolylineHelper.create(pts) + // 偏移 倒角:直角不倒角 2023.10.27 QQ陈雄 + const pls = isOffsetRounding ? pl.GetFeedingToolPath(offvalue, 0) : pl.GetOffsetCurves(offvalue) + if (pls.length == 0) + return borders + const npls = pls[0] // 偏移多段线 + const ita = ConverToPolylineAndSplitArc(npls, false, true) // 拆分多段线 + + const offsetCurves: Curve2d[] = [] + for (let i = 0; i < ita.pts.length; i++) { + let j = i + 1 + if (j == ita.pts.length) + j = 0 + const p1 = ita.pts[i] + const p2 = ita.pts[j] + const np1 = new Point2d(p1.x, p1.y) + const np2 = new Point2d(p2.x, p2.y) + + if (ita.buls[i] == 0) { + const line = new Line2d(np1, np2) + if (line.m_Length < 0.01) + continue + offsetCurves.push(line) + } + else { + const arc = new Arc2d(np1, np2, ita.buls[i]) + if (arc.m_Radius < 0.01) + continue + offsetCurves.push(arc) + } + } + + return offsetCurves + } + + /** 轮廓 转成 多断线 */ + static borderToPolyline(border: Curve2d[], CloseMark = true): Polyline { + return PolylineHelper.createByCurve2d(border, CloseMark) + } + + /** 多段线 转成 轮廓 */ + static polylineToBorder(pl: Polyline): Curve2d[] { + const pts = pl.LineData.map((t) => { return { x: t.pt.x, y: t.pt.y, bul: t.bul } }) + return this.createBorderByPts(pts) + } + + /** 合并 多段线 */ + static unionPolylines(pl: Polyline, childs: Polyline[]): Polyline { + const strPLS = PolylineHelper.getStrPLs([pl].concat(childs)) + + if (childs.length == 0) + return pl + + try { + const contour1 = Contour.CreateContour(pl) + + const reg1 = new ShapeManager([new Shape(contour1)]) + for (const c of childs) { + const contour2 = Contour.CreateContour(c, false) + if (contour2 == null) { + return pl + } else { + PolylineHelper.getArcRadius + const reg2 = new ShapeManager([new Shape(contour2)]) + reg1.UnionBoolOperation(reg2) + } + + } + + if (reg1.ShapeList.length == 0) + return pl + + let newStr = '' + const pls: Array = [] + for (const sh of reg1.ShapeList) { + pls.push(sh.Outline.Curve) + } + newStr = PolylineHelper.getStrPLs(pls) + + const cur = reg1.ShapeList[0].Outline.Curve + let newpl: Polyline + if (cur instanceof Circle) { + newpl = PolylineHelper.cicleToPolyline(cur) + } + else { + newpl = cur + } + newStr = PolylineHelper.getStrPLs([newpl]) + return PolylineHelper.resetPosition(newpl) + } + catch (err) { + console.log(err); + throw new Error('有非常规造型或2v刀路在合并空间时发生错误,请导出优化结果cfdat文件,并与软件服务商联系!') + } + } + + /** 小板 标签位置 */ + static resetLabelPos(b: PlaceBlock) { + if (!b.blockDetail) { + console.warn("PlaceBlock没有blockDetail,无法计算标签位置") + return + } + if (b.blockDetail.labelPosX != -1) + return // 已计算 + + const pls = this.getOrgPloylines(b, false) + const pl = pls.pl + const mpls = pls.pls_inner + + const x = b.cutWidth / 2 + const y = b.cutLength / 2 + + b.blockDetail.labelPosX = x + b.blockDetail.labelPosY = y + + // 矩形 且没有挖穿造型 + if (b.isUnRegular == false && mpls.length == 0) + return + + const vp = getValidPos() + if (vp) { + b.blockDetail.labelPosX = vp.x + b.blockDetail.labelPosY = vp.y + } + + // 从中心点出发,左右上下偏移,求点。 + function getValidPos() { + const off = 40 + const offw = x / off + const offl = y / off + + let nx = 0; let ny = 0 + for (let i = 0; i < offw; i++) { + for (let j = 0; j < offl; j++) { + nx = x + i * off + ny = y + j * off + if (isInPolyline(pl, mpls, nx, ny)) + return { x: nx, y: ny } + nx = x + i * off + ny = y - j * off + if (isInPolyline(pl, mpls, nx, ny)) + return { x: nx, y: ny } + nx = x - i * off + ny = y + j * off + if (isInPolyline(pl, mpls, nx, ny)) + return { x: nx, y: ny } + nx = x - i * off + ny = y - j * off + if (isInPolyline(pl, mpls, nx, ny)) + return { x: nx, y: ny } + } + } + + return null + } + + /** 判断点 在范围内 */ + function isInPolyline(pl: Polyline, mpls: Polyline[], x: number, y: number): boolean { + for (const mp of mpls) { + if (PolylineHelper.isPointInPolyline(mp, x, y)) + return false // 在造型洞里头, + } + return PolylineHelper.isPointInPolyline(pl, x, y) + } + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts b/tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts new file mode 100644 index 0000000..0446e03 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts @@ -0,0 +1,473 @@ +import type { Line2d } from '../../common/base/CAD.js' +import { PlaceStyle,PlaceBlock } from '../../confClass.js' + +/** 有造型需要板外下刀, 特别计算要外偏的偏移值, 在排版的时候要算进开料尺寸 */ +export class BlockSizePlus +{ + /** 分析板外尺寸偏移:1 预洗, 同刀辅助开料,出板造型刀, 2V刀路 */ + static analySizeOff(block: PlaceBlock, sysConfig: any) + { + // //1.预铣 预铣值先设置=1; + // this.analyePreCut(block,sysConfig); + // //2.同刀辅助 + // this.analyeSameKnifeToHelpCut(block, sysConfig); + // //3.造型刀超出板外 + // this.analyeModelKnifeR(block); + // //3.二维刀路 板外下刀 + // this.analye2VModels(block); + } + + /** 分析1: 预铣尺寸扩展, 分析每条异形边预铣标识 */ + static analyePreCut(block: PlaceBlock, sysConfig: any) + { + // 先默认 预洗值为1 ,真正使用时 * 真正的预洗值 + // let bd = block.SaleBlockDetail; + // if (bd.preCutSizeOutOff) return; + + // if(sysConfig.PreCutValue <= 0) //没有启动预铣 + // { + // bd.preCutSizeOutOff = new SizeOutOff(); + // return ; + // } + // let width = block.Width; + // let length = block.Length; + // let zp = 0; + // let yp = 0; + // let sp = 0; + // let xp = 0; + // if (block.IsUnRegular == false) //矩形板 + // { + // if (block.BorderLeft > 0.001) zp = 1; + // if (block.BorderRight > 0.001) yp = 1; + // if (block.BorderUpper > 0.001) sp = 1; + // if (block.BorderUnder > 0.001) xp = 1; + // } + // else //异形板 + // { + // //1 判断异形边 预洗 + // for (let i = 0; i < bd.OrgPoints.length; i++) + // { + // let s = i - 1; + // let j = i + 1; + // if (s == -1) s = bd.OrgPoints.length - 1; + // if (j == bd.OrgPoints.length) j = 0; + + // let p0 = bd.OrgPoints[s]; + // let p1 = bd.OrgPoints[i]; + // let p2 = bd.OrgPoints[j]; + + // p1.needPreCut = checkYX(p0,p1,p2); + // setFX(p1,p2); + // } + // //2 如果是同一水平线,或垂直线的, 有一条不能预铣,所有都不能预铣 + // // //底 + // // let pts = bd.OrgPoints.filter(t=>t['fx']==0 && equal(t.PointY,0)); + // // let noPreCut = pts.some(t=>!t.needPreCut); + // // if(noPreCut) pts.forEach(t=>t.needPreCut = false); + // // xp = pts.some(t=>t.needPreCut) ? 1:0; + // // //右 + // // pts = bd.OrgPoints.filter(t=>t['fx']==1 && equal(t.PointX,width)); + // // noPreCut = pts.some(t=>!t.needPreCut); + // // if(noPreCut) pts.forEach(t=>t.needPreCut = false); + // // yp = pts.some(t=>t.needPreCut) ? 1:0; + // // //上 + // // pts = bd.OrgPoints.filter(t=>t['fx']==2 && equal(t.PointY,length)); + // // noPreCut = pts.some(t=>!t.needPreCut); + // // if(noPreCut) pts.forEach(t=>t.needPreCut = false); + // // sp = pts.some(t=>t.needPreCut) ? 1:0; + // // //左 + // // pts = bd.OrgPoints.filter(t=>t['fx']==3 && equal(t.PointX,0)); + // // noPreCut = pts.some(t=>!t.needPreCut); + // // if(noPreCut) pts.forEach(t=>t.needPreCut = false); + // // zp = pts.some(t=>t.needPreCut) ? 1:0; + + // //3 .内部有缺角的边 ,不能预铣 + // //底 + // let pts = bd.OrgPoints.filter(t=>t['fx']==0 && equal(t.PointY,0)); + // if(pts.length > 1) pts.forEach(t=>t.needPreCut = false); //底的边,有多条线段,表示内部有缺角,全部不能预铣 + // xp = pts.some(t=>t.needPreCut) ? 1:0; + // //右 + // pts = bd.OrgPoints.filter(t=>t['fx']==1 && equal(t.PointX,width)); + // if(pts.length > 1) pts.forEach(t=>t.needPreCut = false); + // yp = pts.some(t=>t.needPreCut) ? 1:0; + // //上 + // pts = bd.OrgPoints.filter(t=>t['fx']==2 && equal(t.PointY,length)); + // if(pts.length > 1) pts.forEach(t=>t.needPreCut = false); + // sp = pts.some(t=>t.needPreCut) ? 1:0; + // //左 + // pts = bd.OrgPoints.filter(t=>t['fx']==3 && equal(t.PointX,0)); + // if(pts.length > 1) pts.forEach(t=>t.needPreCut = false); + // zp = pts.some(t=>t.needPreCut) ? 1:0; + + // //如果斜边 会影响 + + // //3 再计算 扩展 有预铣边的斜线 起点或终点 在这边上的,这边就要扩展 + // for(let i = 0 ; i < bd.OrgPoints.length ;i++) + // { + + // let j = i + 1; + // if (j == bd.OrgPoints.length) j = 0; + // let p1 = bd.OrgPoints[i]; + // let p2 = bd.OrgPoints[j]; + + // if(p1.needPreCut == false) continue; + // //判断 起点 + // if(p1['fx']==4 && xp == 0 && equal(p1.PointY,0)) xp = 1 + // if(p1['fx']==5 && yp == 0 && equal(p1.PointX,width)) yp = 1 + // if(p1['fx']==6 && sp == 0 && equal(p1.PointY,length)) sp = 1 + // if(p1['fx']==7 && zp == 0 && equal(p1.PointX,0)) zp = 1 + // //判断终点 + // if(p1['fx']==4 && yp == 0 && equal(p2.PointX,width)) yp = 1 + // if(p1['fx']==5 && sp == 0 && equal(p2.PointY,length)) sp = 1 + // if(p1['fx']==6 && zp == 0 && equal(p2.PointX,0)) zp = 1 + // if(p1['fx']==7 && xp == 0 && equal(p2.PointY,0)) xp = 1 + // } + // } + + // let sizePlus = new SizeOutOff(); + // sizePlus.left = zp; + // sizePlus.right = yp; + // sizePlus.bottom = xp; + // sizePlus.top = sp; + // sizePlus.width = zp + yp; + // sizePlus.length = sp + xp; + + // bd.preCutSizeOutOff = sizePlus; + + // function checkYX(p0,p1,p2) //判断p1是否需要预铣 + // { + // if (p1.Curve != 0) return false; //本身是圆弧 + // if (p1.SealSize < 0.001) return false;// 本身不封边 + // if (p0.Curve != 0) return false; //前一段是圆弧 + // if (p2.Curve != 0) return false;//后一段是圆弧 + // //p1.p2 只要有一点在板内,就不行 + // let isIn1 = (p1.PointX > 0.001 && p1.PointX < width - 0.001 && p1.PointY > 0.001 && p1.PointY < length - 0.001); + // if (isIn1) return false; + // let isIn2 = (p2.PointX > 0.001 && p2.PointX < width - 0.001 && p2.PointY > 0.001 && p2.PointY < length - 0.001); + // if (isIn2) return false; + // return true; //需要预洗 + // } + + // function setFX(p1,p2) //设置p1的方向 + // { + // let fx = -1; //向右 0,向上 1,向左 2,向下 3 ,右上 4,左上5,左下6,右下 7 + // if(p2.PointX > p1.PointX && equal(p2.PointY,p1.PointY)) + // { + // fx = 0; + // } + // else if(p2.PointX < p1.PointX && equal(p2.PointY,p1.PointY)) + // { + // fx = 2; + // } + // else if(p2.PointY > p1.PointY && equal(p2.PointX,p1.PointX)) + // { + // fx = 1; + // } + // else if(p2.PointY < p1.PointY && equal(p2.PointX,p1.PointX)) + // { + // fx = 3; + // } + // else if(p2.PointX > p1.PointX && p2.PointY > p1.PointY) + // { + // fx = 4; + // } + // else if(p2.PointX < p1.PointX && p2.PointY > p1.PointY) + // { + // fx = 5; + // } + // else if(p2.PointX < p1.PointX && p2.PointY < p1.PointY) + // { + // fx = 6; + // } + // else if(p2.PointX > p1.PointX && p2.PointY < p1.PointY) + // { + // fx = 7; + // } + // p1['fx'] = fx; + // } + } + + // 分析2 同刀辅助 + static analyeSameKnifeToHelpCut(block: PlaceBlock, sysconfig: any) + { + // let bDetail = block.SaleBlockDetail; + // let isSameKnifeToCut = sysconfig.UseSameKnifeToHelpCut && sysconfig.UseSameKnifeToHelpCutGap > 0; + // //未启动同刀辅助, 或 该小板 不需要同刀辅助 + // if (isSameKnifeToCut == false || bDetail.isNeedHelpCut == false) + // { + // bDetail.sameKnifeToHelpCutGap = 0; + // bDetail.sameKnfieHelpOutOff = new SizeOutOff(); + // } + // else + // { + // let gap = sysconfig.UseSameKnifeToHelpCutGap; + // bDetail.sameKnifeToHelpCutGap = gap; + // bDetail.sameKnfieHelpOutOff = new SizeOutOff({ left: gap, right: gap, under: gap, upper: gap }); + // } + + } + + // 分析3 造型刀 超出板外 + static analyeModelKnifeR(block: PlaceBlock) + { + // let bDetail = block.SaleBlockDetail; + // if (bDetail.modelKnifeOutOff) return; + + // let outValue = (block.PlaceMetrial.CutDia + block.PlaceMetrial.CutGap) /2; + + // let minX = -outValue; + // let maxX = bDetail.CuttingWidth + outValue; + // let minY = -outValue; + // let maxY = bDetail.CuttingLength + outValue; + // //求 造型点 最偏 值 + // for (let model of bDetail.Models) + // { + // if(model.isVKnifeModel) continue; + // if(model.IsDo==false) continue; + // if(model.Depth > block.Thickness - 0.001) continue; + // let r = model.KnifeRadius; + // for (let mp of model.PointList) + // { + // if (mp.PointX - r < minX) minX = mp.PointX - r; + // if (mp.PointX + r > maxX) maxX = mp.PointX + r; + // if (mp.PointY - r < minY) minY = mp.PointY - r; + // if (mp.PointY + r > maxY) maxY = mp.PointY + r; + // } + // } + + // let off = {left:0,right :0,upper:0,under:0}; + // /**暂时屏蔽 造型外扩 */ + // // if (minX < - outValue) off.left = (-minX) - outValue; + // // if (maxX > bDetail.CuttingWidth + outValue) off.right = maxX - bDetail.CuttingWidth - outValue; + // // if (minY < - outValue) off.bottom = (-minY) - outValue; + // // if (maxY > bDetail.CuttingLength + outValue) off.top = maxY - bDetail.CuttingLength - outValue; + + // bDetail.modelKnifeOutOff = new SizeOutOff(off); + } + + // 分析4 板外下刀 2v刀路 + static analye2VModels(block: PlaceBlock) + { + + // let blockDetail = block.SaleBlockDetail; + // if (blockDetail.vKnifeModelSizeOutOff) return;//已存在, 不用重复分析 + // let sizePlus = new SizeOutOff(); + + // let cutR = block.PlaceMetrial.CutDia /2; + // let minX = - cutR; + // let maxX = block.CuttingWidth + cutR ; + // let minY = - cutR; + // let maxY = block.CuttingLength + cutR; + + // for(let model of blockDetail.Models) + // { + // if(!model.IsDo || model.isVKnifeModel ==false ) continue; + // if(!model.VLines) continue; + // if(model.VLines.length == 0) continue; + // for(let vm of model.VLines) + // { + // let knifeR = vm.knifeRadius; + // let points = vm.points.map((t) => + // { + // return { + // PointX: t.x, + // PointY: t.y, + // Radius: t.r, + // Depth: vm.depth - t.z, + // Curve: t.bul, + // }; + // }); + + // let isOut = vm.points.some(t=>t.x - knifeR < -cutR) + // || vm.points.some(t=>t.x + knifeR > block.CuttingWidth + cutR) + // || vm.points.some(t=>t.y - knifeR < -cutR) + // || vm.points.some(t=>t.y + knifeR > block.CuttingLength + cutR); + // if(isOut ) //超出板外 + // { + // vm['isOut'] = true; + // for (let mp of points) + // { + // if (mp.PointX - knifeR < minX) minX = mp.PointX - knifeR; + // if (mp.PointX + knifeR > maxX) maxX = mp.PointX + knifeR; + // if (mp.PointY - knifeR < minY) minY = mp.PointY - knifeR; + // if (mp.PointY + knifeR > maxY) maxY = mp.PointY + knifeR; + // } + // } + // } + // } + + // if (minX < -cutR) sizePlus.left = -minX; + // if (maxX > block.CuttingWidth + cutR) sizePlus.right = maxX - block.CuttingWidth; + // if (minY < -cutR) sizePlus.bottom = - minY; + // if (maxY > block.CuttingLength + cutR) sizePlus.top = maxY - block.CuttingLength; + + // sizePlus.width = sizePlus.left + sizePlus.right; + // sizePlus.length = sizePlus.bottom + sizePlus.top; + // sizePlus.hasDone = true; + + // //blockDetail.vKnifeModelSizeOutOff = sizePlus; + // blockDetail.vKnifeModelSizeOutOff = new SizeOutOff(); + } + + /** 获得板件偏移值 */ + static getOffDis(block: PlaceBlock, placeStyle?: PlaceStyle): any + { + // console.log('获得板件偏移值') + if (placeStyle == null || placeStyle == undefined) + { + placeStyle = block.placeStyle + } + + let expandSize :any = block.sizeExpand() + let posOff = { x: 0, y: 0, left: 0, right: 0, top: 0, bottom: 0 } + if(expandSize){ + switch (placeStyle) + { + case PlaceStyle.FRONT: // 正面 + posOff.x = expandSize.left + posOff.y = expandSize.bottom + posOff.left = expandSize.left + posOff.right = expandSize.right + posOff.bottom = expandSize.bottom + posOff.top = expandSize.top + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + posOff.x = expandSize.bottom + posOff.y = expandSize.right + posOff.left = expandSize.bottom + posOff.right = expandSize.top + posOff.bottom = expandSize.right + posOff.top = expandSize.left + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + posOff.x = expandSize.right + posOff.y = expandSize.top + posOff.left = expandSize.right + posOff.right = expandSize.left + posOff.bottom = expandSize.top + posOff.top = expandSize.bottom + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + posOff.x = expandSize.top + posOff.y = expandSize.left + posOff.left = expandSize.top + posOff.right = expandSize.bottom + posOff.bottom = expandSize.left + posOff.top = expandSize.right + break + case PlaceStyle.BACK: // 反面 + posOff.x = expandSize.right + posOff.y = expandSize.bottom + posOff.left = expandSize.right + posOff.right = expandSize.left + posOff.bottom = expandSize.bottom + posOff.top = expandSize.top + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + posOff.x = expandSize.bottom + posOff.y = expandSize.left + posOff.left = expandSize.bottom + posOff.right = expandSize.top + posOff.bottom = expandSize.left + posOff.top = expandSize.right + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + posOff.x = expandSize.left + posOff.y = expandSize.top + posOff.left = expandSize.left + posOff.right = expandSize.right + posOff.bottom = expandSize.top + posOff.top = expandSize.bottom + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + posOff.x = expandSize.top + posOff.y = expandSize.right + posOff.left = expandSize.bottom + posOff.right = expandSize.bottom + posOff.bottom = expandSize.right + posOff.top = expandSize.left + break + default: + break + } + } + + return posOff + } + + // 设置板件的位置 + static resetNewPlace(block: PlaceBlock) + { + let posOff = this.getOffDis(block) + block.placeOffX = posOff.x + block.placeOffY = posOff.y + block.placeX = block.placeX + block.placeOffX + block.placeY = block.placeY + block.placeOffY + } + + static checkPreBorder(block: PlaceBlock, line: Line2d): boolean // 判断 开料刀路中的一条 line 是否需要预洗 + { + let x1 = line.StartPoint.m_X + let y1 = line.StartPoint.m_Y + let x2 = line.EndPoint.m_X + let y2 = line.EndPoint.m_Y + + if (block.isUnRegular == false) // 矩形 + { + if (this.eqaul(x1, 0, 0.01) && this.eqaul(x1, x2)) + return block.sealLeft > 0 + if (this.eqaul(x1, block.cutWidth, 0.01) && this.eqaul(x1, x2)) + return block.sealRight > 0 + if (this.eqaul(y1, 0, 0.01) && this.eqaul(y1, y2)) + return block.sealBottom > 0 + if (this.eqaul(y1, block.cutLength, 0.01) && this.eqaul(y1, y2)) + return block.sealTop > 0 + + return false + } + else // 异形 + { + // 找出原始轮廓中 对应的边 , 是否有 预洗信息 + for (let i = 0; i < block.orgPoints.length; i++) + { + let j = i + 1 + if (j == block.orgPoints.length) + j = 0 + + if (block.orgPoints[i].curve != 0) + continue + let dis = block.orgPoints[i].sealSize + + let w1 = block.orgPoints[i].pointX - (block.blockDetail?.offsetX || 0) + let v1 = block.orgPoints[i].pointY - (block.blockDetail?.offsetY || 0) + let w2 = block.orgPoints[j].pointX - (block.blockDetail?.offsetX || 0) + let v2 = block.orgPoints[j].pointY - (block.blockDetail?.offsetY || 0) + + let dis1 = Math.sqrt((x1 - w1) * (x1 - w1) + (y1 - v1) * (y1 - v1)) + if (dis1 > dis * 2) + continue + let dis2 = Math.sqrt((x2 - w2) * (x2 - w2) + (y2 - v2) * (y2 - v2)) + if (dis2 < dis * 2) + return block.orgPoints[i].isPreCutRequired + } + return false + } + } + + static equal2Point(p1, p2, dis = 0.001) + { + let x1 = p1.m_X + let y1 = p1.m_Y + let x2 = p2.m_X + let y2 = p2.m_Y + let len1 = (x1 - x2) * (x1 - x2) + let len2 = (y1 - y2) * (y1 - y2) + let len = Math.sqrt(len2 + len1) + return len < dis + } + + static eqaul(a, b, dis = 0.001) + { + return Math.abs(a - b) < 0.001 + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts b/tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts new file mode 100644 index 0000000..5245a03 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts @@ -0,0 +1,266 @@ +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 '../Vector2' +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 + this.Area = 0 + 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[] + if (clipperCpp.lib && typeof clipperCpp.lib.offsetToPaths === 'function') { + const result = clipperCpp.lib.offsetToPaths({ + delta: offset * 1e4, + offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], + }); + pts = result && result[0] ? result[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 [] + } + } + 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[] + if (clipperCpp.lib && typeof clipperCpp.lib.offsetToPaths === 'function') { + const result = clipperCpp.lib.offsetToPaths({ + delta: offset * 1e4, + offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], + }); + pts = result && result[0] ? result[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(v: T, an: number, dis: number): T { + v.x += Math.cos(an) * dis + v.y += Math.sin(an) * dis + return v +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/DisposeModelInBoardBorder.ts b/tests/dev1/dataHandle/common/LayoutEngine/DisposeModelInBoardBorder.ts new file mode 100644 index 0000000..54af991 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/DisposeModelInBoardBorder.ts @@ -0,0 +1,321 @@ + +import { BlockSizePlus } from './BlockSizePlus.js' +import { BlockHelper } from './BlockHelper.js' +import { BlockModel,PlaceBlock,PlaceBoard,PlaceMaterial } from '../../confClass.js' + +/** + * 大板长边的两侧多少范围内 避免造型 ,如果出现,则那片小板掉头放置 + * @param pm + * @param 大板边缘范围 + * @face 大板面 反面 1; 正面 0 ;双面 2 + * @forCNC 针对的是cnc数据 + * @isReverse 反转: 大板边缘尽量 安排造型 + */ +export function DisPoseModelInBoardBorder(pm: PlaceMaterial, width: number, face: number, forCNC: number, isReverse: boolean) +{ + if (width == 0) + return + for (const pb of pm.boardList) + { + if (pb.isAdnormal()) + continue + if (pb.isLocked || pb.cutedType != 0) + continue + if (isReverse == false) + { + // 避免大板边出现 造型 + DisPoseModelInBoardBorder_pb(pb, width, face, forCNC) + } + else + { + // 尽量 安排 造型在大板边 + needModelInBoardBorder_pb(pb, width, face, forCNC) + } + } +} + +/** + * 大板边缘 避免造型 如果出现,则那片小板掉头放置 + * + * @param pb + * @param width + * @param face + * @param forCNC 针对的是cnc的加工 + */ +function DisPoseModelInBoardBorder_pb(pb: PlaceBoard, width: number, face: number, forCNC: number) +{ + const bw = pb.width + for (const block of pb.blockList) + { + if (block.isUnRegular) + continue + if (block.placeX + block.placeWidth < width) + continue // 小板整片板都在边缘, 转不转没意义 + if (block.placeX > bw - width) + continue + // 如果内部有小板, 也不能掉头 + if (hasChildBlock(block, pb.blockList)) + continue + + let isInBorder = false + const models = getModels(block, face, forCNC) + for (const model of models) + { + isInBorder = isInBoardBorder(block, model, bw, width) + if (isInBorder) + break + } + + if (isInBorder == false) + continue + + turnBlock(block) + } +} + +/** + * 大板边缘 尽量安排造型 + * + * @param pb + * @param width + * @param face + * @param forCNC + */ +function needModelInBoardBorder_pb(pb: PlaceBoard, width: number, face: number, forCNC: number) +{ + const blocks_turn: any = [] + const bw = pb.width + + // 大板左边 + const blocks_z = pb.blockList.filter(t => t.isUnRegular == false && t.placeX < width) + for (const block of blocks_z) + { + // 如果内部有小板, 也不能掉头 + if (hasChildBlock(block, pb.blockList)) + continue + // 造型 + const models = getModels(block, face, forCNC) + + let hasModelOnLeft = false // 左边有造型 + let hasModelOnRight = false // 右边有造型 + for (const model of models) + { + if (hasModelOnLeft == false && isInBoardLeft(block, model, width, false)) + hasModelOnLeft = true + if (hasModelOnRight == false && isInBoardLeft(block, model, width, true)) + hasModelOnRight = true + } + // 左边没有造型, 右边有造型,则掉头 + if (hasModelOnLeft == false && hasModelOnRight) + { + blocks_turn.push(block) + } + } + + // 大板右边 + const blocks_y = pb.blockList.filter(t => t.isUnRegular == false && t.placeX + t.placeWidth > bw - width) + for (const block of blocks_y) + { + // 如果内部有小板, 也不能掉头 + if (hasChildBlock(block, pb.blockList)) + continue + // 造型 + const models = getModels(block, face, forCNC) + + let hasModelOnLeft = false // 小板左边有造型 + let hasModelOnRight = false // 小板右边有造型 + for (const model of models) + { + if (hasModelOnLeft == false && isInBoardRight(block, model, bw, width, true)) + hasModelOnLeft = true + if (hasModelOnRight == false && isInBoardRight(block, model, bw, width, false)) + hasModelOnRight = true + } + // 右边没有造型, 左边有造型,则掉头 + if (hasModelOnLeft && hasModelOnRight == false) + { + blocks_turn.push(block) + } + } + + // 翻转小板 + for (const block of blocks_turn) + { + turnBlock(block) + } +} + +/** 获取小板指定面造型 */ +function getModels(block: PlaceBlock, face: number, forCNC: number): BlockModel[] +{ + let models: any = [] + if (face == 0) // 正面 + { + models = block.modelListFaceA + if (forCNC == 1) + { + const ms_A = block.isTurnOver() == false ? block.modelListOrgFaceA() : block.modelListOrgFaceB() + models = ms_A.filter(t => t.isCutting == false).concat(block.modelListOrgTrough().filter(t => t.isCutting == false)) + } + } + else if (face == 1) // 反面 + { + models = block.modelListFaceB + if (forCNC == 1) + { + const ms_B = block.isTurnOver() == false ? block.modelListOrgFaceB() : block.modelListOrgFaceA() + models = ms_B.filter(t => t.isCutting == false) + } + } + else // 随意面 + { + models = block.models().filter(t => t.isCutting != (forCNC == 1)) + } + return models +} + +/** 判断是否有造型出现在无法加工的区域 */ +export function hasModelInBoardBorder(pb: PlaceBoard, width: number, face: number, forCNC: number) +{ + pb.hasModelOnLeft = false + pb.hasModelOnRight = false + + const bw = pb.width + for (const block of pb.blockList) + { + if (block.isUnRegular) + continue + if (block.placeX + block.placeWidth < width) + continue // 小板整片板都在边缘, 转不转没意义 + if (block.placeX > bw - width) + continue + + const models = getModels(block, face, forCNC) + + if (pb.hasModelOnLeft == false) + { + for (const model of models) + { + let isLeft = false + for (const p of model.pointList) + { + const rp = BlockHelper.getPlaceXYInBoard(block, p.pointX, p.pointY) + if (rp.x < width) + { + isLeft = true + break + } + } + if (isLeft) + { + pb.hasModelOnLeft = true + break + } + } + } + if (pb.hasModelOnRight == false) + { + for (const model of models) + { + let isRight = false + for (const p of model.pointList) + { + const rp = BlockHelper.getPlaceXYInBoard(block, p.pointX, p.pointY) + if (rp.x > bw - width) + { + isRight = true + break + } + } + if (isRight) + { + pb.hasModelOnRight = true + break + } + } + } + if (pb.hasModelOnLeft && pb.hasModelOnRight) + return // 已经有结果了 + } +} + +/** 判断造型是否在大板的边缘 */ +function isInBoardBorder(block: PlaceBlock, model: BlockModel, bw: number, width: number): boolean +{ + for (const p of model.pointList) + { + const rp = BlockHelper.getPlaceXYInBoard(block, p.pointX, p.pointY) + if (rp.x < width || rp.x > bw - width) + return true + } + return false +} + +/** 造型是否在大板左边 toTurn 翻转后 */ +function isInBoardLeft(block: PlaceBlock, model: BlockModel, width: number, toTurn: boolean) +{ + for (const p of model.pointList) + { + const rp = BlockHelper.getPlaceXYInBlock(block, p.pointX, p.pointY, false, false) + let rx = toTurn ? block.placeWidth - rp.x : rp.x + rx = rx + block.placeX + if (rx < width) + return true + } + return false +} + +/** 造型 是否在大板 右边 toTurn 翻转后 */ +function isInBoardRight(block: PlaceBlock, model: BlockModel, boardWidth: number, width: number, toTurn: boolean) +{ + for (const p of model.pointList) + { + const rp = BlockHelper.getPlaceXYInBlock(block, p.pointX, p.pointY, false, false) + let rx = toTurn ? block.placeWidth - rp.x : rp.x + rx = rx + block.placeX + if (rx > boardWidth - width) + return true + } + return false +} + +/** 判断block里有没有小板 */ +function hasChildBlock(block: PlaceBlock, blocks: PlaceBlock[]): boolean +{ + for (const b of blocks) + { + if (b.blockNo == block.blockNo) + continue + + if (b.placeX > block.placeX && b.placeX + b.placeWidth < block.placeX + block.placeWidth && b.placeY > block.placeY && b.placeY + b.placeLength < block.placeY + block.placeLength) + return true + } + return false +} + +function turnBlock(block: PlaceBlock) +{ + // 有造型在大板边 + + const orgStyle = block.placeStyle + const newStyle = BlockHelper.getTurnedPlaceStyle(orgStyle, 2) + + const orgPlaceX = block.placeX - block.placeOffX + const orgPlaceY = block.placeY - block.placeOffY + + const offset = BlockSizePlus.getOffDis(block, newStyle) + + // 左右上下,外扩尺寸不一样, 翻转有可能会导致 小板与其他小板 干涉。 + if (offset.left != offset.right || offset.top != offset.bottom) + return + + const newPlaceX = orgPlaceX + offset.x + const newPlaceY = orgPlaceY + offset.y + block.placeX = newPlaceX + block.placeY = newPlaceY + block.placeOffX = offset.x + block.placeOffY = offset.y + + // 修改小板的放置方式 + BlockHelper.resetPlaceStyle(block, newStyle) +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts b/tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts new file mode 100644 index 0000000..cc5e80d --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts @@ -0,0 +1,94 @@ +import { ArrayExt } from '../../common/base/ArrayExt.js'; +import { PlaceBoard,PlaceMaterial } from '../../confClass.js'; +import { hasModelInBoardBorder } from './DisposeModelInBoardBorder.js'; +import { CutOrder } from '../cutorder/CutOrder.js'; +// import { PlaceStore } from './PlaceStore.js'; + +/** + * 返回大板信息 + * @param pm + * @param bid + */ +export function getPlaceBoard(pm: PlaceMaterial, bid: number): PlaceBoard +{ + if (bid < pm.minBoardId) return null; + if (bid > pm.maxBoardId) return null; + let pb = pm.boardList.find(t => t.boardId == bid); + if (pb == null) + { + pb = new PlaceBoard(bid, pm.width, pm.length); + pb.blockList = pm.blockList.filter(t => t.boardId == bid); + resetPlaceBoard(pm, pb); + pm.boardList.push(pb); + } + return pb; +} + +/**重设大板汇总 */ +export function resetPlaceBoard(pm: PlaceMaterial, pb: PlaceBoard, blocks = null) +{ + if (pm == null) return; + if (pb == null) return; + if (blocks != null) + { + pb.blockList = blocks; + } + + pb.blockCount = pb.blockList.length; + pb.blockArea = ArrayExt.sum(pb.blockList, t => t.area); + pb.usageRate = 100 * pb.blockArea / pb.area; + + //判断 有造型出现在无法加工的区域 + // const sys = PlaceStore.sysConfig; + // if(sys && sys.boardBorderModelRange > 1 && sys.modelNearBoardBorder == false) + // { + // hasModelInBoardBorder(pb, sys.boardBorderModelRange, sys.boardBorderModelModeToFace, sys.boardBorderModelByMachine); + // } + // CutOrder.autoCalcCutOrder(pm, pb); +} + +/** + * 重设板材 优化率等, 是否排序大板列表 + * @param pm + * @param sortBoard + */ +export function resetPlaceMaterial(pm: PlaceMaterial, sortBoard = false) +{ + if (pm.boardCount == 0) + { + pm.blockCount = 0; + pm.blockArea = 0; + pm.avgUsageRateAll = 0; + pm.avgUsageRateExcludeLastBoard = 0; + pm.usageRateLastBoard = 0; + pm.boardCountFlipFace = 0; + return; + } + + pm.blockCount = pm.blockList.length; + pm.blockArea = ArrayExt.sum(pm.blockList, t => t.area); + if (pm.boardCount == 1) + { + pm.avgUsageRateAll = pm.blockArea; + pm.avgUsageRateExcludeLastBoard = pm.blockArea; + pm.usageRateLastBoard = pm.blockArea; + } + else + { + const size_last = getPlaceBoard(pm, pm.maxBoardId).blockArea; + pm.avgUsageRateAll = pm.blockArea / pm.boardCount; + pm.avgUsageRateExcludeLastBoard = (pm.blockArea - size_last) / (pm.boardCount - 1); + pm.usageRateLastBoard = size_last; + if (sortBoard) sortBoardPlace(pm); + } + pm.boardCountFlipFace = ArrayExt.count(pm.boardList, t => t.isTwoFaceProcessing); +} + +/** + * 排序大板信息 + * @param pm + */ +export function sortBoardPlace(pm: PlaceMaterial) +{ + pm.boardList = ArrayExt.sortBy(pm.boardList, t => t.boardId); +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts b/tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts new file mode 100644 index 0000000..00a71c6 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts @@ -0,0 +1,359 @@ +import type { PolylineProps } from 'cadapi' +import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi' +import type { Box3 } from 'three' +import { Vector2, Vector3 } from 'three' +import { arrayRemoveDuplicateBySort } from '../ArrayExt' +import type { Curve2d } from '../../common/base/CAD' +import { Arc2d, Point2d, copyTextToClipboard } from '../../common/base/CAD' +import { CurveWrap } from './Curves2Parts' + +// import type { Curve2d } from '../../common/base/CAD' + +export class PolylineHelper { + /** 创建闭合多段线 */ + static create(pts: any[], closeMark = false): Polyline { + let lined: PolylineProps[] = [] + let count = pts.length + for (let i = 0; i < count; i++) { + let p0 = pts[i] + + lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul || 0 }) + } + let pls = new Polyline(lined) + pls.CloseMark = closeMark + return pls + } + + static createByCurve2d(curs: Curve2d[]): 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:any[] = [] + 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] + + 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 | undefined { + 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|undefined { + const curves = pl2.Explode() + arrayRemoveDuplicateBySort(curves, (c1, c2) => { + return c1.Join(c2) === Status.True + }) + + return Polyline.FastCombine(curves) + } + + + + /** + * 两片板的干涉检查 + * + * @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 (!Reflect.has(pl1,'box_tp')) + { + Reflect.set(pl1,'box_tp',pl1.BoundingBox) + } + + return pl1['box_tp'] as Box3 + } + + static getArea(pl1: Polyline): number { + if (!Reflect.has(pl1,'area_tp')) + { + Reflect.set(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() + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts b/tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts new file mode 100644 index 0000000..52b6d24 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts @@ -0,0 +1,404 @@ +import type { Polyline } from 'cadapi' +import { Polyline2Points } from 'cadapi' +import { Vector3 } from 'three' +import { ArrayExt } from '../base/ArrayExt.js' +import { Arc2d } from '../base/CAD.js' +import { arrayRemoveDuplicateBySort } from '../ArrayExt.js' +import { InitClipperCpp } from '../ClipperCpp.js' +import type { Vector2 } from '../Vector2.js' +import { Container } from '../core/Container.js' +import { NestCache } from '../core/NestCache.js' +import { ParseOddments } from '../core/ParseOddments.js' +import { Part } from '../core/Part.js' +import { Path } from '../core/Path.js' +import { PathGeneratorSingle } from '../core/PathGenerator.js' +import type { PlaceBlock,PlaceBoard,PlaceMaterial,BlockModel,RemainBlock} from '../../confClass.js' + +import { BlockPlus } from './BlockPlus.js' +import { BlockSizePlus } from './BlockSizePlus.js' +import { BlockHelper } from './BlockHelper.js' +// import { PlaceStore } from '../../vo/order/PlaceStore.js' +import { PolylineHelper } from './PolylineHelper.js' +import { CurveWrap, Points2Polyline } from './Curves2Parts.js' + +/** 自动生成余料板空间 */ +export class RemainHelper { + squarePath: Path + canPutPaths: Path[] + offset = 3.5 + + initSquarePath() { + // let squareWidth = PlaceStore.sysConfig.scrapBlockSquare || 200; + // let widthMin = PlaceStore.sysConfig.srcapBlockWidthMin || 100; + // let widthMax = PlaceStore.sysConfig.scrapBlockWidthMax || 600; + let squareWidth = 200 + let widthMin = 100 + let widthMax = 600 + this.squarePath = NestCache.CreatePath(60, 60, 0) + this.canPutPaths = [ + NestCache.CreatePath(squareWidth, squareWidth, 0), + NestCache.CreatePath(widthMin, widthMax, 0), + NestCache.CreatePath(widthMax, widthMin, 0), + ] + } + + /** 分析所有余料空间 */ + async analyzeAllRemainSpace(pm1: PlaceMaterial, pb1: PlaceBoard, preMillingSize: number) { + this.initSquarePath() + console.log('分析所有余料空间'); + if (pm1 && pb1) // 当前板 先做 + { + + this.offset = (pm1.diameter + pm1.cutKnifeGap) / 2 + preMillingSize + pb1.isCreateRemainSpace = true + await this.analyzeScrapSpace(pm1, pb1) + } + let order = PlaceStore.order + for (let pm of order.materialList) { + this.offset = (pm.diameter + pm.cutKnifeGap ) / 2 + preMillingSize + for (let pb of pm.boardList) { + await this.analyzeScrapSpace(pm, pb) + } + } + } + + /** 分析所有余料空间 */ + async analyzeScrapSpace(pm: PlaceMaterial, board: PlaceBoard) { + if (board.isCreateRemainSpace == false) + return + await InitClipperCpp() + + let cutBoardBorder = PlaceStore.sysConfig.cutBoardBorder; + let cutBoardBorderB = PlaceStore.sysConfig.cutBoardBorderB + if (board.isTwoFaceProcessing == false) + cutBoardBorderB = 0 + let binPath: Path + if (board.points && board.points.length > 0) // 非矩形板 + { + binPath = new Path(board.points) + } + else // 矩形板 + { + binPath = new Path([{ x: cutBoardBorder, y: cutBoardBorder }, + { x: board.width - cutBoardBorderB, y: cutBoardBorder }, + { x: board.width - cutBoardBorderB, y: board.length - cutBoardBorderB }, + { x: cutBoardBorder, y: board.length - cutBoardBorderB }, + ]) + } + binPath.Id = undefined + PathGeneratorSingle.RegisterId(binPath) + // 容器 + let container = new Container(binPath) + + for (let i = 0; i < board.blockList.length; i++) { + let block = board.blockList[i] + let part = this.toPart(block, binPath, this.offset * 2, i) + // 设置位置 + part[0].PlacePosition = { x: (part[1].x + block.placeX + block.placeOffX - cutBoardBorder) * 1e4, y: (part[1].y + block.placeY + block.placeOffY - cutBoardBorder) * 1e4 } + container.PlacedParts.push(part[0]) + } + // for (let i = 0; i < board.BlockList.length; i++) + // { + // let block = board.BlockList[i]; + // let part = this.toPart(block, binPath, 0, board.BlockList.length + i); + // //设置位置 + // part[0].PlacePosition = { x: (part[1].x + block.placeX - border) * 1e4, y: (part[1].y + block.placeY - border) * 1e4 }; + // container.PlacedParts.push(part[0]); + // } + + // let f = new NestFiler(); + // f.Write(container.PlacedParts.length); + // for (let part of container.PlacedParts) + // f.Write(part.State.Contour.BigIntPoints); + // container.WriteFile(f); + // console.log(JSON.stringify(f._datas)); + + // FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas)); + + board.remainBlockList = [] // 清空 + try { + let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths) + for (let space of spaces) { + let sb = this.toRemainBlock(space) + sb.placeX = sb.placeX + cutBoardBorder + sb.placeY = sb.placeY + cutBoardBorder + board.remainBlockList.push(sb) + } + } + catch (err) { + // throw new Error(`计算余料空间异常:${pm.FullName} 第${board.boardId}片`); + console.log(`计算余料空间异常:${pm.fullName} 第${board.boardId}片`, err) + throw new Error(`计算余料空间异常:${pm.fullName} 第${board.boardId}片;请保存优化联系售后工程师分析`) + } + + // 分析小板内 造型孔洞 余料空间 + for (let block of board.blockList) { + let childBlocks: PlaceBlock[] + // 造型孔 + for (let m of block.models) { + if (m.depth < block.thickness - 0.05) + continue + if (m.hasContour == false) + continue + if (m.isCutting == false) + continue + if (!childBlocks) { + childBlocks = board.blockList.filter(t => t.placeX > block.placeX + && t.placeX + t.placeWidth < block.placeX + block.placeWidth + && t.placeY > block.placeY + && t.placeY + t.placeLength < block.placeY + block.placeLength) + } + let spaces = await this.getModelSpace(block, m, childBlocks) + for (let space of spaces) { + let sb = this.toRemainBlock(space) + // sb.placeX += border; + // sb.placeY += border; + sb.placeX += block.placeX + sb.placeY += block.placeY + board.remainBlockList.push(sb) + } + } + } + + board.isCreateRemainSpace = false + // console.log(`board id=${board.boardId} find scrap block count = ${board.remainBlockList.length}`); + // console.log(`${board.boardId} scraping ok`) + } + + /** 获取造型空间 */ + async getModelSpace(block: PlaceBlock, m: BlockModel, blocks: PlaceBlock[]): Promise { + if (m.originModeling == null) + return [] + let pts = m.originModeling.outline.pts + let buls = m.originModeling.outline.buls + + if (pts == undefined) { + pts = m.originModeling.outline.map(e => e.pts) + } + + if (buls == undefined) { + buls = m.originModeling.outline.map(e => e.buls) + } + // blocks = []; + // 起点 + let pts_n = [] + for (let i = 0; i < pts.length; i++) { + let p0 = BlockHelper.getPlaceXYInBlock(block, pts[i].x, pts[i].y, false, false) + pts_n.push({ x: p0.x, y: p0.y, bul: buls[i] }) + } + // 如果反面,翻转方向,重设坐标 + if (block.isTurnOver) + pts_n = this.reversePoint(pts_n, block.cutWidth) + + // pts_n.forEach(t => { t.x += block.placeX; t.y += block.placeY; }); + + let polyLine + polyLine = Points2Polyline(pts_n) + let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine) + if (cicle) + polyLine = cicle + let cureW2 = new CurveWrap(polyLine, this.offset, false) + let pts2 = cureW2.GetInsidePoints2(this.offset) + if (!pts2) + return [] + let posx = 10000 + let posy = 10000 + for (let p of pts2) { + if (p.x < posx) + posx = p.x + if (p.y < posy) + posy = p.y + } + for (let p of pts2) { + p.x -= posx + p.y -= posy + } + let binPath = new Path(pts2) + if (binPath.Area < 15000) + return [] + + await InitClipperCpp() + binPath.Id = undefined + PathGeneratorSingle.RegisterId(binPath) + // 容器 + let container = new Container(binPath) + let i = 0 + for (let b of blocks) { + let part = this.toPart(b, binPath, this.offset, i) + if (part[0].State.Contour == null) + continue + part[0].Id = i++ + // let isin = this.blockInBlock(block, b, polyLine); + // if (isin == false) continue; + // let part = this.toPart(b, binPath, this.offset, i++); + + // 设置位置 + part[0].PlacePosition = { x: (b.placeX - block.placeX - posx) * 1e4, y: (b.placeY - block.placeY - posy) * 1e4 } + container.PlacedParts.push(part[0]) + } + try { + // let f = new NestFiler(); + // f.Write(container.PlacedParts.length); + // for (let part of container.PlacedParts) + // f.Write(part.State.Contour.BigIntPoints); + // container.WriteFile(f); + // console.log(JSON.stringify(f._datas)); + // FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas)); + + let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths) + for (let space of spaces) { + space.Points.forEach((t) => { t.x += posx; t.y += posy }) + } + return spaces + } + catch (err) { + console.log(`板${block.blockNo}造型孔分析余料空间失败.`) + throw new Error(`板${block.blockNo}造型孔分析余料空间失败.请保存优化联系售后工程师分析`) + } + // let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths); + // for (let space of spaces) + // { + // space.Points.forEach(t => { t.x += posx; t.y += posy; }); + // } + // return spaces; + } + + /** 转配件 */ + toPart(block: PlaceBlock, binPath: Path, offset: number, id: number): [Part, Vector2] { + let w = 10000 + let binPath2 = new Path([{ x: -w, y: -w }, { x: w, y: -w }, { x: w, y: w }, { x: -w, y: w }]) + let part = new Part() + part.Id = id + + part.UserData = { bno: block.blockNo, area: block.area, isUnRegular: block.isUnRegular, width: block.cutWidth, length: block.cutLength } + + let path: Path + let bPoint: Vector2 + + let sizeOff = BlockSizePlus.getOffDis(block) // 外扩偏移 + let hasSizeOff = (sizeOff.left + sizeOff.right + sizeOff.top + sizeOff.bottom) > (block.placeMaterial.diameter * 2 + block.placeMaterial.cutKnifeGap * 2 + 0.01) + + // 矩形且带板外不偏移 + if (block.isUnRegular == false && hasSizeOff == false) { + path = NestCache.CreatePath(block.placeWidth + sizeOff.left + sizeOff.right, block.placeLength + sizeOff.top + sizeOff.bottom, offset) + bPoint = path.OrigionMinPoint + // 轮廓 + part.Init2(path, binPath2, [0]) + } + else // 异形 + { + let pts = [] + + let cured = BlockPlus.getBorder_moving(block) + for (let l of cured) { + if (l instanceof Arc2d) { + pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul }) + } + else { + pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 }) + } + } + + let polyLine + polyLine = Points2Polyline(pts) + polyLine.CloseMark = true + + let cureW = new CurveWrap(polyLine, offset, true) + let pts2 = cureW.GetOutsidePoints() + arrayRemoveDuplicateBySort(pts, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2) + path = new Path(pts2) + + let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine) + if (!cicle) { + let area = path.BoundingBox.area - path.Area + if (area < 15000 && pts.length > 6) + path = NestCache.CreatePath(block.placeWidth, block.placeLength, offset) + } + part.Init2(path, binPath2, [0]) + + // 不能放下,那么尝试不简化路径 + if (!path.IsRect && !cicle && part.RotatedStates.length == 0) { + let pts2 = Polyline2Points(polyLine, true, offset)[1] + arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2) + path = new Path(pts2) + part.Init2(path, binPath, [0]) + } + // 轮廓 + bPoint = path.OrigionMinPoint + } + + if (hasSizeOff) { + bPoint.x = bPoint.x - sizeOff.left + bPoint.y = bPoint.y - sizeOff.bottom + } + + return [part, bPoint] + } + + /** 余料板空间 */ + toRemainBlock(path: Path): RemainBlock { + let bx = path.OrigionMinPoint.x + let by = path.OrigionMinPoint.y + let points = path.Points + let ptsX = points.map((t) => { return t.x }) + let ptsY = points.map((t) => { return t.y }) + + let x0 = ArrayExt.min(ptsX, t => t) + let y0 = ArrayExt.min(ptsY, t => t) + let x1 = ArrayExt.max(ptsX, t => t) + let y1 = ArrayExt.max(ptsY, t => t) + + let sp = new RemainBlock(x0, y0, x1 - x0, y1 - y0) + + let pts = points.map((t) => { return { x: t.x - x0, y: t.y - y0 } }) + + sp.placeX = bx + x0 + sp.placeY = by + y0 + + sp.setPoints(pts) + return sp + } + + /** 判断板是否在造型洞里头 */ + blockInModelOfBlock(block: PlaceBlock, b: PlaceBlock, modlPl: Polyline, posX = 0, posY = 0): boolean { + let cured = BlockPlus.getBorder(b) + let pts = [] + for (let l of cured) { + if (l instanceof Arc2d) { + pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul }) + } + else { + pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 }) + } + } + let polyLine = Points2Polyline(pts) + let cureW2 = new CurveWrap(polyLine, this.offset, false) + let pts2 = cureW2.GetInsidePoints2(this.offset) + + // pts2 = pts2.map(t => { return { x: t.x + b.placeX - block.placeX - posX, y: t.y + b.placeY - block.placeY - posY }; }); + + for (let t of pts2) { + let x = t.x + b.placeX - block.placeX - posX + let y = t.y + b.placeY - block.placeY - posY + if (modlPl.PtInCurve(new Vector3(x, y, 0)) == false) + return false + } + + return true + } + + /** 反向点 */ + private reversePoint(pts, w): any[] { + let newPts = [] + for (let i = pts.length - 1; i >= 0; i--) { + let p = pts[i] + let x = p.x + let y = p.y + let j = i == 0 ? pts.length - 1 : i - 1 + + let bul = pts[j].bul + newPts.push({ x, y, bul }) + } + return newPts + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts b/tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts new file mode 100644 index 0000000..3954661 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts @@ -0,0 +1,85 @@ +import { Vector2 } from '../Vector2' + +interface P { + x: number + y: number +} + +export interface IOffset { + negativeOffset: number + positiveOffset: number +} + +/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */ +function GetSqSegDist(p: P, p1: P, p2: P): number { + let x = p1.x + let y = p1.y + let dx = p2.x - x + let dy = p2.y - y + + if (dx !== 0 || dy !== 0)// 不是0长度线 + { + const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy) + if (t > 1) { + x = p2.x + y = p2.y + } + else if (t > 0) { + x += dx * t + y += dy * t + } + } + dx = p.x - x + dy = p.y - y + return dx * dx + dy * dy +} + +function CrossVector2(a: P, b: P) +{ + return a.x * b.y - a.y * b.x +} + +// Ramer-Douglas-Peucker algorithm +function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void { + let maxSqDist = 0 + let index: number + const fp = points[first] + const lp = points[last] + + for (let i = first + 1; i < last; i++) { + const p = points[i] + const sqDist = GetSqSegDist(p, fp, lp) + if (sqDist > maxSqDist) { + index = i + maxSqDist = sqDist + } + } + + if (maxSqDist > sqTolerance) { + if (index - first > 1) + SimplifyDPStep(points, first, index, sqTolerance, simplified, offset) + simplified.push(points[index]) + if (last - index > 1) + SimplifyDPStep(points, index, last, sqTolerance, simplified, offset) + } + else { + // 记录偏移 + const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize() + for (let i = first + 1; i < last; i++) { + const p = points[i] + const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y }) + offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist) + offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist) + } + } +} + +// Ramer-Douglas-Peucker 算法 +export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] { + const last = points.length - 1 + const simplified: P[] = [points[0]] + const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 } + SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset) + simplified.push(points[last]) + return [simplified, offset] +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts b/tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts new file mode 100644 index 0000000..d5d3ea3 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts @@ -0,0 +1,773 @@ +import { PolylineHelper } from '../../common/LayoutEngine/PolylineHelper.js' +import type { Curve2d } from '../../common/base/CAD.js' +import { Arc2d, CADExt, Line2d, Point2d } from '../../common/base/CAD.js' +import { equal } from '../../common/base/MathComm.js' +import { PlaceSpace,PlaceBlock,TextureType } from '../../confClass.js' + +/** 生成矩形空间的相关算法 */ +export class SpacePlus +{ + /** 纹路:0正文 1反纹 */ + static texture :TextureType = 0 // = TextureType.NORMAL_TEXTURE // 正纹 + + /** 设置空间的方向,横向或竖向 */ + static setSpaceStyle(block: PlaceBlock) + { + + if (block.texture == TextureType.NORMAL_TEXTURE) // 正纹 + { + this.texture = 0 + } + else if (block.texture == TextureType.REVERSE_TEXTURE) // 反纹 + { + this.texture = 1 + } + else // 可翻转 + { + if (block.cutWidth < block.cutLength) + { + this.texture = 0 + } + else + { + this.texture = 1 + } + } + } + + /** 复制轮廓 */ + static copyBorder(curves: Curve2d[]): Curve2d[] + { + const newCurves: Curve2d[] = [] + for (let i = 0; i < curves.length; i++) + { + const curve = curves[i] + const sp = curve.StartPoint + const ep = curve.EndPoint + if (curve instanceof Arc2d) + { + const arc = new Arc2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y), curve.Bul) + newCurves.push(arc) + } + else + { + // 后面会修改line里头的起点终点,所以必须重新初始化, 确保不能影响原来的轮廓 border . + const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) + newCurves.push(newLine) + } + } + return newCurves + } + + /** 从轮廓中分析空间 */ + static borderToSpace(orgcurves: Curve2d[]) + { + const curves = this.copyBorder(orgcurves) + // 1.新建线段列表 不影响源数据 + // 弧线转成 直线 + const lines: Line2d[] = [] + for (let i = 0; i < curves.length;) + { + const curve = curves[i] + const sp = curve.StartPoint + const ep = curve.EndPoint + if (curve instanceof Arc2d) + { + const len = Math.abs(curve.m_Radius * curve.m_AllAngle) // 圆弧长度 + + if (len > 200) // 圆弧长大于 200 则拆分 + { + const count = Math.ceil(len / 100) + const newArcs = CADExt.SplitArc(curve, count) + curves.splice(i, 1) + for (let j = newArcs.length - 1; j >= 0; j--) + { + curves.splice(i, 0, newArcs[j]) + } + continue + } + else // 圆弧转换 直线 + { + if (curve.Bul == 0) + { + curve.Parse() + } + const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) + // 外凸 直接转 + if (curve.Bul >= 0) + { + newLine.canotSplit = true // 不能拆分 + lines.push(newLine) + } + else // 内凹 算要算一下 弧形外接 矩形面积 + { + const pts:any[] = [] + pts.push({ x: curve.StartPoint.m_X, y: curve.StartPoint.m_Y, bul: curve.Bul }) + pts.push({ x: curve.EndPoint.m_X, y: curve.EndPoint.m_Y, bul: 0 }) + const pl = PolylineHelper.create(pts) + const box = pl.BoundingBox + this.setDirect(newLine) + const fx = newLine.fx + const newPoints :any[] = [] + newPoints.push({ x: sp.m_X, y: sp.m_Y }) + switch (fx) + { + case 0: + newPoints.push({ x: box.min.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.min.y }) + break + case 1: + newPoints.push({ x: box.max.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.max.y }) + break + case 2: + newPoints.push({ x: box.max.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.max.y }) + break + case 3: + newPoints.push({ x: box.min.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.min.y }) + break + case 4: + newPoints.push({ x: box.min.x, y: sp.m_Y }) + newPoints.push({ x: box.min.x, y: box.max.y }) + newPoints.push({ x: ep.m_X, y: box.max.y }) + break + case 5: + newPoints.push({ x: sp.m_X, y: box.min.y }) + newPoints.push({ x: box.min.x, y: box.min.y }) + newPoints.push({ x: box.min.x, y: ep.m_Y }) + break + case 6: + newPoints.push({ x: box.max.x, y: sp.m_Y }) + newPoints.push({ x: box.max.x, y: box.min.y }) + newPoints.push({ x: ep.m_X, y: box.min.y }) + break + case 7: + newPoints.push({ x: sp.m_X, y: box.max.y }) + newPoints.push({ x: box.max.x, y: box.max.y }) + newPoints.push({ x: box.max.x, y: ep.m_Y }) + break + default: + } + newPoints.push({ x: ep.m_X, y: ep.m_Y }) + for (let n = 0; n < newPoints.length - 1; n++) + { + const m = n + 1 + const newLine = new Line2d(new Point2d(newPoints[n].x, newPoints[n].y), new Point2d(newPoints[m].x, newPoints[m].y)) + lines.push(newLine) + } + } + } + } + else + { + // 后面会修改line里头的起点终点,所以必须重新初始化 ,确保不能影响原来的轮廓 border . + const newLine = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) + lines.push(newLine) + } + + i++ + } + + // 2 轮廓需要够大 + let minX = 1000 + let minY = 1000 + let maxX = -1000 + let maxY = -1000 + for (const line of lines) + { + if (line.StartPoint.m_X < minX) + minX = line.StartPoint.m_X + if (line.StartPoint.m_X > maxX) + maxX = line.StartPoint.m_X + if (line.StartPoint.m_Y < minY) + minY = line.StartPoint.m_Y + if (line.StartPoint.m_Y > maxY) + maxY = line.StartPoint.m_Y + if (line.EndPoint.m_X < minX) + minX = line.EndPoint.m_X + if (line.EndPoint.m_X > maxX) + maxX = line.EndPoint.m_X + if (line.EndPoint.m_Y < minY) + minY = line.EndPoint.m_Y + if (line.EndPoint.m_Y > maxY) + maxY = line.EndPoint.m_Y + } + if (maxX - minX < 50) + return // 太小了,就不要分析 + if (maxY - minY < 50) + return + if ((maxX - minX) * (maxY - minY) < 10000) + return [] + + // 3.将长斜线 ,转成 多段 + + for (let i = 0; i < lines.length;) + { + const line = lines[i] + if (this.isXL(line)) + { + if (line.canotSplit) // 不能拆分 + { + + } + else + { + const sp = line.StartPoint + const ep = line.EndPoint + if (line.m_Length > 300 && Math.abs(sp.m_X - ep.m_X) > 50 && Math.abs(sp.m_X - ep.m_X) > 50) // 够长,截断 + { + const cx = (line.StartPoint.m_X + line.EndPoint.m_X) / 2 + const cy = (line.StartPoint.m_Y + line.EndPoint.m_Y) / 2 + + line.EndPoint = new Point2d(cx, cy) + line.Parse() + const newLine = new Line2d(new Point2d(cx, cy), new Point2d(ep.m_X, ep.m_Y)) + lines.splice(i + 1, 0, newLine) + continue + } + } + } + i++ + } + + // 3.斜线 -> 横平竖直 + for (let i = 0; i < lines.length; i++) + { + this.turnToLine(lines, i) + } + + // 6.将line 标识方向 + for (let i = lines.length - 1; i >= 0; i--) + { + if (this.setDirect(lines[i]) == -1) + lines.splice(i, 1) + } + + const tmpSpaces = [] + // 7.开始分析空间 + this.parseSpace(lines, tmpSpaces) + // 8.合并空间 + this.mergeSpaces(tmpSpaces) + // 9 生成 + const spaces: PlaceSpace[] = [] + for (const tmp of tmpSpaces) + { + const space = PlaceSpace.create(tmp.x1, tmp.y1, tmp.x2, tmp.y2) + spaces.push(space) + } + return spaces + } + + /** 闭合(逆时针)的线段内分析空间 */ + private static parseSpace(lines: Line2d[], spaces: any[]) + { + const childs = this.resetLines(lines) + if (childs.length > 1) // 分叉 + { + for (const child of childs) + { + this.parseSpace(child, spaces) + } + return + } + + // 没有分支,才开始真正计算 + lines = childs[0] + if (lines.length < 4) + return // 至少有4条 + + /** + * 思路:在线段里头 找到一段连续且成矩形的3条直线,即可组成一个矩形 + * 然后:去掉(或缩短)这3条直线,再重新找 + */ + let space + for (let i = 0; i < lines.length; i++) + { + space = this.createSpace(lines, i) + if (space) + break + } + // 没找到任何空间, 直接退出 + if (!space) + return + + // 找到了,继续往下找 + spaces.push(space) + this.parseSpace(lines, spaces) + } + + /** 分析空间大的保留,小的合并 */ + static mergeSpaces(spaces: any[]) + { + const minW = 50 // 空间最小宽度 + const minL = 200 + const avgW = 60 + + // 不损失 合并空间 + for (let i = 0; i < spaces.length;) + { + const space1 = spaces[i] + let hasMerge = false // 是否合并一个空间 + for (let j = i + 1; j < spaces.length;) + { + const space2 = spaces[j] + hasMerge = mergeSpace(space1, space2) // space2 是否被合并 + if (hasMerge) + { + spaces.splice(j, 1) + break + } + j++ + } + if (hasMerge) + break + i++ + } + // 小空间 委屈合并 + for (let i = 0; i < spaces.length;) + { + const space1 = spaces[i] + if (big(space1) == false) // 小空间,找别人合并 + { + mergeSpace2(i) + spaces.splice(i, 1)// 删除小空间 + continue + } + i++ + } + + /** 空间 大否 */ + function big(space) + { + let w = space.x2 - space.x1 + if (w < minW) + return false + let l = space.y2 - space.y1 + if (l < minW) + return false + + if (w > l) + [w, l] = [l, w] + + if (w >= minW && l >= minL) + return true + if (w >= avgW && l >= avgW) + return true + + return false + } + + /** 不损失 合并空间 */ + function mergeSpace(sp1, sp2) + { + // 上下 宽一样 + if (equal(sp1.x1, sp2.x1) && equal(sp1.x2, sp2.x2) && (equal(sp1.y2, sp2.y1) || equal(sp2.y2, sp1.y1))) + { + sp1.y1 = Math.min(sp1.y1, sp2.y1) + sp1.y2 = Math.max(sp1.y2, sp2.y2) + return true + } + // 左右 高一样 + if (equal(sp1.y1, sp2.y1) && equal(sp1.y2, sp2.y2) && (equal(sp1.x2, sp2.x1) || equal(sp2.x2, sp1.x1))) + { + sp1.x1 = Math.min(sp1.x1, sp2.x1) + sp1.x2 = Math.max(sp1.x2, sp2.x2) + return true + } + + return false + } + + /** 损失 合并空间,四周找一个空间,合并后效果最好的 */ + function mergeSpace2(index: number) + { + const cur = spaces[index] + const canSpaces = [] + for (let i = 0; i < spaces.length; i++) + { + if (i == index) + continue + const oth = spaces[i] + let x1, y1, x2, y2 + // 右边的 + if (equal(cur.x2, oth.x1) && cur.y1 < oth.y2 && cur.y2 > oth.y1) + { + x1 = cur.x1 + y1 = Math.max(cur.y1, oth.y1) + x2 = oth.x2 + y2 = Math.min(cur.y2, oth.y2) + } + // 左边的 + else if (equal(cur.x1, oth.x2) && cur.y1 < oth.y2 && cur.y2 > oth.y1) + { + x1 = oth.x1 + y1 = Math.max(cur.y1, oth.y1) + x2 = cur.x2 + y2 = Math.min(cur.y2, oth.y2) + } + // 下边的 + else if (equal(cur.y1, oth.y2) && cur.x1 < oth.x2 && cur.x2 > oth.x1) + { + x1 = Math.max(cur.x1, oth.x1) + y1 = oth.y1 + x2 = Math.min(cur.x2, oth.x2) + y2 = cur.y2 + } + // 上边的 + else if (equal(cur.y2, oth.y1) && cur.x1 < oth.x2 && cur.x2 > oth.x1) + { + x1 = Math.max(cur.x1, oth.x1) + y1 = cur.y1 + x2 = Math.min(cur.x2, oth.x2) + y2 = oth.y2 + } + else + { + continue + } + + // oth 原来面积 + const size_org = (oth.x2 - oth.x1) * (oth.y2 - oth.y1) + const size_new = (x2 - x1) * (y2 - y1) + + const size_plus = size_new - size_org + if (size_plus < 0) + continue // 合并 面积更小,就不要合并 + + const space = { x1, y1, x2, y2 } + canSpaces.push({ size_plus, space, i }) + } + if (canSpaces.length == 0) + return // 没有可以合并的 + + // 按增大面积 排序 + canSpaces.sort((a, b) => b.size_plus - a.size_plus) + // 取效果最好的。 + const newSpace = canSpaces[0].space + // 替换 oth , + spaces.splice(canSpaces[0].i, 1, newSpace) + } + } + + /** 整理多段线 */ + private static resetLines(lines: Line2d[]): Line2d[][] + { + for (let i = 0; i < lines.length;) + { + const lp = lines[i] + let n = i + 1 + if (n == lines.length) + n = 0 + const ln = lines[n] + if (lp.m_Length < 0.001) // 太短了,移除 + { + lines.splice(i, 1) + i = 0 + continue + } + + if (lp.fx == ln.fx) // 同向, + { + lp.EndPoint = ln.EndPoint + lp.Parse() + lines.splice(n, 1) + i = 0 + continue + } + if (lp.fx % 2 == ln.fx % 2) // 反向 + { + lp.EndPoint = ln.EndPoint + lp.Parse() + this.setDirect(lp) + lines.splice(n, 1) + if ((lp.fx == 0 && lp.EndPoint.m_X < lp.StartPoint.m_X) + || (lp.fx == 1 && lp.EndPoint.m_Y < lp.StartPoint.m_Y) + || (lp.fx == 2 && lp.EndPoint.m_X > lp.StartPoint.m_X) + || (lp.fx == 3 && lp.EndPoint.m_Y > lp.StartPoint.m_Y)) // 这很奇葩,该空间 不能分析,直接清除所有线段 + { + lines.splice(0, lines.length) + return + } + i = 0 + continue + } + i++ + } + + // 判断 有向下的线,与向上的线 重叠。 则要分开; 类似 与 管 的下面 2个口 + let line0: Line2d + let line1: Line2d + let i = -1 + let j = -1 + let x = 0 + for (i = 0; i < lines.length; i++) + { + if (lines[i].fx == 3) + { + x = lines[i].StartPoint.m_X + j = lines.findIndex(t => t.fx == 1 && t.StartPoint.m_X == x) + if (j >= 0) + { + line0 = lines[i] + line1 = lines[j] + break + } + } + } + + if (!line0 || !line1) + return [lines] // 没找到 + + const rt = [] + const si = Math.min(i, j) + const ei = Math.max(i, j) + + const lines_s: Line2d[] = [] + const lines_x: Line2d[] = [] + for (let i = si + 1; i <= ei - 1; i++) + { + lines_s.push(lines[i]) + } + for (let i = ei + 1; i <= lines.length + si - 1; i++) + { + const ri = i % lines.length + lines_x.push(lines[ri]) + } + + if (lines_s.length >= 3) + { + const pe = lines_s[lines_s.length - 1].EndPoint + const ps = lines_s[0].StartPoint + const newLine = new Line2d(new Point2d(pe.m_X, pe.m_Y), new Point2d(ps.m_X, ps.m_Y)) + this.setDirect(newLine) + lines_s.push(newLine) + rt.push(lines_s) + } + if (lines_x.length >= 3) + { + const pe = lines_x[lines_x.length - 1].EndPoint + const ps = lines_x[0].StartPoint + const newLine = new Line2d(new Point2d(pe.m_X, pe.m_Y), new Point2d(ps.m_X, ps.m_Y)) + this.setDirect(newLine) + lines_x.push(newLine) + rt.push(lines_x) + } + + return rt + } + + /** line如果是斜线,转换成直线 */ + private static turnToLine(lines: Line2d[], i: number) + { + if (!this.isXL(lines[i])) + return + + const sp = lines[i].StartPoint + const ep = lines[i].EndPoint + const sx = sp.m_X + const sy = sp.m_Y + const ex = ep.m_X + const ey = ep.m_Y + let cx = 0 + let cy = 0 + // ↖ 换成 ←↑ + if (ex < sx && ey > sy) + { + cx = ex + cy = sy + } + else if (ex < sx && ey < sy) // ↙ 换成 ←↓ + { + cx = sx + cy = ey + } + else if (ex > sx && ey < sy) // ↘ 换成 →↓ + { + cx = ex + cy = sy + } + else if (ex > sx && ey > sy) // ↗ 换成 →↑ + { + cx = sx + cy = ey + } + if (cx == 0 && cy == 0) + return + + const line1 = new Line2d(new Point2d(sx, sy), new Point2d(cx, cy)) + const line2 = new Line2d(new Point2d(cx, cy), new Point2d(ex, ey)) + lines.splice(i, 1, line1, line2) + } + + /** 线段设置方向 */ + private static setDirect(line: Line2d) + { + let fx = -1 // 向右 0,向上 1,向左 2,向下 3 ,右上 4,左上5,左下6,右下 7 + if (line.EndPoint.m_X > line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) + { + fx = 0 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && equal(line.EndPoint.m_Y, line.StartPoint.m_Y)) + { + fx = 2 + } + else if (line.EndPoint.m_Y > line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) + { + fx = 1 + } + else if (line.EndPoint.m_Y < line.StartPoint.m_Y && equal(line.EndPoint.m_X, line.StartPoint.m_X)) + { + fx = 3 + } + else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) + { + fx = 4 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y > line.StartPoint.m_Y) + { + fx = 5 + } + else if (line.EndPoint.m_X < line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) + { + fx = 6 + } + else if (line.EndPoint.m_X > line.StartPoint.m_X && line.EndPoint.m_Y < line.StartPoint.m_Y) + { + fx = 7 + } + line.fx = fx + return fx + } + + /** 斜线 */ + private static isXL(line: Curve2d) { return !equal(line.StartPoint.m_X, line.EndPoint.m_X) && !equal(line.StartPoint.m_Y, line.EndPoint.m_Y) } + + /** 3条线,方向持续, 组成空间 */ + private static createSpace(lines: Line2d[], i: number) + { + let j = i + 1 + let n = i + 2 + if (j >= lines.length) + j = j - lines.length + if (n >= lines.length) + n = n - lines.length + + const line1 = lines[i] + const line2 = lines[j] + const line3 = lines[n] + + const fx1 = line1.fx + if (line2.fx != (fx1 + 1) % 4) + return + if (line3.fx != (fx1 + 2) % 4) + return + + // 安装板的纹路,进行开始计算空间,横的,还是竖的 + if (fx1 % 2 != this.texture) + return + + let x1, y1, x2, y2 + let sp, ep + if (fx1 == 0) + { + x1 = Math.max(line1.StartPoint.m_X, line3.EndPoint.m_X) + y1 = line2.StartPoint.m_Y + x2 = line2.EndPoint.m_X + y2 = line2.EndPoint.m_Y + + sp = new Point2d(x1, y1) + ep = new Point2d(x1, y2) + } + else if (fx1 == 1) + { + x1 = line2.EndPoint.m_X + y1 = Math.max(line1.StartPoint.m_Y, line3.EndPoint.m_Y) + x2 = line2.StartPoint.m_X + y2 = line2.StartPoint.m_Y + sp = new Point2d(x2, y1) + ep = new Point2d(x1, y1) + } + else if (fx1 == 2) + { + x1 = line2.EndPoint.m_X + y1 = line2.EndPoint.m_Y + x2 = Math.min(line1.StartPoint.m_X, line3.EndPoint.m_X) + y2 = line2.StartPoint.m_Y + + sp = new Point2d(x2, y2) + ep = new Point2d(x2, y1) + } + else if (fx1 == 3) + { + x1 = line2.StartPoint.m_X + y1 = line2.StartPoint.m_Y + x2 = line2.EndPoint.m_X + y2 = Math.min(line1.StartPoint.m_Y, line3.EndPoint.m_Y) + + sp = new Point2d(x1, y2) + ep = new Point2d(x2, y2) + } + else + { + return + } + + // 在新空间内,不能出现其他的线段, + for (let o = 0; o < lines.length; o++) + { + if (o == i || o == j || o == n) + continue + const oth = lines[o] + if (oth.EndPoint.m_X > x1 + 0.001 && oth.EndPoint.m_X < x2 - 0.001 && oth.EndPoint.m_Y > y1 + 0.001 && oth.EndPoint.m_Y < y2 - 0.001) + return + } + + // 缩短line1, line3 + line1.EndPoint = sp + line1.Parse() + this.setDirect(line1) + line3.StartPoint = ep + line3.Parse() + this.setDirect(line3) + // 删除 line2 ,添加 新连接线 + const newline = new Line2d(new Point2d(sp.m_X, sp.m_Y), new Point2d(ep.m_X, ep.m_Y)) + this.setDirect(newline) + + lines.splice(j, 1, newline) + const space = { x1, y1, x2, y2 } + return space + } + + /** 反转 */ + static reverseCurves(curves: Curve2d[]): Curve2d[] + { + const newCurs: Curve2d[] = [] + for (let i = curves.length - 1; i >= 0; i--) + { + const cur = curves[i] + const sp = cur.StartPoint + const ep = cur.EndPoint + if (cur instanceof Arc2d) + { + const newArc = new Arc2d(ep, sp, -cur.Bul) + newCurs.push(newArc) + } + else + { + const newline = new Line2d(ep, sp) + newCurs.push(newline) + } + } + return newCurs + } +} diff --git a/tests/dev1/dataHandle/common/LayoutEngine/writeP.ts b/tests/dev1/dataHandle/common/LayoutEngine/writeP.ts new file mode 100644 index 0000000..2c92a90 --- /dev/null +++ b/tests/dev1/dataHandle/common/LayoutEngine/writeP.ts @@ -0,0 +1,104 @@ +import type { PolylineProps } from 'cadapi' +import { CADFiler, Polyline } from 'cadapi' +import { Vector2 } from 'three' +import { copyTextToClipboard } from '../base/CAD' + +export class ClipboardTest { + public static writePolyline1(pl: Polyline, pts1) { + // pl 原图 + // pts :偏移后的点 + + const lined: PolylineProps[] = [] + const count = pts1.length + for (let i = 0; i < count; i++) { + const p0 = pts1[i] + lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 }) + } + const pl1 = new Polyline(lined) + pl1.CloseMark = true + + const f = new CADFiler() + f.Clear() + f.Write(2) + f.WriteObject(pl) + f.WriteObject(pl1) + + const test = JSON.stringify(f.Data) + + // for (let pl of lined) + // f.WriteObject(pl) + copyTextToClipboard(test) + } + + public static writePolyline2(pl: Polyline, pts1, pts2) { + // pl 原图 + // pts :偏移后的点 + + const lined: PolylineProps[] = [] + const count = pts1.length + for (let i = 0; i < count; i++) { + const p0 = pts1[i] + lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 }) + } + const pl1 = new Polyline(lined) + + const lined2: PolylineProps[] = [] + const count2 = pts2.length + for (let i = 0; i < count2; i++) { + const p0 = pts2[i] + lined2.push({ pt: new Vector2(p0.x, p0.y), bul: 0 }) + } + const pl2 = new Polyline(lined2) + + const f = new CADFiler() + f.Clear() + f.Write(3) + f.WriteObject(pl) + f.WriteObject(pl1) + f.WriteObject(pl2) + const test = JSON.stringify(f.Data) + + // for (let pl of lined) + // f.WriteObject(pl) + copyTextToClipboard(test) + } + + public static writeClipboard(pts) { + const lined: PolylineProps[] = [] + const count = pts.length + for (let i = 0; i < count; i++) { + const p0 = pts[i] + lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 }) + } + + const pls = new Polyline(lined) + const f = new CADFiler() + f.Clear() + f.Write(1) + f.WriteObject(pls) + const test = JSON.stringify(f.Data) + + // for (let pl of lined) + // f.WriteObject(pl) + copyTextToClipboard(test) + } + + public static writePolyLine(pls) { + const f = new CADFiler() + f.Clear() + f.Write(1) + f.WriteObject(pls) + const test = JSON.stringify(f.Data) + copyTextToClipboard(test) + } + + public static write2PolyLine(pls, pls2) { + const f = new CADFiler() + f.Clear() + f.Write(2) + f.WriteObject(pls) + f.WriteObject(pls2) + const test = JSON.stringify(f.Data) + copyTextToClipboard(test) + } +} diff --git a/tests/dev1/dataHandle/common/PlacePosition.ts b/tests/dev1/dataHandle/common/PlacePosition.ts new file mode 100644 index 0000000..ce19408 --- /dev/null +++ b/tests/dev1/dataHandle/common/PlacePosition.ts @@ -0,0 +1,143 @@ +import { PlaceStyle, PlaceBlock, PlaceBoard, BoardPosition } from "../confClass"; +/** 靠板类 */ +export class PlacePosition { + static turnPlacePosition(pb: PlaceBoard, newlocator: BoardPosition, config) { + const { placeOriginByBoardLocation } = config + if (placeOriginByBoardLocation == false) return; + if (pb.isAdnormal()) return; //余料板是余料板,不参与翻转 + let width = pb.width; + let length = pb.length; + //右下角靠板 + if (newlocator == BoardPosition.RIGHT_BOTTOM) { + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth; + let y = block.placeY; + let placeStyle = this.getPlaceStyleLeftRight(block); + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + //右上角靠板 + if (newlocator == BoardPosition.RIGHT_TOP) { + // console.log('BoardPosition=BoardPosition.RIGHT_TOP'); + for (let block of pb.blockList) { + + let x = width - block.placeX - block.placeWidth; + let y = length - block.placeLength - block.placeY; + let placeStyle = this.getPlaceStyleAcrossCorner(block); + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + //左上角靠板 + if (newlocator == BoardPosition.LEFT_TOP) { + for (let block of pb.blockList) { + let x = block.placeX; + let y = length - block.placeLength - block.placeY; + let placeStyle = this.getPlaceStyleTopBottom(block); + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + } + + /** 重置排版位置 */ + static resetPlacePosition(pb: PlaceBoard, config) { + const { placeOriginByBoardLocation = false, boardLocation } = config + const newlocator: BoardPosition = boardLocation + if (placeOriginByBoardLocation == false) return; + + if (newlocator == BoardPosition.LEFT_BOTTOM) return; + + let width = pb.width; + let length = pb.length; + //右下角靠板 + if (newlocator == BoardPosition.RIGHT_BOTTOM) { + for (let block of pb.blockList) { + + let x = width - block.placeX - block.placeWidth; + let y = block.placeY; + let placeStyle = this.getPlaceStyleLeftRight(block); + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + + //右上角靠板 + if (newlocator == BoardPosition.RIGHT_TOP) { + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth; + let y = length - block.placeLength - block.placeY; + let placeStyle = this.getPlaceStyleAcrossCorner(block); + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + //左上角, 靠板 + if (newlocator == BoardPosition.LEFT_TOP) { + for (let block of pb.blockList) { + let x = block.placeX; + let y = length - block.placeLength - block.placeY; + let placeStyle = this.getPlaceStyleTopBottom(block); + + block.placeX = x; + block.placeY = y; + block.placeStyle = placeStyle; + } + } + } + + + /** 获得新的放置方式(左右翻) */ + static getPlaceStyleLeftRight(block: PlaceBlock): PlaceStyle { + if (block.placeStyle == PlaceStyle.FRONT) return PlaceStyle.BACK; + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) return PlaceStyle.BACK_TURN_LEFT; + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) return PlaceStyle.BACK_TURN_BACK; + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) return PlaceStyle.BACK_TURN_RIGHT; + + if (block.placeStyle == PlaceStyle.BACK) return PlaceStyle.FRONT; + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) return PlaceStyle.FRONT_TURN_RIGHT; + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) return PlaceStyle.FRONT_TURN_BACK; + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) return PlaceStyle.FRONT_TURN_LEFT; + + return PlaceStyle.FRONT; + + + } + + /** 获得新的放置方式(上下翻) */ + static getPlaceStyleTopBottom(block: PlaceBlock): PlaceStyle { + if (block.placeStyle == PlaceStyle.FRONT) return PlaceStyle.BACK_TURN_BACK; + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) return PlaceStyle.BACK_TURN_RIGHT; + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) return PlaceStyle.BACK; + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) return PlaceStyle.BACK_TURN_LEFT; + + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) return PlaceStyle.FRONT; + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) return PlaceStyle.FRONT_TURN_RIGHT; + if (block.placeStyle == PlaceStyle.BACK) return PlaceStyle.FRONT_TURN_BACK; + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) return PlaceStyle.FRONT_TURN_LEFT; + + return PlaceStyle.FRONT; + } + + /** 获得新的放置方式(对角翻) */ + static getPlaceStyleAcrossCorner(block: PlaceBlock): PlaceStyle { + if (block.placeStyle == PlaceStyle.FRONT) return PlaceStyle.FRONT_TURN_BACK; + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) return PlaceStyle.FRONT_TURN_LEFT; + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) return PlaceStyle.FRONT; + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) return PlaceStyle.FRONT_TURN_RIGHT; + + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) return PlaceStyle.BACK; + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) return PlaceStyle.BACK_TURN_LEFT; + if (block.placeStyle == PlaceStyle.BACK) return PlaceStyle.BACK_TURN_BACK; + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) return PlaceStyle.BACK_TURN_RIGHT; + + return PlaceStyle.FRONT; + } + +} diff --git a/tests/dev1/dataHandle/common/PlacePostionHelper.ts b/tests/dev1/dataHandle/common/PlacePostionHelper.ts new file mode 100644 index 0000000..cbc2205 --- /dev/null +++ b/tests/dev1/dataHandle/common/PlacePostionHelper.ts @@ -0,0 +1,176 @@ +import { PlaceStyle, BoardPosition, PlaceBoard, PlaceBlock } from '../confClass' + + +export class PlacePositionHelper // 靠板类 +{ + static turnPlacePosition(pb: PlaceBoard, newlocator: BoardPosition, config: any) { + const { placeOriginByBoardLocation } = config + + if (placeOriginByBoardLocation == false) + return + if (pb.isAdnormal()) + return // 余料板是余料板,不参与翻转 + let width = pb.width + let length = pb.length + // RIGHT_BOTTOM, 靠板 + if (newlocator == BoardPosition.RIGHT_BOTTOM) { + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth + let y = block.placeY + let placeStyle = this.getPlaceStyle_zy(block) + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + // RIGHT_TOP, 靠板 + if (newlocator == BoardPosition.RIGHT_TOP) { + console.log('BoardPosition=BoardPosition.RIGHT_TOP') + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth + let y = length - block.placeLength - block.placeY + let placeStyle = this.getPlaceStyle_dj(block) + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + // 左上角, 靠板 + if (newlocator == BoardPosition.LEFT_TOP) { + console.log('BoardPosition=BoardPosition.左上角') + for (let block of pb.blockList) { + let x = block.placeX + let y = length - block.placeLength - block.placeY + let placeStyle = this.getPlaceStyle_sx(block) + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + } + + static resetPlacePosition(pb: PlaceBoard, newlocator: BoardPosition, config: any) { + + const { placeOriginByBoardLocation } = config + if (placeOriginByBoardLocation == false) + return + + if (newlocator == BoardPosition.RIGHT_BOTTOM) + return + + let width = pb.width + let length = pb.length + // RIGHT_BOTTOM, 靠板 + if (newlocator == BoardPosition.RIGHT_TOP) { + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth + let y = block.placeY + let placeStyle = this.getPlaceStyle_zy(block) + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + // RIGHT_TOP, 靠板 + if (newlocator == BoardPosition.RIGHT_TOP) { + for (let block of pb.blockList) { + let x = width - block.placeX - block.placeWidth + let y = length - block.placeLength - block.placeY + let placeStyle = this.getPlaceStyle_dj(block) + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + // 左上角, 靠板 + if (newlocator == BoardPosition.LEFT_TOP) { + for (let block of pb.blockList) { + let x = block.placeX + let y = length - block.placeLength - block.placeY + let placeStyle = this.getPlaceStyle_sx(block) + + block.placeX = x + block.placeY = y + block.placeStyle = placeStyle + } + } + } + + // 获得新的放置方式 + static getPlaceStyle_zy(block: PlaceBlock): PlaceStyle // 左右翻, + { + // if (block.IsUnRegular == false && block.HasThroghModel == false) return block.placeStyle; + + if (block.placeStyle == PlaceStyle.FRONT) + return PlaceStyle.BACK + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) + return PlaceStyle.BACK_TURN_LEFT + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) + return PlaceStyle.BACK_TURN_BACK + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) + return PlaceStyle.BACK_TURN_RIGHT + + if (block.placeStyle == PlaceStyle.BACK) + return PlaceStyle.FRONT + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) + return PlaceStyle.FRONT_TURN_RIGHT + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) + return PlaceStyle.FRONT_TURN_BACK + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) + return PlaceStyle.FRONT_TURN_LEFT + + + return PlaceStyle.FRONT + } + + static getPlaceStyle_sx(block: PlaceBlock): PlaceStyle // 上下翻, + { + // if (block.IsUnRegular == false && block.HasThroghModel == false) return block.placeStyle; + + if (block.placeStyle == PlaceStyle.FRONT) + return PlaceStyle.BACK_TURN_BACK + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) + return PlaceStyle.BACK_TURN_RIGHT + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) + return PlaceStyle.BACK + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) + return PlaceStyle.BACK_TURN_LEFT + + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) + return PlaceStyle.FRONT + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) + return PlaceStyle.FRONT_TURN_RIGHT + if (block.placeStyle == PlaceStyle.BACK) + return PlaceStyle.FRONT_TURN_BACK + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) + return PlaceStyle.FRONT_TURN_LEFT + + + return PlaceStyle.FRONT + } + + static getPlaceStyle_dj(block: PlaceBlock): PlaceStyle // 对角翻 + { + // if (block.IsUnRegular == false && block.HasThroghModel == false) return block.placeStyle; + + if (block.placeStyle == PlaceStyle.FRONT) + return PlaceStyle.FRONT_TURN_BACK + if (block.placeStyle == PlaceStyle.FRONT_TURN_RIGHT) + return PlaceStyle.FRONT_TURN_RIGHT + if (block.placeStyle == PlaceStyle.FRONT_TURN_BACK) + return PlaceStyle.FRONT + if (block.placeStyle == PlaceStyle.FRONT_TURN_LEFT) + return PlaceStyle.FRONT_TURN_RIGHT + + if (block.placeStyle == PlaceStyle.BACK_TURN_BACK) + return PlaceStyle.BACK + if (block.placeStyle == PlaceStyle.BACK_TURN_RIGHT) + return PlaceStyle.BACK_TURN_LEFT + if (block.placeStyle == PlaceStyle.BACK) + return PlaceStyle.BACK_TURN_BACK + if (block.placeStyle == PlaceStyle.BACK_TURN_LEFT) + return PlaceStyle.BACK_TURN_RIGHT + + return PlaceStyle.FRONT + } +} diff --git a/tests/dev1/dataHandle/common/Random.ts b/tests/dev1/dataHandle/common/Random.ts new file mode 100644 index 0000000..2e25e51 --- /dev/null +++ b/tests/dev1/dataHandle/common/Random.ts @@ -0,0 +1,11 @@ +import { FixIndex } from './Util' + +export function RandomIndex(count: number, exclude?: number): number +{ + let index = Math.floor(Math.random() * count) + if (index === count) + index = 0 + if (index === exclude) + index = FixIndex(index + 1, count) + return index +} diff --git a/tests/dev1/dataHandle/common/Sleep.ts b/tests/dev1/dataHandle/common/Sleep.ts new file mode 100644 index 0000000..868f879 --- /dev/null +++ b/tests/dev1/dataHandle/common/Sleep.ts @@ -0,0 +1,7 @@ +export async function Sleep(time: number) +{ + return new Promise((res) => + { + setTimeout(res, time) + }) +} diff --git a/tests/dev1/dataHandle/common/Util.ts b/tests/dev1/dataHandle/common/Util.ts new file mode 100644 index 0000000..a42dd3e --- /dev/null +++ b/tests/dev1/dataHandle/common/Util.ts @@ -0,0 +1,38 @@ +/** 判断两个值是否相等 */ +export function equaln(v1: number, v2: number, fuzz = 1e-5) +{ + return Math.abs(v1 - v2) <= fuzz +} + +/** 修正数组索引号 */ +export function FixIndex(index: number, arr: Array | 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(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 +} diff --git a/tests/dev1/dataHandle/common/Vector2.ts b/tests/dev1/dataHandle/common/Vector2.ts new file mode 100644 index 0000000..46a9806 --- /dev/null +++ b/tests/dev1/dataHandle/common/Vector2.ts @@ -0,0 +1,348 @@ +/** 点 */ +export class Point +{ + /** 坐标x */ + x: number + /** 坐标y */ + y: number + + constructor(x: number, y: number) + { + this.x = x + this.y = y + } +} + +/** 二维向量 */ +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 + } +} diff --git a/tests/dev1/dataHandle/common/base/ArrayExt.ts b/tests/dev1/dataHandle/common/base/ArrayExt.ts new file mode 100644 index 0000000..7c73843 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/ArrayExt.ts @@ -0,0 +1,375 @@ +export class List extends Array +{ + /** 返回符合条件的第一个元素 */ + 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(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(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(fn: (item: Item) => T): Item + { + return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b) + } +} + +export class ArrayExt +{ + /** 返回满足条件的元素数量 */ + static count(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(list: Item[], obj: Item) + { + let index = list.findIndex(t => t == obj) + if (index == -1) + return + list.splice(index, 1) + } + + /** 返回符合条件的第一个元素 */ + static first(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(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(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(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(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(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(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(list: Item[], fn: (item: Item) => number) + { + list.sort((a: Item, b: Item): number => fn(b) - fn(a)) + } + + /** 排序成新的数组 */ + static orderBy(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(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(list: Item[], fn: (item: Item) => gT): GroupItem[] + { + let groups = new Array>() + + 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(list: Item[], fn: (item: Item) => rT): rT[] + { + let newList = new Array() + for (const t of list) + { + newList.push(fn(t)) + } + return newList + } + + /** 过来,并按顺序排序 */ + static where(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 +{ + 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) + } +} diff --git a/tests/dev1/dataHandle/common/base/CAD.ts b/tests/dev1/dataHandle/common/base/CAD.ts new file mode 100644 index 0000000..e236560 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/CAD.ts @@ -0,0 +1,1627 @@ +export class Vector2d +{ + m_X = 0 + m_Y = 0 + + /** 长度 */ + Length = 0 + /** 角度 */ + Angle = 0 + + constructor(x: number, y: number) + { + this.m_X = x + this.m_Y = y + + this.GetLength() + this.GetAngle() + } + + private GetLength() + { + this.Length = Math.sqrt(this.m_X * this.m_X + this.m_Y * this.m_Y) + } + + // 角度 + private GetAngle() + { + let a = Math.atan2(this.m_Y, this.m_X) + if (a < 0) + a = Math.PI * 2 + a + this.Angle = a + } + + Normal(): Vector2d + { + if (this.Length != 0) + { + let k = 1 / this.Length + this.m_X *= k + this.m_Y *= k + this.Length = 1 + } + return this + } + + static OpDivide(v: Vector2d, c: number): Vector2d + { + return new Vector2d(v.m_X / c, v.m_Y / c) + } + + static OpMultiply(v: Vector2d, c: number): Vector2d + { + return new Vector2d(v.m_X * c, v.m_Y * c) + } + + static OpAdd(v1: Vector2d, v2: Vector2d): Vector2d + { + return new Vector2d(v1.m_X + v2.m_X, v1.m_Y + v2.m_Y) + } + + static OpMultiplyValue(v1: Vector2d, v2: Vector2d): number + { + return v1.m_X * v2.m_X + v1.m_Y * v2.m_Y + } + + /** 点积 */ + DotProduct(v1: Vector2d): number + { + return Vector2d.OpMultiplyValue(this, v1) + } + + /** 叉积 */ + CrossProduct(v: Vector2d): number + { + return this.m_X * v.m_Y - this.m_Y * v.m_X + } + + IsEqual(v: Vector2d, fuzz = 1e-3): boolean + { + return DoubleUtil.EqualX(this.m_X, v.m_X, fuzz) && DoubleUtil.EqualX(this.m_Y, v.m_Y, fuzz) + } + + /** 返回和另外一个向量的夹角 */ + AngleTo(v: Vector2d): number + { + let an = Math.abs(this.Angle - v.Angle) + if (DoubleUtil.EqualX(v.Length, 0, 1e-5) || DoubleUtil.EqualX(this.Length, 0, 1e-5)) + { + return -Math.PI// 随缘给 + } + else if (Math.abs(Math.sin(an)) < 0) + { + return this.Normal().IsEqual(v.Normal()) ? 0 : Math.PI + } + else + { + let p0 = new Point2d(0, 0) + let p1 = Point2d.OpAdd(new Point2d(0, 0), this) + let p2 = Point2d.OpAdd(new Point2d(0, 0), v) + let s = this.sign(Utils.Det(p1, p0, p2)) + if (an > Math.PI) + { + return ((2 * Math.PI) - an) * s + } + else + { + return an * s + } + } + } + + private sign(x: number): number + { + if (x == 0) + { + return 0 + } + else if (x > 0) + { + return -1 + } + else + { + return 1 + } + } +} + +/** CAD 点 */ +export class Point2d +{ + m_X = 0 + m_Y = 0 + + constructor(x: number, y: number) + { + this.m_X = x + this.m_Y = y + } + + DistensTo(pt: Point2d): number + { + return Point2d.OpSubtract(pt, this).Length + } + + Polar(angle: number, distens: number): Point2d + { + let x1 = this.m_X + Math.cos(angle) * distens + let y1 = this.m_Y + Math.sin(angle) * distens + return new Point2d(x1, y1) + } + + Mid(pt: Point2d): Point2d + { + return new Point2d((this.m_X + pt.m_X) * 0.5, (this.m_Y + pt.m_Y) * 0.5) + } + + AsVector(): Vector2d + { + return new Vector2d(this.m_X, this.m_Y) + } + + /** 减以 - */ + static OpSubtract(p1: Point2d, p2: Point2d): Vector2d + { + return new Vector2d(p1.m_X - p2.m_X, p1.m_Y - p2.m_Y) + } + + static OpAdd(p1: Point2d, p2: Vector2d): Point2d + { + return new Point2d(p1.m_X + p2.m_X, p1.m_Y + p2.m_Y) + } +} + +function equaln(v1: number, v2: number, fuzz = 1e-5) +{ + return Math.abs(v1 - v2) <= fuzz +} +function equalp(v1: Point2d, v2: Point2d, fuzz = 1e-5) +{ + return equaln(v1.m_X, v2.m_X, fuzz) && equaln(v1.m_Y, v2.m_Y, fuzz) +} + +/** 二维曲线:直线,圆弧 */ +export abstract class Curve2d +{ + /** 方向 */ + fx?: number + // 求交点 + abstract IntersectWith(cu: Curve2d, retIns: Point2d[]): void + abstract Offset(distens: number): Curve2d + + // 点在线内部 + abstract PtInCurve(pt: Point2d): boolean + abstract ClosePointTo(pt: Point2d): Point2d + + // 获得参数 返回0或1 + abstract GetParametersAt(pt: Point2d): number + + abstract Parse() + + PtOnCurve(p: Point2d): boolean + { + return equalp(p, this.m_StartPoint) || equalp(p, this.m_EndPoint) || this.ParamOnCurve(this.GetParametersAt(p)) + } + + ParamOnCurve(param: number, fuzz = 1e-6): boolean { return !Number.isNaN(param) && param >= -fuzz && param <= 1 + fuzz } + + get EndPoint(): Point2d + { + return this.m_EndPoint + } + + set EndPoint(p: Point2d) + { + this.m_EndPoint = p + try + { + this.Parse() + } catch (error) + { + + } + } + + get StartPoint(): Point2d + { + return this.m_StartPoint + } + + set StartPoint(p: Point2d) + { + this.m_StartPoint = p + try + { + this.Parse() + } catch (error) + { + + } + } + + protected m_StartPoint: Point2d + protected m_EndPoint: Point2d + tagData: number // 特殊数据 异形边 封边值 + tagData2: number // 特殊数据 侧孔数 +} + +/** 直线 */ +export class Line2d extends Curve2d +{ + get toString() + { + return `[${this.m_StartPoint.m_X.toFixed(1)},${this.m_StartPoint.m_Y.toFixed(1)}] [${this.m_EndPoint.m_X.toFixed(1)},${this.m_EndPoint.m_Y.toFixed(1)}]` + } + + constructor(p1: Point2d, p2: Point2d) + { + super() + this.m_StartPoint = p1 + this.m_EndPoint = p2 + this.Parse() + } + + /** 求交点 */ + IntersectWith(cu: Curve2d, retIns: Point2d[]) + { + if (cu instanceof Line2d) + { + let l = cu as Line2d + let dx1 = this.StartPoint.m_X - this.EndPoint.m_X + let dx2 = l.StartPt.m_X - l.EndPt.m_X + let dx3 = l.EndPt.m_X - this.EndPoint.m_X + let dy1 = this.StartPoint.m_Y - this.EndPoint.m_Y + let dy2 = l.StartPt.m_Y - l.EndPt.m_Y + let dy3 = l.EndPt.m_Y - this.EndPoint.m_Y + + let det = (dx2 * dy1) - (dy2 * dx1) + let pt = new Point2d(0, 0) + + if (DoubleUtil.EqualX(det, 0.0, 1e-5)) + { + if (DoubleUtil.EqualX(dx2 * dy3, dy2 * dx3, 1e-5)) + { + if (l.StartPoint.DistensTo(this.EndPoint) < 1e-3) + { + retIns.push(this.EndPoint) + } + } + return + } + + let ratio = ((dx1 * dy3) - (dy1 * dx3)) / det + pt.m_X = (ratio * dx2) + l.EndPt.m_X + pt.m_Y = (ratio * dy2) + l.EndPt.m_Y + retIns.push(pt) + } + else + { + let arc = cu as Arc2d + arc.IntersectWith(this, retIns) + } + } + + /** 偏移 */ + Offset(distens: number): Curve2d + { + let an = Point2d.OpSubtract(this.EndPt, this.StartPt).Angle - Math.PI * 0.5 + return new Line2d(this.StartPt.Polar(an, distens), this.EndPt.Polar(an, distens)) + } + + /** 判断点在线内 */ + PtInCurve(pt: Point2d): boolean + { + // 首先判断平行 + let minX = Math.min(this.m_StartPoint.m_X, this.m_EndPoint.m_X) + let maxX = Math.max(this.m_StartPoint.m_X, this.m_EndPoint.m_X) + + if (DoubleUtil.EqualX(minX, maxX, 1e-3)) + { + let minY = Math.min(this.m_StartPoint.m_Y, this.m_EndPoint.m_Y) + let maxY = Math.max(this.m_StartPoint.m_Y, this.m_EndPoint.m_Y) + return (pt.m_Y >= minY - 1e-4 && pt.m_Y <= maxY + 1e-4) + } + + if (pt.m_X >= minX - 0.0001 && pt.m_X <= maxX + 0.0001) + { + let vec = Point2d.OpSubtract(this.m_EndPoint, this.m_StartPoint)// 标准向量 + let k = vec.m_Y / vec.m_X + return DoubleUtil.EqualX(this.m_StartPoint.m_Y + k * (pt.m_X - this.m_StartPoint.m_X), pt.m_Y, 0.01) + } + else + { + return false + } + } + + ClosePointTo(pt: Point2d): Point2d + { + let an = Point2d.OpSubtract(this.m_EndPoint, this.m_StartPoint).Angle + Math.PI / 2 + let vec = new Point2d(0, 0).Polar(an, 1).AsVector() + let p2 = Point2d.OpAdd(pt, vec) + let line = new Line2d(pt, p2) + let ptLst: Point2d[] = [] + this.IntersectWith(line, ptLst) + return ptLst[0] + } + + Parse() + { + if (this.EndPoint != null) + { + this.m_Length = this.StartPoint.DistensTo(this.EndPoint) + } + } + + GetParametersAt(pt: Point2d): number + { + let nearPt = this.ClosePointTo(pt) + + let vec = Point2d.OpSubtract(nearPt, this.m_StartPoint) + let vec2 = Point2d.OpSubtract(this.EndPoint, this.StartPoint) + + let an = vec.DotProduct(vec2) + + return Math.sign(an) * vec.Length / this.m_Length + } + + get StartPt(): Point2d { return this.StartPoint } + get EndPt(): Point2d { return this.EndPoint } + + m_Length: number + + static New(x0: number, y0: number, x1: number, y1: number): Line2d + { + let p0 = new Point2d(x0, y0) + let p1 = new Point2d(x1, y1) + return new Line2d(p0, p1) + } +} + +/** 圆弧 */ +export class Arc2d extends Curve2d +{ + constructor(p1: Point2d, p2: Point2d, bul: number) + { + super() + this.m_StartPoint = p1 + this.m_EndPoint = p2 + + this.m_Bul = bul + let vec = Point2d.OpSubtract(p2, p1) + let an = vec.Angle + let length = vec.Length + + this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2 + this.m_AllAngle = Math.atan(bul) * 4 + let delDis = bul * length / 2 + + let toDis = this.m_Radius - delDis + + an += Math.PI * 0.5 + + this.m_Center = p1.Mid(p2) + this.m_Center = this.m_Center.Polar(an, toDis) + + this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle + this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle + + if (this.m_Bul < 0) + this.m_Radius = Math.abs(this.m_Radius) + } + + /** 圆心 */ + m_Center: Point2d + /** 半径 */ + m_Radius: number + /** 起始弧度 */ + m_StartAngle: number + /** 结束弧度 */ + m_EndAngle: number + /** 所有的弧度 */ + m_AllAngle: number + + IntersectWith(cu: Curve2d, retIns: Point2d[]) + { + if (cu instanceof Line2d) + { + let l = cu as Line2d + let a = DoubleUtil.Sqr(l.EndPt.m_X - l.StartPt.m_X) + DoubleUtil.Sqr(l.EndPt.m_Y - l.StartPt.m_Y) + + let b = (2.0) * ((l.EndPt.m_X - l.StartPt.m_X) * (l.StartPt.m_X - this.m_Center.m_X) + + (l.EndPt.m_Y - l.StartPt.m_Y) * (l.StartPt.m_Y - this.m_Center.m_Y)) + + let c = DoubleUtil.Sqr(this.m_Center.m_X) + DoubleUtil.Sqr(this.m_Center.m_Y) + + DoubleUtil.Sqr(l.StartPt.m_X) + DoubleUtil.Sqr(l.StartPt.m_Y) + - (2.0) * (this.m_Center.m_X * l.StartPt.m_X + this.m_Center.m_Y * l.StartPt.m_Y) - DoubleUtil.Sqr(this.m_Radius) + + let det = b * b - (4.0) * a * c + + if (DoubleUtil.EqualX(det, 0.0, 0.1)) + { + let delta = -b / ((2.0) * a) + + let pt = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + retIns.push(pt) + return + } + else if (det > (0.0)) + { + let sqrt_det = Math.sqrt(det) + let delta = (-b + sqrt_det) / ((2.0) * a) + let p2 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + + delta = (-b - sqrt_det) / ((2.0) * a) + let p3 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + + retIns.push(p2) + retIns.push(p3) + return + } + } + else if (cu instanceof Arc2d) + { + let arc = cu as (Arc2d) + let dist = arc.m_Center.DistensTo(this.m_Center) + + if (dist < Math.abs(this.m_Radius - cu.m_Radius) - 1e-3 + || dist > (this.m_Radius + cu.m_Radius + 1e-3)) + return + + let dstsqr = dist * dist + let r1sqr = this.m_Radius * this.m_Radius + let r2sqr = arc.m_Radius * arc.m_Radius + + let a = (dstsqr - r2sqr + r1sqr) / (2 * dist) + let h = Math.sqrt(Math.abs(r1sqr - (a * a))) + + let ratio_a = a / dist + let ratio_h = h / dist + + let dx = arc.m_Center.m_X - this.m_Center.m_X + let dy = arc.m_Center.m_Y - this.m_Center.m_Y + + let phix = this.m_Center.m_X + (ratio_a * dx) + let phiy = this.m_Center.m_Y + (ratio_a * dy) + + dx = dx * ratio_h + dy = dy * ratio_h + + let pt = new Point2d(phix + dy, phiy - dx) + let p2 = new Point2d(phix - dy, phiy + dx) + retIns.push(pt) + retIns.push(p2) + } + } + + Offset(distens: number): Curve2d + { + let rn = new Arc2d(new Point2d(0, 0), new Point2d(0, 0), 0) + rn.m_Center = this.m_Center + rn.m_Radius = this.m_Radius + rn.m_StartAngle = this.m_StartAngle + rn.m_EndAngle = this.m_EndAngle + if (this.m_Bul > 0) + rn.m_Radius += distens + else + rn.m_Radius -= distens + rn.Bul = this.m_Bul + rn.ParsePointFormAngle() + return rn + } + + Parse() + { + // 解析角度 + this.ParseAngleFormPoint() + this.ParseAllAngle() + this.ParseBul() + } + + ParseAllAngle() + { + this.m_AllAngle = this.ComputeAnlge(this.m_EndAngle) + } + + ComputeAnlge(endAngle: number) + { + // 顺时针 + if (this.m_Bul < 0) + { + if (this.m_StartAngle > endAngle) + return this.m_StartAngle - endAngle + else // 越过0点绘制圆弧 + return (Math.PI * 2) - (endAngle - this.m_StartAngle) + } + else + { + if (endAngle > this.m_StartAngle) + return endAngle - this.m_StartAngle + else + return (Math.PI * 2) - (this.m_StartAngle - endAngle) + } + } + + ParseBul() + { + this.m_Bul = Math.tan(this.m_AllAngle * 0.25) * (this.m_Bul < 0 ? -1 : 1) + } + + ParsePointFormAngle() + { + this.m_StartPoint = this.m_Center.Polar(this.m_StartAngle, this.m_Radius) + this.m_EndPoint = this.m_Center.Polar(this.m_EndAngle, this.m_Radius) + } + + ParseAngleFormPoint() + { + this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle + this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle + } + + PtInCurve(pt: Point2d): boolean + { + let param = this.GetParametersAt(pt) + return param > -1e-3 && param < 1.0001 + } + + ClosePointTo(pt: Point2d): Point2d + { + return null + } + + GetParametersAt(pt: Point2d): number + { + let vec = Point2d.OpSubtract(pt, this.m_Center) + let an = vec.Angle + this.ParseAllAngle() + + // 如果以pt为终点,那么所有的角度为 + let ptAllAn = this.ComputeAnlge(an) + let allAn = this.m_AllAngle + + // 减去圆弧角度,剩余角度的一半 + let surplusAngleHalf = Math.PI - allAn / 2 + + if (ptAllAn > allAn + surplusAngleHalf)// 返回负数 + return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn + else// 返回正数 + return ptAllAn / allAn + } + + GetAngleAtParam(param: number) + { + return this.clampRad(this.m_StartAngle + param * this.m_AllAngle) + } + + private clampRad(an: number) + { + an = an % (Math.PI * 2) + if (an < 0) + an += Math.PI * 2 + return an + } + + /** 凸度. */ + m_Bul: number + get Bul() { return this.m_Bul } + set Bul(v: number) { this.m_Bul = v } + + static New(x0: number, y0: number, x1: number, y1: number, bul: number): Arc2d + { + return new Arc2d(new Point2d(x0, y0), new Point2d(x1, y1), bul) + } +} +/** 圆弧 */ +export class Arc2d_new extends Curve2d +{ + constructor(p1: Point2d, p2: Point2d, bul: number) + { + super() + this.m_StartPoint = p1 + this.m_EndPoint = p2 + + this.m_Bul = bul + let vec = Point2d.OpSubtract(p2, p1) + let an = vec.Angle + let length = vec.Length + + this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2 + this.m_AllAngle = Math.atan(bul) * 4 + let delDis = bul * length / 2 + + let toDis = this.m_Radius - delDis + + an += Math.PI * 0.5 + + this.m_Center = p1.Mid(p2) + this.m_Center = this.m_Center.Polar(an, toDis) + + this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle + this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle + + if (this.m_Bul < 0) + this.m_Radius = Math.abs(this.m_Radius) + } + + /** 圆心 */ + m_Center: Point2d + /** 半径 */ + m_Radius: number + /** 起始弧度 */ + m_StartAngle: number + /** 结束弧度 */ + m_EndAngle: number + /** 所有的弧度 */ + m_AllAngle: number + + IntersectWith(cu: Curve2d, retIns: Point2d[]) + { + if (cu instanceof Line2d) + { + let l = cu as Line2d + let a = DoubleUtil.Sqr(l.EndPt.m_X - l.StartPt.m_X) + DoubleUtil.Sqr(l.EndPt.m_Y - l.StartPt.m_Y) + + let b = (2.0) * ((l.EndPt.m_X - l.StartPt.m_X) * (l.StartPt.m_X - this.m_Center.m_X) + + (l.EndPt.m_Y - l.StartPt.m_Y) * (l.StartPt.m_Y - this.m_Center.m_Y)) + + let c = DoubleUtil.Sqr(this.m_Center.m_X) + DoubleUtil.Sqr(this.m_Center.m_Y) + + DoubleUtil.Sqr(l.StartPt.m_X) + DoubleUtil.Sqr(l.StartPt.m_Y) + - (2.0) * (this.m_Center.m_X * l.StartPt.m_X + this.m_Center.m_Y * l.StartPt.m_Y) - DoubleUtil.Sqr(this.m_Radius) + + let det = b * b - (4.0) * a * c + + if (DoubleUtil.EqualX(det, 0.0, 0.1)) + { + let delta = -b / ((2.0) * a) + + let pt = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + retIns.push(pt) + return + } + else if (det > (0.0)) + { + let sqrt_det = Math.sqrt(det) + let delta = (-b + sqrt_det) / ((2.0) * a) + let p2 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + + delta = (-b - sqrt_det) / ((2.0) * a) + let p3 = new Point2d(l.StartPt.m_X + delta * (l.EndPt.m_X - l.StartPt.m_X), l.StartPt.m_Y + delta * (l.EndPt.m_Y - l.StartPt.m_Y)) + + retIns.push(p2) + retIns.push(p3) + return + } + } + else if (cu instanceof Arc2d) + { + let arc = cu as (Arc2d) + let dist = arc.m_Center.DistensTo(this.m_Center) + + if (dist < Math.abs(this.m_Radius - cu.m_Radius) - 1e-3 + || dist > (this.m_Radius + cu.m_Radius + 1e-3)) + return + + let dstsqr = dist * dist + let r1sqr = this.m_Radius * this.m_Radius + let r2sqr = arc.m_Radius * arc.m_Radius + + let a = (dstsqr - r2sqr + r1sqr) / (2 * dist) + let h = Math.sqrt(Math.abs(r1sqr - (a * a))) + + let ratio_a = a / dist + let ratio_h = h / dist + + let dx = arc.m_Center.m_X - this.m_Center.m_X + let dy = arc.m_Center.m_Y - this.m_Center.m_Y + + let phix = this.m_Center.m_X + (ratio_a * dx) + let phiy = this.m_Center.m_Y + (ratio_a * dy) + + dx = dx * ratio_h + dy = dy * ratio_h + + let pt = new Point2d(phix + dy, phiy - dx) + let p2 = new Point2d(phix - dy, phiy + dx) + retIns.push(pt) + retIns.push(p2) + } + } + + Offset(distens: number): Curve2d + { + let rn = new Arc2d(new Point2d(0, 0), new Point2d(0, 0), 0) + rn.m_Center = this.m_Center + rn.m_Radius = this.m_Radius + rn.m_StartAngle = this.m_StartAngle + rn.m_EndAngle = this.m_EndAngle + if (this.m_Bul > 0) + rn.m_Radius += distens + else + rn.m_Radius -= distens + rn.Bul = this.m_Bul + rn.ParsePointFormAngle() + return rn + } + + Parse() + { + // 解析角度 + this.ParseAngleFormPoint() + this.ParseAllAngle() + this.ParseBul() + } + + ParseAllAngle() + { + this.m_AllAngle = this.ComputeAnlge(this.m_EndAngle) + } + + ComputeAnlge(endAngle: number) + { + // 顺时针 + if (this.m_Bul < 0) + { + if (this.m_StartAngle > endAngle) + return this.m_StartAngle - endAngle + else // 越过0点绘制圆弧 + return (Math.PI * 2) - (endAngle - this.m_StartAngle) + } + else + { + if (endAngle > this.m_StartAngle) + return endAngle - this.m_StartAngle + else + return (Math.PI * 2) - (this.m_StartAngle - endAngle) + } + } + + ParseBul() + { + this.m_Bul = Math.tan(this.m_AllAngle * 0.25) * (this.m_Bul < 0 ? -1 : 1) + } + + ParsePointFormAngle() + { + this.m_StartPoint = this.m_Center.Polar(this.m_StartAngle, this.m_Radius) + this.m_EndPoint = this.m_Center.Polar(this.m_EndAngle, this.m_Radius) + } + + ParseAngleFormPoint() + { + this.m_StartAngle = Point2d.OpSubtract(this.StartPoint, this.m_Center).Angle + this.m_EndAngle = Point2d.OpSubtract(this.EndPoint, this.m_Center).Angle + } + + PtInCurve(pt: Point2d): boolean + { + let param = this.GetParametersAt(pt) + return param > -1e-3 && param < 1.0001 + } + + ClosePointTo(pt: Point2d): Point2d + { + return null + } + + GetParametersAt(pt: Point2d): number + { + let vec = Point2d.OpSubtract(pt, this.m_Center) + let an = vec.Angle + this.ParseAllAngle() + + // 如果以pt为终点,那么所有的角度为 + let ptAllAn = this.ComputeAnlge(an) + let allAn = this.m_AllAngle + + // 减去圆弧角度,剩余角度的一半 + let surplusAngleHalf = Math.PI - allAn / 2 + + if (ptAllAn > allAn + surplusAngleHalf)// 返回负数 + return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn + else// 返回正数 + return ptAllAn / allAn + } + + GetAngleAtParam(param: number) + { + return this.clampRad(this.m_StartAngle + param * this.m_AllAngle) + } + + private clampRad(an: number) + { + an = an % (Math.PI * 2) + if (an < 0) + an += Math.PI * 2 + return an + } + + /** 凸度. */ + m_Bul: number + get Bul() { return this.m_Bul } + set Bul(v: number) { this.m_Bul = v } + + static New(x0: number, y0: number, x1: number, y1: number, bul: number): Arc2d + { + return new Arc2d(new Point2d(x0, y0), new Point2d(x1, y1), bul) + } +} +function EntityEncode(c: Curve2d) +{ + if (c instanceof Line2d) + return 1 + else return 2 +} +function EntityEncode2(c1: Curve2d, c2: Curve2d) +{ + return EntityEncode(c1) & EntityEncode(c2) +} + +/** CAD 运算库 */ +export class Utils +{ + static Intersec(e1: Curve2d, e2: Curve2d, oldE1: Curve2d): Point2d + { + if (e1.EndPoint.DistensTo(e2.StartPoint) < 1e-2) + { + return e1.EndPoint + } + let ptsIns: Point2d[] = [] + e1.IntersectWith(e2, ptsIns) + if (ptsIns.length == 1) + { + return ptsIns[0] + } + else if (ptsIns.length == 2) + { + // 求一个最近的点. + let pt = oldE1.EndPoint + let d1 = pt.DistensTo(ptsIns[0]) + let d2 = pt.DistensTo(ptsIns[1]) + return d1 < d2 ? ptsIns[0] : ptsIns[1] + } + else // 0点 + { + return null + } + } + + /** 曲线偏移 */ + + static offsetPoints(points, offsetDistens: number): any[] + { + let ens: Curve2d[] = [] + for (let i = 0; i < points.length; i++) + { + let j = i + 1 + if (j == points.length) + j = 0 + + let line = new Line2d(new Point2d(points[i].x, points[i].y), new Point2d(points[j].x, points[j].y)) + if (line.m_Length == 0) + continue + ens.push(line) + } + + let ens2 = Utils.OffsetCurveList(ens, offsetDistens) + + let newPs = [] + for (let c of ens2) + { + newPs.push({ x: c.StartPoint.m_X, y: c.StartPoint.m_Y }) + } + return newPs + } + + // 1.曲线外偏移. + //* 2.曲线尝试首尾相连 + //* ->如果两条不相连,导致曲线连接失败,将添加圆弧过度 + //* + //* 2017-11-17对曲线相连算法进行修改. + //* 现在会先求出连接点,然后在重新进行曲线连接,避免连接点出错. 参见#118 + static OffsetCurveList(ens: Curve2d[], offsetDistens: number): Curve2d[] + { + // 只处理偏移的曲线 + let offEns: Curve2d[] = [] + + ens = ens.filter(e => e.StartPoint.DistensTo(e.EndPoint) > 0.05) + for (const item of ens) + { + offEns.push(item.Offset(offsetDistens)) + } + // 处理偏移 并且对交点进行处理 + let offEns2: Curve2d[] = [] + let linkPts: Point2d[] = [] + + // 计算链接点,如果有必要会新增圆弧 + for (let i = 0; i < offEns.length; i++) + { + let index2 = i + 1 == offEns.length ? 0 : i + 1 + + let e1 = offEns[i] + let e2 = offEns[index2] + + offEns2.push(offEns[i]) + + let iPts: Point2d[] = [] + e1.IntersectWith(e2, iPts) + let tPts = iPts.filter(p => e1.PtOnCurve(p) && e2.PtOnCurve(p)) + + let code = EntityEncode2(e1, e2) + if (code === 1) + { + if (tPts.length > 0) + linkPts.push(tPts[0])// 直接连接 + else + { + let refP = ens[i].EndPoint + let distSq = iPts[0].DistensTo(refP) + if (distSq > offsetDistens ** 2 * 2.1) // 补圆弧 + { + let arc = new Arc2d(e1.EndPoint, e2.StartPoint, Math.sign(offsetDistens)) + arc.m_Center = ens[i].EndPoint + arc.m_Radius = offsetDistens// 半径 + + linkPts.push(e1.EndPoint) + linkPts.push(e2.StartPoint) + offEns2.push(arc)// 添加圆弧. + } + else + linkPts.push(iPts[0]) + } + } + else if ( + code === 2 + && ( + equalp((e1).m_Center, (e2).m_Center, 0.1) // 都是圆弧,且同心圆(由于精度丢失,这里我们给0.01的容差) + || equalp(e1.EndPoint, e2.StartPoint, 0.1)) + ) + linkPts.push(e1.EndPoint) + else + { + function SelectNearP(pts: Point2d[], refPt: Point2d): Point2d + { + if (pts.length > 1) + { + let dist1 = refPt.DistensTo(pts[0]) + let dist2 = refPt.DistensTo(pts[1]) + return dist1 <= dist2 ? pts[0] : pts[1] + } + return pts[0] + } + + let refP = ens[i].EndPoint + if (tPts.length > 0) + linkPts.push(SelectNearP(iPts, refP))// 直接连接 + else + { + let arc = new Arc2d(e1.EndPoint, e2.StartPoint, Math.sign(offsetDistens)) + arc.m_Center = ens[i].EndPoint + arc.m_Radius = offsetDistens// 半径 + + linkPts.push(e1.EndPoint) + linkPts.push(e2.StartPoint) + offEns2.push(arc)// 添加圆弧. + } + } + } + + // 更新链接点位置 + for (let i = 0; i < offEns2.length; i++) + { + offEns2[i].EndPoint = linkPts[i] + let nextIndex = i == offEns2.length - 1 ? 0 : i + 1 + offEns2[nextIndex].StartPoint = linkPts[i] + } + + // 测试数据 + // TestPolyline([ens, offEns2]) + + return offEns2 + } + + /** //自交曲线优化 去除自交部分. */ + static RemoveSelfIntersect(cus: Curve2d[]): Curve2d[] + { + let res = new Array() + let cout = cus.length + for (let i = 0; i < cout; i++) + { + let cu = cus[i] + res.push(cu) + for (let j = cout - 2; j > i + 1; j--) + { + let cu2 = cus[j] + + let pts = new Array() + cu.IntersectWith(cu2, pts) + + // + if (pts.length == 0) + { + continue + } + if (pts.length == 2) + { + if (cu instanceof Line2d) + { + if (cu.GetParametersAt(pts[0]) > cu.GetParametersAt(pts[1])) + { + pts[0] = pts[1] + } + } + else if (cu instanceof Arc2d) + { + // 使用上一条线. 因为我们保证它和上一条线是有一个交点的 + + let arc = cu as Arc2d + + // last cu + let lastIndex = i - 1 + if (i == 0) + lastIndex = cout - 1 + + let lastCu = cus[lastIndex] + + let insPts = new Array() + lastCu.IntersectWith(cu, insPts) + + for (let insP of insPts) + { + let p1 = arc.GetParametersAt(insP) + let p2 = arc.GetParametersAt(insP) + // 从起点圆弧开始 + if (insP.DistensTo(cu.StartPoint) < 1e-3) + { + if (p1 > p2) + { + pts[0] = pts[1] + } + break + } + // 从终点圆弧开始. + else if (insP.DistensTo(cu.EndPoint) < 1e-3) + { + if (p1 < p2) + { + pts[0] = pts[1] + } + break + } + } + } + } + + if (cu.PtInCurve(pts[0]) && cu2.PtInCurve(pts[0])) + { + Utils.changeP(cus, cu, i, -1, pts[0]) + Utils.changeP(cus, cu, j, 1, pts[0]) + + i = j - 1 + break + } + } + } + + Utils.RemoveZeroCurve(res) + return res + } + + static changeP(cus: Curve2d[], cu: Curve2d, index: number, next: number, _pt: Point2d) + { + let cout = cus.length + let lastIndex = index + next + + let _cu = cus[index] + if (lastIndex == -1) + lastIndex = cout - 1 + else if (lastIndex == cout) + lastIndex = 0 + + let lastCu = cus[lastIndex] + + let insPts = new Array() + lastCu.IntersectWith(_cu, insPts) + + for (const insP of insPts) + { + if (insP.DistensTo(_cu.StartPoint) < 1e-3) + { + _cu.EndPoint = _pt + break + } + else if (insP.DistensTo(_cu.EndPoint) < 1e-3) + { + _cu.StartPoint = _pt + break + } + } + if (cu instanceof Arc2d) + { + cu.Parse() + } + } + + static RemoveZeroCurve(cus: Curve2d[]) + { + // remove zero length curve + /* + cus.RemoveAll((Curve2d o) => + { + if (o is Line2d) + { + return ((Line2d)o).m_Length < 1e-3; + } + else if (o is Arc2d) + { + return ((Arc2d)o).m_AllAngle < 1e-3; + } + return false; + }); + */ + + let length = cus.length + let isZero = false + for (let i = length - 1; i >= 0; i--) + { + let o = cus[i] + + isZero = false + if (o instanceof Line2d) + { + let line = o as Line2d + isZero = line.m_Length < 1e-3 + } + else if (o instanceof Arc2d) + { + let arc = o as Arc2d + isZero = arc.m_AllAngle < 1e-3 + } + + if (isZero) + { + cus.splice(i, 1) + } + } + } + + /** 曲线倒角 */ + static FilletCurveList(culist: Curve2d[], offsetDistens: number): Curve2d[] + { + /* + let rn = new List(); + let len = cuList.Count; + for (int i = 0; i < len; i++) + { + let en = cuList[i]; + let index =i==len-1 ? 0 : i + 1; + rn.Add(en); + if (en is Line2d && cuList[index] is Line2d) + { + let l1 = en as Line2d; + let l2 = cuList[index] as Line2d; + if (l1.m_Length < offsetDistens || l2.m_Length < offsetDistens + || (l1.EndPoint-l1.StartPoint).CrossProduct(l2.EndPoint-l2.StartPoint) <1e-3 + ) + { + continue; + } + + let tempEn1 = en.Offset(-offsetDistens) as Line2d; + let tempEn2 = cuList[index].Offset(-offsetDistens)as Line2d; + + let pts = new List(); + tempEn1.IntersectWith(tempEn2, pts); + if (pts.Count>0 && tempEn1.PtInCurve(pts.First())) + { + let pt = pts.First(); + //求交点. + en.EndPoint= en.ClosePointTo(pt); + let en2 = cuList[index]; + en2.StartPoint = en2.ClosePointTo(pt); + + let arc2d = new Arc2d(en.EndPoint, en2.StartPoint, 1);//凸圆弧 + arc2d.m_Center = pt; + arc2d.m_Radius = pt.DistensTo(en.EndPoint); + arc2d.Parse();//解析凸度 角度 + rn.Add(arc2d); + } + } + } + return rn; + */ + + let rn = new Array() + let len = culist.length + for (let i = 0; i < len; i++) + { + let en = culist[i] + let index = i == len - 1 ? 0 : i + 1 + rn.push(en) + + if (en instanceof Line2d && culist[index] instanceof Line2d) + { + let l1 = en as Line2d + let l2 = culist[index] as Line2d + if (l1.m_Length < offsetDistens || l2.m_Length < offsetDistens + || (Point2d.OpSubtract(l1.EndPoint, l1.StartPoint)).CrossProduct(Point2d.OpSubtract(l2.EndPoint, l2.StartPoint)) < 1e-3 + ) + { + continue + } + + let tempEn1 = en.Offset(-offsetDistens) as Line2d + let tempEn2 = culist[index].Offset(-offsetDistens) as Line2d + + let pts = new Array() + tempEn1.IntersectWith(tempEn2, pts) + if (pts.length > 0 && tempEn1.PtInCurve(pts[0])) + { + let pt = pts[0] + // 求交点. + en.EndPoint = en.ClosePointTo(pt) + let en2 = culist[index] + en2.StartPoint = en2.ClosePointTo(pt) + + let arc2d = new Arc2d(en.EndPoint, en2.StartPoint, 1)// 凸圆弧 + arc2d.m_Center = pt + arc2d.m_Radius = pt.DistensTo(en.EndPoint) + arc2d.Parse()// 解析凸度 角度 + rn.push(arc2d) + } + } + } + return rn + } + + /** 圆弧细分 如果大于90度则分割 */ + static SplitCurveListArc(cuList: Curve2d[]): Curve2d[] + { + /* C# 代码 + let rnList = new List();//返回的曲线表 + foreach (let cu in cuList) + { + if (cu is Arc2d) + { + let arc = cu as Arc2d; + arc.Parse();//解析圆弧的凸度 角度 + if (arc.m_AllAngle > Math.PI * 0.5) + { + int cout =(int)( arc.m_AllAngle / (Math.PI * 0.5)) + 1; + let an = arc.m_AllAngle / cout; + let startAn = arc.m_StartAngle; + for (int i = 0; i < cout; i++) + { + let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0); + arcNew.m_Radius = arc.m_Radius; + arcNew.m_Center = arc.m_Center; + arcNew.m_StartAngle = startAn; + arcNew.m_EndAngle = arcNew.m_StartAngle + an; + startAn = arcNew.m_EndAngle; + rnList.Add(arcNew); + } + } + else + { + rnList.Add(cu); + } + } + else + { + rnList.Add(cu); + } + } + return rnList; + + */ + let rnList = new Array()// 返回的曲线表 + for (let cu of cuList) + { + if (cu instanceof Arc2d) + { + let arc = cu as Arc2d + arc.Parse()// 解析圆弧的凸度 角度 + if (arc.m_AllAngle > Math.PI * 0.5) + { + let cout = arc.m_AllAngle / (Math.PI * 0.5) + 1 + let an = arc.m_AllAngle / cout + let startAn = arc.m_StartAngle + for (let i = 0; i < cout; i++) + { + let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) + arcNew.m_Radius = arc.m_Radius + arcNew.m_Center = arc.m_Center + arcNew.m_StartAngle = startAn + arcNew.m_EndAngle = arcNew.m_StartAngle + an + startAn = arcNew.m_EndAngle + rnList.push(arcNew) + } + } + else + { + rnList.push(cu) + } + } + else + { + rnList.push(cu) + } + } + return rnList + } + + // 三点面积 + static Det(p1: Point2d, p2: Point2d, p3: Point2d): number + { + return this.Det2(p1.AsVector(), p2.AsVector(), p3.AsVector()) + } + + // 三点面积 + static Det2(p1: Vector2d, p2: Vector2d, p3: Vector2d): number + { + return p1.CrossProduct(p2) + p2.CrossProduct(p3) + p3.CrossProduct(p1) + } + + // + static AngleForm3Pt(pSrc: Point2d, p1: Point2d, p2: Point2d): number + { + /* + double angle = 0.0f; // 夹角 + + let vec1 = p1 - pSrc; + let vec2 = p2 - pSrc; + + double productValue = vec1 * vec2; + double cosValue = productValue / (vec1.Length * vec2.Length); // 余弦公式 + + // acos的输入参数范围必须在[-1, 1]之间,否则会"domain error" + // 对输入参数作校验和处理 + if (cosValue < -1 && cosValue > -2) + cosValue = -1; + else if (cosValue > 1 && cosValue < 2) + cosValue = 1; + + // acos返回的是弧度值,转换为角度值 + angle = Math.Acos(cosValue) * 180 / Math.PI; + return angle; + */ + let angle = 0.0 // 夹角 + + let vec1 = Point2d.OpSubtract(p1, pSrc) + let vec2 = Point2d.OpSubtract(p2, pSrc) + + let productValue = Vector2d.OpMultiplyValue(vec1, vec2) + let cosValue = productValue / (vec1.Length * vec2.Length) // 余弦公式 + + // acos的输入参数范围必须在[-1, 1]之间,否则会"domain error" + // 对输入参数作校验和处理 + if (cosValue < -1 && cosValue > -2) + cosValue = -1 + else if (cosValue > 1 && cosValue < 2) + cosValue = 1 + + // acos返回的是弧度值,转换为角度值 + angle = Math.acos(cosValue) * 180 / Math.PI + return angle + } +} + +export class DoubleUtil +{ + static EqualX(v1: number, v2: number, fuzz: number): boolean + { + return (Math.abs(v1 - v2) < fuzz) + } + + static Sqr(v: number): number + { + return v * v + } +} + +export class CADExt +{ + static GetX(arc: Curve2d): number + { + return Math.min(arc.StartPoint.m_X, arc.EndPoint.m_X) + } + + static GetY(arc: Curve2d): number + { + return Math.min(arc.StartPoint.m_Y, arc.EndPoint.m_Y) + } + + static GetWidth(arc: Curve2d): number + { + return Math.abs(arc.StartPoint.m_X - arc.EndPoint.m_X) + } + + static GetHeight(arc: Curve2d): number + { + return Math.abs(arc.StartPoint.m_Y - arc.EndPoint.m_Y) + } + + static GetRadius(p1: Point2d, p2: Point2d, bul: number): number + { + // let arc = new Arc2d(p1, p2, bul); + // return arc.m_Radius; + // this.m_Radius = length / Math.sin(2 * Math.atan(bul)) / 2; + return CADExt.getR(p1.m_X, p1.m_Y, p2.m_X, p2.m_Y, bul) + } + + static getR(x1: number, y1: number, x2: number, y2: number, bul: number): number + { + let length = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + return length / Math.sin(2 * Math.atan(bul)) / 2 + } + + /** 圆弧分割成count段 */ + static SplitArc(arc: Arc2d, count: number): Arc2d[] + { + let list: Arc2d[] + list = [] + + let an = arc.m_AllAngle / count + let an_0 = arc.m_StartAngle + for (let i = 0; i < count; i++) + { + let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) + arcNew.m_Radius = arc.m_Radius + arcNew.m_Center = arc.m_Center + arcNew.m_StartAngle = an_0 + arcNew.m_EndAngle = arcNew.m_StartAngle + an + an_0 = arcNew.m_EndAngle + arcNew.ParsePointFormAngle() + + list.push(arcNew) + } + return list + } + + static SplitArcByCell(ar: Arc2d, cellWidth: number): any[] + { + // 弧长 + let fullLenth = Math.abs(ar.m_Radius * (ar.m_EndAngle - ar.m_StartAngle)) + let count = fullLenth / cellWidth + if (count < 2) + count = 2 + let childs = CADExt.SplitArc(ar, count) + let pts = [] + for (let c of childs) + { + pts.push({ x: c.StartPoint.m_X, y: c.StartPoint.m_Y }) + } + pts.push({ x: ar.EndPoint.m_X, y: ar.EndPoint.m_Y }) + return pts + } + + /** 根据两点,深的弧度 中间的n个点 */ + static getCurvePoints(x1: number, y1: number, x2: number, y2: number, d: number, num: number): any + { + // atan(h/(L/2)) * 4 h: + let dis = 0.5 * Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + let mul = d / dis + let arc = new Arc2d(new Point2d(x1, y1), new Point2d(x2, y2), mul) + + let arc_list = CADExt.SplitArc(arc, num - 1) + let points = [] + for (let li of arc_list) + { + points.push({ x: li.StartPoint.m_X, y: li.StartPoint.m_Y }) + } + points.push({ x: x2, y: y2 }) + return points + } + + /** 求弧长 */ + static getLength(arc: Arc2d): number + { + return arc.m_Radius * Math.abs(arc.m_AllAngle) + } + + /** 按固定线长分割圆弧 */ + static getPointsFromCurve(x1: number, y1: number, x2: number, y2: number, bul: number, dis: number) + { + let arc = new Arc2d(new Point2d(x1, y1), new Point2d(x2, y2), bul) + let length = this.getLength(arc) + let count = Math.max(4, Math.ceil(length / dis)) + let arc_list = CADExt.SplitArc(arc, count) + let points = [] + for (let li of arc_list) + { + points.push({ x: li.StartPoint.m_X, y: li.StartPoint.m_Y }) + } + points.push({ x: x2, y: y2 }) + return points + } + + /** 返回弧形中心点 (不是圆心点) */ + static getCenterPoint(arc: Arc2d): Point2d + { + let arcNew = new Arc2d(arc.StartPoint, arc.EndPoint, 0) + arcNew.m_Radius = arc.m_Radius + arcNew.m_Center = arc.m_Center + arcNew.m_StartAngle = arc.m_StartAngle + arcNew.m_EndAngle = arc.m_StartAngle + arc.m_AllAngle / 2 + arcNew.ParsePointFormAngle() + return arcNew.EndPoint + } + + /** 获得第3点到前2点的距离差 */ + static getDisOff(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): number + { + let dis31 = Math.sqrt((x3 - x1) * (x3 - x1) + (y3 - y1) * (y3 - y1)) + let dis32 = Math.sqrt((x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2)) + let dis12 = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + return dis31 + dis32 - dis12 + } +} +function TestPolyline(offEnsS: Curve2d[][]) +{ + let str = `[${offEnsS.length}` + for (let i = 0; i < offEnsS.length; i++) + { + let offEns = offEnsS[i] + let linedString = `${offEns.length},` + for (let cu of offEns) + { + let bul = (cu instanceof Arc2d) ? cu.m_Bul : 0 + linedString += `[${cu.StartPoint.m_X},${cu.StartPoint.m_Y}],${bul},` + } + str += `,"Polyline",8,2,116,false,1,${7 + i % 2},0,[1,0,0,0,0,1,0,0,0,0,1,0,${Math.floor(i / 2) * 1000},0,0,1],0,0,true,[1,0,0,0,0,1,0,0,0,0,1,0,3000,0,0,1],0,2,${linedString}true` + } + str += ']' + console.log(str) + copyTextToClipboard(str) +} + +let DebugCurves: (Curve2d[])[] = [] +export function CADDebugInit() +{ + DebugCurves = [] +} + +export function CADCopyPolylines() +{ + TestPolyline(DebugCurves) + DebugCurves = [] +} + +function fallbackCopyTextToClipboard(text: string) +{ + let textArea = document.createElement('textarea') + textArea.value = text + document.body.appendChild(textArea) + textArea.focus() + textArea.select() + + try + { + let successful = document.execCommand('copy') + } catch (err) + { + } + document.body.removeChild(textArea) +} +export async function copyTextToClipboard(text: string) +{ + if (!navigator.clipboard) + { + fallbackCopyTextToClipboard(text) + return + } + return await navigator.clipboard.writeText(text) +} + +// ref: https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript + +/** + * 读取剪切板的字符串 + */ +export async function readClipboardText() +{ + if (navigator.clipboard) + return await navigator.clipboard.readText() + return '' +} diff --git a/tests/dev1/dataHandle/common/base/Dxf.ts b/tests/dev1/dataHandle/common/base/Dxf.ts new file mode 100644 index 0000000..8138898 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/Dxf.ts @@ -0,0 +1,490 @@ +export class DxfWritor +{ + layers: WeakSet + activeLayer: Layer + lineTypes: WeakSet + offX = 0 + offY = 0 // 基点 + constructor() + { + this.layers = new WeakSet() + this.activeLayer = null + this.lineTypes = new WeakSet() + + for (let i = 0; i < DrawingType.LINE_TYPES.length; ++i) + { + this.addLineType( + DrawingType.LINE_TYPES[i].name, + DrawingType.LINE_TYPES[i].description, + DrawingType.LINE_TYPES[i].elements, + ) + } + + for (let i = 0; i < DrawingType.LAYERS.length; ++i) + { + this.addLayer( + DrawingType.LAYERS[i].name, + DrawingType.LAYERS[i].colorNumber, + DrawingType.LAYERS[i].lineTypeName, + ) + } + + this.setActiveLayer('0') + } + + /** + * @param {string} name + * @param {string} description + * @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a + */ + addLineType(name, description, elements) + { + this.lineTypes[name] = new LineType(name, description, elements) + return this + } + + addLayer(name, colorNumber, lineTypeName) + { + this.layers[name] = new Layer(name, colorNumber, lineTypeName) + return this + } + + /** 设置当前图层 白=0, 红=1, 黄=2, 绿=3,CYAN=4,BLUE=5,MAGENTA=6 */ + setActiveLayer(name) + { + this.activeLayer = this.layers[name] + return this + } + + setOffset(x1, y1) + { + this.offX = x1 + this.offY = y1 + } + + clear() + { + + } + + drawLine(x1, y1, x2, y2) + { + this.activeLayer.addShape(new Line(x1 + this.offX, y1 + this.offY, x2 + this.offX, y2 + this.offY)) + return this + } + + drawRect(x1, y1, w, h) + { + x1 = x1 + this.offX + y1 = y1 + this.offY + this.activeLayer.addShape(new Line(x1, y1, x1 + w, y1)) + this.activeLayer.addShape(new Line(x1 + w, y1, x1 + w, y1 + h)) + this.activeLayer.addShape(new Line(x1 + w, y1 + h, x1, y1 + h)) + this.activeLayer.addShape(new Line(x1, y1 + h, x1, y1)) + return this + } + + /** + * @param {number} x1 - Center x + * @param {number} y1 - Center y + * @param {number} r - radius + * @param {number} startAngle - degree + * @param {number} endAngle - degree + */ + drawArc(x1, y1, r, startAngle, endAngle) + { + this.activeLayer.addShape(new Arc(x1 + this.offX, y1 + this.offY, r, startAngle, endAngle)) + return this + } + + /** + * @param {number} x1 - Center x + * @param {number} y1 - Center y + * @param {number} r - radius + */ + drawCircle(x1, y1, r) + { + this.activeLayer.addShape(new Circle(x1 + this.offX, y1 + this.offY, r)) + return this + } + + /** + * @param {number} x1 - x + * @param {number} y1 - y + * @param {number} height - Text height + * @param {number} rotation - Text rotation + * @param {string} value - the string itself + */ + drawText(x1, y1, height, rotation, value) + { + this.activeLayer.addShape(new Text(x1 + this.offX, y1 + this.offY, height, rotation, value)) + return this + } + + /** + * @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ] + */ + drawPolyline(points, isClose = false) + { + for (const p of points) + { + p.x += this.offX + p.y += this.offY + } + this.activeLayer.addShape(new Polyline(points, isClose)) + return this + } + + _getDxfLtypeTable() + { + let s = '0\nTABLE\n' // start table + s += '2\nLTYPE\n' // name table as LTYPE table + + for (let lineTypeName in this.lineTypes) + { + s += this.lineTypes[lineTypeName].toDxfString() + } + + s += '0\nENDTAB\n' // end table + + return s + } + + _getDxfLayerTable() + { + let s = '0\nTABLE\n' // start table + s += '2\nLAYER\n' // name table as LAYER table + + for (let layerName in this.layers) + { + s += this.layers[layerName].toDxfString() + } + + s += '0\nENDTAB\n' + + return s + } + + toDxfString() + { + let s = '' + + // start section + s += '0\nSECTION\n' + // name section as TABLES section + s += '2\nTABLES\n' + + s += this._getDxfLtypeTable() + s += this._getDxfLayerTable() + + // end section + s += '0\nENDSEC\n' + + // ENTITES section + s += '0\nSECTION\n' + s += '2\nENTITIES\n' + + for (let layerName in this.layers) + { + let layer = this.layers[layerName] + s += layer.shapesToDxf() + // let shapes = layer.getShapes(); + } + + s += '0\nENDSEC\n' + + // close file + s += '0\nEOF' + + return s + } +} + +namespace DrawingType +{ + // AutoCAD Color Index (ACI) + // http://sub-atomic.com/~moses/acadcolors.html + export const ACI = { + LAYER: 0, + RED: 1, + YELLOW: 2, + GREEN: 3, + CYAN: 4, + BLUE: 5, + MAGENTA: 6, + WHITE: 7, + } + export const LINE_TYPES = [ + { name: 'CONTINUOUS', description: '______', elements: [] }, + { name: 'DASHED', description: '_ _ _ ', elements: [5.0, -5.0] }, + { name: 'DOTTED', description: '. . . ', elements: [0.0, -5.0] }, + ] + export const LAYERS = [ + { name: '0', colorNumber: ACI.WHITE, lineTypeName: 'CONTINUOUS' }, + { name: '1', colorNumber: ACI.RED, lineTypeName: 'CONTINUOUS' }, + { name: '2', colorNumber: ACI.YELLOW, lineTypeName: 'CONTINUOUS' }, + { name: '3', colorNumber: ACI.GREEN, lineTypeName: 'CONTINUOUS' }, + { name: '4', colorNumber: ACI.CYAN, lineTypeName: 'CONTINUOUS' }, + { name: '5', colorNumber: ACI.BLUE, lineTypeName: 'CONTINUOUS' }, + { name: '6', colorNumber: ACI.MAGENTA, lineTypeName: 'CONTINUOUS' }, + ] +} + +export class LineType +{ + name: string + description: string + elements: any[] + + /** + * @param {string} name + * @param {string} description + * @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a + */ + constructor(name, description, elements) + { + this.name = name + this.description = description + this.elements = elements + } + + /** + * @link https://www.autodesk.com/techpubs/autocad/acadr14/dxf/ltype_al_u05_c.htm + */ + toDxfString() + { + let s = '0\nLTYPE\n' + s += '72\n65\n' + s += '70\n64\n' + s += `2\n${this.name}\n` + s += `3\n${this.description}\n` + s += `73\n${this.elements.length}\n` + s += `40\n${this.getElementsSum()}\n` + + for (let i = 0; i < this.elements.length; ++i) + { + s += `49\n${this.elements[i]}\n` + } + + return s + } + + getElementsSum() + { + let sum = 0 + for (let i = 0; i < this.elements.length; ++i) + { + sum += Math.abs(this.elements[i]) + } + + return sum + } +} + +export class Layer +{ + name: string + colorNumber: string + lineTypeName: string + shapes: any[] + constructor(name, colorNumber, lineTypeName) + { + this.name = name + this.colorNumber = colorNumber + this.lineTypeName = lineTypeName + this.shapes = [] + } + + toDxfString() + { + let s = '0\nLAYER\n' + s += '70\n64\n' + s += `2\n${this.name}\n` + s += `62\n${this.colorNumber}\n` + s += `6\n${this.lineTypeName}\n` + return s + } + + addShape(shape) + { + this.shapes.push(shape) + shape.layer = this + } + + getShapes() + { + return this.shapes + } + + shapesToDxf() + { + let s = '' + for (let i = 0; i < this.shapes.length; ++i) + { + s += this.shapes[i].toDxfString() + } + + return s + } +} + +export class Arc +{ + x1: number + y1: number + r: number + startAngle: number + endAngle: number + layer: Layer + /** + * @param {number} x1 - Center x + * @param {number} y1 - Center y + * @param {number} r - radius + * @param {number} startAngle - degree + * @param {number} endAngle - degree + */ + constructor(x1, y1, r, startAngle, endAngle) + { + this.x1 = x1 + this.y1 = y1 + this.r = r + this.startAngle = startAngle + this.endAngle = endAngle + } + + toDxfString() + { + // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm + let s = `0\nARC\n` + s += `8\n${this.layer.name}\n` + s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n` + s += `40\n${this.r}\n50\n${this.startAngle}\n51\n${this.endAngle}\n` + return s + } +} + +export class Circle +{ + x1: number + y1: number + r: number + layer: Layer + /** + * @param {number} x1 - Center x + * @param {number} y1 - Center y + * @param {number} r - radius + */ + constructor(x1, y1, r) + { + this.x1 = x1 + this.y1 = y1 + this.r = r + } + + toDxfString() + { + // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/circle_al_u05_c.htm + let s = `0\nCIRCLE\n` + s += `8\n${this.layer.name}\n` + s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n` + s += `40\n${this.r}\n` + return s + } +} + +export class Line +{ + x1: number + y1: number + x2: number + y2: number + layer: Layer + constructor(x1: number, y1: number, x2: number, y2: number) + { + this.x1 = x1 + this.y1 = y1 + this.x2 = x2 + this.y2 = y2 + } + + toDxfString() + { + // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm + let s = `0\nLINE\n` + s += `8\n${this.layer.name}\n` + s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n` + s += `11\n${this.x2}\n21\n${this.y2}\n31\n0\n` + return s + } +} + +export class Polyline +{ + points: any[] + isClose = false + layer: Layer + /** + * @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ] + */ + constructor(points, isClose) + { + this.points = points + this.isClose = isClose + } + + toDxfString() + { + // https://www.autodesk.com/techpubs/autocad/acad2000/dxf/polyline_dxf_06.htm + // https://www.autodesk.com/techpubs/autocad/acad2000/dxf/vertex_dxf_06.htm + let s = `0\nPOLYLINE\n` + s += `8\n${this.layer.name}\n` + s += `66\n1\n70\n${this.isClose ? 1 : 0}\n` + + for (let i = 0; i < this.points.length; ++i) + { + s += `0\nVERTEX\n` + s += `8\n${this.layer.name}\n` + s += `70\n0\n` + s += `10\n${this.points[i].x}\n20\n${this.points[i].y}\n42\n${this.points[i].r}\n` + } + + s += `0\nSEQEND\n` + return s + } +} + +export class Text +{ + x1: number + y1: number + height: number + rotation: number + value: string + layer: Layer + /** + * @param {number} x1 - x + * @param {number} y1 - y + * @param {number} height - Text height + * @param {number} rotation - Text rotation + * @param {string} value - the string itself + */ + constructor(x1, y1, height, rotation, value) + { + this.x1 = x1 + this.y1 = y1 + this.height = height + this.rotation = rotation + this.value = value + } + + toDxfString() + { + // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/text_al_u05_c.htm + let s = `0\nTEXT\n` + s += `8\n${this.layer.name}\n` + s += `1\n${this.value}\n` + s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n` + s += `40\n${this.height}\n50\n${this.rotation}\n` + return s + } +} diff --git a/tests/dev1/dataHandle/common/base/File.ts b/tests/dev1/dataHandle/common/base/File.ts new file mode 100644 index 0000000..d2c64db --- /dev/null +++ b/tests/dev1/dataHandle/common/base/File.ts @@ -0,0 +1,122 @@ + + +export class textFile +{ + /**保存单文件 text类型的 */ + static saveFile(filename: string, content: string) + { + let blob = new Blob([content], { type: "text/plain;charset=utf-8" }); + this.saveAs(filename, blob); + } + + /**读取文件 选择文件组件,读取文档处理函数 */ + static readFile(eleFile, fn_doText, fileType = "", fn_msg = null) + { + let noEleFile = !(eleFile); + if (noEleFile) + { + eleFile = document.createElement('input'); + eleFile.type = 'file'; + eleFile.accept = 'text/*'; + eleFile.hidden = true; + document.body.appendChild(eleFile); + } + + + if (fileType && fileType != "") eleFile.accept = fileType; + let reader = new FileReader(); + reader.onload = function (event) + { + let text = event.target["result"]; + if (fn_doText) fn_doText(text); + }; + // 选择文件 + eleFile.onchange = function (event) + { + let file = event.target.files[0]; + if (file) + { + reader.readAsText(file); + } + if (fn_msg != null) + { + fn_msg(); + } + }; + eleFile.click(); + + if (noEleFile) + { + document.body.removeChild(eleFile); + } + + } + + static getStringFromFile(eleFile, fileType = "", fn_msg = null): string + { + let noEleFile = !(eleFile); + if (noEleFile) + { + eleFile = document.createElement('input'); + eleFile.type = 'file'; + eleFile.accept = 'text/*'; + eleFile.hidden = true; + document.body.appendChild(eleFile); + } + + + if (fileType && fileType != "") eleFile.accept = fileType; + let reader = new FileReader(); + reader.onload = function (event) + { + let text = event.target["result"]; + return text; + }; + // 选择文件 + eleFile.onchange = function (event) + { + let file = event.target.files[0]; + if (file) + { + reader.readAsText(file); + } + if (fn_msg != null) + { + fn_msg(); + } + }; + eleFile.click(); + + if (noEleFile) + { + document.body.removeChild(eleFile); + } + return ''; + } + /**保存文件 */ + static saveAs(fileName: string, data: Blob) + { + // 创建隐藏的可下载链接 + let eleLink = document.createElement('a'); + eleLink.download = fileName; + eleLink.style.display = 'none'; + eleLink.href = URL.createObjectURL(data); + // 触发点击 + document.body.appendChild(eleLink); + eleLink.click(); + // 然后移除 + document.body.removeChild(eleLink); + } + + static readFile2() + { + let rf = document.createElement('input'); + rf.type = 'file'; + rf.accept = 'text/*'; + rf.hidden = true; + + + document.body.removeChild(rf); + } + +} diff --git a/tests/dev1/dataHandle/common/base/MaskedNumberRange.ts b/tests/dev1/dataHandle/common/base/MaskedNumberRange.ts new file mode 100644 index 0000000..c4b7202 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/MaskedNumberRange.ts @@ -0,0 +1,118 @@ +/** 文本表示的数字取值范围,比如 : "2,3,5-8" 表示 + * () => 大于等于 小于等于 + * [] => 大于 小于 +*/ +export class MaskedNumberRange { + maskedNumbers: any[] + constructor(strRs: string) { + let rs = [] + let strs = strRs.split(',') + for (let str of strs) { + if (str.trim() == '') + continue + let n = Number(str) + + let s_flag = 'in' + let e_flag = 'in' + if (!Number.isNaN(n)) { + rs.push({ s: n, e: n, s_flag, e_flag }) + } + else { + let zys = str.split('-') + if (zys.length != 2) + continue + if (zys[0].trim() == '' || zys[1].trim() == '') + continue + + + + let start :any = zys[0] + let end :any = zys[1] + if (zys[0].trim().includes('(')) { + s_flag = 'notIn' + start = start.replaceAll('(', '') + } else if (zys[0].trim().includes('[')) { + s_flag = 'in' + start = start.replaceAll('[', '') + } + + if (start == 'x') { + s_flag = 'infinite' + }else{ + start = Number(start) + } + + if (zys[1].trim().includes(')')) { + e_flag = 'notIn' + end = end.replaceAll(')', '') + } else if (zys[1].trim().includes(']')) { + e_flag = 'in' + end = end.replaceAll(']', '') + } + + if (end == 'x') { + e_flag = 'infinite' + }else{ + end = Number(end) + } + + + + let s = start // Number(zys[0]) + let e = end //Number(zys[1]) + + + + rs.push({ s: s, e: e, s_flag, e_flag }) + } + } + this.maskedNumbers = rs + } + + /** 参数是否在给定范围内 */ + isInRange(r: number): boolean { + let res = false + + for (let se of this.maskedNumbers) { + + if (se.s_flag == 'infinite') { + // 无穷 不判断 + res = true + } else if (se.s_flag == 'notIn') { + if (r > se.s) { + res = true + } + } else if (se.s_flag == 'in') { + if (r >= se.s) { + res = true + } + } + + if (res == true) { + let res1 = false + + if (se.e_flag == 'infinite') { + res1 = true + } else if (se.e_flag == 'notIn') { + if (r < se.e) { + res1 = true + } + } else if (se.e_flag == 'in') { + if (r <= se.e) { + res1 = true + } + } + + res = res && res1 + } + + if(res == true){ + return true + } + + // if (r > se.s && r < se.e) + // return true + } + return res + } +} diff --git a/tests/dev1/dataHandle/common/base/MathComm.ts b/tests/dev1/dataHandle/common/base/MathComm.ts new file mode 100644 index 0000000..9fecb22 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/MathComm.ts @@ -0,0 +1,128 @@ +/** 相等, 相差小于diff */ +export function equal(a: number, b: number, diff = 0.001): boolean +{ + return Math.abs(a - b) < diff +} + +/** ab相差dis */ +export function isDiffer(a: number, b: number, dis: number, diff = 0.001): boolean +{ + let rdis = Math.abs(a - b) + return rdis > dis - diff && rdis < dis + diff +} + +/** 两点间距离 */ +export function getDis2(p1, p2): number +{ + let x1 = p1.pointX || p1.X || p1.x || 0 + let y1 = p1.pointY || p1.Y || p1.y || 0 + let x2 = p2.pointX || p2.X || p2.x || 0 + let y2 = p2.pointY || p2.Y || p2.y || 0 + return getDis(x1, y1, x2, y2) +} + +/** 两点间距离 */ +export function getDis(x1: number, y1: number, x2: number, y2: number): number +{ + let xt = x1 - x2 + let yt = y1 - y2 + return Math.sqrt(xt * xt + yt * yt) +} + +/** 两点间弧度半径 */ +export function getR(x1: number, y1: number, x2: number, y2: number, bul: number): number +{ + if (bul == 0) + return 0 + let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2 + return 0.5 * d * (1 + bul ** 2) / bul +} + +/** 获取两点间的半径 */ +export function getR2(p1, p2): number +{ + let bul = p1.Curve || p1.bul || 0 + if (bul == 0) + return 0 + + let x1 = p1.pointX || p1.X || p1.x + let y1 = p1.pointY || p1.Y || p1.y + let x2 = p2.pointX || p2.X || p2.x + let y2 = p2.pointY || p2.Y || p2.y + + return getR(x1, y1, x2, y2, bul) +} + +/** 截取小数点 */ +export function round(n: number, bit = 3): number +{ + if (bit < 1) + return n + let tt = 10 ** bit + return Math.round(n * tt) / tt +} + +/** + * 获得四点p{x,y}形成的矩形{x,y,w,l}; + * 不是矩形 返回null + */ +export function getRect(p0, p1, p2, p3): any +{ + if (equal(p0.x, p1.x) == false && equal(p0.y, p1.y) == false) + return null + + // 中心点 + let xc = (p0.x + p1.x + p2.x + p3.x) / 4 + let yc = (p0.y + p1.y + p2.y + p3.y) / 4 + + let dis0 = getDis(p0.x, p0.y, xc, yc) + let dis1 = getDis(p1.x, p1.y, xc, yc) + let dis2 = getDis(p2.x, p2.y, xc, yc) + let dis3 = getDis(p3.x, p3.y, xc, yc) + + if (equal(dis0, dis1) && equal(dis0, dis2) && equal(dis0, dis3)) + { + let x = Math.min(p0.x, p1.x, p2.x, p3.x) + let y = Math.min(p0.y, p1.y, p2.y, p3.y) + + let w = Math.max(p0.x, p1.x, p2.x, p3.x) - x + let l = Math.max(p0.y, p1.y, p2.y, p3.y) - y + if (w < 0.01 || l < 0.01) + return null + return { x, y, w, l } + } + return null +} + +/** 获取两点的直线的一般式方程AX+BY+C=0 ABC */ +export function getLineABC(x1: number, y1: number, x2: number, y2: number) +{ + let A = y2 - y1 + let B = x1 - x2 + let C = x2 * y1 - x1 * y2 + return { A, B, C } +} + +/** 获取点 p(x,y) 到两点的线的距离 */ +export function getDis_PointLine(x1: number, y1: number, x2: number, y2: number, x: number, y: number) +{ + let abc = getLineABC(x1, y1, x2, y2) + let aabb = Math.sqrt(abc.A * abc.A + abc.B * abc.B) + if (aabb == 0) + return 0 + + return Math.abs(abc.A * x + abc.B * y + abc.C) / aabb +} + +/** 删除重复点 */ +export function removeRepeatPoint(pts, fuzz = 0.01) +{ + for (let n = pts.length - 1; n >= 0; n--) { + let p = n - 1 + if (p < 0) + p = pts.length - 1 + + if (getDis2(pts[p], pts[n]) < fuzz) + pts.splice(n, 1) + } +} diff --git a/tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts b/tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts new file mode 100644 index 0000000..9a2b98e --- /dev/null +++ b/tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts @@ -0,0 +1,170 @@ +import { PlaceStyle,EdgeType } from '../../confClass.js' +// import { EdgeType } from '../../vo/enums/EdgeType.js' + +/** + * 排版方式,返回排版后的[下右上左]所对应原始边 rt = [,,,] 下0 右1 上2 左3 + * 比如 rt[0] = 3 表示现在的下边是原始的左边 + * rt[2] = 0 表示现在的上边是原始的下边 + * @param placeStyle 放置方式 + */ +export function getOriginalSides(placeStyle: PlaceStyle): number[] +{ + // let orgSides = [0, 1, 2, 3]; + let orgSides = [EdgeType.BOTTOM, EdgeType.RIGHT, EdgeType.TOP, EdgeType.LEFT] + switch (placeStyle) + { + case PlaceStyle.FRONT: // 正面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + case PlaceStyle.BACK: // 反面 + orgSides[EdgeType.BOTTOM] = EdgeType.BOTTOM + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.TOP + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.RIGHT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.LEFT + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + default: + break + } + return orgSides +} + +/** + * 返回原始边[下右上左] 放置后位置 rt = [,,,,] + * 例如 rt[0] = 3 表示原始下边,现在放置后的新位置是左边 + * rt[2] = 0 表示原始上边,现在放置后的新位置是下边 + * @param placeStyle 放置方式 + */ +export function getPlacedSides(placeStyle: PlaceStyle): number[] +{ + // let orgSides = [0, 1, 2, 3]; + let orgSides = [EdgeType.BOTTOM, EdgeType.RIGHT, EdgeType.TOP, EdgeType.LEFT] + switch (placeStyle) + { + case PlaceStyle.FRONT: // 正面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.BACK: // 反面 + orgSides[EdgeType.BOTTOM] = EdgeType.BOTTOM + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.TOP + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.RIGHT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.LEFT + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + default: + break + } + return orgSides +} + +/** 获取排版位置 */ +export function getPlacePosition(x: number, y: number, width: number, length: number, placeStyle: PlaceStyle): any +{ + let posX = x + let posY = y + switch (placeStyle) + { + case PlaceStyle.FRONT: // 正面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + posX = y + posY = width - x + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + posX = width - x + posY = length - y + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + posX = length - y + posY = x + break + case PlaceStyle.BACK: // 反面 + posX = width - x + posY = y + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + posX = y + posY = x + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + posX = x + posY = length - y + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + posX = length - y + posY = width - x + break + default: + break + } + + return { x: posX, y: posY } +} diff --git a/tests/dev1/dataHandle/common/base/StringBase64.ts b/tests/dev1/dataHandle/common/base/StringBase64.ts new file mode 100644 index 0000000..06b0b6f --- /dev/null +++ b/tests/dev1/dataHandle/common/base/StringBase64.ts @@ -0,0 +1,152 @@ +import gb2312 from './gb2312.json' + +export class StringBase64 { + static ToBase64_gb2312(str: string): string { + let bin = this.toGB2312Bytes(str || '') + let str64 = this.encode(bin) + return str64 + } + + static _table = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'] + + /** 将二进制转换成base64 */ + static encode(bin: number[]): string { + let codes = [] + let un = 0 + un = bin.length % 3 + if (un == 1) + bin.push(0, 0) + else if (un == 2) + bin.push(0) + for (let i = 2; i < bin.length; i += 3) { + let c = bin[i - 2] << 16 + c |= bin[i - 1] << 8 + c |= bin[i] + codes.push(this._table[c >> 18 & 0x3F]) + codes.push(this._table[c >> 12 & 0x3F]) + codes.push(this._table[c >> 6 & 0x3F]) + codes.push(this._table[c & 0x3F]) + } + if (un >= 1) { + codes[codes.length - 1] = '=' + bin.pop() + } + if (un == 1) { + codes[codes.length - 2] = '=' + bin.pop() + } + return codes.join('') + } + + /** 将utf8 转成gb2312字符串 */ + static toGb2312String(str1: string): string { + let substr = '' + let a = '' + let b = '' + let c = '' + let i = -1 + i = str1.indexOf('%') + if (i == -1) { + return str1 + } + while (i != -1) { + if (i < 3) { + substr = substr + str1.substr(0, i - 1) + str1 = str1.substr(i + 1, str1.length - i) + a = str1.substr(0, 2) + str1 = str1.substr(2, str1.length - 2) + if ((Number.parseInt(`0x${a}`) & 0x80) == 0) { + substr = substr + String.fromCharCode(Number.parseInt(`0x${a}`)) + } + else if ((Number.parseInt(`0x${a}`) & 0xE0) == 0xC0) { // two byte + b = str1.substr(1, 2) + str1 = str1.substr(3, str1.length - 3) + let widechar = (Number.parseInt(`0x${a}`) & 0x1F) << 6 + widechar = widechar | (Number.parseInt(`0x${b}`) & 0x3F) + substr = substr + String.fromCharCode(widechar) + } + else { + b = str1.substr(1, 2) + str1 = str1.substr(3, str1.length - 3) + c = str1.substr(1, 2) + str1 = str1.substr(3, str1.length - 3) + let widechar = (Number.parseInt(`0x${a}`) & 0x0F) << 12 + widechar = widechar | ((Number.parseInt(`0x${b}`) & 0x3F) << 6) + widechar = widechar | (Number.parseInt(`0x${c}`) & 0x3F) + substr = substr + String.fromCharCode(widechar) + } + } + else { + substr = substr + str1.substring(0, i) + str1 = str1.substring(i) + } + i = str1.indexOf('%') + } + + return substr + str1 + } + + private static _unicode2gb + static getUnicode2gb() { + if (this._unicode2gb == null) { + this._unicode2gb = gb2312 + } + return this._unicode2gb + } + + static toGB2312Bytes(str: string): number[] { + let unicode2gb = this.getUnicode2gb() + let res = []; let len = str.length + for (let i = 0; i < len; i++) { + let code = str.charCodeAt(i) + if (code <= 0x007F) { + res.push(code) + } + else { + let hex = unicode2gb[`0x${code.toString(16).toUpperCase()}`] + let gb = Number(hex) + if (Number.isNaN(gb)) + gb = Number('0xA1F5') + + let arr = [] + while (gb > 0) { + arr.push(gb & 0xFF) + gb >>= 8 + } + while (arr.length > 0) res.push(arr.pop()) + } + } + return res + } + + static fromGB2312Bytes(gb2312Bytes: number[]): string { + let unicode2gb = this.getUnicode2gb() + let res = [] + // var i = 0 + for (let i = 0; i < gb2312Bytes.length; i++) { + let code = gb2312Bytes[i] + if (code < 0xA1 || code > 0xFE || i + 1 == gb2312Bytes.length) { + res.push(String.fromCharCode(code)) + continue + } + let c2 = gb2312Bytes[i + 1] + if (code < 0xA1 || code > 0xFE) { + res.push(String.fromCharCode(code)) + continue + } + let g = c2 | code << 8 + + c2 = Number(unicode2gb[`0x${g.toString(16).toUpperCase()}`]) + if (typeof c2 == 'undefined') { + res.push(String.fromCharCode(code)) + continue + } + res.push(String.fromCharCode(c2)) + i++ + } + return res.join('') + } +} diff --git a/tests/dev1/dataHandle/common/base/StringBuidler.ts b/tests/dev1/dataHandle/common/base/StringBuidler.ts new file mode 100644 index 0000000..4edafc4 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/StringBuidler.ts @@ -0,0 +1,82 @@ +export class StringBuider +{ + strings: string[] + + constructor() + { + this.strings = [] + } + + /** 插入 */ + Append(obj: any) + { + this.strings.push(obj) + } + + /** 插入文本 */ + AppendString(str: string) { this.strings.push(str) } + + /** 格式化 插入 */ + AppendFormat(str: string, ...arg: any[]): string + { + for (let i = 0; i < arg.length; i++) + { + let parent = `\\{${i}\\}` + let reg = new RegExp(parent, 'g') + str = str.replace(reg, arg[i]) + } + this.strings.push(str) + return str + } + + /** 添加一行(回车) */ + AppendLine(str = '') + { + if (str != null) + { + this.AppendString(str) + } + } + + /** 插入 */ + Insert(index: number, ...strs) + { + if (strs.length == 0) + return + if (index > this.strings.length) + index = this.strings.length + if (index < 0) + index = 0 + this.strings.splice(index, 0, ...strs) + } + + /** 删除片段 */ + Remove(startIndex: number, length: number): string[] + { + return this.strings.splice(startIndex, length) + } + + /** 所有文本 */ + ToString(): string + { + return this.strings.join('\r\n') + } + + /** 清空 */ + Clear() { this.strings = [] } + + /** 容量 */ + get size() { return this.strings.length } + /** 文本数 */ + get Count() { return this.strings.length } + + GetStringLength(): number + { + let length = 0 + for (const str of this.strings) + { + length += str.length + } + return length + } +} diff --git a/tests/dev1/dataHandle/common/base/StringFormat.ts b/tests/dev1/dataHandle/common/base/StringFormat.ts new file mode 100644 index 0000000..6ed1616 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/StringFormat.ts @@ -0,0 +1,126 @@ +export class StringFormat +{ + /** + * 对Date的扩展,将 Date 转化为指定格式的String + * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符 + * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) + * eg: + * (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 + * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04 + * (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04 + * (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04 + * (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 + */ + static Date(date: Date, fmt): string + { + let o = { + 'M+': date.getMonth() + 1, // 月份 + 'd+': date.getDate(), // 日 + 'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时 + 'H+': date.getHours(), // 小时 + 'm+': date.getMinutes(), // 分 + 's+': date.getSeconds(), // 秒 + 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 + 'S': date.getMilliseconds(), // 毫秒 + } + let week = { + 0: '/u65e5', + 1: '/u4e00', + 2: '/u4e8c', + 3: '/u4e09', + 4: '/u56db', + 5: '/u4e94', + 6: '/u516d', + } + if (/(y+)/.test(fmt)) + { + fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length)) + } + if (/(E+)/.test(fmt)) + { + fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`]) + } + for (let k in o) + { + if (new RegExp(`(${k})`).test(fmt)) + { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length))) + } + } + return fmt + } + + /** 返回数字的固定小数点 123.00 */ + static number(value: number, bit: number): string + { + return value?.toFixed(bit).toString() + } + + static toFixed(value: number, bit: number = 3): number + { + let tt = 10 ** bit + return Math.round(value * tt) / tt + } + + static filterIllegalChar(str: string): string + { + // var pattern = new RegExp("[/:*?'<>|\\]"); + // var rs = ""; + // for (var i = 0; i < str.length; i++) + // { + // rs = rs + str.substr(i, 1).replace(pattern, ''); + // } + // return rs; + + let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\/g, '').replace(/\|/g, '').replace(/\'/g, '') + 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]) + } +} diff --git a/tests/dev1/dataHandle/common/base/TextFile.ts b/tests/dev1/dataHandle/common/base/TextFile.ts new file mode 100644 index 0000000..d4e24e0 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/TextFile.ts @@ -0,0 +1,119 @@ +export class textFile +{ + /** 保存单文件 text类型的 */ + static saveFile(filename: string, content: string) + { + let blob = new Blob([content], { type: 'text/plain;charset=utf-8' }) + this.saveAs(filename, blob) + } + + /** 读取文件 选择文件组件,读取文档处理函数 */ + static readFile(eleFile, fn_doText, fileType = '', fn_msg = null) + { + let noEleFile = !(eleFile) + if (noEleFile) + { + eleFile = document.createElement('input') + eleFile.type = 'file' + eleFile.accept = 'text/*' + eleFile.hidden = true + document.body.appendChild(eleFile) + } + + if (fileType && fileType != '') + eleFile.accept = fileType + let reader = new FileReader() + reader.onload = function (event) + { + let text = event.target.result + if (fn_doText) + fn_doText(text) + } + // 选择文件 + eleFile.onchange = function (event) + { + let file = event.target.files[0] + if (file) + { + reader.readAsText(file) + } + if (fn_msg != null) + { + fn_msg() + } + } + eleFile.click() + + if (noEleFile) + { + document.body.removeChild(eleFile) + } + } + + static getStringFromFile(eleFile, fileType = '', fn_msg = null): string + { + let noEleFile = !(eleFile) + if (noEleFile) + { + eleFile = document.createElement('input') + eleFile.type = 'file' + eleFile.accept = 'text/*' + eleFile.hidden = true + document.body.appendChild(eleFile) + } + + if (fileType && fileType != '') + eleFile.accept = fileType + let reader = new FileReader() + reader.onload = function (event) + { + let text = event.target.result + return text + } + // 选择文件 + eleFile.onchange = function (event) + { + let file = event.target.files[0] + if (file) + { + reader.readAsText(file) + } + if (fn_msg != null) + { + fn_msg() + } + } + eleFile.click() + + if (noEleFile) + { + document.body.removeChild(eleFile) + } + return '' + } + + /** 保存文件 */ + static saveAs(fileName: string, data: Blob) + { + // 创建隐藏的可下载链接 + let eleLink = document.createElement('a') + eleLink.download = fileName + eleLink.style.display = 'none' + eleLink.href = URL.createObjectURL(data) + // 触发点击 + document.body.appendChild(eleLink) + eleLink.click() + // 然后移除 + document.body.removeChild(eleLink) + } + + static readFile2() + { + let rf = document.createElement('input') + rf.type = 'file' + rf.accept = 'text/*' + rf.hidden = true + + document.body.removeChild(rf) + } +} diff --git a/tests/dev1/dataHandle/common/base/ZipFile.ts b/tests/dev1/dataHandle/common/base/ZipFile.ts new file mode 100644 index 0000000..8acff2d --- /dev/null +++ b/tests/dev1/dataHandle/common/base/ZipFile.ts @@ -0,0 +1,138 @@ +import { saveAs } from 'file-saver' +import type { ZipProvider } from '../zip.js' +import { DefaultZipProvider, FileInfo } from '../zip.js' +import { StringBuider } from './StringBuidler.js' + +// import { textFile } from './File'; +import { StringBase64 } from './StringBase64.js' + +export class FileZip +{ + zipName: string + // zip对象 + private zip: ZipProvider = DefaultZipProvider() + // 文件数据 + private stringBuider: StringBuider + + constructor() + { + this.stringBuider = new StringBuider() + this.SetZipFileName('') + } + + /** 设置zip文件名 */ + SetZipFileName(_name: string) + { + this.zipName = _name + } + + getZipFileName() + { + return this.zipName + } + + PushBlobFile(fileName: string, content) + { + let newFile = new FileInfo(fileName, content, false) + newFile.binary = true + this.zip.addFile(newFile) + } + + // /**添加文件 */ + // PushFile(fileName: string, fileText: string, isBase64 = false, isgb2312 = false, isUtf8Bom=false) + // { + // if (isgb2312) + // { + // isBase64 = true; + // fileText = StringBase64.ToBase64_gb2312(fileText); + // } + // if(isUtf8Bom) + // { + // fileText = String.fromCharCode(parseInt("0xFEFF")) + fileText; + // } + // let newFile = new FileInfo(fileName, fileText, isBase64); + // this.zip.addFile(newFile); + // } + + /** 添加文件 fileName文件名 fileText文件内容 isBase64是否base64 encoding编码格式(gb2312, utf8bom) */ + PushFile(fileName: string, fileText: string, isBase64 = false, encoding: string = 'gb2312') + { + if (encoding == 'gb2312') + { + isBase64 = true + fileText = StringBase64.ToBase64_gb2312(fileText) + } + if (encoding == 'utf8bom') + { + fileText = String.fromCharCode(Number.parseInt('0xFEFF')) + fileText + } + let newFile = new FileInfo(fileName, fileText, isBase64) + this.zip.addFile(newFile) + } + + pushFile_withBom(fileName: string, fileText: string) + { + this.PushFile(fileName, fileText, false, 'utf8bom') + } + + PushFile_GB2312(fileName: string, fileText: string) + { + this.PushFile(fileName, fileText, false, 'gb2312') + } + + /** 新建文件 */ + NewFile() + { + this.stringBuider.Clear() + } + + /** 推送文本到缓存区 */ + AppentText(comment: string) + { + this.stringBuider.Append(comment) + } + + // //将缓存区的文本 保存起来 + // SaveFile(fileName: string, isgb2312: boolean = false, isutf8bom = false) + // { + // let fileText = this.stringBuider.ToString(); + // this.stringBuider.Clear(); + // this.PushFile(fileName, fileText, false, isgb2312, isutf8bom); + // } + + // 将缓存区的文本 保存起来 + SaveFile(fileName: string, encoding: string = 'gb2312') + { + let fileText = this.stringBuider.ToString() + this.stringBuider.Clear() + this.PushFile(fileName, fileText, false, encoding) + } + + // 下载zip文件 + async Download(zipName = '') + { + let content = await this.zip.saveAsync() + // textFile.saveAs(FileZip.zipName,content); + let name = zipName || this.zipName + saveAs(content, name) + } + + static WriteFile(fname: string, data: BlobPart) + { + let blob = new Blob([data], { type: 'octet/stream' }) + + let download = document.createElement('a') + download.download = fname + download.href = window.URL.createObjectURL(blob) + download.style.display = 'none' + download.onclick = function () + { + document.body.removeChild(download) + } + document.body.appendChild(download) + + download.click() + }; +} + +export { FileInfo } diff --git a/tests/dev1/dataHandle/common/base/gb2312.json b/tests/dev1/dataHandle/common/base/gb2312.json new file mode 100644 index 0000000..8159ac5 --- /dev/null +++ b/tests/dev1/dataHandle/common/base/gb2312.json @@ -0,0 +1,6965 @@ +{ + "0x3001": "0xA1A2", + "0x3002": "0xA1A3", + "0x00B7": "0xA1A4", + "0x02C9": "0xA1A5", + "0x02C7": "0xA1A6", + "0x00A8": "0xA1A7", + "0x3003": "0xA1A8", + "0x2014": "0xA1AA", + "0xFF5E": "0xA1AB", + "0x2016": "0xA1AC", + "0x2026": "0xA1AD", + "0x2018": "0xA1AE", + "0x2019": "0xA1AF", + "0x201C": "0xA1B0", + "0x201D": "0xA1B1", + "0x3014": "0xA1B2", + "0x3015": "0xA1B3", + "0x3008": "0xA1B4", + "0x3009": "0xA1B5", + "0x300A": "0xA1B6", + "0x300B": "0xA1B7", + "0x300C": "0xA1B8", + "0x300D": "0xA1B9", + "0x300E": "0xA1BA", + "0x300F": "0xA1BB", + "0x3016": "0xA1BC", + "0x3017": "0xA1BD", + "0x3010": "0xA1BE", + "0x3011": "0xA1BF", + "0x00B1": "0xA1C0", + "0x00D7": "0xA1C1", + "0x00F7": "0xA1C2", + "0x2236": "0xA1C3", + "0x2227": "0xA1C4", + "0x2238": "0xA1C5", + "0x2211": "0xA1C6", + "0x220F": "0xA1C7", + "0x222A": "0xA1C8", + "0x2229": "0xA1C9", + "0x2208": "0xA1CA", + "0x2237": "0xA1CB", + "0x221A": "0xA1CC", + "0x22A5": "0xA1CD", + "0x2225": "0xA1CE", + "0x2220": "0xA1CF", + "0x2312": "0xA1D0", + "0x2299": "0xA1D1", + "0x222B": "0xA1D2", + "0x222E": "0xA1D3", + "0x2261": "0xA1D4", + "0x224C": "0xA1D5", + "0x2248": "0xA1D6", + "0x223D": "0xA1D7", + "0x221D": "0xA1D8", + "0x2260": "0xA1D9", + "0x226E": "0xA1DA", + "0x226F": "0xA1DB", + "0x2264": "0xA1DC", + "0x2265": "0xA1DD", + "0x221E": "0xA1DE", + "0x2235": "0xA1DF", + "0x2234": "0xA1E0", + "0x2642": "0xA1E1", + "0x2640": "0xA1E2", + "0x00B0": "0xA1E3", + "0x2032": "0xA1E4", + "0x2033": "0xA1E5", + "0x2103": "0xA1E6", + "0xFF04": "0xA1E7", + "0x00A4": "0xA1E8", + "0xFFE0": "0xA1E9", + "0xFFE1": "0xA1EA", + "0x2030": "0xA1EB", + "0x00A7": "0xA1EC", + "0x2116": "0xA1ED", + "0x2606": "0xA1EE", + "0x2605": "0xA1EF", + "0x25CB": "0xA1F0", + "0x25CF": "0xA1F1", + "0x25CE": "0xA1F2", + "0x25C7": "0xA1F3", + "0x25C6": "0xA1F4", + "0x25A1": "0xA1F5", + "0x25A0": "0xA1F6", + "0x25B3": "0xA1F7", + "0x25B2": "0xA1F8", + "0x203B": "0xA1F9", + "0x2192": "0xA1FA", + "0x2190": "0xA1FB", + "0x2191": "0xA1FC", + "0x2193": "0xA1FD", + "0x3013": "0xA1FE", + "0x2170": "0xA2A1", + "0x2171": "0xA2A2", + "0x2172": "0xA2A3", + "0x2173": "0xA2A4", + "0x2174": "0xA2A5", + "0x2175": "0xA2A6", + "0x2176": "0xA2A7", + "0x2177": "0xA2A8", + "0x2178": "0xA2A9", + "0x2179": "0xA2AA", + "0x2488": "0xA2B1", + "0x2489": "0xA2B2", + "0x248A": "0xA2B3", + "0x248B": "0xA2B4", + "0x248C": "0xA2B5", + "0x248D": "0xA2B6", + "0x248E": "0xA2B7", + "0x248F": "0xA2B8", + "0x2490": "0xA2B9", + "0x2491": "0xA2BA", + "0x2492": "0xA2BB", + "0x2493": "0xA2BC", + "0x2494": "0xA2BD", + "0x2495": "0xA2BE", + "0x2496": "0xA2BF", + "0x2497": "0xA2C0", + "0x2498": "0xA2C1", + "0x2499": "0xA2C2", + "0x249A": "0xA2C3", + "0x249B": "0xA2C4", + "0x2474": "0xA2C5", + "0x2475": "0xA2C6", + "0x2476": "0xA2C7", + "0x2477": "0xA2C8", + "0x2478": "0xA2C9", + "0x2479": "0xA2CA", + "0x247A": "0xA2CB", + "0x247B": "0xA2CC", + "0x247C": "0xA2CD", + "0x247D": "0xA2CE", + "0x247E": "0xA2CF", + "0x247F": "0xA2D0", + "0x2480": "0xA2D1", + "0x2481": "0xA2D2", + "0x2482": "0xA2D3", + "0x2483": "0xA2D4", + "0x2484": "0xA2D5", + "0x2485": "0xA2D6", + "0x2486": "0xA2D7", + "0x2487": "0xA2D8", + "0x2460": "0xA2D9", + "0x2461": "0xA2DA", + "0x2462": "0xA2DB", + "0x2463": "0xA2DC", + "0x2464": "0xA2DD", + "0x2465": "0xA2DE", + "0x2466": "0xA2DF", + "0x2467": "0xA2E0", + "0x2468": "0xA2E1", + "0x2469": "0xA2E2", + "0x3220": "0xA2E5", + "0x3221": "0xA2E6", + "0x3222": "0xA2E7", + "0x3223": "0xA2E8", + "0x3224": "0xA2E9", + "0x3225": "0xA2EA", + "0x3226": "0xA2EB", + "0x3227": "0xA2EC", + "0x3228": "0xA2ED", + "0x3229": "0xA2EE", + "0x2160": "0xA2F1", + "0x2161": "0xA2F2", + "0x2162": "0xA2F3", + "0x2163": "0xA2F4", + "0x2164": "0xA2F5", + "0x2165": "0xA2F6", + "0x2166": "0xA2F7", + "0x2167": "0xA2F8", + "0x2168": "0xA2F9", + "0x2169": "0xA2FA", + "0x216A": "0xA2FB", + "0x216B": "0xA2FC", + "0xFF01": "0xA3A1", + "0xFFE5": "0xA3A4", + "0xFF08": "0xA3A8", + "0xFF09": "0xA3A9", + "0xFF0C": "0xA3AC", + "0xFF1A": "0xA3BA", + "0xFF1B": "0xA3BB", + "0xFF1F": "0xA3BF", + "0x2105": "0xA847", + "0x2109": "0xA848", + "0x338E": "0xA94A", + "0x338F": "0xA94B", + "0x339C": "0xA94C", + "0x339D": "0xA94D", + "0x33A1": "0xA94F", + "0x33C4": "0xA950", + "0x33CE": "0xA951", + "0x33D1": "0xA952", + "0x33D2": "0xA953", + "0x33D5": "0xA954", + "0x2121": "0xA959", + "0x554A": "0xB0A1", + "0x963F": "0xB0A2", + "0x57C3": "0xB0A3", + "0x6328": "0xB0A4", + "0x54CE": "0xB0A5", + "0x5509": "0xB0A6", + "0x54C0": "0xB0A7", + "0x7691": "0xB0A8", + "0x764C": "0xB0A9", + "0x853C": "0xB0AA", + "0x77EE": "0xB0AB", + "0x827E": "0xB0AC", + "0x788D": "0xB0AD", + "0x7231": "0xB0AE", + "0x9698": "0xB0AF", + "0x978D": "0xB0B0", + "0x6C28": "0xB0B1", + "0x5B89": "0xB0B2", + "0x4FFA": "0xB0B3", + "0x6309": "0xB0B4", + "0x6697": "0xB0B5", + "0x5CB8": "0xB0B6", + "0x80FA": "0xB0B7", + "0x6848": "0xB0B8", + "0x80AE": "0xB0B9", + "0x6602": "0xB0BA", + "0x76CE": "0xB0BB", + "0x51F9": "0xB0BC", + "0x6556": "0xB0BD", + "0x71AC": "0xB0BE", + "0x7FF1": "0xB0BF", + "0x8884": "0xB0C0", + "0x50B2": "0xB0C1", + "0x5965": "0xB0C2", + "0x61CA": "0xB0C3", + "0x6FB3": "0xB0C4", + "0x82AD": "0xB0C5", + "0x634C": "0xB0C6", + "0x6252": "0xB0C7", + "0x53ED": "0xB0C8", + "0x5427": "0xB0C9", + "0x7B06": "0xB0CA", + "0x516B": "0xB0CB", + "0x75A4": "0xB0CC", + "0x5DF4": "0xB0CD", + "0x62D4": "0xB0CE", + "0x8DCB": "0xB0CF", + "0x9776": "0xB0D0", + "0x628A": "0xB0D1", + "0x8019": "0xB0D2", + "0x575D": "0xB0D3", + "0x9738": "0xB0D4", + "0x7F62": "0xB0D5", + "0x7238": "0xB0D6", + "0x767D": "0xB0D7", + "0x67CF": "0xB0D8", + "0x767E": "0xB0D9", + "0x6446": "0xB0DA", + "0x4F70": "0xB0DB", + "0x8D25": "0xB0DC", + "0x62DC": "0xB0DD", + "0x7A17": "0xB0DE", + "0x6591": "0xB0DF", + "0x73ED": "0xB0E0", + "0x642C": "0xB0E1", + "0x6273": "0xB0E2", + "0x822C": "0xB0E3", + "0x9881": "0xB0E4", + "0x677F": "0xB0E5", + "0x7248": "0xB0E6", + "0x626E": "0xB0E7", + "0x62CC": "0xB0E8", + "0x4F34": "0xB0E9", + "0x74E3": "0xB0EA", + "0x534A": "0xB0EB", + "0x529E": "0xB0EC", + "0x7ECA": "0xB0ED", + "0x90A6": "0xB0EE", + "0x5E2E": "0xB0EF", + "0x6886": "0xB0F0", + "0x699C": "0xB0F1", + "0x8180": "0xB0F2", + "0x7ED1": "0xB0F3", + "0x68D2": "0xB0F4", + "0x78C5": "0xB0F5", + "0x868C": "0xB0F6", + "0x9551": "0xB0F7", + "0x508D": "0xB0F8", + "0x8C24": "0xB0F9", + "0x82DE": "0xB0FA", + "0x80DE": "0xB0FB", + "0x5305": "0xB0FC", + "0x8912": "0xB0FD", + "0x5265": "0xB0FE", + "0x8584": "0xB1A1", + "0x96F9": "0xB1A2", + "0x4FDD": "0xB1A3", + "0x5821": "0xB1A4", + "0x9971": "0xB1A5", + "0x5B9D": "0xB1A6", + "0x62B1": "0xB1A7", + "0x62A5": "0xB1A8", + "0x66B4": "0xB1A9", + "0x8C79": "0xB1AA", + "0x9C8D": "0xB1AB", + "0x7206": "0xB1AC", + "0x676F": "0xB1AD", + "0x7891": "0xB1AE", + "0x60B2": "0xB1AF", + "0x5351": "0xB1B0", + "0x5317": "0xB1B1", + "0x8F88": "0xB1B2", + "0x80CC": "0xB1B3", + "0x8D1D": "0xB1B4", + "0x94A1": "0xB1B5", + "0x500D": "0xB1B6", + "0x72C8": "0xB1B7", + "0x5907": "0xB1B8", + "0x60EB": "0xB1B9", + "0x7119": "0xB1BA", + "0x88AB": "0xB1BB", + "0x5954": "0xB1BC", + "0x82EF": "0xB1BD", + "0x672C": "0xB1BE", + "0x7B28": "0xB1BF", + "0x5D29": "0xB1C0", + "0x7EF7": "0xB1C1", + "0x752D": "0xB1C2", + "0x6CF5": "0xB1C3", + "0x8E66": "0xB1C4", + "0x8FF8": "0xB1C5", + "0x903C": "0xB1C6", + "0x9F3B": "0xB1C7", + "0x6BD4": "0xB1C8", + "0x9119": "0xB1C9", + "0x7B14": "0xB1CA", + "0x5F7C": "0xB1CB", + "0x78A7": "0xB1CC", + "0x84D6": "0xB1CD", + "0x853D": "0xB1CE", + "0x6BD5": "0xB1CF", + "0x6BD9": "0xB1D0", + "0x6BD6": "0xB1D1", + "0x5E01": "0xB1D2", + "0x5E87": "0xB1D3", + "0x75F9": "0xB1D4", + "0x95ED": "0xB1D5", + "0x655D": "0xB1D6", + "0x5F0A": "0xB1D7", + "0x5FC5": "0xB1D8", + "0x8F9F": "0xB1D9", + "0x58C1": "0xB1DA", + "0x81C2": "0xB1DB", + "0x907F": "0xB1DC", + "0x965B": "0xB1DD", + "0x97AD": "0xB1DE", + "0x8FB9": "0xB1DF", + "0x7F16": "0xB1E0", + "0x8D2C": "0xB1E1", + "0x6241": "0xB1E2", + "0x4FBF": "0xB1E3", + "0x53D8": "0xB1E4", + "0x535E": "0xB1E5", + "0x8FA8": "0xB1E6", + "0x8FA9": "0xB1E7", + "0x8FAB": "0xB1E8", + "0x904D": "0xB1E9", + "0x6807": "0xB1EA", + "0x5F6A": "0xB1EB", + "0x8198": "0xB1EC", + "0x8868": "0xB1ED", + "0x9CD6": "0xB1EE", + "0x618B": "0xB1EF", + "0x522B": "0xB1F0", + "0x762A": "0xB1F1", + "0x5F6C": "0xB1F2", + "0x658C": "0xB1F3", + "0x6FD2": "0xB1F4", + "0x6EE8": "0xB1F5", + "0x5BBE": "0xB1F6", + "0x6448": "0xB1F7", + "0x5175": "0xB1F8", + "0x51B0": "0xB1F9", + "0x67C4": "0xB1FA", + "0x4E19": "0xB1FB", + "0x79C9": "0xB1FC", + "0x997C": "0xB1FD", + "0x70B3": "0xB1FE", + "0x75C5": "0xB2A1", + "0x5E76": "0xB2A2", + "0x73BB": "0xB2A3", + "0x83E0": "0xB2A4", + "0x64AD": "0xB2A5", + "0x62E8": "0xB2A6", + "0x94B5": "0xB2A7", + "0x6CE2": "0xB2A8", + "0x535A": "0xB2A9", + "0x52C3": "0xB2AA", + "0x640F": "0xB2AB", + "0x94C2": "0xB2AC", + "0x7B94": "0xB2AD", + "0x4F2F": "0xB2AE", + "0x5E1B": "0xB2AF", + "0x8236": "0xB2B0", + "0x8116": "0xB2B1", + "0x818A": "0xB2B2", + "0x6E24": "0xB2B3", + "0x6CCA": "0xB2B4", + "0x9A73": "0xB2B5", + "0x6355": "0xB2B6", + "0x535C": "0xB2B7", + "0x54FA": "0xB2B8", + "0x8865": "0xB2B9", + "0x57E0": "0xB2BA", + "0x4E0D": "0xB2BB", + "0x5E03": "0xB2BC", + "0x6B65": "0xB2BD", + "0x7C3F": "0xB2BE", + "0x90E8": "0xB2BF", + "0x6016": "0xB2C0", + "0x64E6": "0xB2C1", + "0x731C": "0xB2C2", + "0x88C1": "0xB2C3", + "0x6750": "0xB2C4", + "0x624D": "0xB2C5", + "0x8D22": "0xB2C6", + "0x776C": "0xB2C7", + "0x8E29": "0xB2C8", + "0x91C7": "0xB2C9", + "0x5F69": "0xB2CA", + "0x83DC": "0xB2CB", + "0x8521": "0xB2CC", + "0x9910": "0xB2CD", + "0x53C2": "0xB2CE", + "0x8695": "0xB2CF", + "0x6B8B": "0xB2D0", + "0x60ED": "0xB2D1", + "0x60E8": "0xB2D2", + "0x707F": "0xB2D3", + "0x82CD": "0xB2D4", + "0x8231": "0xB2D5", + "0x4ED3": "0xB2D6", + "0x6CA7": "0xB2D7", + "0x85CF": "0xB2D8", + "0x64CD": "0xB2D9", + "0x7CD9": "0xB2DA", + "0x69FD": "0xB2DB", + "0x66F9": "0xB2DC", + "0x8349": "0xB2DD", + "0x5395": "0xB2DE", + "0x7B56": "0xB2DF", + "0x4FA7": "0xB2E0", + "0x518C": "0xB2E1", + "0x6D4B": "0xB2E2", + "0x5C42": "0xB2E3", + "0x8E6D": "0xB2E4", + "0x63D2": "0xB2E5", + "0x53C9": "0xB2E6", + "0x832C": "0xB2E7", + "0x8336": "0xB2E8", + "0x67E5": "0xB2E9", + "0x78B4": "0xB2EA", + "0x643D": "0xB2EB", + "0x5BDF": "0xB2EC", + "0x5C94": "0xB2ED", + "0x5DEE": "0xB2EE", + "0x8BE7": "0xB2EF", + "0x62C6": "0xB2F0", + "0x67F4": "0xB2F1", + "0x8C7A": "0xB2F2", + "0x6400": "0xB2F3", + "0x63BA": "0xB2F4", + "0x8749": "0xB2F5", + "0x998B": "0xB2F6", + "0x8C17": "0xB2F7", + "0x7F20": "0xB2F8", + "0x94F2": "0xB2F9", + "0x4EA7": "0xB2FA", + "0x9610": "0xB2FB", + "0x98A4": "0xB2FC", + "0x660C": "0xB2FD", + "0x7316": "0xB2FE", + "0x573A": "0xB3A1", + "0x5C1D": "0xB3A2", + "0x5E38": "0xB3A3", + "0x957F": "0xB3A4", + "0x507F": "0xB3A5", + "0x80A0": "0xB3A6", + "0x5382": "0xB3A7", + "0x655E": "0xB3A8", + "0x7545": "0xB3A9", + "0x5531": "0xB3AA", + "0x5021": "0xB3AB", + "0x8D85": "0xB3AC", + "0x6284": "0xB3AD", + "0x949E": "0xB3AE", + "0x671D": "0xB3AF", + "0x5632": "0xB3B0", + "0x6F6E": "0xB3B1", + "0x5DE2": "0xB3B2", + "0x5435": "0xB3B3", + "0x7092": "0xB3B4", + "0x8F66": "0xB3B5", + "0x626F": "0xB3B6", + "0x64A4": "0xB3B7", + "0x63A3": "0xB3B8", + "0x5F7B": "0xB3B9", + "0x6F88": "0xB3BA", + "0x90F4": "0xB3BB", + "0x81E3": "0xB3BC", + "0x8FB0": "0xB3BD", + "0x5C18": "0xB3BE", + "0x6668": "0xB3BF", + "0x5FF1": "0xB3C0", + "0x6C89": "0xB3C1", + "0x9648": "0xB3C2", + "0x8D81": "0xB3C3", + "0x886C": "0xB3C4", + "0x6491": "0xB3C5", + "0x79F0": "0xB3C6", + "0x57CE": "0xB3C7", + "0x6A59": "0xB3C8", + "0x6210": "0xB3C9", + "0x5448": "0xB3CA", + "0x4E58": "0xB3CB", + "0x7A0B": "0xB3CC", + "0x60E9": "0xB3CD", + "0x6F84": "0xB3CE", + "0x8BDA": "0xB3CF", + "0x627F": "0xB3D0", + "0x901E": "0xB3D1", + "0x9A8B": "0xB3D2", + "0x79E4": "0xB3D3", + "0x5403": "0xB3D4", + "0x75F4": "0xB3D5", + "0x6301": "0xB3D6", + "0x5319": "0xB3D7", + "0x6C60": "0xB3D8", + "0x8FDF": "0xB3D9", + "0x5F1B": "0xB3DA", + "0x9A70": "0xB3DB", + "0x803B": "0xB3DC", + "0x9F7F": "0xB3DD", + "0x4F88": "0xB3DE", + "0x5C3A": "0xB3DF", + "0x8D64": "0xB3E0", + "0x7FC5": "0xB3E1", + "0x65A5": "0xB3E2", + "0x70BD": "0xB3E3", + "0x5145": "0xB3E4", + "0x51B2": "0xB3E5", + "0x866B": "0xB3E6", + "0x5D07": "0xB3E7", + "0x5BA0": "0xB3E8", + "0x62BD": "0xB3E9", + "0x916C": "0xB3EA", + "0x7574": "0xB3EB", + "0x8E0C": "0xB3EC", + "0x7A20": "0xB3ED", + "0x6101": "0xB3EE", + "0x7B79": "0xB3EF", + "0x4EC7": "0xB3F0", + "0x7EF8": "0xB3F1", + "0x7785": "0xB3F2", + "0x4E11": "0xB3F3", + "0x81ED": "0xB3F4", + "0x521D": "0xB3F5", + "0x51FA": "0xB3F6", + "0x6A71": "0xB3F7", + "0x53A8": "0xB3F8", + "0x8E87": "0xB3F9", + "0x9504": "0xB3FA", + "0x96CF": "0xB3FB", + "0x6EC1": "0xB3FC", + "0x9664": "0xB3FD", + "0x695A": "0xB3FE", + "0x7840": "0xB4A1", + "0x50A8": "0xB4A2", + "0x77D7": "0xB4A3", + "0x6410": "0xB4A4", + "0x89E6": "0xB4A5", + "0x5904": "0xB4A6", + "0x63E3": "0xB4A7", + "0x5DDD": "0xB4A8", + "0x7A7F": "0xB4A9", + "0x693D": "0xB4AA", + "0x4F20": "0xB4AB", + "0x8239": "0xB4AC", + "0x5598": "0xB4AD", + "0x4E32": "0xB4AE", + "0x75AE": "0xB4AF", + "0x7A97": "0xB4B0", + "0x5E62": "0xB4B1", + "0x5E8A": "0xB4B2", + "0x95EF": "0xB4B3", + "0x521B": "0xB4B4", + "0x5439": "0xB4B5", + "0x708A": "0xB4B6", + "0x6376": "0xB4B7", + "0x9524": "0xB4B8", + "0x5782": "0xB4B9", + "0x6625": "0xB4BA", + "0x693F": "0xB4BB", + "0x9187": "0xB4BC", + "0x5507": "0xB4BD", + "0x6DF3": "0xB4BE", + "0x7EAF": "0xB4BF", + "0x8822": "0xB4C0", + "0x6233": "0xB4C1", + "0x7EF0": "0xB4C2", + "0x75B5": "0xB4C3", + "0x8328": "0xB4C4", + "0x78C1": "0xB4C5", + "0x96CC": "0xB4C6", + "0x8F9E": "0xB4C7", + "0x6148": "0xB4C8", + "0x74F7": "0xB4C9", + "0x8BCD": "0xB4CA", + "0x6B64": "0xB4CB", + "0x523A": "0xB4CC", + "0x8D50": "0xB4CD", + "0x6B21": "0xB4CE", + "0x806A": "0xB4CF", + "0x8471": "0xB4D0", + "0x56F1": "0xB4D1", + "0x5306": "0xB4D2", + "0x4ECE": "0xB4D3", + "0x4E1B": "0xB4D4", + "0x51D1": "0xB4D5", + "0x7C97": "0xB4D6", + "0x918B": "0xB4D7", + "0x7C07": "0xB4D8", + "0x4FC3": "0xB4D9", + "0x8E7F": "0xB4DA", + "0x7BE1": "0xB4DB", + "0x7A9C": "0xB4DC", + "0x6467": "0xB4DD", + "0x5D14": "0xB4DE", + "0x50AC": "0xB4DF", + "0x8106": "0xB4E0", + "0x7601": "0xB4E1", + "0x7CB9": "0xB4E2", + "0x6DEC": "0xB4E3", + "0x7FE0": "0xB4E4", + "0x6751": "0xB4E5", + "0x5B58": "0xB4E6", + "0x5BF8": "0xB4E7", + "0x78CB": "0xB4E8", + "0x64AE": "0xB4E9", + "0x6413": "0xB4EA", + "0x63AA": "0xB4EB", + "0x632B": "0xB4EC", + "0x9519": "0xB4ED", + "0x642D": "0xB4EE", + "0x8FBE": "0xB4EF", + "0x7B54": "0xB4F0", + "0x7629": "0xB4F1", + "0x6253": "0xB4F2", + "0x5927": "0xB4F3", + "0x5446": "0xB4F4", + "0x6B79": "0xB4F5", + "0x50A3": "0xB4F6", + "0x6234": "0xB4F7", + "0x5E26": "0xB4F8", + "0x6B86": "0xB4F9", + "0x4EE3": "0xB4FA", + "0x8D37": "0xB4FB", + "0x888B": "0xB4FC", + "0x5F85": "0xB4FD", + "0x902E": "0xB4FE", + "0x6020": "0xB5A1", + "0x803D": "0xB5A2", + "0x62C5": "0xB5A3", + "0x4E39": "0xB5A4", + "0x5355": "0xB5A5", + "0x90F8": "0xB5A6", + "0x63B8": "0xB5A7", + "0x80C6": "0xB5A8", + "0x65E6": "0xB5A9", + "0x6C2E": "0xB5AA", + "0x4F46": "0xB5AB", + "0x60EE": "0xB5AC", + "0x6DE1": "0xB5AD", + "0x8BDE": "0xB5AE", + "0x5F39": "0xB5AF", + "0x86CB": "0xB5B0", + "0x5F53": "0xB5B1", + "0x6321": "0xB5B2", + "0x515A": "0xB5B3", + "0x8361": "0xB5B4", + "0x6863": "0xB5B5", + "0x5200": "0xB5B6", + "0x6363": "0xB5B7", + "0x8E48": "0xB5B8", + "0x5012": "0xB5B9", + "0x5C9B": "0xB5BA", + "0x7977": "0xB5BB", + "0x5BFC": "0xB5BC", + "0x5230": "0xB5BD", + "0x7A3B": "0xB5BE", + "0x60BC": "0xB5BF", + "0x9053": "0xB5C0", + "0x76D7": "0xB5C1", + "0x5FB7": "0xB5C2", + "0x5F97": "0xB5C3", + "0x7684": "0xB5C4", + "0x8E6C": "0xB5C5", + "0x706F": "0xB5C6", + "0x767B": "0xB5C7", + "0x7B49": "0xB5C8", + "0x77AA": "0xB5C9", + "0x51F3": "0xB5CA", + "0x9093": "0xB5CB", + "0x5824": "0xB5CC", + "0x4F4E": "0xB5CD", + "0x6EF4": "0xB5CE", + "0x8FEA": "0xB5CF", + "0x654C": "0xB5D0", + "0x7B1B": "0xB5D1", + "0x72C4": "0xB5D2", + "0x6DA4": "0xB5D3", + "0x7FDF": "0xB5D4", + "0x5AE1": "0xB5D5", + "0x62B5": "0xB5D6", + "0x5E95": "0xB5D7", + "0x5730": "0xB5D8", + "0x8482": "0xB5D9", + "0x7B2C": "0xB5DA", + "0x5E1D": "0xB5DB", + "0x5F1F": "0xB5DC", + "0x9012": "0xB5DD", + "0x7F14": "0xB5DE", + "0x98A0": "0xB5DF", + "0x6382": "0xB5E0", + "0x6EC7": "0xB5E1", + "0x7898": "0xB5E2", + "0x70B9": "0xB5E3", + "0x5178": "0xB5E4", + "0x975B": "0xB5E5", + "0x57AB": "0xB5E6", + "0x7535": "0xB5E7", + "0x4F43": "0xB5E8", + "0x7538": "0xB5E9", + "0x5E97": "0xB5EA", + "0x60E6": "0xB5EB", + "0x5960": "0xB5EC", + "0x6DC0": "0xB5ED", + "0x6BBF": "0xB5EE", + "0x7889": "0xB5EF", + "0x53FC": "0xB5F0", + "0x96D5": "0xB5F1", + "0x51CB": "0xB5F2", + "0x5201": "0xB5F3", + "0x6389": "0xB5F4", + "0x540A": "0xB5F5", + "0x9493": "0xB5F6", + "0x8C03": "0xB5F7", + "0x8DCC": "0xB5F8", + "0x7239": "0xB5F9", + "0x789F": "0xB5FA", + "0x8776": "0xB5FB", + "0x8FED": "0xB5FC", + "0x8C0D": "0xB5FD", + "0x53E0": "0xB5FE", + "0x4E01": "0xB6A1", + "0x76EF": "0xB6A2", + "0x53EE": "0xB6A3", + "0x9489": "0xB6A4", + "0x9876": "0xB6A5", + "0x9F0E": "0xB6A6", + "0x952D": "0xB6A7", + "0x5B9A": "0xB6A8", + "0x8BA2": "0xB6A9", + "0x4E22": "0xB6AA", + "0x4E1C": "0xB6AB", + "0x51AC": "0xB6AC", + "0x8463": "0xB6AD", + "0x61C2": "0xB6AE", + "0x52A8": "0xB6AF", + "0x680B": "0xB6B0", + "0x4F97": "0xB6B1", + "0x606B": "0xB6B2", + "0x51BB": "0xB6B3", + "0x6D1E": "0xB6B4", + "0x515C": "0xB6B5", + "0x6296": "0xB6B6", + "0x6597": "0xB6B7", + "0x9661": "0xB6B8", + "0x8C46": "0xB6B9", + "0x9017": "0xB6BA", + "0x75D8": "0xB6BB", + "0x90FD": "0xB6BC", + "0x7763": "0xB6BD", + "0x6BD2": "0xB6BE", + "0x728A": "0xB6BF", + "0x72EC": "0xB6C0", + "0x8BFB": "0xB6C1", + "0x5835": "0xB6C2", + "0x7779": "0xB6C3", + "0x8D4C": "0xB6C4", + "0x675C": "0xB6C5", + "0x9540": "0xB6C6", + "0x809A": "0xB6C7", + "0x5EA6": "0xB6C8", + "0x6E21": "0xB6C9", + "0x5992": "0xB6CA", + "0x7AEF": "0xB6CB", + "0x77ED": "0xB6CC", + "0x953B": "0xB6CD", + "0x6BB5": "0xB6CE", + "0x65AD": "0xB6CF", + "0x7F0E": "0xB6D0", + "0x5806": "0xB6D1", + "0x5151": "0xB6D2", + "0x961F": "0xB6D3", + "0x5BF9": "0xB6D4", + "0x58A9": "0xB6D5", + "0x5428": "0xB6D6", + "0x8E72": "0xB6D7", + "0x6566": "0xB6D8", + "0x987F": "0xB6D9", + "0x56E4": "0xB6DA", + "0x949D": "0xB6DB", + "0x76FE": "0xB6DC", + "0x9041": "0xB6DD", + "0x6387": "0xB6DE", + "0x54C6": "0xB6DF", + "0x591A": "0xB6E0", + "0x593A": "0xB6E1", + "0x579B": "0xB6E2", + "0x8EB2": "0xB6E3", + "0x6735": "0xB6E4", + "0x8DFA": "0xB6E5", + "0x8235": "0xB6E6", + "0x5241": "0xB6E7", + "0x60F0": "0xB6E8", + "0x5815": "0xB6E9", + "0x86FE": "0xB6EA", + "0x5CE8": "0xB6EB", + "0x9E45": "0xB6EC", + "0x4FC4": "0xB6ED", + "0x989D": "0xB6EE", + "0x8BB9": "0xB6EF", + "0x5A25": "0xB6F0", + "0x6076": "0xB6F1", + "0x5384": "0xB6F2", + "0x627C": "0xB6F3", + "0x904F": "0xB6F4", + "0x9102": "0xB6F5", + "0x997F": "0xB6F6", + "0x6069": "0xB6F7", + "0x800C": "0xB6F8", + "0x513F": "0xB6F9", + "0x8033": "0xB6FA", + "0x5C14": "0xB6FB", + "0x9975": "0xB6FC", + "0x6D31": "0xB6FD", + "0x4E8C": "0xB6FE", + "0x8D30": "0xB7A1", + "0x53D1": "0xB7A2", + "0x7F5A": "0xB7A3", + "0x7B4F": "0xB7A4", + "0x4F10": "0xB7A5", + "0x4E4F": "0xB7A6", + "0x9600": "0xB7A7", + "0x6CD5": "0xB7A8", + "0x73D0": "0xB7A9", + "0x85E9": "0xB7AA", + "0x5E06": "0xB7AB", + "0x756A": "0xB7AC", + "0x7FFB": "0xB7AD", + "0x6A0A": "0xB7AE", + "0x77FE": "0xB7AF", + "0x9492": "0xB7B0", + "0x7E41": "0xB7B1", + "0x51E1": "0xB7B2", + "0x70E6": "0xB7B3", + "0x53CD": "0xB7B4", + "0x8FD4": "0xB7B5", + "0x8303": "0xB7B6", + "0x8D29": "0xB7B7", + "0x72AF": "0xB7B8", + "0x996D": "0xB7B9", + "0x6CDB": "0xB7BA", + "0x574A": "0xB7BB", + "0x82B3": "0xB7BC", + "0x65B9": "0xB7BD", + "0x80AA": "0xB7BE", + "0x623F": "0xB7BF", + "0x9632": "0xB7C0", + "0x59A8": "0xB7C1", + "0x4EFF": "0xB7C2", + "0x8BBF": "0xB7C3", + "0x7EBA": "0xB7C4", + "0x653E": "0xB7C5", + "0x83F2": "0xB7C6", + "0x975E": "0xB7C7", + "0x5561": "0xB7C8", + "0x98DE": "0xB7C9", + "0x80A5": "0xB7CA", + "0x532A": "0xB7CB", + "0x8BFD": "0xB7CC", + "0x5420": "0xB7CD", + "0x80BA": "0xB7CE", + "0x5E9F": "0xB7CF", + "0x6CB8": "0xB7D0", + "0x8D39": "0xB7D1", + "0x82AC": "0xB7D2", + "0x915A": "0xB7D3", + "0x5429": "0xB7D4", + "0x6C1B": "0xB7D5", + "0x5206": "0xB7D6", + "0x7EB7": "0xB7D7", + "0x575F": "0xB7D8", + "0x711A": "0xB7D9", + "0x6C7E": "0xB7DA", + "0x7C89": "0xB7DB", + "0x594B": "0xB7DC", + "0x4EFD": "0xB7DD", + "0x5FFF": "0xB7DE", + "0x6124": "0xB7DF", + "0x7CAA": "0xB7E0", + "0x4E30": "0xB7E1", + "0x5C01": "0xB7E2", + "0x67AB": "0xB7E3", + "0x8702": "0xB7E4", + "0x5CF0": "0xB7E5", + "0x950B": "0xB7E6", + "0x98CE": "0xB7E7", + "0x75AF": "0xB7E8", + "0x70FD": "0xB7E9", + "0x9022": "0xB7EA", + "0x51AF": "0xB7EB", + "0x7F1D": "0xB7EC", + "0x8BBD": "0xB7ED", + "0x5949": "0xB7EE", + "0x51E4": "0xB7EF", + "0x4F5B": "0xB7F0", + "0x5426": "0xB7F1", + "0x592B": "0xB7F2", + "0x6577": "0xB7F3", + "0x80A4": "0xB7F4", + "0x5B75": "0xB7F5", + "0x6276": "0xB7F6", + "0x62C2": "0xB7F7", + "0x8F90": "0xB7F8", + "0x5E45": "0xB7F9", + "0x6C1F": "0xB7FA", + "0x7B26": "0xB7FB", + "0x4F0F": "0xB7FC", + "0x4FD8": "0xB7FD", + "0x670D": "0xB7FE", + "0x6D6E": "0xB8A1", + "0x6DAA": "0xB8A2", + "0x798F": "0xB8A3", + "0x88B1": "0xB8A4", + "0x5F17": "0xB8A5", + "0x752B": "0xB8A6", + "0x629A": "0xB8A7", + "0x8F85": "0xB8A8", + "0x4FEF": "0xB8A9", + "0x91DC": "0xB8AA", + "0x65A7": "0xB8AB", + "0x812F": "0xB8AC", + "0x8151": "0xB8AD", + "0x5E9C": "0xB8AE", + "0x8150": "0xB8AF", + "0x8D74": "0xB8B0", + "0x526F": "0xB8B1", + "0x8986": "0xB8B2", + "0x8D4B": "0xB8B3", + "0x590D": "0xB8B4", + "0x5085": "0xB8B5", + "0x4ED8": "0xB8B6", + "0x961C": "0xB8B7", + "0x7236": "0xB8B8", + "0x8179": "0xB8B9", + "0x8D1F": "0xB8BA", + "0x5BCC": "0xB8BB", + "0x8BA3": "0xB8BC", + "0x9644": "0xB8BD", + "0x5987": "0xB8BE", + "0x7F1A": "0xB8BF", + "0x5490": "0xB8C0", + "0x5676": "0xB8C1", + "0x560E": "0xB8C2", + "0x8BE5": "0xB8C3", + "0x6539": "0xB8C4", + "0x6982": "0xB8C5", + "0x9499": "0xB8C6", + "0x76D6": "0xB8C7", + "0x6E89": "0xB8C8", + "0x5E72": "0xB8C9", + "0x7518": "0xB8CA", + "0x6746": "0xB8CB", + "0x67D1": "0xB8CC", + "0x7AFF": "0xB8CD", + "0x809D": "0xB8CE", + "0x8D76": "0xB8CF", + "0x611F": "0xB8D0", + "0x79C6": "0xB8D1", + "0x6562": "0xB8D2", + "0x8D63": "0xB8D3", + "0x5188": "0xB8D4", + "0x521A": "0xB8D5", + "0x94A2": "0xB8D6", + "0x7F38": "0xB8D7", + "0x809B": "0xB8D8", + "0x7EB2": "0xB8D9", + "0x5C97": "0xB8DA", + "0x6E2F": "0xB8DB", + "0x6760": "0xB8DC", + "0x7BD9": "0xB8DD", + "0x768B": "0xB8DE", + "0x9AD8": "0xB8DF", + "0x818F": "0xB8E0", + "0x7F94": "0xB8E1", + "0x7CD5": "0xB8E2", + "0x641E": "0xB8E3", + "0x9550": "0xB8E4", + "0x7A3F": "0xB8E5", + "0x544A": "0xB8E6", + "0x54E5": "0xB8E7", + "0x6B4C": "0xB8E8", + "0x6401": "0xB8E9", + "0x6208": "0xB8EA", + "0x9E3D": "0xB8EB", + "0x80F3": "0xB8EC", + "0x7599": "0xB8ED", + "0x5272": "0xB8EE", + "0x9769": "0xB8EF", + "0x845B": "0xB8F0", + "0x683C": "0xB8F1", + "0x86E4": "0xB8F2", + "0x9601": "0xB8F3", + "0x9694": "0xB8F4", + "0x94EC": "0xB8F5", + "0x4E2A": "0xB8F6", + "0x5404": "0xB8F7", + "0x7ED9": "0xB8F8", + "0x6839": "0xB8F9", + "0x8DDF": "0xB8FA", + "0x8015": "0xB8FB", + "0x66F4": "0xB8FC", + "0x5E9A": "0xB8FD", + "0x7FB9": "0xB8FE", + "0x57C2": "0xB9A1", + "0x803F": "0xB9A2", + "0x6897": "0xB9A3", + "0x5DE5": "0xB9A4", + "0x653B": "0xB9A5", + "0x529F": "0xB9A6", + "0x606D": "0xB9A7", + "0x9F9A": "0xB9A8", + "0x4F9B": "0xB9A9", + "0x8EAC": "0xB9AA", + "0x516C": "0xB9AB", + "0x5BAB": "0xB9AC", + "0x5F13": "0xB9AD", + "0x5DE9": "0xB9AE", + "0x6C5E": "0xB9AF", + "0x62F1": "0xB9B0", + "0x8D21": "0xB9B1", + "0x5171": "0xB9B2", + "0x94A9": "0xB9B3", + "0x52FE": "0xB9B4", + "0x6C9F": "0xB9B5", + "0x82DF": "0xB9B6", + "0x72D7": "0xB9B7", + "0x57A2": "0xB9B8", + "0x6784": "0xB9B9", + "0x8D2D": "0xB9BA", + "0x591F": "0xB9BB", + "0x8F9C": "0xB9BC", + "0x83C7": "0xB9BD", + "0x5495": "0xB9BE", + "0x7B8D": "0xB9BF", + "0x4F30": "0xB9C0", + "0x6CBD": "0xB9C1", + "0x5B64": "0xB9C2", + "0x59D1": "0xB9C3", + "0x9F13": "0xB9C4", + "0x53E4": "0xB9C5", + "0x86CA": "0xB9C6", + "0x9AA8": "0xB9C7", + "0x8C37": "0xB9C8", + "0x80A1": "0xB9C9", + "0x6545": "0xB9CA", + "0x987E": "0xB9CB", + "0x56FA": "0xB9CC", + "0x96C7": "0xB9CD", + "0x522E": "0xB9CE", + "0x74DC": "0xB9CF", + "0x5250": "0xB9D0", + "0x5BE1": "0xB9D1", + "0x6302": "0xB9D2", + "0x8902": "0xB9D3", + "0x4E56": "0xB9D4", + "0x62D0": "0xB9D5", + "0x602A": "0xB9D6", + "0x68FA": "0xB9D7", + "0x5173": "0xB9D8", + "0x5B98": "0xB9D9", + "0x51A0": "0xB9DA", + "0x89C2": "0xB9DB", + "0x7BA1": "0xB9DC", + "0x9986": "0xB9DD", + "0x7F50": "0xB9DE", + "0x60EF": "0xB9DF", + "0x704C": "0xB9E0", + "0x8D2F": "0xB9E1", + "0x5149": "0xB9E2", + "0x5E7F": "0xB9E3", + "0x901B": "0xB9E4", + "0x7470": "0xB9E5", + "0x89C4": "0xB9E6", + "0x572D": "0xB9E7", + "0x7845": "0xB9E8", + "0x5F52": "0xB9E9", + "0x9F9F": "0xB9EA", + "0x95FA": "0xB9EB", + "0x8F68": "0xB9EC", + "0x9B3C": "0xB9ED", + "0x8BE1": "0xB9EE", + "0x7678": "0xB9EF", + "0x6842": "0xB9F0", + "0x67DC": "0xB9F1", + "0x8DEA": "0xB9F2", + "0x8D35": "0xB9F3", + "0x523D": "0xB9F4", + "0x8F8A": "0xB9F5", + "0x6EDA": "0xB9F6", + "0x68CD": "0xB9F7", + "0x9505": "0xB9F8", + "0x90ED": "0xB9F9", + "0x56FD": "0xB9FA", + "0x679C": "0xB9FB", + "0x88F9": "0xB9FC", + "0x8FC7": "0xB9FD", + "0x54C8": "0xB9FE", + "0x9AB8": "0xBAA1", + "0x5B69": "0xBAA2", + "0x6D77": "0xBAA3", + "0x6C26": "0xBAA4", + "0x4EA5": "0xBAA5", + "0x5BB3": "0xBAA6", + "0x9A87": "0xBAA7", + "0x9163": "0xBAA8", + "0x61A8": "0xBAA9", + "0x90AF": "0xBAAA", + "0x97E9": "0xBAAB", + "0x542B": "0xBAAC", + "0x6DB5": "0xBAAD", + "0x5BD2": "0xBAAE", + "0x51FD": "0xBAAF", + "0x558A": "0xBAB0", + "0x7F55": "0xBAB1", + "0x7FF0": "0xBAB2", + "0x64BC": "0xBAB3", + "0x634D": "0xBAB4", + "0x65F1": "0xBAB5", + "0x61BE": "0xBAB6", + "0x608D": "0xBAB7", + "0x710A": "0xBAB8", + "0x6C57": "0xBAB9", + "0x6C49": "0xBABA", + "0x592F": "0xBABB", + "0x676D": "0xBABC", + "0x822A": "0xBABD", + "0x58D5": "0xBABE", + "0x568E": "0xBABF", + "0x8C6A": "0xBAC0", + "0x6BEB": "0xBAC1", + "0x90DD": "0xBAC2", + "0x597D": "0xBAC3", + "0x8017": "0xBAC4", + "0x53F7": "0xBAC5", + "0x6D69": "0xBAC6", + "0x5475": "0xBAC7", + "0x559D": "0xBAC8", + "0x8377": "0xBAC9", + "0x83CF": "0xBACA", + "0x6838": "0xBACB", + "0x79BE": "0xBACC", + "0x548C": "0xBACD", + "0x4F55": "0xBACE", + "0x5408": "0xBACF", + "0x76D2": "0xBAD0", + "0x8C89": "0xBAD1", + "0x9602": "0xBAD2", + "0x6CB3": "0xBAD3", + "0x6DB8": "0xBAD4", + "0x8D6B": "0xBAD5", + "0x8910": "0xBAD6", + "0x9E64": "0xBAD7", + "0x8D3A": "0xBAD8", + "0x563F": "0xBAD9", + "0x9ED1": "0xBADA", + "0x75D5": "0xBADB", + "0x5F88": "0xBADC", + "0x72E0": "0xBADD", + "0x6068": "0xBADE", + "0x54FC": "0xBADF", + "0x4EA8": "0xBAE0", + "0x6A2A": "0xBAE1", + "0x8861": "0xBAE2", + "0x6052": "0xBAE3", + "0x8F70": "0xBAE4", + "0x54C4": "0xBAE5", + "0x70D8": "0xBAE6", + "0x8679": "0xBAE7", + "0x9E3F": "0xBAE8", + "0x6D2A": "0xBAE9", + "0x5B8F": "0xBAEA", + "0x5F18": "0xBAEB", + "0x7EA2": "0xBAEC", + "0x5589": "0xBAED", + "0x4FAF": "0xBAEE", + "0x7334": "0xBAEF", + "0x543C": "0xBAF0", + "0x539A": "0xBAF1", + "0x5019": "0xBAF2", + "0x540E": "0xBAF3", + "0x547C": "0xBAF4", + "0x4E4E": "0xBAF5", + "0x5FFD": "0xBAF6", + "0x745A": "0xBAF7", + "0x58F6": "0xBAF8", + "0x846B": "0xBAF9", + "0x80E1": "0xBAFA", + "0x8774": "0xBAFB", + "0x72D0": "0xBAFC", + "0x7CCA": "0xBAFD", + "0x6E56": "0xBAFE", + "0x5F27": "0xBBA1", + "0x864E": "0xBBA2", + "0x552C": "0xBBA3", + "0x62A4": "0xBBA4", + "0x4E92": "0xBBA5", + "0x6CAA": "0xBBA6", + "0x6237": "0xBBA7", + "0x82B1": "0xBBA8", + "0x54D7": "0xBBA9", + "0x534E": "0xBBAA", + "0x733E": "0xBBAB", + "0x6ED1": "0xBBAC", + "0x753B": "0xBBAD", + "0x5212": "0xBBAE", + "0x5316": "0xBBAF", + "0x8BDD": "0xBBB0", + "0x69D0": "0xBBB1", + "0x5F8A": "0xBBB2", + "0x6000": "0xBBB3", + "0x6DEE": "0xBBB4", + "0x574F": "0xBBB5", + "0x6B22": "0xBBB6", + "0x73AF": "0xBBB7", + "0x6853": "0xBBB8", + "0x8FD8": "0xBBB9", + "0x7F13": "0xBBBA", + "0x6362": "0xBBBB", + "0x60A3": "0xBBBC", + "0x5524": "0xBBBD", + "0x75EA": "0xBBBE", + "0x8C62": "0xBBBF", + "0x7115": "0xBBC0", + "0x6DA3": "0xBBC1", + "0x5BA6": "0xBBC2", + "0x5E7B": "0xBBC3", + "0x8352": "0xBBC4", + "0x614C": "0xBBC5", + "0x9EC4": "0xBBC6", + "0x78FA": "0xBBC7", + "0x8757": "0xBBC8", + "0x7C27": "0xBBC9", + "0x7687": "0xBBCA", + "0x51F0": "0xBBCB", + "0x60F6": "0xBBCC", + "0x714C": "0xBBCD", + "0x6643": "0xBBCE", + "0x5E4C": "0xBBCF", + "0x604D": "0xBBD0", + "0x8C0E": "0xBBD1", + "0x7070": "0xBBD2", + "0x6325": "0xBBD3", + "0x8F89": "0xBBD4", + "0x5FBD": "0xBBD5", + "0x6062": "0xBBD6", + "0x86D4": "0xBBD7", + "0x56DE": "0xBBD8", + "0x6BC1": "0xBBD9", + "0x6094": "0xBBDA", + "0x6167": "0xBBDB", + "0x5349": "0xBBDC", + "0x60E0": "0xBBDD", + "0x6666": "0xBBDE", + "0x8D3F": "0xBBDF", + "0x79FD": "0xBBE0", + "0x4F1A": "0xBBE1", + "0x70E9": "0xBBE2", + "0x6C47": "0xBBE3", + "0x8BB3": "0xBBE4", + "0x8BF2": "0xBBE5", + "0x7ED8": "0xBBE6", + "0x8364": "0xBBE7", + "0x660F": "0xBBE8", + "0x5A5A": "0xBBE9", + "0x9B42": "0xBBEA", + "0x6D51": "0xBBEB", + "0x6DF7": "0xBBEC", + "0x8C41": "0xBBED", + "0x6D3B": "0xBBEE", + "0x4F19": "0xBBEF", + "0x706B": "0xBBF0", + "0x83B7": "0xBBF1", + "0x6216": "0xBBF2", + "0x60D1": "0xBBF3", + "0x970D": "0xBBF4", + "0x8D27": "0xBBF5", + "0x7978": "0xBBF6", + "0x51FB": "0xBBF7", + "0x573E": "0xBBF8", + "0x57FA": "0xBBF9", + "0x673A": "0xBBFA", + "0x7578": "0xBBFB", + "0x7A3D": "0xBBFC", + "0x79EF": "0xBBFD", + "0x7B95": "0xBBFE", + "0x808C": "0xBCA1", + "0x9965": "0xBCA2", + "0x8FF9": "0xBCA3", + "0x6FC0": "0xBCA4", + "0x8BA5": "0xBCA5", + "0x9E21": "0xBCA6", + "0x59EC": "0xBCA7", + "0x7EE9": "0xBCA8", + "0x7F09": "0xBCA9", + "0x5409": "0xBCAA", + "0x6781": "0xBCAB", + "0x68D8": "0xBCAC", + "0x8F91": "0xBCAD", + "0x7C4D": "0xBCAE", + "0x96C6": "0xBCAF", + "0x53CA": "0xBCB0", + "0x6025": "0xBCB1", + "0x75BE": "0xBCB2", + "0x6C72": "0xBCB3", + "0x5373": "0xBCB4", + "0x5AC9": "0xBCB5", + "0x7EA7": "0xBCB6", + "0x6324": "0xBCB7", + "0x51E0": "0xBCB8", + "0x810A": "0xBCB9", + "0x5DF1": "0xBCBA", + "0x84DF": "0xBCBB", + "0x6280": "0xBCBC", + "0x5180": "0xBCBD", + "0x5B63": "0xBCBE", + "0x4F0E": "0xBCBF", + "0x796D": "0xBCC0", + "0x5242": "0xBCC1", + "0x60B8": "0xBCC2", + "0x6D4E": "0xBCC3", + "0x5BC4": "0xBCC4", + "0x5BC2": "0xBCC5", + "0x8BA1": "0xBCC6", + "0x8BB0": "0xBCC7", + "0x65E2": "0xBCC8", + "0x5FCC": "0xBCC9", + "0x9645": "0xBCCA", + "0x5993": "0xBCCB", + "0x7EE7": "0xBCCC", + "0x7EAA": "0xBCCD", + "0x5609": "0xBCCE", + "0x67B7": "0xBCCF", + "0x5939": "0xBCD0", + "0x4F73": "0xBCD1", + "0x5BB6": "0xBCD2", + "0x52A0": "0xBCD3", + "0x835A": "0xBCD4", + "0x988A": "0xBCD5", + "0x8D3E": "0xBCD6", + "0x7532": "0xBCD7", + "0x94BE": "0xBCD8", + "0x5047": "0xBCD9", + "0x7A3C": "0xBCDA", + "0x4EF7": "0xBCDB", + "0x67B6": "0xBCDC", + "0x9A7E": "0xBCDD", + "0x5AC1": "0xBCDE", + "0x6B7C": "0xBCDF", + "0x76D1": "0xBCE0", + "0x575A": "0xBCE1", + "0x5C16": "0xBCE2", + "0x7B3A": "0xBCE3", + "0x95F4": "0xBCE4", + "0x714E": "0xBCE5", + "0x517C": "0xBCE6", + "0x80A9": "0xBCE7", + "0x8270": "0xBCE8", + "0x5978": "0xBCE9", + "0x7F04": "0xBCEA", + "0x8327": "0xBCEB", + "0x68C0": "0xBCEC", + "0x67EC": "0xBCED", + "0x78B1": "0xBCEE", + "0x7877": "0xBCEF", + "0x62E3": "0xBCF0", + "0x6361": "0xBCF1", + "0x7B80": "0xBCF2", + "0x4FED": "0xBCF3", + "0x526A": "0xBCF4", + "0x51CF": "0xBCF5", + "0x8350": "0xBCF6", + "0x69DB": "0xBCF7", + "0x9274": "0xBCF8", + "0x8DF5": "0xBCF9", + "0x8D31": "0xBCFA", + "0x89C1": "0xBCFB", + "0x952E": "0xBCFC", + "0x7BAD": "0xBCFD", + "0x4EF6": "0xBCFE", + "0x5065": "0xBDA1", + "0x8230": "0xBDA2", + "0x5251": "0xBDA3", + "0x996F": "0xBDA4", + "0x6E10": "0xBDA5", + "0x6E85": "0xBDA6", + "0x6DA7": "0xBDA7", + "0x5EFA": "0xBDA8", + "0x50F5": "0xBDA9", + "0x59DC": "0xBDAA", + "0x5C06": "0xBDAB", + "0x6D46": "0xBDAC", + "0x6C5F": "0xBDAD", + "0x7586": "0xBDAE", + "0x848B": "0xBDAF", + "0x6868": "0xBDB0", + "0x5956": "0xBDB1", + "0x8BB2": "0xBDB2", + "0x5320": "0xBDB3", + "0x9171": "0xBDB4", + "0x964D": "0xBDB5", + "0x8549": "0xBDB6", + "0x6912": "0xBDB7", + "0x7901": "0xBDB8", + "0x7126": "0xBDB9", + "0x80F6": "0xBDBA", + "0x4EA4": "0xBDBB", + "0x90CA": "0xBDBC", + "0x6D47": "0xBDBD", + "0x9A84": "0xBDBE", + "0x5A07": "0xBDBF", + "0x56BC": "0xBDC0", + "0x6405": "0xBDC1", + "0x94F0": "0xBDC2", + "0x77EB": "0xBDC3", + "0x4FA5": "0xBDC4", + "0x811A": "0xBDC5", + "0x72E1": "0xBDC6", + "0x89D2": "0xBDC7", + "0x997A": "0xBDC8", + "0x7F34": "0xBDC9", + "0x7EDE": "0xBDCA", + "0x527F": "0xBDCB", + "0x6559": "0xBDCC", + "0x9175": "0xBDCD", + "0x8F7F": "0xBDCE", + "0x8F83": "0xBDCF", + "0x53EB": "0xBDD0", + "0x7A96": "0xBDD1", + "0x63ED": "0xBDD2", + "0x63A5": "0xBDD3", + "0x7686": "0xBDD4", + "0x79F8": "0xBDD5", + "0x8857": "0xBDD6", + "0x9636": "0xBDD7", + "0x622A": "0xBDD8", + "0x52AB": "0xBDD9", + "0x8282": "0xBDDA", + "0x6854": "0xBDDB", + "0x6770": "0xBDDC", + "0x6377": "0xBDDD", + "0x776B": "0xBDDE", + "0x7AED": "0xBDDF", + "0x6D01": "0xBDE0", + "0x7ED3": "0xBDE1", + "0x89E3": "0xBDE2", + "0x59D0": "0xBDE3", + "0x6212": "0xBDE4", + "0x85C9": "0xBDE5", + "0x82A5": "0xBDE6", + "0x754C": "0xBDE7", + "0x501F": "0xBDE8", + "0x4ECB": "0xBDE9", + "0x75A5": "0xBDEA", + "0x8BEB": "0xBDEB", + "0x5C4A": "0xBDEC", + "0x5DFE": "0xBDED", + "0x7B4B": "0xBDEE", + "0x65A4": "0xBDEF", + "0x91D1": "0xBDF0", + "0x4ECA": "0xBDF1", + "0x6D25": "0xBDF2", + "0x895F": "0xBDF3", + "0x7D27": "0xBDF4", + "0x9526": "0xBDF5", + "0x4EC5": "0xBDF6", + "0x8C28": "0xBDF7", + "0x8FDB": "0xBDF8", + "0x9773": "0xBDF9", + "0x664B": "0xBDFA", + "0x7981": "0xBDFB", + "0x8FD1": "0xBDFC", + "0x70EC": "0xBDFD", + "0x6D78": "0xBDFE", + "0x5C3D": "0xBEA1", + "0x52B2": "0xBEA2", + "0x8346": "0xBEA3", + "0x5162": "0xBEA4", + "0x830E": "0xBEA5", + "0x775B": "0xBEA6", + "0x6676": "0xBEA7", + "0x9CB8": "0xBEA8", + "0x4EAC": "0xBEA9", + "0x60CA": "0xBEAA", + "0x7CBE": "0xBEAB", + "0x7CB3": "0xBEAC", + "0x7ECF": "0xBEAD", + "0x4E95": "0xBEAE", + "0x8B66": "0xBEAF", + "0x666F": "0xBEB0", + "0x9888": "0xBEB1", + "0x9759": "0xBEB2", + "0x5883": "0xBEB3", + "0x656C": "0xBEB4", + "0x955C": "0xBEB5", + "0x5F84": "0xBEB6", + "0x75C9": "0xBEB7", + "0x9756": "0xBEB8", + "0x7ADF": "0xBEB9", + "0x7ADE": "0xBEBA", + "0x51C0": "0xBEBB", + "0x70AF": "0xBEBC", + "0x7A98": "0xBEBD", + "0x63EA": "0xBEBE", + "0x7A76": "0xBEBF", + "0x7EA0": "0xBEC0", + "0x7396": "0xBEC1", + "0x97ED": "0xBEC2", + "0x4E45": "0xBEC3", + "0x7078": "0xBEC4", + "0x4E5D": "0xBEC5", + "0x9152": "0xBEC6", + "0x53A9": "0xBEC7", + "0x6551": "0xBEC8", + "0x65E7": "0xBEC9", + "0x81FC": "0xBECA", + "0x8205": "0xBECB", + "0x548E": "0xBECC", + "0x5C31": "0xBECD", + "0x759A": "0xBECE", + "0x97A0": "0xBECF", + "0x62D8": "0xBED0", + "0x72D9": "0xBED1", + "0x75BD": "0xBED2", + "0x5C45": "0xBED3", + "0x9A79": "0xBED4", + "0x83CA": "0xBED5", + "0x5C40": "0xBED6", + "0x5480": "0xBED7", + "0x77E9": "0xBED8", + "0x4E3E": "0xBED9", + "0x6CAE": "0xBEDA", + "0x805A": "0xBEDB", + "0x62D2": "0xBEDC", + "0x636E": "0xBEDD", + "0x5DE8": "0xBEDE", + "0x5177": "0xBEDF", + "0x8DDD": "0xBEE0", + "0x8E1E": "0xBEE1", + "0x952F": "0xBEE2", + "0x4FF1": "0xBEE3", + "0x53E5": "0xBEE4", + "0x60E7": "0xBEE5", + "0x70AC": "0xBEE6", + "0x5267": "0xBEE7", + "0x6350": "0xBEE8", + "0x9E43": "0xBEE9", + "0x5A1F": "0xBEEA", + "0x5026": "0xBEEB", + "0x7737": "0xBEEC", + "0x5377": "0xBEED", + "0x7EE2": "0xBEEE", + "0x6485": "0xBEEF", + "0x652B": "0xBEF0", + "0x6289": "0xBEF1", + "0x6398": "0xBEF2", + "0x5014": "0xBEF3", + "0x7235": "0xBEF4", + "0x89C9": "0xBEF5", + "0x51B3": "0xBEF6", + "0x8BC0": "0xBEF7", + "0x7EDD": "0xBEF8", + "0x5747": "0xBEF9", + "0x83CC": "0xBEFA", + "0x94A7": "0xBEFB", + "0x519B": "0xBEFC", + "0x541B": "0xBEFD", + "0x5CFB": "0xBEFE", + "0x4FCA": "0xBFA1", + "0x7AE3": "0xBFA2", + "0x6D5A": "0xBFA3", + "0x90E1": "0xBFA4", + "0x9A8F": "0xBFA5", + "0x5580": "0xBFA6", + "0x5496": "0xBFA7", + "0x5361": "0xBFA8", + "0x54AF": "0xBFA9", + "0x5F00": "0xBFAA", + "0x63E9": "0xBFAB", + "0x6977": "0xBFAC", + "0x51EF": "0xBFAD", + "0x6168": "0xBFAE", + "0x520A": "0xBFAF", + "0x582A": "0xBFB0", + "0x52D8": "0xBFB1", + "0x574E": "0xBFB2", + "0x780D": "0xBFB3", + "0x770B": "0xBFB4", + "0x5EB7": "0xBFB5", + "0x6177": "0xBFB6", + "0x7CE0": "0xBFB7", + "0x625B": "0xBFB8", + "0x6297": "0xBFB9", + "0x4EA2": "0xBFBA", + "0x7095": "0xBFBB", + "0x8003": "0xBFBC", + "0x62F7": "0xBFBD", + "0x70E4": "0xBFBE", + "0x9760": "0xBFBF", + "0x5777": "0xBFC0", + "0x82DB": "0xBFC1", + "0x67EF": "0xBFC2", + "0x68F5": "0xBFC3", + "0x78D5": "0xBFC4", + "0x9897": "0xBFC5", + "0x79D1": "0xBFC6", + "0x58F3": "0xBFC7", + "0x54B3": "0xBFC8", + "0x53EF": "0xBFC9", + "0x6E34": "0xBFCA", + "0x514B": "0xBFCB", + "0x523B": "0xBFCC", + "0x5BA2": "0xBFCD", + "0x8BFE": "0xBFCE", + "0x80AF": "0xBFCF", + "0x5543": "0xBFD0", + "0x57A6": "0xBFD1", + "0x6073": "0xBFD2", + "0x5751": "0xBFD3", + "0x542D": "0xBFD4", + "0x7A7A": "0xBFD5", + "0x6050": "0xBFD6", + "0x5B54": "0xBFD7", + "0x63A7": "0xBFD8", + "0x62A0": "0xBFD9", + "0x53E3": "0xBFDA", + "0x6263": "0xBFDB", + "0x5BC7": "0xBFDC", + "0x67AF": "0xBFDD", + "0x54ED": "0xBFDE", + "0x7A9F": "0xBFDF", + "0x82E6": "0xBFE0", + "0x9177": "0xBFE1", + "0x5E93": "0xBFE2", + "0x88E4": "0xBFE3", + "0x5938": "0xBFE4", + "0x57AE": "0xBFE5", + "0x630E": "0xBFE6", + "0x8DE8": "0xBFE7", + "0x80EF": "0xBFE8", + "0x5757": "0xBFE9", + "0x7B77": "0xBFEA", + "0x4FA9": "0xBFEB", + "0x5FEB": "0xBFEC", + "0x5BBD": "0xBFED", + "0x6B3E": "0xBFEE", + "0x5321": "0xBFEF", + "0x7B50": "0xBFF0", + "0x72C2": "0xBFF1", + "0x6846": "0xBFF2", + "0x77FF": "0xBFF3", + "0x7736": "0xBFF4", + "0x65F7": "0xBFF5", + "0x51B5": "0xBFF6", + "0x4E8F": "0xBFF7", + "0x76D4": "0xBFF8", + "0x5CBF": "0xBFF9", + "0x7AA5": "0xBFFA", + "0x8475": "0xBFFB", + "0x594E": "0xBFFC", + "0x9B41": "0xBFFD", + "0x5080": "0xBFFE", + "0x9988": "0xC0A1", + "0x6127": "0xC0A2", + "0x6E83": "0xC0A3", + "0x5764": "0xC0A4", + "0x6606": "0xC0A5", + "0x6346": "0xC0A6", + "0x56F0": "0xC0A7", + "0x62EC": "0xC0A8", + "0x6269": "0xC0A9", + "0x5ED3": "0xC0AA", + "0x9614": "0xC0AB", + "0x5783": "0xC0AC", + "0x62C9": "0xC0AD", + "0x5587": "0xC0AE", + "0x8721": "0xC0AF", + "0x814A": "0xC0B0", + "0x8FA3": "0xC0B1", + "0x5566": "0xC0B2", + "0x83B1": "0xC0B3", + "0x6765": "0xC0B4", + "0x8D56": "0xC0B5", + "0x84DD": "0xC0B6", + "0x5A6A": "0xC0B7", + "0x680F": "0xC0B8", + "0x62E6": "0xC0B9", + "0x7BEE": "0xC0BA", + "0x9611": "0xC0BB", + "0x5170": "0xC0BC", + "0x6F9C": "0xC0BD", + "0x8C30": "0xC0BE", + "0x63FD": "0xC0BF", + "0x89C8": "0xC0C0", + "0x61D2": "0xC0C1", + "0x7F06": "0xC0C2", + "0x70C2": "0xC0C3", + "0x6EE5": "0xC0C4", + "0x7405": "0xC0C5", + "0x6994": "0xC0C6", + "0x72FC": "0xC0C7", + "0x5ECA": "0xC0C8", + "0x90CE": "0xC0C9", + "0x6717": "0xC0CA", + "0x6D6A": "0xC0CB", + "0x635E": "0xC0CC", + "0x52B3": "0xC0CD", + "0x7262": "0xC0CE", + "0x8001": "0xC0CF", + "0x4F6C": "0xC0D0", + "0x59E5": "0xC0D1", + "0x916A": "0xC0D2", + "0x70D9": "0xC0D3", + "0x6D9D": "0xC0D4", + "0x52D2": "0xC0D5", + "0x4E50": "0xC0D6", + "0x96F7": "0xC0D7", + "0x956D": "0xC0D8", + "0x857E": "0xC0D9", + "0x78CA": "0xC0DA", + "0x7D2F": "0xC0DB", + "0x5121": "0xC0DC", + "0x5792": "0xC0DD", + "0x64C2": "0xC0DE", + "0x808B": "0xC0DF", + "0x7C7B": "0xC0E0", + "0x6CEA": "0xC0E1", + "0x68F1": "0xC0E2", + "0x695E": "0xC0E3", + "0x51B7": "0xC0E4", + "0x5398": "0xC0E5", + "0x68A8": "0xC0E6", + "0x7281": "0xC0E7", + "0x9ECE": "0xC0E8", + "0x7BF1": "0xC0E9", + "0x72F8": "0xC0EA", + "0x79BB": "0xC0EB", + "0x6F13": "0xC0EC", + "0x7406": "0xC0ED", + "0x674E": "0xC0EE", + "0x91CC": "0xC0EF", + "0x9CA4": "0xC0F0", + "0x793C": "0xC0F1", + "0x8389": "0xC0F2", + "0x8354": "0xC0F3", + "0x540F": "0xC0F4", + "0x6817": "0xC0F5", + "0x4E3D": "0xC0F6", + "0x5389": "0xC0F7", + "0x52B1": "0xC0F8", + "0x783E": "0xC0F9", + "0x5386": "0xC0FA", + "0x5229": "0xC0FB", + "0x5088": "0xC0FC", + "0x4F8B": "0xC0FD", + "0x4FD0": "0xC0FE", + "0x75E2": "0xC1A1", + "0x7ACB": "0xC1A2", + "0x7C92": "0xC1A3", + "0x6CA5": "0xC1A4", + "0x96B6": "0xC1A5", + "0x529B": "0xC1A6", + "0x7483": "0xC1A7", + "0x54E9": "0xC1A8", + "0x4FE9": "0xC1A9", + "0x8054": "0xC1AA", + "0x83B2": "0xC1AB", + "0x8FDE": "0xC1AC", + "0x9570": "0xC1AD", + "0x5EC9": "0xC1AE", + "0x601C": "0xC1AF", + "0x6D9F": "0xC1B0", + "0x5E18": "0xC1B1", + "0x655B": "0xC1B2", + "0x8138": "0xC1B3", + "0x94FE": "0xC1B4", + "0x604B": "0xC1B5", + "0x70BC": "0xC1B6", + "0x7EC3": "0xC1B7", + "0x7CAE": "0xC1B8", + "0x51C9": "0xC1B9", + "0x6881": "0xC1BA", + "0x7CB1": "0xC1BB", + "0x826F": "0xC1BC", + "0x4E24": "0xC1BD", + "0x8F86": "0xC1BE", + "0x91CF": "0xC1BF", + "0x667E": "0xC1C0", + "0x4EAE": "0xC1C1", + "0x8C05": "0xC1C2", + "0x64A9": "0xC1C3", + "0x804A": "0xC1C4", + "0x50DA": "0xC1C5", + "0x7597": "0xC1C6", + "0x71CE": "0xC1C7", + "0x5BE5": "0xC1C8", + "0x8FBD": "0xC1C9", + "0x6F66": "0xC1CA", + "0x4E86": "0xC1CB", + "0x6482": "0xC1CC", + "0x9563": "0xC1CD", + "0x5ED6": "0xC1CE", + "0x6599": "0xC1CF", + "0x5217": "0xC1D0", + "0x88C2": "0xC1D1", + "0x70C8": "0xC1D2", + "0x52A3": "0xC1D3", + "0x730E": "0xC1D4", + "0x7433": "0xC1D5", + "0x6797": "0xC1D6", + "0x78F7": "0xC1D7", + "0x9716": "0xC1D8", + "0x4E34": "0xC1D9", + "0x90BB": "0xC1DA", + "0x9CDE": "0xC1DB", + "0x6DCB": "0xC1DC", + "0x51DB": "0xC1DD", + "0x8D41": "0xC1DE", + "0x541D": "0xC1DF", + "0x62CE": "0xC1E0", + "0x73B2": "0xC1E1", + "0x83F1": "0xC1E2", + "0x96F6": "0xC1E3", + "0x9F84": "0xC1E4", + "0x94C3": "0xC1E5", + "0x4F36": "0xC1E6", + "0x7F9A": "0xC1E7", + "0x51CC": "0xC1E8", + "0x7075": "0xC1E9", + "0x9675": "0xC1EA", + "0x5CAD": "0xC1EB", + "0x9886": "0xC1EC", + "0x53E6": "0xC1ED", + "0x4EE4": "0xC1EE", + "0x6E9C": "0xC1EF", + "0x7409": "0xC1F0", + "0x69B4": "0xC1F1", + "0x786B": "0xC1F2", + "0x998F": "0xC1F3", + "0x7559": "0xC1F4", + "0x5218": "0xC1F5", + "0x7624": "0xC1F6", + "0x6D41": "0xC1F7", + "0x67F3": "0xC1F8", + "0x516D": "0xC1F9", + "0x9F99": "0xC1FA", + "0x804B": "0xC1FB", + "0x5499": "0xC1FC", + "0x7B3C": "0xC1FD", + "0x7ABF": "0xC1FE", + "0x9686": "0xC2A1", + "0x5784": "0xC2A2", + "0x62E2": "0xC2A3", + "0x9647": "0xC2A4", + "0x697C": "0xC2A5", + "0x5A04": "0xC2A6", + "0x6402": "0xC2A7", + "0x7BD3": "0xC2A8", + "0x6F0F": "0xC2A9", + "0x964B": "0xC2AA", + "0x82A6": "0xC2AB", + "0x5362": "0xC2AC", + "0x9885": "0xC2AD", + "0x5E90": "0xC2AE", + "0x7089": "0xC2AF", + "0x63B3": "0xC2B0", + "0x5364": "0xC2B1", + "0x864F": "0xC2B2", + "0x9C81": "0xC2B3", + "0x9E93": "0xC2B4", + "0x788C": "0xC2B5", + "0x9732": "0xC2B6", + "0x8DEF": "0xC2B7", + "0x8D42": "0xC2B8", + "0x9E7F": "0xC2B9", + "0x6F5E": "0xC2BA", + "0x7984": "0xC2BB", + "0x5F55": "0xC2BC", + "0x9646": "0xC2BD", + "0x622E": "0xC2BE", + "0x9A74": "0xC2BF", + "0x5415": "0xC2C0", + "0x94DD": "0xC2C1", + "0x4FA3": "0xC2C2", + "0x65C5": "0xC2C3", + "0x5C65": "0xC2C4", + "0x5C61": "0xC2C5", + "0x7F15": "0xC2C6", + "0x8651": "0xC2C7", + "0x6C2F": "0xC2C8", + "0x5F8B": "0xC2C9", + "0x7387": "0xC2CA", + "0x6EE4": "0xC2CB", + "0x7EFF": "0xC2CC", + "0x5CE6": "0xC2CD", + "0x631B": "0xC2CE", + "0x5B6A": "0xC2CF", + "0x6EE6": "0xC2D0", + "0x5375": "0xC2D1", + "0x4E71": "0xC2D2", + "0x63A0": "0xC2D3", + "0x7565": "0xC2D4", + "0x62A1": "0xC2D5", + "0x8F6E": "0xC2D6", + "0x4F26": "0xC2D7", + "0x4ED1": "0xC2D8", + "0x6CA6": "0xC2D9", + "0x7EB6": "0xC2DA", + "0x8BBA": "0xC2DB", + "0x841D": "0xC2DC", + "0x87BA": "0xC2DD", + "0x7F57": "0xC2DE", + "0x903B": "0xC2DF", + "0x9523": "0xC2E0", + "0x7BA9": "0xC2E1", + "0x9AA1": "0xC2E2", + "0x88F8": "0xC2E3", + "0x843D": "0xC2E4", + "0x6D1B": "0xC2E5", + "0x9A86": "0xC2E6", + "0x7EDC": "0xC2E7", + "0x5988": "0xC2E8", + "0x9EBB": "0xC2E9", + "0x739B": "0xC2EA", + "0x7801": "0xC2EB", + "0x8682": "0xC2EC", + "0x9A6C": "0xC2ED", + "0x9A82": "0xC2EE", + "0x561B": "0xC2EF", + "0x5417": "0xC2F0", + "0x57CB": "0xC2F1", + "0x4E70": "0xC2F2", + "0x9EA6": "0xC2F3", + "0x5356": "0xC2F4", + "0x8FC8": "0xC2F5", + "0x8109": "0xC2F6", + "0x7792": "0xC2F7", + "0x9992": "0xC2F8", + "0x86EE": "0xC2F9", + "0x6EE1": "0xC2FA", + "0x8513": "0xC2FB", + "0x66FC": "0xC2FC", + "0x6162": "0xC2FD", + "0x6F2B": "0xC2FE", + "0x8C29": "0xC3A1", + "0x8292": "0xC3A2", + "0x832B": "0xC3A3", + "0x76F2": "0xC3A4", + "0x6C13": "0xC3A5", + "0x5FD9": "0xC3A6", + "0x83BD": "0xC3A7", + "0x732B": "0xC3A8", + "0x8305": "0xC3A9", + "0x951A": "0xC3AA", + "0x6BDB": "0xC3AB", + "0x77DB": "0xC3AC", + "0x94C6": "0xC3AD", + "0x536F": "0xC3AE", + "0x8302": "0xC3AF", + "0x5192": "0xC3B0", + "0x5E3D": "0xC3B1", + "0x8C8C": "0xC3B2", + "0x8D38": "0xC3B3", + "0x4E48": "0xC3B4", + "0x73AB": "0xC3B5", + "0x679A": "0xC3B6", + "0x6885": "0xC3B7", + "0x9176": "0xC3B8", + "0x9709": "0xC3B9", + "0x7164": "0xC3BA", + "0x6CA1": "0xC3BB", + "0x7709": "0xC3BC", + "0x5A92": "0xC3BD", + "0x9541": "0xC3BE", + "0x6BCF": "0xC3BF", + "0x7F8E": "0xC3C0", + "0x6627": "0xC3C1", + "0x5BD0": "0xC3C2", + "0x59B9": "0xC3C3", + "0x5A9A": "0xC3C4", + "0x95E8": "0xC3C5", + "0x95F7": "0xC3C6", + "0x4EEC": "0xC3C7", + "0x840C": "0xC3C8", + "0x8499": "0xC3C9", + "0x6AAC": "0xC3CA", + "0x76DF": "0xC3CB", + "0x9530": "0xC3CC", + "0x731B": "0xC3CD", + "0x68A6": "0xC3CE", + "0x5B5F": "0xC3CF", + "0x772F": "0xC3D0", + "0x919A": "0xC3D1", + "0x9761": "0xC3D2", + "0x7CDC": "0xC3D3", + "0x8FF7": "0xC3D4", + "0x8C1C": "0xC3D5", + "0x5F25": "0xC3D6", + "0x7C73": "0xC3D7", + "0x79D8": "0xC3D8", + "0x89C5": "0xC3D9", + "0x6CCC": "0xC3DA", + "0x871C": "0xC3DB", + "0x5BC6": "0xC3DC", + "0x5E42": "0xC3DD", + "0x68C9": "0xC3DE", + "0x7720": "0xC3DF", + "0x7EF5": "0xC3E0", + "0x5195": "0xC3E1", + "0x514D": "0xC3E2", + "0x52C9": "0xC3E3", + "0x5A29": "0xC3E4", + "0x7F05": "0xC3E5", + "0x9762": "0xC3E6", + "0x82D7": "0xC3E7", + "0x63CF": "0xC3E8", + "0x7784": "0xC3E9", + "0x85D0": "0xC3EA", + "0x79D2": "0xC3EB", + "0x6E3A": "0xC3EC", + "0x5E99": "0xC3ED", + "0x5999": "0xC3EE", + "0x8511": "0xC3EF", + "0x706D": "0xC3F0", + "0x6C11": "0xC3F1", + "0x62BF": "0xC3F2", + "0x76BF": "0xC3F3", + "0x654F": "0xC3F4", + "0x60AF": "0xC3F5", + "0x95FD": "0xC3F6", + "0x660E": "0xC3F7", + "0x879F": "0xC3F8", + "0x9E23": "0xC3F9", + "0x94ED": "0xC3FA", + "0x540D": "0xC3FB", + "0x547D": "0xC3FC", + "0x8C2C": "0xC3FD", + "0x6478": "0xC3FE", + "0x6479": "0xC4A1", + "0x8611": "0xC4A2", + "0x6A21": "0xC4A3", + "0x819C": "0xC4A4", + "0x78E8": "0xC4A5", + "0x6469": "0xC4A6", + "0x9B54": "0xC4A7", + "0x62B9": "0xC4A8", + "0x672B": "0xC4A9", + "0x83AB": "0xC4AA", + "0x58A8": "0xC4AB", + "0x9ED8": "0xC4AC", + "0x6CAB": "0xC4AD", + "0x6F20": "0xC4AE", + "0x5BDE": "0xC4AF", + "0x964C": "0xC4B0", + "0x8C0B": "0xC4B1", + "0x725F": "0xC4B2", + "0x67D0": "0xC4B3", + "0x62C7": "0xC4B4", + "0x7261": "0xC4B5", + "0x4EA9": "0xC4B6", + "0x59C6": "0xC4B7", + "0x6BCD": "0xC4B8", + "0x5893": "0xC4B9", + "0x66AE": "0xC4BA", + "0x5E55": "0xC4BB", + "0x52DF": "0xC4BC", + "0x6155": "0xC4BD", + "0x6728": "0xC4BE", + "0x76EE": "0xC4BF", + "0x7766": "0xC4C0", + "0x7267": "0xC4C1", + "0x7A46": "0xC4C2", + "0x62FF": "0xC4C3", + "0x54EA": "0xC4C4", + "0x5450": "0xC4C5", + "0x94A0": "0xC4C6", + "0x90A3": "0xC4C7", + "0x5A1C": "0xC4C8", + "0x7EB3": "0xC4C9", + "0x6C16": "0xC4CA", + "0x4E43": "0xC4CB", + "0x5976": "0xC4CC", + "0x8010": "0xC4CD", + "0x5948": "0xC4CE", + "0x5357": "0xC4CF", + "0x7537": "0xC4D0", + "0x96BE": "0xC4D1", + "0x56CA": "0xC4D2", + "0x6320": "0xC4D3", + "0x8111": "0xC4D4", + "0x607C": "0xC4D5", + "0x95F9": "0xC4D6", + "0x6DD6": "0xC4D7", + "0x5462": "0xC4D8", + "0x9981": "0xC4D9", + "0x5185": "0xC4DA", + "0x5AE9": "0xC4DB", + "0x80FD": "0xC4DC", + "0x59AE": "0xC4DD", + "0x9713": "0xC4DE", + "0x502A": "0xC4DF", + "0x6CE5": "0xC4E0", + "0x5C3C": "0xC4E1", + "0x62DF": "0xC4E2", + "0x4F60": "0xC4E3", + "0x533F": "0xC4E4", + "0x817B": "0xC4E5", + "0x9006": "0xC4E6", + "0x6EBA": "0xC4E7", + "0x852B": "0xC4E8", + "0x62C8": "0xC4E9", + "0x5E74": "0xC4EA", + "0x78BE": "0xC4EB", + "0x64B5": "0xC4EC", + "0x637B": "0xC4ED", + "0x5FF5": "0xC4EE", + "0x5A18": "0xC4EF", + "0x917F": "0xC4F0", + "0x9E1F": "0xC4F1", + "0x5C3F": "0xC4F2", + "0x634F": "0xC4F3", + "0x8042": "0xC4F4", + "0x5B7D": "0xC4F5", + "0x556E": "0xC4F6", + "0x954A": "0xC4F7", + "0x954D": "0xC4F8", + "0x6D85": "0xC4F9", + "0x60A8": "0xC4FA", + "0x67E0": "0xC4FB", + "0x72DE": "0xC4FC", + "0x51DD": "0xC4FD", + "0x5B81": "0xC4FE", + "0x62E7": "0xC5A1", + "0x6CDE": "0xC5A2", + "0x725B": "0xC5A3", + "0x626D": "0xC5A4", + "0x94AE": "0xC5A5", + "0x7EBD": "0xC5A6", + "0x8113": "0xC5A7", + "0x6D53": "0xC5A8", + "0x519C": "0xC5A9", + "0x5F04": "0xC5AA", + "0x5974": "0xC5AB", + "0x52AA": "0xC5AC", + "0x6012": "0xC5AD", + "0x5973": "0xC5AE", + "0x6696": "0xC5AF", + "0x8650": "0xC5B0", + "0x759F": "0xC5B1", + "0x632A": "0xC5B2", + "0x61E6": "0xC5B3", + "0x7CEF": "0xC5B4", + "0x8BFA": "0xC5B5", + "0x54E6": "0xC5B6", + "0x6B27": "0xC5B7", + "0x9E25": "0xC5B8", + "0x6BB4": "0xC5B9", + "0x85D5": "0xC5BA", + "0x5455": "0xC5BB", + "0x5076": "0xC5BC", + "0x6CA4": "0xC5BD", + "0x556A": "0xC5BE", + "0x8DB4": "0xC5BF", + "0x722C": "0xC5C0", + "0x5E15": "0xC5C1", + "0x6015": "0xC5C2", + "0x7436": "0xC5C3", + "0x62CD": "0xC5C4", + "0x6392": "0xC5C5", + "0x724C": "0xC5C6", + "0x5F98": "0xC5C7", + "0x6E43": "0xC5C8", + "0x6D3E": "0xC5C9", + "0x6500": "0xC5CA", + "0x6F58": "0xC5CB", + "0x76D8": "0xC5CC", + "0x78D0": "0xC5CD", + "0x76FC": "0xC5CE", + "0x7554": "0xC5CF", + "0x5224": "0xC5D0", + "0x53DB": "0xC5D1", + "0x4E53": "0xC5D2", + "0x5E9E": "0xC5D3", + "0x65C1": "0xC5D4", + "0x802A": "0xC5D5", + "0x80D6": "0xC5D6", + "0x629B": "0xC5D7", + "0x5486": "0xC5D8", + "0x5228": "0xC5D9", + "0x70AE": "0xC5DA", + "0x888D": "0xC5DB", + "0x8DD1": "0xC5DC", + "0x6CE1": "0xC5DD", + "0x5478": "0xC5DE", + "0x80DA": "0xC5DF", + "0x57F9": "0xC5E0", + "0x88F4": "0xC5E1", + "0x8D54": "0xC5E2", + "0x966A": "0xC5E3", + "0x914D": "0xC5E4", + "0x4F69": "0xC5E5", + "0x6C9B": "0xC5E6", + "0x55B7": "0xC5E7", + "0x76C6": "0xC5E8", + "0x7830": "0xC5E9", + "0x62A8": "0xC5EA", + "0x70F9": "0xC5EB", + "0x6F8E": "0xC5EC", + "0x5F6D": "0xC5ED", + "0x84EC": "0xC5EE", + "0x68DA": "0xC5EF", + "0x787C": "0xC5F0", + "0x7BF7": "0xC5F1", + "0x81A8": "0xC5F2", + "0x670B": "0xC5F3", + "0x9E4F": "0xC5F4", + "0x6367": "0xC5F5", + "0x78B0": "0xC5F6", + "0x576F": "0xC5F7", + "0x7812": "0xC5F8", + "0x9739": "0xC5F9", + "0x6279": "0xC5FA", + "0x62AB": "0xC5FB", + "0x5288": "0xC5FC", + "0x7435": "0xC5FD", + "0x6BD7": "0xC5FE", + "0x5564": "0xC6A1", + "0x813E": "0xC6A2", + "0x75B2": "0xC6A3", + "0x76AE": "0xC6A4", + "0x5339": "0xC6A5", + "0x75DE": "0xC6A6", + "0x50FB": "0xC6A7", + "0x5C41": "0xC6A8", + "0x8B6C": "0xC6A9", + "0x7BC7": "0xC6AA", + "0x504F": "0xC6AB", + "0x7247": "0xC6AC", + "0x9A97": "0xC6AD", + "0x98D8": "0xC6AE", + "0x6F02": "0xC6AF", + "0x74E2": "0xC6B0", + "0x7968": "0xC6B1", + "0x6487": "0xC6B2", + "0x77A5": "0xC6B3", + "0x62FC": "0xC6B4", + "0x9891": "0xC6B5", + "0x8D2B": "0xC6B6", + "0x54C1": "0xC6B7", + "0x8058": "0xC6B8", + "0x4E52": "0xC6B9", + "0x576A": "0xC6BA", + "0x82F9": "0xC6BB", + "0x840D": "0xC6BC", + "0x5E73": "0xC6BD", + "0x51ED": "0xC6BE", + "0x74F6": "0xC6BF", + "0x8BC4": "0xC6C0", + "0x5C4F": "0xC6C1", + "0x5761": "0xC6C2", + "0x6CFC": "0xC6C3", + "0x9887": "0xC6C4", + "0x5A46": "0xC6C5", + "0x7834": "0xC6C6", + "0x9B44": "0xC6C7", + "0x8FEB": "0xC6C8", + "0x7C95": "0xC6C9", + "0x5256": "0xC6CA", + "0x6251": "0xC6CB", + "0x94FA": "0xC6CC", + "0x4EC6": "0xC6CD", + "0x8386": "0xC6CE", + "0x8461": "0xC6CF", + "0x83E9": "0xC6D0", + "0x84B2": "0xC6D1", + "0x57D4": "0xC6D2", + "0x6734": "0xC6D3", + "0x5703": "0xC6D4", + "0x666E": "0xC6D5", + "0x6D66": "0xC6D6", + "0x8C31": "0xC6D7", + "0x66DD": "0xC6D8", + "0x7011": "0xC6D9", + "0x671F": "0xC6DA", + "0x6B3A": "0xC6DB", + "0x6816": "0xC6DC", + "0x621A": "0xC6DD", + "0x59BB": "0xC6DE", + "0x4E03": "0xC6DF", + "0x51C4": "0xC6E0", + "0x6F06": "0xC6E1", + "0x67D2": "0xC6E2", + "0x6C8F": "0xC6E3", + "0x5176": "0xC6E4", + "0x68CB": "0xC6E5", + "0x5947": "0xC6E6", + "0x6B67": "0xC6E7", + "0x7566": "0xC6E8", + "0x5D0E": "0xC6E9", + "0x8110": "0xC6EA", + "0x9F50": "0xC6EB", + "0x65D7": "0xC6EC", + "0x7948": "0xC6ED", + "0x7941": "0xC6EE", + "0x9A91": "0xC6EF", + "0x8D77": "0xC6F0", + "0x5C82": "0xC6F1", + "0x4E5E": "0xC6F2", + "0x4F01": "0xC6F3", + "0x542F": "0xC6F4", + "0x5951": "0xC6F5", + "0x780C": "0xC6F6", + "0x5668": "0xC6F7", + "0x6C14": "0xC6F8", + "0x8FC4": "0xC6F9", + "0x5F03": "0xC6FA", + "0x6C7D": "0xC6FB", + "0x6CE3": "0xC6FC", + "0x8BAB": "0xC6FD", + "0x6390": "0xC6FE", + "0x6070": "0xC7A1", + "0x6D3D": "0xC7A2", + "0x7275": "0xC7A3", + "0x6266": "0xC7A4", + "0x948E": "0xC7A5", + "0x94C5": "0xC7A6", + "0x5343": "0xC7A7", + "0x8FC1": "0xC7A8", + "0x7B7E": "0xC7A9", + "0x4EDF": "0xC7AA", + "0x8C26": "0xC7AB", + "0x4E7E": "0xC7AC", + "0x9ED4": "0xC7AD", + "0x94B1": "0xC7AE", + "0x94B3": "0xC7AF", + "0x524D": "0xC7B0", + "0x6F5C": "0xC7B1", + "0x9063": "0xC7B2", + "0x6D45": "0xC7B3", + "0x8C34": "0xC7B4", + "0x5811": "0xC7B5", + "0x5D4C": "0xC7B6", + "0x6B20": "0xC7B7", + "0x6B49": "0xC7B8", + "0x67AA": "0xC7B9", + "0x545B": "0xC7BA", + "0x8154": "0xC7BB", + "0x7F8C": "0xC7BC", + "0x5899": "0xC7BD", + "0x8537": "0xC7BE", + "0x5F3A": "0xC7BF", + "0x62A2": "0xC7C0", + "0x6A47": "0xC7C1", + "0x9539": "0xC7C2", + "0x6572": "0xC7C3", + "0x6084": "0xC7C4", + "0x6865": "0xC7C5", + "0x77A7": "0xC7C6", + "0x4E54": "0xC7C7", + "0x4FA8": "0xC7C8", + "0x5DE7": "0xC7C9", + "0x9798": "0xC7CA", + "0x64AC": "0xC7CB", + "0x7FD8": "0xC7CC", + "0x5CED": "0xC7CD", + "0x4FCF": "0xC7CE", + "0x7A8D": "0xC7CF", + "0x5207": "0xC7D0", + "0x8304": "0xC7D1", + "0x4E14": "0xC7D2", + "0x602F": "0xC7D3", + "0x7A83": "0xC7D4", + "0x94A6": "0xC7D5", + "0x4FB5": "0xC7D6", + "0x4EB2": "0xC7D7", + "0x79E6": "0xC7D8", + "0x7434": "0xC7D9", + "0x52E4": "0xC7DA", + "0x82B9": "0xC7DB", + "0x64D2": "0xC7DC", + "0x79BD": "0xC7DD", + "0x5BDD": "0xC7DE", + "0x6C81": "0xC7DF", + "0x9752": "0xC7E0", + "0x8F7B": "0xC7E1", + "0x6C22": "0xC7E2", + "0x503E": "0xC7E3", + "0x537F": "0xC7E4", + "0x6E05": "0xC7E5", + "0x64CE": "0xC7E6", + "0x6674": "0xC7E7", + "0x6C30": "0xC7E8", + "0x60C5": "0xC7E9", + "0x9877": "0xC7EA", + "0x8BF7": "0xC7EB", + "0x5E86": "0xC7EC", + "0x743C": "0xC7ED", + "0x7A77": "0xC7EE", + "0x79CB": "0xC7EF", + "0x4E18": "0xC7F0", + "0x90B1": "0xC7F1", + "0x7403": "0xC7F2", + "0x6C42": "0xC7F3", + "0x56DA": "0xC7F4", + "0x914B": "0xC7F5", + "0x6CC5": "0xC7F6", + "0x8D8B": "0xC7F7", + "0x533A": "0xC7F8", + "0x86C6": "0xC7F9", + "0x66F2": "0xC7FA", + "0x8EAF": "0xC7FB", + "0x5C48": "0xC7FC", + "0x9A71": "0xC7FD", + "0x6E20": "0xC7FE", + "0x53D6": "0xC8A1", + "0x5A36": "0xC8A2", + "0x9F8B": "0xC8A3", + "0x8DA3": "0xC8A4", + "0x53BB": "0xC8A5", + "0x5708": "0xC8A6", + "0x98A7": "0xC8A7", + "0x6743": "0xC8A8", + "0x919B": "0xC8A9", + "0x6CC9": "0xC8AA", + "0x5168": "0xC8AB", + "0x75CA": "0xC8AC", + "0x62F3": "0xC8AD", + "0x72AC": "0xC8AE", + "0x5238": "0xC8AF", + "0x529D": "0xC8B0", + "0x7F3A": "0xC8B1", + "0x7094": "0xC8B2", + "0x7638": "0xC8B3", + "0x5374": "0xC8B4", + "0x9E4A": "0xC8B5", + "0x69B7": "0xC8B6", + "0x786E": "0xC8B7", + "0x96C0": "0xC8B8", + "0x88D9": "0xC8B9", + "0x7FA4": "0xC8BA", + "0x7136": "0xC8BB", + "0x71C3": "0xC8BC", + "0x5189": "0xC8BD", + "0x67D3": "0xC8BE", + "0x74E4": "0xC8BF", + "0x58E4": "0xC8C0", + "0x6518": "0xC8C1", + "0x56B7": "0xC8C2", + "0x8BA9": "0xC8C3", + "0x9976": "0xC8C4", + "0x6270": "0xC8C5", + "0x7ED5": "0xC8C6", + "0x60F9": "0xC8C7", + "0x70ED": "0xC8C8", + "0x58EC": "0xC8C9", + "0x4EC1": "0xC8CA", + "0x4EBA": "0xC8CB", + "0x5FCD": "0xC8CC", + "0x97E7": "0xC8CD", + "0x4EFB": "0xC8CE", + "0x8BA4": "0xC8CF", + "0x5203": "0xC8D0", + "0x598A": "0xC8D1", + "0x7EAB": "0xC8D2", + "0x6254": "0xC8D3", + "0x4ECD": "0xC8D4", + "0x65E5": "0xC8D5", + "0x620E": "0xC8D6", + "0x8338": "0xC8D7", + "0x84C9": "0xC8D8", + "0x8363": "0xC8D9", + "0x878D": "0xC8DA", + "0x7194": "0xC8DB", + "0x6EB6": "0xC8DC", + "0x5BB9": "0xC8DD", + "0x7ED2": "0xC8DE", + "0x5197": "0xC8DF", + "0x63C9": "0xC8E0", + "0x67D4": "0xC8E1", + "0x8089": "0xC8E2", + "0x8339": "0xC8E3", + "0x8815": "0xC8E4", + "0x5112": "0xC8E5", + "0x5B7A": "0xC8E6", + "0x5982": "0xC8E7", + "0x8FB1": "0xC8E8", + "0x4E73": "0xC8E9", + "0x6C5D": "0xC8EA", + "0x5165": "0xC8EB", + "0x8925": "0xC8EC", + "0x8F6F": "0xC8ED", + "0x962E": "0xC8EE", + "0x854A": "0xC8EF", + "0x745E": "0xC8F0", + "0x9510": "0xC8F1", + "0x95F0": "0xC8F2", + "0x6DA6": "0xC8F3", + "0x82E5": "0xC8F4", + "0x5F31": "0xC8F5", + "0x6492": "0xC8F6", + "0x6D12": "0xC8F7", + "0x8428": "0xC8F8", + "0x816E": "0xC8F9", + "0x9CC3": "0xC8FA", + "0x585E": "0xC8FB", + "0x8D5B": "0xC8FC", + "0x4E09": "0xC8FD", + "0x53C1": "0xC8FE", + "0x4F1E": "0xC9A1", + "0x6563": "0xC9A2", + "0x6851": "0xC9A3", + "0x55D3": "0xC9A4", + "0x4E27": "0xC9A5", + "0x6414": "0xC9A6", + "0x9A9A": "0xC9A7", + "0x626B": "0xC9A8", + "0x5AC2": "0xC9A9", + "0x745F": "0xC9AA", + "0x8272": "0xC9AB", + "0x6DA9": "0xC9AC", + "0x68EE": "0xC9AD", + "0x50E7": "0xC9AE", + "0x838E": "0xC9AF", + "0x7802": "0xC9B0", + "0x6740": "0xC9B1", + "0x5239": "0xC9B2", + "0x6C99": "0xC9B3", + "0x7EB1": "0xC9B4", + "0x50BB": "0xC9B5", + "0x5565": "0xC9B6", + "0x715E": "0xC9B7", + "0x7B5B": "0xC9B8", + "0x6652": "0xC9B9", + "0x73CA": "0xC9BA", + "0x82EB": "0xC9BB", + "0x6749": "0xC9BC", + "0x5C71": "0xC9BD", + "0x5220": "0xC9BE", + "0x717D": "0xC9BF", + "0x886B": "0xC9C0", + "0x95EA": "0xC9C1", + "0x9655": "0xC9C2", + "0x64C5": "0xC9C3", + "0x8D61": "0xC9C4", + "0x81B3": "0xC9C5", + "0x5584": "0xC9C6", + "0x6C55": "0xC9C7", + "0x6247": "0xC9C8", + "0x7F2E": "0xC9C9", + "0x5892": "0xC9CA", + "0x4F24": "0xC9CB", + "0x5546": "0xC9CC", + "0x8D4F": "0xC9CD", + "0x664C": "0xC9CE", + "0x4E0A": "0xC9CF", + "0x5C1A": "0xC9D0", + "0x88F3": "0xC9D1", + "0x68A2": "0xC9D2", + "0x634E": "0xC9D3", + "0x7A0D": "0xC9D4", + "0x70E7": "0xC9D5", + "0x828D": "0xC9D6", + "0x52FA": "0xC9D7", + "0x97F6": "0xC9D8", + "0x5C11": "0xC9D9", + "0x54E8": "0xC9DA", + "0x90B5": "0xC9DB", + "0x7ECD": "0xC9DC", + "0x5962": "0xC9DD", + "0x8D4A": "0xC9DE", + "0x86C7": "0xC9DF", + "0x820C": "0xC9E0", + "0x820D": "0xC9E1", + "0x8D66": "0xC9E2", + "0x6444": "0xC9E3", + "0x5C04": "0xC9E4", + "0x6151": "0xC9E5", + "0x6D89": "0xC9E6", + "0x793E": "0xC9E7", + "0x8BBE": "0xC9E8", + "0x7837": "0xC9E9", + "0x7533": "0xC9EA", + "0x547B": "0xC9EB", + "0x4F38": "0xC9EC", + "0x8EAB": "0xC9ED", + "0x6DF1": "0xC9EE", + "0x5A20": "0xC9EF", + "0x7EC5": "0xC9F0", + "0x795E": "0xC9F1", + "0x6C88": "0xC9F2", + "0x5BA1": "0xC9F3", + "0x5A76": "0xC9F4", + "0x751A": "0xC9F5", + "0x80BE": "0xC9F6", + "0x614E": "0xC9F7", + "0x6E17": "0xC9F8", + "0x58F0": "0xC9F9", + "0x751F": "0xC9FA", + "0x7525": "0xC9FB", + "0x7272": "0xC9FC", + "0x5347": "0xC9FD", + "0x7EF3": "0xC9FE", + "0x7701": "0xCAA1", + "0x76DB": "0xCAA2", + "0x5269": "0xCAA3", + "0x80DC": "0xCAA4", + "0x5723": "0xCAA5", + "0x5E08": "0xCAA6", + "0x5931": "0xCAA7", + "0x72EE": "0xCAA8", + "0x65BD": "0xCAA9", + "0x6E7F": "0xCAAA", + "0x8BD7": "0xCAAB", + "0x5C38": "0xCAAC", + "0x8671": "0xCAAD", + "0x5341": "0xCAAE", + "0x77F3": "0xCAAF", + "0x62FE": "0xCAB0", + "0x65F6": "0xCAB1", + "0x4EC0": "0xCAB2", + "0x98DF": "0xCAB3", + "0x8680": "0xCAB4", + "0x5B9E": "0xCAB5", + "0x8BC6": "0xCAB6", + "0x53F2": "0xCAB7", + "0x77E2": "0xCAB8", + "0x4F7F": "0xCAB9", + "0x5C4E": "0xCABA", + "0x9A76": "0xCABB", + "0x59CB": "0xCABC", + "0x5F0F": "0xCABD", + "0x793A": "0xCABE", + "0x58EB": "0xCABF", + "0x4E16": "0xCAC0", + "0x67FF": "0xCAC1", + "0x4E8B": "0xCAC2", + "0x62ED": "0xCAC3", + "0x8A93": "0xCAC4", + "0x901D": "0xCAC5", + "0x52BF": "0xCAC6", + "0x662F": "0xCAC7", + "0x55DC": "0xCAC8", + "0x566C": "0xCAC9", + "0x9002": "0xCACA", + "0x4ED5": "0xCACB", + "0x4F8D": "0xCACC", + "0x91CA": "0xCACD", + "0x9970": "0xCACE", + "0x6C0F": "0xCACF", + "0x5E02": "0xCAD0", + "0x6043": "0xCAD1", + "0x5BA4": "0xCAD2", + "0x89C6": "0xCAD3", + "0x8BD5": "0xCAD4", + "0x6536": "0xCAD5", + "0x624B": "0xCAD6", + "0x9996": "0xCAD7", + "0x5B88": "0xCAD8", + "0x5BFF": "0xCAD9", + "0x6388": "0xCADA", + "0x552E": "0xCADB", + "0x53D7": "0xCADC", + "0x7626": "0xCADD", + "0x517D": "0xCADE", + "0x852C": "0xCADF", + "0x67A2": "0xCAE0", + "0x68B3": "0xCAE1", + "0x6B8A": "0xCAE2", + "0x6292": "0xCAE3", + "0x8F93": "0xCAE4", + "0x53D4": "0xCAE5", + "0x8212": "0xCAE6", + "0x6DD1": "0xCAE7", + "0x758F": "0xCAE8", + "0x4E66": "0xCAE9", + "0x8D4E": "0xCAEA", + "0x5B70": "0xCAEB", + "0x719F": "0xCAEC", + "0x85AF": "0xCAED", + "0x6691": "0xCAEE", + "0x66D9": "0xCAEF", + "0x7F72": "0xCAF0", + "0x8700": "0xCAF1", + "0x9ECD": "0xCAF2", + "0x9F20": "0xCAF3", + "0x5C5E": "0xCAF4", + "0x672F": "0xCAF5", + "0x8FF0": "0xCAF6", + "0x6811": "0xCAF7", + "0x675F": "0xCAF8", + "0x620D": "0xCAF9", + "0x7AD6": "0xCAFA", + "0x5885": "0xCAFB", + "0x5EB6": "0xCAFC", + "0x6570": "0xCAFD", + "0x6F31": "0xCAFE", + "0x6055": "0xCBA1", + "0x5237": "0xCBA2", + "0x800D": "0xCBA3", + "0x6454": "0xCBA4", + "0x8870": "0xCBA5", + "0x7529": "0xCBA6", + "0x5E05": "0xCBA7", + "0x6813": "0xCBA8", + "0x62F4": "0xCBA9", + "0x971C": "0xCBAA", + "0x53CC": "0xCBAB", + "0x723D": "0xCBAC", + "0x8C01": "0xCBAD", + "0x6C34": "0xCBAE", + "0x7761": "0xCBAF", + "0x7A0E": "0xCBB0", + "0x542E": "0xCBB1", + "0x77AC": "0xCBB2", + "0x987A": "0xCBB3", + "0x821C": "0xCBB4", + "0x8BF4": "0xCBB5", + "0x7855": "0xCBB6", + "0x6714": "0xCBB7", + "0x70C1": "0xCBB8", + "0x65AF": "0xCBB9", + "0x6495": "0xCBBA", + "0x5636": "0xCBBB", + "0x601D": "0xCBBC", + "0x79C1": "0xCBBD", + "0x53F8": "0xCBBE", + "0x4E1D": "0xCBBF", + "0x6B7B": "0xCBC0", + "0x8086": "0xCBC1", + "0x5BFA": "0xCBC2", + "0x55E3": "0xCBC3", + "0x56DB": "0xCBC4", + "0x4F3A": "0xCBC5", + "0x4F3C": "0xCBC6", + "0x9972": "0xCBC7", + "0x5DF3": "0xCBC8", + "0x677E": "0xCBC9", + "0x8038": "0xCBCA", + "0x6002": "0xCBCB", + "0x9882": "0xCBCC", + "0x9001": "0xCBCD", + "0x5B8B": "0xCBCE", + "0x8BBC": "0xCBCF", + "0x8BF5": "0xCBD0", + "0x641C": "0xCBD1", + "0x8258": "0xCBD2", + "0x64DE": "0xCBD3", + "0x55FD": "0xCBD4", + "0x82CF": "0xCBD5", + "0x9165": "0xCBD6", + "0x4FD7": "0xCBD7", + "0x7D20": "0xCBD8", + "0x901F": "0xCBD9", + "0x7C9F": "0xCBDA", + "0x50F3": "0xCBDB", + "0x5851": "0xCBDC", + "0x6EAF": "0xCBDD", + "0x5BBF": "0xCBDE", + "0x8BC9": "0xCBDF", + "0x8083": "0xCBE0", + "0x9178": "0xCBE1", + "0x849C": "0xCBE2", + "0x7B97": "0xCBE3", + "0x867D": "0xCBE4", + "0x968B": "0xCBE5", + "0x968F": "0xCBE6", + "0x7EE5": "0xCBE7", + "0x9AD3": "0xCBE8", + "0x788E": "0xCBE9", + "0x5C81": "0xCBEA", + "0x7A57": "0xCBEB", + "0x9042": "0xCBEC", + "0x96A7": "0xCBED", + "0x795F": "0xCBEE", + "0x5B59": "0xCBEF", + "0x635F": "0xCBF0", + "0x7B0B": "0xCBF1", + "0x84D1": "0xCBF2", + "0x68AD": "0xCBF3", + "0x5506": "0xCBF4", + "0x7F29": "0xCBF5", + "0x7410": "0xCBF6", + "0x7D22": "0xCBF7", + "0x9501": "0xCBF8", + "0x6240": "0xCBF9", + "0x584C": "0xCBFA", + "0x4ED6": "0xCBFB", + "0x5B83": "0xCBFC", + "0x5979": "0xCBFD", + "0x5854": "0xCBFE", + "0x736D": "0xCCA1", + "0x631E": "0xCCA2", + "0x8E4B": "0xCCA3", + "0x8E0F": "0xCCA4", + "0x80CE": "0xCCA5", + "0x82D4": "0xCCA6", + "0x62AC": "0xCCA7", + "0x53F0": "0xCCA8", + "0x6CF0": "0xCCA9", + "0x915E": "0xCCAA", + "0x592A": "0xCCAB", + "0x6001": "0xCCAC", + "0x6C70": "0xCCAD", + "0x574D": "0xCCAE", + "0x644A": "0xCCAF", + "0x8D2A": "0xCCB0", + "0x762B": "0xCCB1", + "0x6EE9": "0xCCB2", + "0x575B": "0xCCB3", + "0x6A80": "0xCCB4", + "0x75F0": "0xCCB5", + "0x6F6D": "0xCCB6", + "0x8C2D": "0xCCB7", + "0x8C08": "0xCCB8", + "0x5766": "0xCCB9", + "0x6BEF": "0xCCBA", + "0x8892": "0xCCBB", + "0x78B3": "0xCCBC", + "0x63A2": "0xCCBD", + "0x53F9": "0xCCBE", + "0x70AD": "0xCCBF", + "0x6C64": "0xCCC0", + "0x5858": "0xCCC1", + "0x642A": "0xCCC2", + "0x5802": "0xCCC3", + "0x68E0": "0xCCC4", + "0x819B": "0xCCC5", + "0x5510": "0xCCC6", + "0x7CD6": "0xCCC7", + "0x5018": "0xCCC8", + "0x8EBA": "0xCCC9", + "0x6DCC": "0xCCCA", + "0x8D9F": "0xCCCB", + "0x70EB": "0xCCCC", + "0x638F": "0xCCCD", + "0x6D9B": "0xCCCE", + "0x6ED4": "0xCCCF", + "0x7EE6": "0xCCD0", + "0x8404": "0xCCD1", + "0x6843": "0xCCD2", + "0x9003": "0xCCD3", + "0x6DD8": "0xCCD4", + "0x9676": "0xCCD5", + "0x8BA8": "0xCCD6", + "0x5957": "0xCCD7", + "0x7279": "0xCCD8", + "0x85E4": "0xCCD9", + "0x817E": "0xCCDA", + "0x75BC": "0xCCDB", + "0x8A8A": "0xCCDC", + "0x68AF": "0xCCDD", + "0x5254": "0xCCDE", + "0x8E22": "0xCCDF", + "0x9511": "0xCCE0", + "0x63D0": "0xCCE1", + "0x9898": "0xCCE2", + "0x8E44": "0xCCE3", + "0x557C": "0xCCE4", + "0x4F53": "0xCCE5", + "0x66FF": "0xCCE6", + "0x568F": "0xCCE7", + "0x60D5": "0xCCE8", + "0x6D95": "0xCCE9", + "0x5243": "0xCCEA", + "0x5C49": "0xCCEB", + "0x5929": "0xCCEC", + "0x6DFB": "0xCCED", + "0x586B": "0xCCEE", + "0x7530": "0xCCEF", + "0x751C": "0xCCF0", + "0x606C": "0xCCF1", + "0x8214": "0xCCF2", + "0x8146": "0xCCF3", + "0x6311": "0xCCF4", + "0x6761": "0xCCF5", + "0x8FE2": "0xCCF6", + "0x773A": "0xCCF7", + "0x8DF3": "0xCCF8", + "0x8D34": "0xCCF9", + "0x94C1": "0xCCFA", + "0x5E16": "0xCCFB", + "0x5385": "0xCCFC", + "0x542C": "0xCCFD", + "0x70C3": "0xCCFE", + "0x6C40": "0xCDA1", + "0x5EF7": "0xCDA2", + "0x505C": "0xCDA3", + "0x4EAD": "0xCDA4", + "0x5EAD": "0xCDA5", + "0x633A": "0xCDA6", + "0x8247": "0xCDA7", + "0x901A": "0xCDA8", + "0x6850": "0xCDA9", + "0x916E": "0xCDAA", + "0x77B3": "0xCDAB", + "0x540C": "0xCDAC", + "0x94DC": "0xCDAD", + "0x5F64": "0xCDAE", + "0x7AE5": "0xCDAF", + "0x6876": "0xCDB0", + "0x6345": "0xCDB1", + "0x7B52": "0xCDB2", + "0x7EDF": "0xCDB3", + "0x75DB": "0xCDB4", + "0x5077": "0xCDB5", + "0x6295": "0xCDB6", + "0x5934": "0xCDB7", + "0x900F": "0xCDB8", + "0x51F8": "0xCDB9", + "0x79C3": "0xCDBA", + "0x7A81": "0xCDBB", + "0x56FE": "0xCDBC", + "0x5F92": "0xCDBD", + "0x9014": "0xCDBE", + "0x6D82": "0xCDBF", + "0x5C60": "0xCDC0", + "0x571F": "0xCDC1", + "0x5410": "0xCDC2", + "0x5154": "0xCDC3", + "0x6E4D": "0xCDC4", + "0x56E2": "0xCDC5", + "0x63A8": "0xCDC6", + "0x9893": "0xCDC7", + "0x817F": "0xCDC8", + "0x8715": "0xCDC9", + "0x892A": "0xCDCA", + "0x9000": "0xCDCB", + "0x541E": "0xCDCC", + "0x5C6F": "0xCDCD", + "0x81C0": "0xCDCE", + "0x62D6": "0xCDCF", + "0x6258": "0xCDD0", + "0x8131": "0xCDD1", + "0x9E35": "0xCDD2", + "0x9640": "0xCDD3", + "0x9A6E": "0xCDD4", + "0x9A7C": "0xCDD5", + "0x692D": "0xCDD6", + "0x59A5": "0xCDD7", + "0x62D3": "0xCDD8", + "0x553E": "0xCDD9", + "0x6316": "0xCDDA", + "0x54C7": "0xCDDB", + "0x86D9": "0xCDDC", + "0x6D3C": "0xCDDD", + "0x5A03": "0xCDDE", + "0x74E6": "0xCDDF", + "0x889C": "0xCDE0", + "0x6B6A": "0xCDE1", + "0x5916": "0xCDE2", + "0x8C4C": "0xCDE3", + "0x5F2F": "0xCDE4", + "0x6E7E": "0xCDE5", + "0x73A9": "0xCDE6", + "0x987D": "0xCDE7", + "0x4E38": "0xCDE8", + "0x70F7": "0xCDE9", + "0x5B8C": "0xCDEA", + "0x7897": "0xCDEB", + "0x633D": "0xCDEC", + "0x665A": "0xCDED", + "0x7696": "0xCDEE", + "0x60CB": "0xCDEF", + "0x5B9B": "0xCDF0", + "0x5A49": "0xCDF1", + "0x4E07": "0xCDF2", + "0x8155": "0xCDF3", + "0x6C6A": "0xCDF4", + "0x738B": "0xCDF5", + "0x4EA1": "0xCDF6", + "0x6789": "0xCDF7", + "0x7F51": "0xCDF8", + "0x5F80": "0xCDF9", + "0x65FA": "0xCDFA", + "0x671B": "0xCDFB", + "0x5FD8": "0xCDFC", + "0x5984": "0xCDFD", + "0x5A01": "0xCDFE", + "0x5DCD": "0xCEA1", + "0x5FAE": "0xCEA2", + "0x5371": "0xCEA3", + "0x97E6": "0xCEA4", + "0x8FDD": "0xCEA5", + "0x6845": "0xCEA6", + "0x56F4": "0xCEA7", + "0x552F": "0xCEA8", + "0x60DF": "0xCEA9", + "0x4E3A": "0xCEAA", + "0x6F4D": "0xCEAB", + "0x7EF4": "0xCEAC", + "0x82C7": "0xCEAD", + "0x840E": "0xCEAE", + "0x59D4": "0xCEAF", + "0x4F1F": "0xCEB0", + "0x4F2A": "0xCEB1", + "0x5C3E": "0xCEB2", + "0x7EAC": "0xCEB3", + "0x672A": "0xCEB4", + "0x851A": "0xCEB5", + "0x5473": "0xCEB6", + "0x754F": "0xCEB7", + "0x80C3": "0xCEB8", + "0x5582": "0xCEB9", + "0x9B4F": "0xCEBA", + "0x4F4D": "0xCEBB", + "0x6E2D": "0xCEBC", + "0x8C13": "0xCEBD", + "0x5C09": "0xCEBE", + "0x6170": "0xCEBF", + "0x536B": "0xCEC0", + "0x761F": "0xCEC1", + "0x6E29": "0xCEC2", + "0x868A": "0xCEC3", + "0x6587": "0xCEC4", + "0x95FB": "0xCEC5", + "0x7EB9": "0xCEC6", + "0x543B": "0xCEC7", + "0x7A33": "0xCEC8", + "0x7D0A": "0xCEC9", + "0x95EE": "0xCECA", + "0x55E1": "0xCECB", + "0x7FC1": "0xCECC", + "0x74EE": "0xCECD", + "0x631D": "0xCECE", + "0x8717": "0xCECF", + "0x6DA1": "0xCED0", + "0x7A9D": "0xCED1", + "0x6211": "0xCED2", + "0x65A1": "0xCED3", + "0x5367": "0xCED4", + "0x63E1": "0xCED5", + "0x6C83": "0xCED6", + "0x5DEB": "0xCED7", + "0x545C": "0xCED8", + "0x94A8": "0xCED9", + "0x4E4C": "0xCEDA", + "0x6C61": "0xCEDB", + "0x8BEC": "0xCEDC", + "0x5C4B": "0xCEDD", + "0x65E0": "0xCEDE", + "0x829C": "0xCEDF", + "0x68A7": "0xCEE0", + "0x543E": "0xCEE1", + "0x5434": "0xCEE2", + "0x6BCB": "0xCEE3", + "0x6B66": "0xCEE4", + "0x4E94": "0xCEE5", + "0x6342": "0xCEE6", + "0x5348": "0xCEE7", + "0x821E": "0xCEE8", + "0x4F0D": "0xCEE9", + "0x4FAE": "0xCEEA", + "0x575E": "0xCEEB", + "0x620A": "0xCEEC", + "0x96FE": "0xCEED", + "0x6664": "0xCEEE", + "0x7269": "0xCEEF", + "0x52FF": "0xCEF0", + "0x52A1": "0xCEF1", + "0x609F": "0xCEF2", + "0x8BEF": "0xCEF3", + "0x6614": "0xCEF4", + "0x7199": "0xCEF5", + "0x6790": "0xCEF6", + "0x897F": "0xCEF7", + "0x7852": "0xCEF8", + "0x77FD": "0xCEF9", + "0x6670": "0xCEFA", + "0x563B": "0xCEFB", + "0x5438": "0xCEFC", + "0x9521": "0xCEFD", + "0x727A": "0xCEFE", + "0x7A00": "0xCFA1", + "0x606F": "0xCFA2", + "0x5E0C": "0xCFA3", + "0x6089": "0xCFA4", + "0x819D": "0xCFA5", + "0x5915": "0xCFA6", + "0x60DC": "0xCFA7", + "0x7184": "0xCFA8", + "0x70EF": "0xCFA9", + "0x6EAA": "0xCFAA", + "0x6C50": "0xCFAB", + "0x7280": "0xCFAC", + "0x6A84": "0xCFAD", + "0x88AD": "0xCFAE", + "0x5E2D": "0xCFAF", + "0x4E60": "0xCFB0", + "0x5AB3": "0xCFB1", + "0x559C": "0xCFB2", + "0x94E3": "0xCFB3", + "0x6D17": "0xCFB4", + "0x7CFB": "0xCFB5", + "0x9699": "0xCFB6", + "0x620F": "0xCFB7", + "0x7EC6": "0xCFB8", + "0x778E": "0xCFB9", + "0x867E": "0xCFBA", + "0x5323": "0xCFBB", + "0x971E": "0xCFBC", + "0x8F96": "0xCFBD", + "0x6687": "0xCFBE", + "0x5CE1": "0xCFBF", + "0x4FA0": "0xCFC0", + "0x72ED": "0xCFC1", + "0x4E0B": "0xCFC2", + "0x53A6": "0xCFC3", + "0x590F": "0xCFC4", + "0x5413": "0xCFC5", + "0x6380": "0xCFC6", + "0x9528": "0xCFC7", + "0x5148": "0xCFC8", + "0x4ED9": "0xCFC9", + "0x9C9C": "0xCFCA", + "0x7EA4": "0xCFCB", + "0x54B8": "0xCFCC", + "0x8D24": "0xCFCD", + "0x8854": "0xCFCE", + "0x8237": "0xCFCF", + "0x95F2": "0xCFD0", + "0x6D8E": "0xCFD1", + "0x5F26": "0xCFD2", + "0x5ACC": "0xCFD3", + "0x663E": "0xCFD4", + "0x9669": "0xCFD5", + "0x73B0": "0xCFD6", + "0x732E": "0xCFD7", + "0x53BF": "0xCFD8", + "0x817A": "0xCFD9", + "0x9985": "0xCFDA", + "0x7FA1": "0xCFDB", + "0x5BAA": "0xCFDC", + "0x9677": "0xCFDD", + "0x9650": "0xCFDE", + "0x7EBF": "0xCFDF", + "0x76F8": "0xCFE0", + "0x53A2": "0xCFE1", + "0x9576": "0xCFE2", + "0x9999": "0xCFE3", + "0x7BB1": "0xCFE4", + "0x8944": "0xCFE5", + "0x6E58": "0xCFE6", + "0x4E61": "0xCFE7", + "0x7FD4": "0xCFE8", + "0x7965": "0xCFE9", + "0x8BE6": "0xCFEA", + "0x60F3": "0xCFEB", + "0x54CD": "0xCFEC", + "0x4EAB": "0xCFED", + "0x9879": "0xCFEE", + "0x5DF7": "0xCFEF", + "0x6A61": "0xCFF0", + "0x50CF": "0xCFF1", + "0x5411": "0xCFF2", + "0x8C61": "0xCFF3", + "0x8427": "0xCFF4", + "0x785D": "0xCFF5", + "0x9704": "0xCFF6", + "0x524A": "0xCFF7", + "0x54EE": "0xCFF8", + "0x56A3": "0xCFF9", + "0x9500": "0xCFFA", + "0x6D88": "0xCFFB", + "0x5BB5": "0xCFFC", + "0x6DC6": "0xCFFD", + "0x6653": "0xCFFE", + "0x5C0F": "0xD0A1", + "0x5B5D": "0xD0A2", + "0x6821": "0xD0A3", + "0x8096": "0xD0A4", + "0x5578": "0xD0A5", + "0x7B11": "0xD0A6", + "0x6548": "0xD0A7", + "0x6954": "0xD0A8", + "0x4E9B": "0xD0A9", + "0x6B47": "0xD0AA", + "0x874E": "0xD0AB", + "0x978B": "0xD0AC", + "0x534F": "0xD0AD", + "0x631F": "0xD0AE", + "0x643A": "0xD0AF", + "0x90AA": "0xD0B0", + "0x659C": "0xD0B1", + "0x80C1": "0xD0B2", + "0x8C10": "0xD0B3", + "0x5199": "0xD0B4", + "0x68B0": "0xD0B5", + "0x5378": "0xD0B6", + "0x87F9": "0xD0B7", + "0x61C8": "0xD0B8", + "0x6CC4": "0xD0B9", + "0x6CFB": "0xD0BA", + "0x8C22": "0xD0BB", + "0x5C51": "0xD0BC", + "0x85AA": "0xD0BD", + "0x82AF": "0xD0BE", + "0x950C": "0xD0BF", + "0x6B23": "0xD0C0", + "0x8F9B": "0xD0C1", + "0x65B0": "0xD0C2", + "0x5FFB": "0xD0C3", + "0x5FC3": "0xD0C4", + "0x4FE1": "0xD0C5", + "0x8845": "0xD0C6", + "0x661F": "0xD0C7", + "0x8165": "0xD0C8", + "0x7329": "0xD0C9", + "0x60FA": "0xD0CA", + "0x5174": "0xD0CB", + "0x5211": "0xD0CC", + "0x578B": "0xD0CD", + "0x5F62": "0xD0CE", + "0x90A2": "0xD0CF", + "0x884C": "0xD0D0", + "0x9192": "0xD0D1", + "0x5E78": "0xD0D2", + "0x674F": "0xD0D3", + "0x6027": "0xD0D4", + "0x59D3": "0xD0D5", + "0x5144": "0xD0D6", + "0x51F6": "0xD0D7", + "0x80F8": "0xD0D8", + "0x5308": "0xD0D9", + "0x6C79": "0xD0DA", + "0x96C4": "0xD0DB", + "0x718A": "0xD0DC", + "0x4F11": "0xD0DD", + "0x4FEE": "0xD0DE", + "0x7F9E": "0xD0DF", + "0x673D": "0xD0E0", + "0x55C5": "0xD0E1", + "0x9508": "0xD0E2", + "0x79C0": "0xD0E3", + "0x8896": "0xD0E4", + "0x7EE3": "0xD0E5", + "0x589F": "0xD0E6", + "0x620C": "0xD0E7", + "0x9700": "0xD0E8", + "0x865A": "0xD0E9", + "0x5618": "0xD0EA", + "0x987B": "0xD0EB", + "0x5F90": "0xD0EC", + "0x8BB8": "0xD0ED", + "0x84C4": "0xD0EE", + "0x9157": "0xD0EF", + "0x53D9": "0xD0F0", + "0x65ED": "0xD0F1", + "0x5E8F": "0xD0F2", + "0x755C": "0xD0F3", + "0x6064": "0xD0F4", + "0x7D6E": "0xD0F5", + "0x5A7F": "0xD0F6", + "0x7EEA": "0xD0F7", + "0x7EED": "0xD0F8", + "0x8F69": "0xD0F9", + "0x55A7": "0xD0FA", + "0x5BA3": "0xD0FB", + "0x60AC": "0xD0FC", + "0x65CB": "0xD0FD", + "0x7384": "0xD0FE", + "0x9009": "0xD1A1", + "0x7663": "0xD1A2", + "0x7729": "0xD1A3", + "0x7EDA": "0xD1A4", + "0x9774": "0xD1A5", + "0x859B": "0xD1A6", + "0x5B66": "0xD1A7", + "0x7A74": "0xD1A8", + "0x96EA": "0xD1A9", + "0x8840": "0xD1AA", + "0x52CB": "0xD1AB", + "0x718F": "0xD1AC", + "0x5FAA": "0xD1AD", + "0x65EC": "0xD1AE", + "0x8BE2": "0xD1AF", + "0x5BFB": "0xD1B0", + "0x9A6F": "0xD1B1", + "0x5DE1": "0xD1B2", + "0x6B89": "0xD1B3", + "0x6C5B": "0xD1B4", + "0x8BAD": "0xD1B5", + "0x8BAF": "0xD1B6", + "0x900A": "0xD1B7", + "0x8FC5": "0xD1B8", + "0x538B": "0xD1B9", + "0x62BC": "0xD1BA", + "0x9E26": "0xD1BB", + "0x9E2D": "0xD1BC", + "0x5440": "0xD1BD", + "0x4E2B": "0xD1BE", + "0x82BD": "0xD1BF", + "0x7259": "0xD1C0", + "0x869C": "0xD1C1", + "0x5D16": "0xD1C2", + "0x8859": "0xD1C3", + "0x6DAF": "0xD1C4", + "0x96C5": "0xD1C5", + "0x54D1": "0xD1C6", + "0x4E9A": "0xD1C7", + "0x8BB6": "0xD1C8", + "0x7109": "0xD1C9", + "0x54BD": "0xD1CA", + "0x9609": "0xD1CB", + "0x70DF": "0xD1CC", + "0x6DF9": "0xD1CD", + "0x76D0": "0xD1CE", + "0x4E25": "0xD1CF", + "0x7814": "0xD1D0", + "0x8712": "0xD1D1", + "0x5CA9": "0xD1D2", + "0x5EF6": "0xD1D3", + "0x8A00": "0xD1D4", + "0x989C": "0xD1D5", + "0x960E": "0xD1D6", + "0x708E": "0xD1D7", + "0x6CBF": "0xD1D8", + "0x5944": "0xD1D9", + "0x63A9": "0xD1DA", + "0x773C": "0xD1DB", + "0x884D": "0xD1DC", + "0x6F14": "0xD1DD", + "0x8273": "0xD1DE", + "0x5830": "0xD1DF", + "0x71D5": "0xD1E0", + "0x538C": "0xD1E1", + "0x781A": "0xD1E2", + "0x96C1": "0xD1E3", + "0x5501": "0xD1E4", + "0x5F66": "0xD1E5", + "0x7130": "0xD1E6", + "0x5BB4": "0xD1E7", + "0x8C1A": "0xD1E8", + "0x9A8C": "0xD1E9", + "0x6B83": "0xD1EA", + "0x592E": "0xD1EB", + "0x9E2F": "0xD1EC", + "0x79E7": "0xD1ED", + "0x6768": "0xD1EE", + "0x626C": "0xD1EF", + "0x4F6F": "0xD1F0", + "0x75A1": "0xD1F1", + "0x7F8A": "0xD1F2", + "0x6D0B": "0xD1F3", + "0x9633": "0xD1F4", + "0x6C27": "0xD1F5", + "0x4EF0": "0xD1F6", + "0x75D2": "0xD1F7", + "0x517B": "0xD1F8", + "0x6837": "0xD1F9", + "0x6F3E": "0xD1FA", + "0x9080": "0xD1FB", + "0x8170": "0xD1FC", + "0x5996": "0xD1FD", + "0x7476": "0xD1FE", + "0x6447": "0xD2A1", + "0x5C27": "0xD2A2", + "0x9065": "0xD2A3", + "0x7A91": "0xD2A4", + "0x8C23": "0xD2A5", + "0x59DA": "0xD2A6", + "0x54AC": "0xD2A7", + "0x8200": "0xD2A8", + "0x836F": "0xD2A9", + "0x8981": "0xD2AA", + "0x8000": "0xD2AB", + "0x6930": "0xD2AC", + "0x564E": "0xD2AD", + "0x8036": "0xD2AE", + "0x7237": "0xD2AF", + "0x91CE": "0xD2B0", + "0x51B6": "0xD2B1", + "0x4E5F": "0xD2B2", + "0x9875": "0xD2B3", + "0x6396": "0xD2B4", + "0x4E1A": "0xD2B5", + "0x53F6": "0xD2B6", + "0x66F3": "0xD2B7", + "0x814B": "0xD2B8", + "0x591C": "0xD2B9", + "0x6DB2": "0xD2BA", + "0x4E00": "0xD2BB", + "0x58F9": "0xD2BC", + "0x533B": "0xD2BD", + "0x63D6": "0xD2BE", + "0x94F1": "0xD2BF", + "0x4F9D": "0xD2C0", + "0x4F0A": "0xD2C1", + "0x8863": "0xD2C2", + "0x9890": "0xD2C3", + "0x5937": "0xD2C4", + "0x9057": "0xD2C5", + "0x79FB": "0xD2C6", + "0x4EEA": "0xD2C7", + "0x80F0": "0xD2C8", + "0x7591": "0xD2C9", + "0x6C82": "0xD2CA", + "0x5B9C": "0xD2CB", + "0x59E8": "0xD2CC", + "0x5F5D": "0xD2CD", + "0x6905": "0xD2CE", + "0x8681": "0xD2CF", + "0x501A": "0xD2D0", + "0x5DF2": "0xD2D1", + "0x4E59": "0xD2D2", + "0x77E3": "0xD2D3", + "0x4EE5": "0xD2D4", + "0x827A": "0xD2D5", + "0x6291": "0xD2D6", + "0x6613": "0xD2D7", + "0x9091": "0xD2D8", + "0x5C79": "0xD2D9", + "0x4EBF": "0xD2DA", + "0x5F79": "0xD2DB", + "0x81C6": "0xD2DC", + "0x9038": "0xD2DD", + "0x8084": "0xD2DE", + "0x75AB": "0xD2DF", + "0x4EA6": "0xD2E0", + "0x88D4": "0xD2E1", + "0x610F": "0xD2E2", + "0x6BC5": "0xD2E3", + "0x5FC6": "0xD2E4", + "0x4E49": "0xD2E5", + "0x76CA": "0xD2E6", + "0x6EA2": "0xD2E7", + "0x8BE3": "0xD2E8", + "0x8BAE": "0xD2E9", + "0x8C0A": "0xD2EA", + "0x8BD1": "0xD2EB", + "0x5F02": "0xD2EC", + "0x7FFC": "0xD2ED", + "0x7FCC": "0xD2EE", + "0x7ECE": "0xD2EF", + "0x8335": "0xD2F0", + "0x836B": "0xD2F1", + "0x56E0": "0xD2F2", + "0x6BB7": "0xD2F3", + "0x97F3": "0xD2F4", + "0x9634": "0xD2F5", + "0x59FB": "0xD2F6", + "0x541F": "0xD2F7", + "0x94F6": "0xD2F8", + "0x6DEB": "0xD2F9", + "0x5BC5": "0xD2FA", + "0x996E": "0xD2FB", + "0x5C39": "0xD2FC", + "0x5F15": "0xD2FD", + "0x9690": "0xD2FE", + "0x5370": "0xD3A1", + "0x82F1": "0xD3A2", + "0x6A31": "0xD3A3", + "0x5A74": "0xD3A4", + "0x9E70": "0xD3A5", + "0x5E94": "0xD3A6", + "0x7F28": "0xD3A7", + "0x83B9": "0xD3A8", + "0x8424": "0xD3A9", + "0x8425": "0xD3AA", + "0x8367": "0xD3AB", + "0x8747": "0xD3AC", + "0x8FCE": "0xD3AD", + "0x8D62": "0xD3AE", + "0x76C8": "0xD3AF", + "0x5F71": "0xD3B0", + "0x9896": "0xD3B1", + "0x786C": "0xD3B2", + "0x6620": "0xD3B3", + "0x54DF": "0xD3B4", + "0x62E5": "0xD3B5", + "0x4F63": "0xD3B6", + "0x81C3": "0xD3B7", + "0x75C8": "0xD3B8", + "0x5EB8": "0xD3B9", + "0x96CD": "0xD3BA", + "0x8E0A": "0xD3BB", + "0x86F9": "0xD3BC", + "0x548F": "0xD3BD", + "0x6CF3": "0xD3BE", + "0x6D8C": "0xD3BF", + "0x6C38": "0xD3C0", + "0x607F": "0xD3C1", + "0x52C7": "0xD3C2", + "0x7528": "0xD3C3", + "0x5E7D": "0xD3C4", + "0x4F18": "0xD3C5", + "0x60A0": "0xD3C6", + "0x5FE7": "0xD3C7", + "0x5C24": "0xD3C8", + "0x7531": "0xD3C9", + "0x90AE": "0xD3CA", + "0x94C0": "0xD3CB", + "0x72B9": "0xD3CC", + "0x6CB9": "0xD3CD", + "0x6E38": "0xD3CE", + "0x9149": "0xD3CF", + "0x6709": "0xD3D0", + "0x53CB": "0xD3D1", + "0x53F3": "0xD3D2", + "0x4F51": "0xD3D3", + "0x91C9": "0xD3D4", + "0x8BF1": "0xD3D5", + "0x53C8": "0xD3D6", + "0x5E7C": "0xD3D7", + "0x8FC2": "0xD3D8", + "0x6DE4": "0xD3D9", + "0x4E8E": "0xD3DA", + "0x76C2": "0xD3DB", + "0x6986": "0xD3DC", + "0x865E": "0xD3DD", + "0x611A": "0xD3DE", + "0x8206": "0xD3DF", + "0x4F59": "0xD3E0", + "0x4FDE": "0xD3E1", + "0x903E": "0xD3E2", + "0x9C7C": "0xD3E3", + "0x6109": "0xD3E4", + "0x6E1D": "0xD3E5", + "0x6E14": "0xD3E6", + "0x9685": "0xD3E7", + "0x4E88": "0xD3E8", + "0x5A31": "0xD3E9", + "0x96E8": "0xD3EA", + "0x4E0E": "0xD3EB", + "0x5C7F": "0xD3EC", + "0x79B9": "0xD3ED", + "0x5B87": "0xD3EE", + "0x8BED": "0xD3EF", + "0x7FBD": "0xD3F0", + "0x7389": "0xD3F1", + "0x57DF": "0xD3F2", + "0x828B": "0xD3F3", + "0x90C1": "0xD3F4", + "0x5401": "0xD3F5", + "0x9047": "0xD3F6", + "0x55BB": "0xD3F7", + "0x5CEA": "0xD3F8", + "0x5FA1": "0xD3F9", + "0x6108": "0xD3FA", + "0x6B32": "0xD3FB", + "0x72F1": "0xD3FC", + "0x80B2": "0xD3FD", + "0x8A89": "0xD3FE", + "0x6D74": "0xD4A1", + "0x5BD3": "0xD4A2", + "0x88D5": "0xD4A3", + "0x9884": "0xD4A4", + "0x8C6B": "0xD4A5", + "0x9A6D": "0xD4A6", + "0x9E33": "0xD4A7", + "0x6E0A": "0xD4A8", + "0x51A4": "0xD4A9", + "0x5143": "0xD4AA", + "0x57A3": "0xD4AB", + "0x8881": "0xD4AC", + "0x539F": "0xD4AD", + "0x63F4": "0xD4AE", + "0x8F95": "0xD4AF", + "0x56ED": "0xD4B0", + "0x5458": "0xD4B1", + "0x5706": "0xD4B2", + "0x733F": "0xD4B3", + "0x6E90": "0xD4B4", + "0x7F18": "0xD4B5", + "0x8FDC": "0xD4B6", + "0x82D1": "0xD4B7", + "0x613F": "0xD4B8", + "0x6028": "0xD4B9", + "0x9662": "0xD4BA", + "0x66F0": "0xD4BB", + "0x7EA6": "0xD4BC", + "0x8D8A": "0xD4BD", + "0x8DC3": "0xD4BE", + "0x94A5": "0xD4BF", + "0x5CB3": "0xD4C0", + "0x7CA4": "0xD4C1", + "0x6708": "0xD4C2", + "0x60A6": "0xD4C3", + "0x9605": "0xD4C4", + "0x8018": "0xD4C5", + "0x4E91": "0xD4C6", + "0x90E7": "0xD4C7", + "0x5300": "0xD4C8", + "0x9668": "0xD4C9", + "0x5141": "0xD4CA", + "0x8FD0": "0xD4CB", + "0x8574": "0xD4CC", + "0x915D": "0xD4CD", + "0x6655": "0xD4CE", + "0x97F5": "0xD4CF", + "0x5B55": "0xD4D0", + "0x531D": "0xD4D1", + "0x7838": "0xD4D2", + "0x6742": "0xD4D3", + "0x683D": "0xD4D4", + "0x54C9": "0xD4D5", + "0x707E": "0xD4D6", + "0x5BB0": "0xD4D7", + "0x8F7D": "0xD4D8", + "0x518D": "0xD4D9", + "0x5728": "0xD4DA", + "0x54B1": "0xD4DB", + "0x6512": "0xD4DC", + "0x6682": "0xD4DD", + "0x8D5E": "0xD4DE", + "0x8D43": "0xD4DF", + "0x810F": "0xD4E0", + "0x846C": "0xD4E1", + "0x906D": "0xD4E2", + "0x7CDF": "0xD4E3", + "0x51FF": "0xD4E4", + "0x85FB": "0xD4E5", + "0x67A3": "0xD4E6", + "0x65E9": "0xD4E7", + "0x6FA1": "0xD4E8", + "0x86A4": "0xD4E9", + "0x8E81": "0xD4EA", + "0x566A": "0xD4EB", + "0x9020": "0xD4EC", + "0x7682": "0xD4ED", + "0x7076": "0xD4EE", + "0x71E5": "0xD4EF", + "0x8D23": "0xD4F0", + "0x62E9": "0xD4F1", + "0x5219": "0xD4F2", + "0x6CFD": "0xD4F3", + "0x8D3C": "0xD4F4", + "0x600E": "0xD4F5", + "0x589E": "0xD4F6", + "0x618E": "0xD4F7", + "0x66FE": "0xD4F8", + "0x8D60": "0xD4F9", + "0x624E": "0xD4FA", + "0x55B3": "0xD4FB", + "0x6E23": "0xD4FC", + "0x672D": "0xD4FD", + "0x8F67": "0xD4FE", + "0x94E1": "0xD5A1", + "0x95F8": "0xD5A2", + "0x7728": "0xD5A3", + "0x6805": "0xD5A4", + "0x69A8": "0xD5A5", + "0x548B": "0xD5A6", + "0x4E4D": "0xD5A7", + "0x70B8": "0xD5A8", + "0x8BC8": "0xD5A9", + "0x6458": "0xD5AA", + "0x658B": "0xD5AB", + "0x5B85": "0xD5AC", + "0x7A84": "0xD5AD", + "0x503A": "0xD5AE", + "0x5BE8": "0xD5AF", + "0x77BB": "0xD5B0", + "0x6BE1": "0xD5B1", + "0x8A79": "0xD5B2", + "0x7C98": "0xD5B3", + "0x6CBE": "0xD5B4", + "0x76CF": "0xD5B5", + "0x65A9": "0xD5B6", + "0x8F97": "0xD5B7", + "0x5D2D": "0xD5B8", + "0x5C55": "0xD5B9", + "0x8638": "0xD5BA", + "0x6808": "0xD5BB", + "0x5360": "0xD5BC", + "0x6218": "0xD5BD", + "0x7AD9": "0xD5BE", + "0x6E5B": "0xD5BF", + "0x7EFD": "0xD5C0", + "0x6A1F": "0xD5C1", + "0x7AE0": "0xD5C2", + "0x5F70": "0xD5C3", + "0x6F33": "0xD5C4", + "0x5F20": "0xD5C5", + "0x638C": "0xD5C6", + "0x6DA8": "0xD5C7", + "0x6756": "0xD5C8", + "0x4E08": "0xD5C9", + "0x5E10": "0xD5CA", + "0x8D26": "0xD5CB", + "0x4ED7": "0xD5CC", + "0x80C0": "0xD5CD", + "0x7634": "0xD5CE", + "0x969C": "0xD5CF", + "0x62DB": "0xD5D0", + "0x662D": "0xD5D1", + "0x627E": "0xD5D2", + "0x6CBC": "0xD5D3", + "0x8D75": "0xD5D4", + "0x7167": "0xD5D5", + "0x7F69": "0xD5D6", + "0x5146": "0xD5D7", + "0x8087": "0xD5D8", + "0x53EC": "0xD5D9", + "0x906E": "0xD5DA", + "0x6298": "0xD5DB", + "0x54F2": "0xD5DC", + "0x86F0": "0xD5DD", + "0x8F99": "0xD5DE", + "0x8005": "0xD5DF", + "0x9517": "0xD5E0", + "0x8517": "0xD5E1", + "0x8FD9": "0xD5E2", + "0x6D59": "0xD5E3", + "0x73CD": "0xD5E4", + "0x659F": "0xD5E5", + "0x771F": "0xD5E6", + "0x7504": "0xD5E7", + "0x7827": "0xD5E8", + "0x81FB": "0xD5E9", + "0x8D1E": "0xD5EA", + "0x9488": "0xD5EB", + "0x4FA6": "0xD5EC", + "0x6795": "0xD5ED", + "0x75B9": "0xD5EE", + "0x8BCA": "0xD5EF", + "0x9707": "0xD5F0", + "0x632F": "0xD5F1", + "0x9547": "0xD5F2", + "0x9635": "0xD5F3", + "0x84B8": "0xD5F4", + "0x6323": "0xD5F5", + "0x7741": "0xD5F6", + "0x5F81": "0xD5F7", + "0x72F0": "0xD5F8", + "0x4E89": "0xD5F9", + "0x6014": "0xD5FA", + "0x6574": "0xD5FB", + "0x62EF": "0xD5FC", + "0x6B63": "0xD5FD", + "0x653F": "0xD5FE", + "0x5E27": "0xD6A1", + "0x75C7": "0xD6A2", + "0x90D1": "0xD6A3", + "0x8BC1": "0xD6A4", + "0x829D": "0xD6A5", + "0x679D": "0xD6A6", + "0x652F": "0xD6A7", + "0x5431": "0xD6A8", + "0x8718": "0xD6A9", + "0x77E5": "0xD6AA", + "0x80A2": "0xD6AB", + "0x8102": "0xD6AC", + "0x6C41": "0xD6AD", + "0x4E4B": "0xD6AE", + "0x7EC7": "0xD6AF", + "0x804C": "0xD6B0", + "0x76F4": "0xD6B1", + "0x690D": "0xD6B2", + "0x6B96": "0xD6B3", + "0x6267": "0xD6B4", + "0x503C": "0xD6B5", + "0x4F84": "0xD6B6", + "0x5740": "0xD6B7", + "0x6307": "0xD6B8", + "0x6B62": "0xD6B9", + "0x8DBE": "0xD6BA", + "0x53EA": "0xD6BB", + "0x65E8": "0xD6BC", + "0x7EB8": "0xD6BD", + "0x5FD7": "0xD6BE", + "0x631A": "0xD6BF", + "0x63B7": "0xD6C0", + "0x81F3": "0xD6C1", + "0x81F4": "0xD6C2", + "0x7F6E": "0xD6C3", + "0x5E1C": "0xD6C4", + "0x5CD9": "0xD6C5", + "0x5236": "0xD6C6", + "0x667A": "0xD6C7", + "0x79E9": "0xD6C8", + "0x7A1A": "0xD6C9", + "0x8D28": "0xD6CA", + "0x7099": "0xD6CB", + "0x75D4": "0xD6CC", + "0x6EDE": "0xD6CD", + "0x6CBB": "0xD6CE", + "0x7A92": "0xD6CF", + "0x4E2D": "0xD6D0", + "0x76C5": "0xD6D1", + "0x5FE0": "0xD6D2", + "0x949F": "0xD6D3", + "0x8877": "0xD6D4", + "0x7EC8": "0xD6D5", + "0x79CD": "0xD6D6", + "0x80BF": "0xD6D7", + "0x91CD": "0xD6D8", + "0x4EF2": "0xD6D9", + "0x4F17": "0xD6DA", + "0x821F": "0xD6DB", + "0x5468": "0xD6DC", + "0x5DDE": "0xD6DD", + "0x6D32": "0xD6DE", + "0x8BCC": "0xD6DF", + "0x7CA5": "0xD6E0", + "0x8F74": "0xD6E1", + "0x8098": "0xD6E2", + "0x5E1A": "0xD6E3", + "0x5492": "0xD6E4", + "0x76B1": "0xD6E5", + "0x5B99": "0xD6E6", + "0x663C": "0xD6E7", + "0x9AA4": "0xD6E8", + "0x73E0": "0xD6E9", + "0x682A": "0xD6EA", + "0x86DB": "0xD6EB", + "0x6731": "0xD6EC", + "0x732A": "0xD6ED", + "0x8BF8": "0xD6EE", + "0x8BDB": "0xD6EF", + "0x9010": "0xD6F0", + "0x7AF9": "0xD6F1", + "0x70DB": "0xD6F2", + "0x716E": "0xD6F3", + "0x62C4": "0xD6F4", + "0x77A9": "0xD6F5", + "0x5631": "0xD6F6", + "0x4E3B": "0xD6F7", + "0x8457": "0xD6F8", + "0x67F1": "0xD6F9", + "0x52A9": "0xD6FA", + "0x86C0": "0xD6FB", + "0x8D2E": "0xD6FC", + "0x94F8": "0xD6FD", + "0x7B51": "0xD6FE", + "0x4F4F": "0xD7A1", + "0x6CE8": "0xD7A2", + "0x795D": "0xD7A3", + "0x9A7B": "0xD7A4", + "0x6293": "0xD7A5", + "0x722A": "0xD7A6", + "0x62FD": "0xD7A7", + "0x4E13": "0xD7A8", + "0x7816": "0xD7A9", + "0x8F6C": "0xD7AA", + "0x64B0": "0xD7AB", + "0x8D5A": "0xD7AC", + "0x7BC6": "0xD7AD", + "0x6869": "0xD7AE", + "0x5E84": "0xD7AF", + "0x88C5": "0xD7B0", + "0x5986": "0xD7B1", + "0x649E": "0xD7B2", + "0x58EE": "0xD7B3", + "0x72B6": "0xD7B4", + "0x690E": "0xD7B5", + "0x9525": "0xD7B6", + "0x8FFD": "0xD7B7", + "0x8D58": "0xD7B8", + "0x5760": "0xD7B9", + "0x7F00": "0xD7BA", + "0x8C06": "0xD7BB", + "0x51C6": "0xD7BC", + "0x6349": "0xD7BD", + "0x62D9": "0xD7BE", + "0x5353": "0xD7BF", + "0x684C": "0xD7C0", + "0x7422": "0xD7C1", + "0x8301": "0xD7C2", + "0x914C": "0xD7C3", + "0x5544": "0xD7C4", + "0x7740": "0xD7C5", + "0x707C": "0xD7C6", + "0x6D4A": "0xD7C7", + "0x5179": "0xD7C8", + "0x54A8": "0xD7C9", + "0x8D44": "0xD7CA", + "0x59FF": "0xD7CB", + "0x6ECB": "0xD7CC", + "0x6DC4": "0xD7CD", + "0x5B5C": "0xD7CE", + "0x7D2B": "0xD7CF", + "0x4ED4": "0xD7D0", + "0x7C7D": "0xD7D1", + "0x6ED3": "0xD7D2", + "0x5B50": "0xD7D3", + "0x81EA": "0xD7D4", + "0x6E0D": "0xD7D5", + "0x5B57": "0xD7D6", + "0x9B03": "0xD7D7", + "0x68D5": "0xD7D8", + "0x8E2A": "0xD7D9", + "0x5B97": "0xD7DA", + "0x7EFC": "0xD7DB", + "0x603B": "0xD7DC", + "0x7EB5": "0xD7DD", + "0x90B9": "0xD7DE", + "0x8D70": "0xD7DF", + "0x594F": "0xD7E0", + "0x63CD": "0xD7E1", + "0x79DF": "0xD7E2", + "0x8DB3": "0xD7E3", + "0x5352": "0xD7E4", + "0x65CF": "0xD7E5", + "0x7956": "0xD7E6", + "0x8BC5": "0xD7E7", + "0x963B": "0xD7E8", + "0x7EC4": "0xD7E9", + "0x94BB": "0xD7EA", + "0x7E82": "0xD7EB", + "0x5634": "0xD7EC", + "0x9189": "0xD7ED", + "0x6700": "0xD7EE", + "0x7F6A": "0xD7EF", + "0x5C0A": "0xD7F0", + "0x9075": "0xD7F1", + "0x6628": "0xD7F2", + "0x5DE6": "0xD7F3", + "0x4F50": "0xD7F4", + "0x67DE": "0xD7F5", + "0x505A": "0xD7F6", + "0x4F5C": "0xD7F7", + "0x5750": "0xD7F8", + "0x5EA7": "0xD7F9", + "0xE810": "0xD7FA", + "0xE811": "0xD7FB", + "0xE812": "0xD7FC", + "0xE813": "0xD7FD", + "0xE814": "0xD7FE", + "0x4E8D": "0xD8A1", + "0x4E0C": "0xD8A2", + "0x5140": "0xD8A3", + "0x4E10": "0xD8A4", + "0x5EFF": "0xD8A5", + "0x5345": "0xD8A6", + "0x4E15": "0xD8A7", + "0x4E98": "0xD8A8", + "0x4E1E": "0xD8A9", + "0x9B32": "0xD8AA", + "0x5B6C": "0xD8AB", + "0x5669": "0xD8AC", + "0x4E28": "0xD8AD", + "0x79BA": "0xD8AE", + "0x4E3F": "0xD8AF", + "0x5315": "0xD8B0", + "0x4E47": "0xD8B1", + "0x592D": "0xD8B2", + "0x723B": "0xD8B3", + "0x536E": "0xD8B4", + "0x6C10": "0xD8B5", + "0x56DF": "0xD8B6", + "0x80E4": "0xD8B7", + "0x9997": "0xD8B8", + "0x6BD3": "0xD8B9", + "0x777E": "0xD8BA", + "0x9F17": "0xD8BB", + "0x4E36": "0xD8BC", + "0x4E9F": "0xD8BD", + "0x9F10": "0xD8BE", + "0x4E5C": "0xD8BF", + "0x4E69": "0xD8C0", + "0x4E93": "0xD8C1", + "0x8288": "0xD8C2", + "0x5B5B": "0xD8C3", + "0x556C": "0xD8C4", + "0x560F": "0xD8C5", + "0x4EC4": "0xD8C6", + "0x538D": "0xD8C7", + "0x539D": "0xD8C8", + "0x53A3": "0xD8C9", + "0x53A5": "0xD8CA", + "0x53AE": "0xD8CB", + "0x9765": "0xD8CC", + "0x8D5D": "0xD8CD", + "0x531A": "0xD8CE", + "0x53F5": "0xD8CF", + "0x5326": "0xD8D0", + "0x532E": "0xD8D1", + "0x533E": "0xD8D2", + "0x8D5C": "0xD8D3", + "0x5366": "0xD8D4", + "0x5363": "0xD8D5", + "0x5202": "0xD8D6", + "0x5208": "0xD8D7", + "0x520E": "0xD8D8", + "0x522D": "0xD8D9", + "0x5233": "0xD8DA", + "0x523F": "0xD8DB", + "0x5240": "0xD8DC", + "0x524C": "0xD8DD", + "0x525E": "0xD8DE", + "0x5261": "0xD8DF", + "0x525C": "0xD8E0", + "0x84AF": "0xD8E1", + "0x527D": "0xD8E2", + "0x5282": "0xD8E3", + "0x5281": "0xD8E4", + "0x5290": "0xD8E5", + "0x5293": "0xD8E6", + "0x5182": "0xD8E7", + "0x7F54": "0xD8E8", + "0x4EBB": "0xD8E9", + "0x4EC3": "0xD8EA", + "0x4EC9": "0xD8EB", + "0x4EC2": "0xD8EC", + "0x4EE8": "0xD8ED", + "0x4EE1": "0xD8EE", + "0x4EEB": "0xD8EF", + "0x4EDE": "0xD8F0", + "0x4F1B": "0xD8F1", + "0x4EF3": "0xD8F2", + "0x4F22": "0xD8F3", + "0x4F64": "0xD8F4", + "0x4EF5": "0xD8F5", + "0x4F25": "0xD8F6", + "0x4F27": "0xD8F7", + "0x4F09": "0xD8F8", + "0x4F2B": "0xD8F9", + "0x4F5E": "0xD8FA", + "0x4F67": "0xD8FB", + "0x6538": "0xD8FC", + "0x4F5A": "0xD8FD", + "0x4F5D": "0xD8FE", + "0x4F5F": "0xD9A1", + "0x4F57": "0xD9A2", + "0x4F32": "0xD9A3", + "0x4F3D": "0xD9A4", + "0x4F76": "0xD9A5", + "0x4F74": "0xD9A6", + "0x4F91": "0xD9A7", + "0x4F89": "0xD9A8", + "0x4F83": "0xD9A9", + "0x4F8F": "0xD9AA", + "0x4F7E": "0xD9AB", + "0x4F7B": "0xD9AC", + "0x4FAA": "0xD9AD", + "0x4F7C": "0xD9AE", + "0x4FAC": "0xD9AF", + "0x4F94": "0xD9B0", + "0x4FE6": "0xD9B1", + "0x4FE8": "0xD9B2", + "0x4FEA": "0xD9B3", + "0x4FC5": "0xD9B4", + "0x4FDA": "0xD9B5", + "0x4FE3": "0xD9B6", + "0x4FDC": "0xD9B7", + "0x4FD1": "0xD9B8", + "0x4FDF": "0xD9B9", + "0x4FF8": "0xD9BA", + "0x5029": "0xD9BB", + "0x504C": "0xD9BC", + "0x4FF3": "0xD9BD", + "0x502C": "0xD9BE", + "0x500F": "0xD9BF", + "0x502E": "0xD9C0", + "0x502D": "0xD9C1", + "0x4FFE": "0xD9C2", + "0x501C": "0xD9C3", + "0x500C": "0xD9C4", + "0x5025": "0xD9C5", + "0x5028": "0xD9C6", + "0x507E": "0xD9C7", + "0x5043": "0xD9C8", + "0x5055": "0xD9C9", + "0x5048": "0xD9CA", + "0x504E": "0xD9CB", + "0x506C": "0xD9CC", + "0x507B": "0xD9CD", + "0x50A5": "0xD9CE", + "0x50A7": "0xD9CF", + "0x50A9": "0xD9D0", + "0x50BA": "0xD9D1", + "0x50D6": "0xD9D2", + "0x5106": "0xD9D3", + "0x50ED": "0xD9D4", + "0x50EC": "0xD9D5", + "0x50E6": "0xD9D6", + "0x50EE": "0xD9D7", + "0x5107": "0xD9D8", + "0x510B": "0xD9D9", + "0x4EDD": "0xD9DA", + "0x6C3D": "0xD9DB", + "0x4F58": "0xD9DC", + "0x4F65": "0xD9DD", + "0x4FCE": "0xD9DE", + "0x9FA0": "0xD9DF", + "0x6C46": "0xD9E0", + "0x7C74": "0xD9E1", + "0x516E": "0xD9E2", + "0x5DFD": "0xD9E3", + "0x9EC9": "0xD9E4", + "0x9998": "0xD9E5", + "0x5181": "0xD9E6", + "0x5914": "0xD9E7", + "0x52F9": "0xD9E8", + "0x530D": "0xD9E9", + "0x8A07": "0xD9EA", + "0x5310": "0xD9EB", + "0x51EB": "0xD9EC", + "0x5919": "0xD9ED", + "0x5155": "0xD9EE", + "0x4EA0": "0xD9EF", + "0x5156": "0xD9F0", + "0x4EB3": "0xD9F1", + "0x886E": "0xD9F2", + "0x88A4": "0xD9F3", + "0x4EB5": "0xD9F4", + "0x8114": "0xD9F5", + "0x88D2": "0xD9F6", + "0x7980": "0xD9F7", + "0x5B34": "0xD9F8", + "0x8803": "0xD9F9", + "0x7FB8": "0xD9FA", + "0x51AB": "0xD9FB", + "0x51B1": "0xD9FC", + "0x51BD": "0xD9FD", + "0x51BC": "0xD9FE", + "0x51C7": "0xDAA1", + "0x5196": "0xDAA2", + "0x51A2": "0xDAA3", + "0x51A5": "0xDAA4", + "0x8BA0": "0xDAA5", + "0x8BA6": "0xDAA6", + "0x8BA7": "0xDAA7", + "0x8BAA": "0xDAA8", + "0x8BB4": "0xDAA9", + "0x8BB5": "0xDAAA", + "0x8BB7": "0xDAAB", + "0x8BC2": "0xDAAC", + "0x8BC3": "0xDAAD", + "0x8BCB": "0xDAAE", + "0x8BCF": "0xDAAF", + "0x8BCE": "0xDAB0", + "0x8BD2": "0xDAB1", + "0x8BD3": "0xDAB2", + "0x8BD4": "0xDAB3", + "0x8BD6": "0xDAB4", + "0x8BD8": "0xDAB5", + "0x8BD9": "0xDAB6", + "0x8BDC": "0xDAB7", + "0x8BDF": "0xDAB8", + "0x8BE0": "0xDAB9", + "0x8BE4": "0xDABA", + "0x8BE8": "0xDABB", + "0x8BE9": "0xDABC", + "0x8BEE": "0xDABD", + "0x8BF0": "0xDABE", + "0x8BF3": "0xDABF", + "0x8BF6": "0xDAC0", + "0x8BF9": "0xDAC1", + "0x8BFC": "0xDAC2", + "0x8BFF": "0xDAC3", + "0x8C00": "0xDAC4", + "0x8C02": "0xDAC5", + "0x8C04": "0xDAC6", + "0x8C07": "0xDAC7", + "0x8C0C": "0xDAC8", + "0x8C0F": "0xDAC9", + "0x8C11": "0xDACA", + "0x8C12": "0xDACB", + "0x8C14": "0xDACC", + "0x8C15": "0xDACD", + "0x8C16": "0xDACE", + "0x8C19": "0xDACF", + "0x8C1B": "0xDAD0", + "0x8C18": "0xDAD1", + "0x8C1D": "0xDAD2", + "0x8C1F": "0xDAD3", + "0x8C20": "0xDAD4", + "0x8C21": "0xDAD5", + "0x8C25": "0xDAD6", + "0x8C27": "0xDAD7", + "0x8C2A": "0xDAD8", + "0x8C2B": "0xDAD9", + "0x8C2E": "0xDADA", + "0x8C2F": "0xDADB", + "0x8C32": "0xDADC", + "0x8C33": "0xDADD", + "0x8C35": "0xDADE", + "0x8C36": "0xDADF", + "0x5369": "0xDAE0", + "0x537A": "0xDAE1", + "0x961D": "0xDAE2", + "0x9622": "0xDAE3", + "0x9621": "0xDAE4", + "0x9631": "0xDAE5", + "0x962A": "0xDAE6", + "0x963D": "0xDAE7", + "0x963C": "0xDAE8", + "0x9642": "0xDAE9", + "0x9649": "0xDAEA", + "0x9654": "0xDAEB", + "0x965F": "0xDAEC", + "0x9667": "0xDAED", + "0x966C": "0xDAEE", + "0x9672": "0xDAEF", + "0x9674": "0xDAF0", + "0x9688": "0xDAF1", + "0x968D": "0xDAF2", + "0x9697": "0xDAF3", + "0x96B0": "0xDAF4", + "0x9097": "0xDAF5", + "0x909B": "0xDAF6", + "0x909D": "0xDAF7", + "0x9099": "0xDAF8", + "0x90AC": "0xDAF9", + "0x90A1": "0xDAFA", + "0x90B4": "0xDAFB", + "0x90B3": "0xDAFC", + "0x90B6": "0xDAFD", + "0x90BA": "0xDAFE", + "0x90B8": "0xDBA1", + "0x90B0": "0xDBA2", + "0x90CF": "0xDBA3", + "0x90C5": "0xDBA4", + "0x90BE": "0xDBA5", + "0x90D0": "0xDBA6", + "0x90C4": "0xDBA7", + "0x90C7": "0xDBA8", + "0x90D3": "0xDBA9", + "0x90E6": "0xDBAA", + "0x90E2": "0xDBAB", + "0x90DC": "0xDBAC", + "0x90D7": "0xDBAD", + "0x90DB": "0xDBAE", + "0x90EB": "0xDBAF", + "0x90EF": "0xDBB0", + "0x90FE": "0xDBB1", + "0x9104": "0xDBB2", + "0x9122": "0xDBB3", + "0x911E": "0xDBB4", + "0x9123": "0xDBB5", + "0x9131": "0xDBB6", + "0x912F": "0xDBB7", + "0x9139": "0xDBB8", + "0x9143": "0xDBB9", + "0x9146": "0xDBBA", + "0x520D": "0xDBBB", + "0x5942": "0xDBBC", + "0x52A2": "0xDBBD", + "0x52AC": "0xDBBE", + "0x52AD": "0xDBBF", + "0x52BE": "0xDBC0", + "0x54FF": "0xDBC1", + "0x52D0": "0xDBC2", + "0x52D6": "0xDBC3", + "0x52F0": "0xDBC4", + "0x53DF": "0xDBC5", + "0x71EE": "0xDBC6", + "0x77CD": "0xDBC7", + "0x5EF4": "0xDBC8", + "0x51F5": "0xDBC9", + "0x51FC": "0xDBCA", + "0x9B2F": "0xDBCB", + "0x53B6": "0xDBCC", + "0x5F01": "0xDBCD", + "0x755A": "0xDBCE", + "0x5DEF": "0xDBCF", + "0x574C": "0xDBD0", + "0x57A9": "0xDBD1", + "0x57A1": "0xDBD2", + "0x587E": "0xDBD3", + "0x58BC": "0xDBD4", + "0x58C5": "0xDBD5", + "0x58D1": "0xDBD6", + "0x5729": "0xDBD7", + "0x572C": "0xDBD8", + "0x572A": "0xDBD9", + "0x5733": "0xDBDA", + "0x5739": "0xDBDB", + "0x572E": "0xDBDC", + "0x572F": "0xDBDD", + "0x575C": "0xDBDE", + "0x573B": "0xDBDF", + "0x5742": "0xDBE0", + "0x5769": "0xDBE1", + "0x5785": "0xDBE2", + "0x576B": "0xDBE3", + "0x5786": "0xDBE4", + "0x577C": "0xDBE5", + "0x577B": "0xDBE6", + "0x5768": "0xDBE7", + "0x576D": "0xDBE8", + "0x5776": "0xDBE9", + "0x5773": "0xDBEA", + "0x57AD": "0xDBEB", + "0x57A4": "0xDBEC", + "0x578C": "0xDBED", + "0x57B2": "0xDBEE", + "0x57CF": "0xDBEF", + "0x57A7": "0xDBF0", + "0x57B4": "0xDBF1", + "0x5793": "0xDBF2", + "0x57A0": "0xDBF3", + "0x57D5": "0xDBF4", + "0x57D8": "0xDBF5", + "0x57DA": "0xDBF6", + "0x57D9": "0xDBF7", + "0x57D2": "0xDBF8", + "0x57B8": "0xDBF9", + "0x57F4": "0xDBFA", + "0x57EF": "0xDBFB", + "0x57F8": "0xDBFC", + "0x57E4": "0xDBFD", + "0x57DD": "0xDBFE", + "0x580B": "0xDCA1", + "0x580D": "0xDCA2", + "0x57FD": "0xDCA3", + "0x57ED": "0xDCA4", + "0x5800": "0xDCA5", + "0x581E": "0xDCA6", + "0x5819": "0xDCA7", + "0x5844": "0xDCA8", + "0x5820": "0xDCA9", + "0x5865": "0xDCAA", + "0x586C": "0xDCAB", + "0x5881": "0xDCAC", + "0x5889": "0xDCAD", + "0x589A": "0xDCAE", + "0x5880": "0xDCAF", + "0x99A8": "0xDCB0", + "0x9F19": "0xDCB1", + "0x61FF": "0xDCB2", + "0x8279": "0xDCB3", + "0x827D": "0xDCB4", + "0x827F": "0xDCB5", + "0x828F": "0xDCB6", + "0x828A": "0xDCB7", + "0x82A8": "0xDCB8", + "0x8284": "0xDCB9", + "0x828E": "0xDCBA", + "0x8291": "0xDCBB", + "0x8297": "0xDCBC", + "0x8299": "0xDCBD", + "0x82AB": "0xDCBE", + "0x82B8": "0xDCBF", + "0x82BE": "0xDCC0", + "0x82B0": "0xDCC1", + "0x82C8": "0xDCC2", + "0x82CA": "0xDCC3", + "0x82E3": "0xDCC4", + "0x8298": "0xDCC5", + "0x82B7": "0xDCC6", + "0x82AE": "0xDCC7", + "0x82CB": "0xDCC8", + "0x82CC": "0xDCC9", + "0x82C1": "0xDCCA", + "0x82A9": "0xDCCB", + "0x82B4": "0xDCCC", + "0x82A1": "0xDCCD", + "0x82AA": "0xDCCE", + "0x829F": "0xDCCF", + "0x82C4": "0xDCD0", + "0x82CE": "0xDCD1", + "0x82A4": "0xDCD2", + "0x82E1": "0xDCD3", + "0x8309": "0xDCD4", + "0x82F7": "0xDCD5", + "0x82E4": "0xDCD6", + "0x830F": "0xDCD7", + "0x8307": "0xDCD8", + "0x82DC": "0xDCD9", + "0x82F4": "0xDCDA", + "0x82D2": "0xDCDB", + "0x82D8": "0xDCDC", + "0x830C": "0xDCDD", + "0x82FB": "0xDCDE", + "0x82D3": "0xDCDF", + "0x8311": "0xDCE0", + "0x831A": "0xDCE1", + "0x8306": "0xDCE2", + "0x8314": "0xDCE3", + "0x8315": "0xDCE4", + "0x82E0": "0xDCE5", + "0x82D5": "0xDCE6", + "0x831C": "0xDCE7", + "0x8351": "0xDCE8", + "0x835B": "0xDCE9", + "0x835C": "0xDCEA", + "0x8308": "0xDCEB", + "0x8392": "0xDCEC", + "0x833C": "0xDCED", + "0x8334": "0xDCEE", + "0x8331": "0xDCEF", + "0x839B": "0xDCF0", + "0x835E": "0xDCF1", + "0x832F": "0xDCF2", + "0x834F": "0xDCF3", + "0x8347": "0xDCF4", + "0x8343": "0xDCF5", + "0x835F": "0xDCF6", + "0x8340": "0xDCF7", + "0x8317": "0xDCF8", + "0x8360": "0xDCF9", + "0x832D": "0xDCFA", + "0x833A": "0xDCFB", + "0x8333": "0xDCFC", + "0x8366": "0xDCFD", + "0x8365": "0xDCFE", + "0x8368": "0xDDA1", + "0x831B": "0xDDA2", + "0x8369": "0xDDA3", + "0x836C": "0xDDA4", + "0x836A": "0xDDA5", + "0x836D": "0xDDA6", + "0x836E": "0xDDA7", + "0x83B0": "0xDDA8", + "0x8378": "0xDDA9", + "0x83B3": "0xDDAA", + "0x83B4": "0xDDAB", + "0x83A0": "0xDDAC", + "0x83AA": "0xDDAD", + "0x8393": "0xDDAE", + "0x839C": "0xDDAF", + "0x8385": "0xDDB0", + "0x837C": "0xDDB1", + "0x83B6": "0xDDB2", + "0x83A9": "0xDDB3", + "0x837D": "0xDDB4", + "0x83B8": "0xDDB5", + "0x837B": "0xDDB6", + "0x8398": "0xDDB7", + "0x839E": "0xDDB8", + "0x83A8": "0xDDB9", + "0x83BA": "0xDDBA", + "0x83BC": "0xDDBB", + "0x83C1": "0xDDBC", + "0x8401": "0xDDBD", + "0x83E5": "0xDDBE", + "0x83D8": "0xDDBF", + "0x5807": "0xDDC0", + "0x8418": "0xDDC1", + "0x840B": "0xDDC2", + "0x83DD": "0xDDC3", + "0x83FD": "0xDDC4", + "0x83D6": "0xDDC5", + "0x841C": "0xDDC6", + "0x8438": "0xDDC7", + "0x8411": "0xDDC8", + "0x8406": "0xDDC9", + "0x83D4": "0xDDCA", + "0x83DF": "0xDDCB", + "0x840F": "0xDDCC", + "0x8403": "0xDDCD", + "0x83F8": "0xDDCE", + "0x83F9": "0xDDCF", + "0x83EA": "0xDDD0", + "0x83C5": "0xDDD1", + "0x83C0": "0xDDD2", + "0x8426": "0xDDD3", + "0x83F0": "0xDDD4", + "0x83E1": "0xDDD5", + "0x845C": "0xDDD6", + "0x8451": "0xDDD7", + "0x845A": "0xDDD8", + "0x8459": "0xDDD9", + "0x8473": "0xDDDA", + "0x8487": "0xDDDB", + "0x8488": "0xDDDC", + "0x847A": "0xDDDD", + "0x8489": "0xDDDE", + "0x8478": "0xDDDF", + "0x843C": "0xDDE0", + "0x8446": "0xDDE1", + "0x8469": "0xDDE2", + "0x8476": "0xDDE3", + "0x848C": "0xDDE4", + "0x848E": "0xDDE5", + "0x8431": "0xDDE6", + "0x846D": "0xDDE7", + "0x84C1": "0xDDE8", + "0x84CD": "0xDDE9", + "0x84D0": "0xDDEA", + "0x84E6": "0xDDEB", + "0x84BD": "0xDDEC", + "0x84D3": "0xDDED", + "0x84CA": "0xDDEE", + "0x84BF": "0xDDEF", + "0x84BA": "0xDDF0", + "0x84E0": "0xDDF1", + "0x84A1": "0xDDF2", + "0x84B9": "0xDDF3", + "0x84B4": "0xDDF4", + "0x8497": "0xDDF5", + "0x84E5": "0xDDF6", + "0x84E3": "0xDDF7", + "0x850C": "0xDDF8", + "0x750D": "0xDDF9", + "0x8538": "0xDDFA", + "0x84F0": "0xDDFB", + "0x8539": "0xDDFC", + "0x851F": "0xDDFD", + "0x853A": "0xDDFE", + "0x8556": "0xDEA1", + "0x853B": "0xDEA2", + "0x84FF": "0xDEA3", + "0x84FC": "0xDEA4", + "0x8559": "0xDEA5", + "0x8548": "0xDEA6", + "0x8568": "0xDEA7", + "0x8564": "0xDEA8", + "0x855E": "0xDEA9", + "0x857A": "0xDEAA", + "0x77A2": "0xDEAB", + "0x8543": "0xDEAC", + "0x8572": "0xDEAD", + "0x857B": "0xDEAE", + "0x85A4": "0xDEAF", + "0x85A8": "0xDEB0", + "0x8587": "0xDEB1", + "0x858F": "0xDEB2", + "0x8579": "0xDEB3", + "0x85AE": "0xDEB4", + "0x859C": "0xDEB5", + "0x8585": "0xDEB6", + "0x85B9": "0xDEB7", + "0x85B7": "0xDEB8", + "0x85B0": "0xDEB9", + "0x85D3": "0xDEBA", + "0x85C1": "0xDEBB", + "0x85DC": "0xDEBC", + "0x85FF": "0xDEBD", + "0x8627": "0xDEBE", + "0x8605": "0xDEBF", + "0x8629": "0xDEC0", + "0x8616": "0xDEC1", + "0x863C": "0xDEC2", + "0x5EFE": "0xDEC3", + "0x5F08": "0xDEC4", + "0x593C": "0xDEC5", + "0x5941": "0xDEC6", + "0x8037": "0xDEC7", + "0x5955": "0xDEC8", + "0x595A": "0xDEC9", + "0x5958": "0xDECA", + "0x530F": "0xDECB", + "0x5C22": "0xDECC", + "0x5C25": "0xDECD", + "0x5C2C": "0xDECE", + "0x5C34": "0xDECF", + "0x624C": "0xDED0", + "0x626A": "0xDED1", + "0x629F": "0xDED2", + "0x62BB": "0xDED3", + "0x62CA": "0xDED4", + "0x62DA": "0xDED5", + "0x62D7": "0xDED6", + "0x62EE": "0xDED7", + "0x6322": "0xDED8", + "0x62F6": "0xDED9", + "0x6339": "0xDEDA", + "0x634B": "0xDEDB", + "0x6343": "0xDEDC", + "0x63AD": "0xDEDD", + "0x63F6": "0xDEDE", + "0x6371": "0xDEDF", + "0x637A": "0xDEE0", + "0x638E": "0xDEE1", + "0x63B4": "0xDEE2", + "0x636D": "0xDEE3", + "0x63AC": "0xDEE4", + "0x638A": "0xDEE5", + "0x6369": "0xDEE6", + "0x63AE": "0xDEE7", + "0x63BC": "0xDEE8", + "0x63F2": "0xDEE9", + "0x63F8": "0xDEEA", + "0x63E0": "0xDEEB", + "0x63FF": "0xDEEC", + "0x63C4": "0xDEED", + "0x63DE": "0xDEEE", + "0x63CE": "0xDEEF", + "0x6452": "0xDEF0", + "0x63C6": "0xDEF1", + "0x63BE": "0xDEF2", + "0x6445": "0xDEF3", + "0x6441": "0xDEF4", + "0x640B": "0xDEF5", + "0x641B": "0xDEF6", + "0x6420": "0xDEF7", + "0x640C": "0xDEF8", + "0x6426": "0xDEF9", + "0x6421": "0xDEFA", + "0x645E": "0xDEFB", + "0x6484": "0xDEFC", + "0x646D": "0xDEFD", + "0x6496": "0xDEFE", + "0x647A": "0xDFA1", + "0x64B7": "0xDFA2", + "0x64B8": "0xDFA3", + "0x6499": "0xDFA4", + "0x64BA": "0xDFA5", + "0x64C0": "0xDFA6", + "0x64D0": "0xDFA7", + "0x64D7": "0xDFA8", + "0x64E4": "0xDFA9", + "0x64E2": "0xDFAA", + "0x6509": "0xDFAB", + "0x6525": "0xDFAC", + "0x652E": "0xDFAD", + "0x5F0B": "0xDFAE", + "0x5FD2": "0xDFAF", + "0x7519": "0xDFB0", + "0x5F11": "0xDFB1", + "0x535F": "0xDFB2", + "0x53F1": "0xDFB3", + "0x53FD": "0xDFB4", + "0x53E9": "0xDFB5", + "0x53E8": "0xDFB6", + "0x53FB": "0xDFB7", + "0x5412": "0xDFB8", + "0x5416": "0xDFB9", + "0x5406": "0xDFBA", + "0x544B": "0xDFBB", + "0x5452": "0xDFBC", + "0x5453": "0xDFBD", + "0x5454": "0xDFBE", + "0x5456": "0xDFBF", + "0x5443": "0xDFC0", + "0x5421": "0xDFC1", + "0x5457": "0xDFC2", + "0x5459": "0xDFC3", + "0x5423": "0xDFC4", + "0x5432": "0xDFC5", + "0x5482": "0xDFC6", + "0x5494": "0xDFC7", + "0x5477": "0xDFC8", + "0x5471": "0xDFC9", + "0x5464": "0xDFCA", + "0x549A": "0xDFCB", + "0x549B": "0xDFCC", + "0x5484": "0xDFCD", + "0x5476": "0xDFCE", + "0x5466": "0xDFCF", + "0x549D": "0xDFD0", + "0x54D0": "0xDFD1", + "0x54AD": "0xDFD2", + "0x54C2": "0xDFD3", + "0x54B4": "0xDFD4", + "0x54D2": "0xDFD5", + "0x54A7": "0xDFD6", + "0x54A6": "0xDFD7", + "0x54D3": "0xDFD8", + "0x54D4": "0xDFD9", + "0x5472": "0xDFDA", + "0x54A3": "0xDFDB", + "0x54D5": "0xDFDC", + "0x54BB": "0xDFDD", + "0x54BF": "0xDFDE", + "0x54CC": "0xDFDF", + "0x54D9": "0xDFE0", + "0x54DA": "0xDFE1", + "0x54DC": "0xDFE2", + "0x54A9": "0xDFE3", + "0x54AA": "0xDFE4", + "0x54A4": "0xDFE5", + "0x54DD": "0xDFE6", + "0x54CF": "0xDFE7", + "0x54DE": "0xDFE8", + "0x551B": "0xDFE9", + "0x54E7": "0xDFEA", + "0x5520": "0xDFEB", + "0x54FD": "0xDFEC", + "0x5514": "0xDFED", + "0x54F3": "0xDFEE", + "0x5522": "0xDFEF", + "0x5523": "0xDFF0", + "0x550F": "0xDFF1", + "0x5511": "0xDFF2", + "0x5527": "0xDFF3", + "0x552A": "0xDFF4", + "0x5567": "0xDFF5", + "0x558F": "0xDFF6", + "0x55B5": "0xDFF7", + "0x5549": "0xDFF8", + "0x556D": "0xDFF9", + "0x5541": "0xDFFA", + "0x5555": "0xDFFB", + "0x553F": "0xDFFC", + "0x5550": "0xDFFD", + "0x553C": "0xDFFE", + "0x5537": "0xE0A1", + "0x5556": "0xE0A2", + "0x5575": "0xE0A3", + "0x5576": "0xE0A4", + "0x5577": "0xE0A5", + "0x5533": "0xE0A6", + "0x5530": "0xE0A7", + "0x555C": "0xE0A8", + "0x558B": "0xE0A9", + "0x55D2": "0xE0AA", + "0x5583": "0xE0AB", + "0x55B1": "0xE0AC", + "0x55B9": "0xE0AD", + "0x5588": "0xE0AE", + "0x5581": "0xE0AF", + "0x559F": "0xE0B0", + "0x557E": "0xE0B1", + "0x55D6": "0xE0B2", + "0x5591": "0xE0B3", + "0x557B": "0xE0B4", + "0x55DF": "0xE0B5", + "0x55BD": "0xE0B6", + "0x55BE": "0xE0B7", + "0x5594": "0xE0B8", + "0x5599": "0xE0B9", + "0x55EA": "0xE0BA", + "0x55F7": "0xE0BB", + "0x55C9": "0xE0BC", + "0x561F": "0xE0BD", + "0x55D1": "0xE0BE", + "0x55EB": "0xE0BF", + "0x55EC": "0xE0C0", + "0x55D4": "0xE0C1", + "0x55E6": "0xE0C2", + "0x55DD": "0xE0C3", + "0x55C4": "0xE0C4", + "0x55EF": "0xE0C5", + "0x55E5": "0xE0C6", + "0x55F2": "0xE0C7", + "0x55F3": "0xE0C8", + "0x55CC": "0xE0C9", + "0x55CD": "0xE0CA", + "0x55E8": "0xE0CB", + "0x55F5": "0xE0CC", + "0x55E4": "0xE0CD", + "0x8F94": "0xE0CE", + "0x561E": "0xE0CF", + "0x5608": "0xE0D0", + "0x560C": "0xE0D1", + "0x5601": "0xE0D2", + "0x5624": "0xE0D3", + "0x5623": "0xE0D4", + "0x55FE": "0xE0D5", + "0x5600": "0xE0D6", + "0x5627": "0xE0D7", + "0x562D": "0xE0D8", + "0x5658": "0xE0D9", + "0x5639": "0xE0DA", + "0x5657": "0xE0DB", + "0x562C": "0xE0DC", + "0x564D": "0xE0DD", + "0x5662": "0xE0DE", + "0x5659": "0xE0DF", + "0x565C": "0xE0E0", + "0x564C": "0xE0E1", + "0x5654": "0xE0E2", + "0x5686": "0xE0E3", + "0x5664": "0xE0E4", + "0x5671": "0xE0E5", + "0x566B": "0xE0E6", + "0x567B": "0xE0E7", + "0x567C": "0xE0E8", + "0x5685": "0xE0E9", + "0x5693": "0xE0EA", + "0x56AF": "0xE0EB", + "0x56D4": "0xE0EC", + "0x56D7": "0xE0ED", + "0x56DD": "0xE0EE", + "0x56E1": "0xE0EF", + "0x56F5": "0xE0F0", + "0x56EB": "0xE0F1", + "0x56F9": "0xE0F2", + "0x56FF": "0xE0F3", + "0x5704": "0xE0F4", + "0x570A": "0xE0F5", + "0x5709": "0xE0F6", + "0x571C": "0xE0F7", + "0x5E0F": "0xE0F8", + "0x5E19": "0xE0F9", + "0x5E14": "0xE0FA", + "0x5E11": "0xE0FB", + "0x5E31": "0xE0FC", + "0x5E3B": "0xE0FD", + "0x5E3C": "0xE0FE", + "0x5E37": "0xE1A1", + "0x5E44": "0xE1A2", + "0x5E54": "0xE1A3", + "0x5E5B": "0xE1A4", + "0x5E5E": "0xE1A5", + "0x5E61": "0xE1A6", + "0x5C8C": "0xE1A7", + "0x5C7A": "0xE1A8", + "0x5C8D": "0xE1A9", + "0x5C90": "0xE1AA", + "0x5C96": "0xE1AB", + "0x5C88": "0xE1AC", + "0x5C98": "0xE1AD", + "0x5C99": "0xE1AE", + "0x5C91": "0xE1AF", + "0x5C9A": "0xE1B0", + "0x5C9C": "0xE1B1", + "0x5CB5": "0xE1B2", + "0x5CA2": "0xE1B3", + "0x5CBD": "0xE1B4", + "0x5CAC": "0xE1B5", + "0x5CAB": "0xE1B6", + "0x5CB1": "0xE1B7", + "0x5CA3": "0xE1B8", + "0x5CC1": "0xE1B9", + "0x5CB7": "0xE1BA", + "0x5CC4": "0xE1BB", + "0x5CD2": "0xE1BC", + "0x5CE4": "0xE1BD", + "0x5CCB": "0xE1BE", + "0x5CE5": "0xE1BF", + "0x5D02": "0xE1C0", + "0x5D03": "0xE1C1", + "0x5D27": "0xE1C2", + "0x5D26": "0xE1C3", + "0x5D2E": "0xE1C4", + "0x5D24": "0xE1C5", + "0x5D1E": "0xE1C6", + "0x5D06": "0xE1C7", + "0x5D1B": "0xE1C8", + "0x5D58": "0xE1C9", + "0x5D3E": "0xE1CA", + "0x5D34": "0xE1CB", + "0x5D3D": "0xE1CC", + "0x5D6C": "0xE1CD", + "0x5D5B": "0xE1CE", + "0x5D6F": "0xE1CF", + "0x5D5D": "0xE1D0", + "0x5D6B": "0xE1D1", + "0x5D4B": "0xE1D2", + "0x5D4A": "0xE1D3", + "0x5D69": "0xE1D4", + "0x5D74": "0xE1D5", + "0x5D82": "0xE1D6", + "0x5D99": "0xE1D7", + "0x5D9D": "0xE1D8", + "0x8C73": "0xE1D9", + "0x5DB7": "0xE1DA", + "0x5DC5": "0xE1DB", + "0x5F73": "0xE1DC", + "0x5F77": "0xE1DD", + "0x5F82": "0xE1DE", + "0x5F87": "0xE1DF", + "0x5F89": "0xE1E0", + "0x5F8C": "0xE1E1", + "0x5F95": "0xE1E2", + "0x5F99": "0xE1E3", + "0x5F9C": "0xE1E4", + "0x5FA8": "0xE1E5", + "0x5FAD": "0xE1E6", + "0x5FB5": "0xE1E7", + "0x5FBC": "0xE1E8", + "0x8862": "0xE1E9", + "0x5F61": "0xE1EA", + "0x72AD": "0xE1EB", + "0x72B0": "0xE1EC", + "0x72B4": "0xE1ED", + "0x72B7": "0xE1EE", + "0x72B8": "0xE1EF", + "0x72C3": "0xE1F0", + "0x72C1": "0xE1F1", + "0x72CE": "0xE1F2", + "0x72CD": "0xE1F3", + "0x72D2": "0xE1F4", + "0x72E8": "0xE1F5", + "0x72EF": "0xE1F6", + "0x72E9": "0xE1F7", + "0x72F2": "0xE1F8", + "0x72F4": "0xE1F9", + "0x72F7": "0xE1FA", + "0x7301": "0xE1FB", + "0x72F3": "0xE1FC", + "0x7303": "0xE1FD", + "0x72FA": "0xE1FE", + "0x72FB": "0xE2A1", + "0x7317": "0xE2A2", + "0x7313": "0xE2A3", + "0x7321": "0xE2A4", + "0x730A": "0xE2A5", + "0x731E": "0xE2A6", + "0x731D": "0xE2A7", + "0x7315": "0xE2A8", + "0x7322": "0xE2A9", + "0x7339": "0xE2AA", + "0x7325": "0xE2AB", + "0x732C": "0xE2AC", + "0x7338": "0xE2AD", + "0x7331": "0xE2AE", + "0x7350": "0xE2AF", + "0x734D": "0xE2B0", + "0x7357": "0xE2B1", + "0x7360": "0xE2B2", + "0x736C": "0xE2B3", + "0x736F": "0xE2B4", + "0x737E": "0xE2B5", + "0x821B": "0xE2B6", + "0x5925": "0xE2B7", + "0x98E7": "0xE2B8", + "0x5924": "0xE2B9", + "0x5902": "0xE2BA", + "0x9963": "0xE2BB", + "0x9967": "0xE2BC", + "0x9968": "0xE2BD", + "0x9969": "0xE2BE", + "0x996A": "0xE2BF", + "0x996B": "0xE2C0", + "0x996C": "0xE2C1", + "0x9974": "0xE2C2", + "0x9977": "0xE2C3", + "0x997D": "0xE2C4", + "0x9980": "0xE2C5", + "0x9984": "0xE2C6", + "0x9987": "0xE2C7", + "0x998A": "0xE2C8", + "0x998D": "0xE2C9", + "0x9990": "0xE2CA", + "0x9991": "0xE2CB", + "0x9993": "0xE2CC", + "0x9994": "0xE2CD", + "0x9995": "0xE2CE", + "0x5E80": "0xE2CF", + "0x5E91": "0xE2D0", + "0x5E8B": "0xE2D1", + "0x5E96": "0xE2D2", + "0x5EA5": "0xE2D3", + "0x5EA0": "0xE2D4", + "0x5EB9": "0xE2D5", + "0x5EB5": "0xE2D6", + "0x5EBE": "0xE2D7", + "0x5EB3": "0xE2D8", + "0x8D53": "0xE2D9", + "0x5ED2": "0xE2DA", + "0x5ED1": "0xE2DB", + "0x5EDB": "0xE2DC", + "0x5EE8": "0xE2DD", + "0x5EEA": "0xE2DE", + "0x81BA": "0xE2DF", + "0x5FC4": "0xE2E0", + "0x5FC9": "0xE2E1", + "0x5FD6": "0xE2E2", + "0x5FCF": "0xE2E3", + "0x6003": "0xE2E4", + "0x5FEE": "0xE2E5", + "0x6004": "0xE2E6", + "0x5FE1": "0xE2E7", + "0x5FE4": "0xE2E8", + "0x5FFE": "0xE2E9", + "0x6005": "0xE2EA", + "0x6006": "0xE2EB", + "0x5FEA": "0xE2EC", + "0x5FED": "0xE2ED", + "0x5FF8": "0xE2EE", + "0x6019": "0xE2EF", + "0x6035": "0xE2F0", + "0x6026": "0xE2F1", + "0x601B": "0xE2F2", + "0x600F": "0xE2F3", + "0x600D": "0xE2F4", + "0x6029": "0xE2F5", + "0x602B": "0xE2F6", + "0x600A": "0xE2F7", + "0x603F": "0xE2F8", + "0x6021": "0xE2F9", + "0x6078": "0xE2FA", + "0x6079": "0xE2FB", + "0x607B": "0xE2FC", + "0x607A": "0xE2FD", + "0x6042": "0xE2FE", + "0x606A": "0xE3A1", + "0x607D": "0xE3A2", + "0x6096": "0xE3A3", + "0x609A": "0xE3A4", + "0x60AD": "0xE3A5", + "0x609D": "0xE3A6", + "0x6083": "0xE3A7", + "0x6092": "0xE3A8", + "0x608C": "0xE3A9", + "0x609B": "0xE3AA", + "0x60EC": "0xE3AB", + "0x60BB": "0xE3AC", + "0x60B1": "0xE3AD", + "0x60DD": "0xE3AE", + "0x60D8": "0xE3AF", + "0x60C6": "0xE3B0", + "0x60DA": "0xE3B1", + "0x60B4": "0xE3B2", + "0x6120": "0xE3B3", + "0x6126": "0xE3B4", + "0x6115": "0xE3B5", + "0x6123": "0xE3B6", + "0x60F4": "0xE3B7", + "0x6100": "0xE3B8", + "0x610E": "0xE3B9", + "0x612B": "0xE3BA", + "0x614A": "0xE3BB", + "0x6175": "0xE3BC", + "0x61AC": "0xE3BD", + "0x6194": "0xE3BE", + "0x61A7": "0xE3BF", + "0x61B7": "0xE3C0", + "0x61D4": "0xE3C1", + "0x61F5": "0xE3C2", + "0x5FDD": "0xE3C3", + "0x96B3": "0xE3C4", + "0x95E9": "0xE3C5", + "0x95EB": "0xE3C6", + "0x95F1": "0xE3C7", + "0x95F3": "0xE3C8", + "0x95F5": "0xE3C9", + "0x95F6": "0xE3CA", + "0x95FC": "0xE3CB", + "0x95FE": "0xE3CC", + "0x9603": "0xE3CD", + "0x9604": "0xE3CE", + "0x9606": "0xE3CF", + "0x9608": "0xE3D0", + "0x960A": "0xE3D1", + "0x960B": "0xE3D2", + "0x960C": "0xE3D3", + "0x960D": "0xE3D4", + "0x960F": "0xE3D5", + "0x9612": "0xE3D6", + "0x9615": "0xE3D7", + "0x9616": "0xE3D8", + "0x9617": "0xE3D9", + "0x9619": "0xE3DA", + "0x961A": "0xE3DB", + "0x4E2C": "0xE3DC", + "0x723F": "0xE3DD", + "0x6215": "0xE3DE", + "0x6C35": "0xE3DF", + "0x6C54": "0xE3E0", + "0x6C5C": "0xE3E1", + "0x6C4A": "0xE3E2", + "0x6CA3": "0xE3E3", + "0x6C85": "0xE3E4", + "0x6C90": "0xE3E5", + "0x6C94": "0xE3E6", + "0x6C8C": "0xE3E7", + "0x6C68": "0xE3E8", + "0x6C69": "0xE3E9", + "0x6C74": "0xE3EA", + "0x6C76": "0xE3EB", + "0x6C86": "0xE3EC", + "0x6CA9": "0xE3ED", + "0x6CD0": "0xE3EE", + "0x6CD4": "0xE3EF", + "0x6CAD": "0xE3F0", + "0x6CF7": "0xE3F1", + "0x6CF8": "0xE3F2", + "0x6CF1": "0xE3F3", + "0x6CD7": "0xE3F4", + "0x6CB2": "0xE3F5", + "0x6CE0": "0xE3F6", + "0x6CD6": "0xE3F7", + "0x6CFA": "0xE3F8", + "0x6CEB": "0xE3F9", + "0x6CEE": "0xE3FA", + "0x6CB1": "0xE3FB", + "0x6CD3": "0xE3FC", + "0x6CEF": "0xE3FD", + "0x6CFE": "0xE3FE", + "0x6D39": "0xE4A1", + "0x6D27": "0xE4A2", + "0x6D0C": "0xE4A3", + "0x6D43": "0xE4A4", + "0x6D48": "0xE4A5", + "0x6D07": "0xE4A6", + "0x6D04": "0xE4A7", + "0x6D19": "0xE4A8", + "0x6D0E": "0xE4A9", + "0x6D2B": "0xE4AA", + "0x6D4D": "0xE4AB", + "0x6D2E": "0xE4AC", + "0x6D35": "0xE4AD", + "0x6D1A": "0xE4AE", + "0x6D4F": "0xE4AF", + "0x6D52": "0xE4B0", + "0x6D54": "0xE4B1", + "0x6D33": "0xE4B2", + "0x6D91": "0xE4B3", + "0x6D6F": "0xE4B4", + "0x6D9E": "0xE4B5", + "0x6DA0": "0xE4B6", + "0x6D5E": "0xE4B7", + "0x6D93": "0xE4B8", + "0x6D94": "0xE4B9", + "0x6D5C": "0xE4BA", + "0x6D60": "0xE4BB", + "0x6D7C": "0xE4BC", + "0x6D63": "0xE4BD", + "0x6E1A": "0xE4BE", + "0x6DC7": "0xE4BF", + "0x6DC5": "0xE4C0", + "0x6DDE": "0xE4C1", + "0x6E0E": "0xE4C2", + "0x6DBF": "0xE4C3", + "0x6DE0": "0xE4C4", + "0x6E11": "0xE4C5", + "0x6DE6": "0xE4C6", + "0x6DDD": "0xE4C7", + "0x6DD9": "0xE4C8", + "0x6E16": "0xE4C9", + "0x6DAB": "0xE4CA", + "0x6E0C": "0xE4CB", + "0x6DAE": "0xE4CC", + "0x6E2B": "0xE4CD", + "0x6E6E": "0xE4CE", + "0x6E4E": "0xE4CF", + "0x6E6B": "0xE4D0", + "0x6EB2": "0xE4D1", + "0x6E5F": "0xE4D2", + "0x6E86": "0xE4D3", + "0x6E53": "0xE4D4", + "0x6E54": "0xE4D5", + "0x6E32": "0xE4D6", + "0x6E25": "0xE4D7", + "0x6E44": "0xE4D8", + "0x6EDF": "0xE4D9", + "0x6EB1": "0xE4DA", + "0x6E98": "0xE4DB", + "0x6EE0": "0xE4DC", + "0x6F2D": "0xE4DD", + "0x6EE2": "0xE4DE", + "0x6EA5": "0xE4DF", + "0x6EA7": "0xE4E0", + "0x6EBD": "0xE4E1", + "0x6EBB": "0xE4E2", + "0x6EB7": "0xE4E3", + "0x6ED7": "0xE4E4", + "0x6EB4": "0xE4E5", + "0x6ECF": "0xE4E6", + "0x6E8F": "0xE4E7", + "0x6EC2": "0xE4E8", + "0x6E9F": "0xE4E9", + "0x6F62": "0xE4EA", + "0x6F46": "0xE4EB", + "0x6F47": "0xE4EC", + "0x6F24": "0xE4ED", + "0x6F15": "0xE4EE", + "0x6EF9": "0xE4EF", + "0x6F2F": "0xE4F0", + "0x6F36": "0xE4F1", + "0x6F4B": "0xE4F2", + "0x6F74": "0xE4F3", + "0x6F2A": "0xE4F4", + "0x6F09": "0xE4F5", + "0x6F29": "0xE4F6", + "0x6F89": "0xE4F7", + "0x6F8D": "0xE4F8", + "0x6F8C": "0xE4F9", + "0x6F78": "0xE4FA", + "0x6F72": "0xE4FB", + "0x6F7C": "0xE4FC", + "0x6F7A": "0xE4FD", + "0x6FD1": "0xE4FE", + "0x6FC9": "0xE5A1", + "0x6FA7": "0xE5A2", + "0x6FB9": "0xE5A3", + "0x6FB6": "0xE5A4", + "0x6FC2": "0xE5A5", + "0x6FE1": "0xE5A6", + "0x6FEE": "0xE5A7", + "0x6FDE": "0xE5A8", + "0x6FE0": "0xE5A9", + "0x6FEF": "0xE5AA", + "0x701A": "0xE5AB", + "0x7023": "0xE5AC", + "0x701B": "0xE5AD", + "0x7039": "0xE5AE", + "0x7035": "0xE5AF", + "0x704F": "0xE5B0", + "0x705E": "0xE5B1", + "0x5B80": "0xE5B2", + "0x5B84": "0xE5B3", + "0x5B95": "0xE5B4", + "0x5B93": "0xE5B5", + "0x5BA5": "0xE5B6", + "0x5BB8": "0xE5B7", + "0x752F": "0xE5B8", + "0x9A9E": "0xE5B9", + "0x6434": "0xE5BA", + "0x5BE4": "0xE5BB", + "0x5BEE": "0xE5BC", + "0x8930": "0xE5BD", + "0x5BF0": "0xE5BE", + "0x8E47": "0xE5BF", + "0x8B07": "0xE5C0", + "0x8FB6": "0xE5C1", + "0x8FD3": "0xE5C2", + "0x8FD5": "0xE5C3", + "0x8FE5": "0xE5C4", + "0x8FEE": "0xE5C5", + "0x8FE4": "0xE5C6", + "0x8FE9": "0xE5C7", + "0x8FE6": "0xE5C8", + "0x8FF3": "0xE5C9", + "0x8FE8": "0xE5CA", + "0x9005": "0xE5CB", + "0x9004": "0xE5CC", + "0x900B": "0xE5CD", + "0x9026": "0xE5CE", + "0x9011": "0xE5CF", + "0x900D": "0xE5D0", + "0x9016": "0xE5D1", + "0x9021": "0xE5D2", + "0x9035": "0xE5D3", + "0x9036": "0xE5D4", + "0x902D": "0xE5D5", + "0x902F": "0xE5D6", + "0x9044": "0xE5D7", + "0x9051": "0xE5D8", + "0x9052": "0xE5D9", + "0x9050": "0xE5DA", + "0x9068": "0xE5DB", + "0x9058": "0xE5DC", + "0x9062": "0xE5DD", + "0x905B": "0xE5DE", + "0x66B9": "0xE5DF", + "0x9074": "0xE5E0", + "0x907D": "0xE5E1", + "0x9082": "0xE5E2", + "0x9088": "0xE5E3", + "0x9083": "0xE5E4", + "0x908B": "0xE5E5", + "0x5F50": "0xE5E6", + "0x5F57": "0xE5E7", + "0x5F56": "0xE5E8", + "0x5F58": "0xE5E9", + "0x5C3B": "0xE5EA", + "0x54AB": "0xE5EB", + "0x5C50": "0xE5EC", + "0x5C59": "0xE5ED", + "0x5B71": "0xE5EE", + "0x5C63": "0xE5EF", + "0x5C66": "0xE5F0", + "0x7FBC": "0xE5F1", + "0x5F2A": "0xE5F2", + "0x5F29": "0xE5F3", + "0x5F2D": "0xE5F4", + "0x8274": "0xE5F5", + "0x5F3C": "0xE5F6", + "0x9B3B": "0xE5F7", + "0x5C6E": "0xE5F8", + "0x5981": "0xE5F9", + "0x5983": "0xE5FA", + "0x598D": "0xE5FB", + "0x59A9": "0xE5FC", + "0x59AA": "0xE5FD", + "0x59A3": "0xE5FE", + "0x5997": "0xE6A1", + "0x59CA": "0xE6A2", + "0x59AB": "0xE6A3", + "0x599E": "0xE6A4", + "0x59A4": "0xE6A5", + "0x59D2": "0xE6A6", + "0x59B2": "0xE6A7", + "0x59AF": "0xE6A8", + "0x59D7": "0xE6A9", + "0x59BE": "0xE6AA", + "0x5A05": "0xE6AB", + "0x5A06": "0xE6AC", + "0x59DD": "0xE6AD", + "0x5A08": "0xE6AE", + "0x59E3": "0xE6AF", + "0x59D8": "0xE6B0", + "0x59F9": "0xE6B1", + "0x5A0C": "0xE6B2", + "0x5A09": "0xE6B3", + "0x5A32": "0xE6B4", + "0x5A34": "0xE6B5", + "0x5A11": "0xE6B6", + "0x5A23": "0xE6B7", + "0x5A13": "0xE6B8", + "0x5A40": "0xE6B9", + "0x5A67": "0xE6BA", + "0x5A4A": "0xE6BB", + "0x5A55": "0xE6BC", + "0x5A3C": "0xE6BD", + "0x5A62": "0xE6BE", + "0x5A75": "0xE6BF", + "0x80EC": "0xE6C0", + "0x5AAA": "0xE6C1", + "0x5A9B": "0xE6C2", + "0x5A77": "0xE6C3", + "0x5A7A": "0xE6C4", + "0x5ABE": "0xE6C5", + "0x5AEB": "0xE6C6", + "0x5AB2": "0xE6C7", + "0x5AD2": "0xE6C8", + "0x5AD4": "0xE6C9", + "0x5AB8": "0xE6CA", + "0x5AE0": "0xE6CB", + "0x5AE3": "0xE6CC", + "0x5AF1": "0xE6CD", + "0x5AD6": "0xE6CE", + "0x5AE6": "0xE6CF", + "0x5AD8": "0xE6D0", + "0x5ADC": "0xE6D1", + "0x5B09": "0xE6D2", + "0x5B17": "0xE6D3", + "0x5B16": "0xE6D4", + "0x5B32": "0xE6D5", + "0x5B37": "0xE6D6", + "0x5B40": "0xE6D7", + "0x5C15": "0xE6D8", + "0x5C1C": "0xE6D9", + "0x5B5A": "0xE6DA", + "0x5B65": "0xE6DB", + "0x5B73": "0xE6DC", + "0x5B51": "0xE6DD", + "0x5B53": "0xE6DE", + "0x5B62": "0xE6DF", + "0x9A75": "0xE6E0", + "0x9A77": "0xE6E1", + "0x9A78": "0xE6E2", + "0x9A7A": "0xE6E3", + "0x9A7F": "0xE6E4", + "0x9A7D": "0xE6E5", + "0x9A80": "0xE6E6", + "0x9A81": "0xE6E7", + "0x9A85": "0xE6E8", + "0x9A88": "0xE6E9", + "0x9A8A": "0xE6EA", + "0x9A90": "0xE6EB", + "0x9A92": "0xE6EC", + "0x9A93": "0xE6ED", + "0x9A96": "0xE6EE", + "0x9A98": "0xE6EF", + "0x9A9B": "0xE6F0", + "0x9A9C": "0xE6F1", + "0x9A9D": "0xE6F2", + "0x9A9F": "0xE6F3", + "0x9AA0": "0xE6F4", + "0x9AA2": "0xE6F5", + "0x9AA3": "0xE6F6", + "0x9AA5": "0xE6F7", + "0x9AA7": "0xE6F8", + "0x7E9F": "0xE6F9", + "0x7EA1": "0xE6FA", + "0x7EA3": "0xE6FB", + "0x7EA5": "0xE6FC", + "0x7EA8": "0xE6FD", + "0x7EA9": "0xE6FE", + "0x7EAD": "0xE7A1", + "0x7EB0": "0xE7A2", + "0x7EBE": "0xE7A3", + "0x7EC0": "0xE7A4", + "0x7EC1": "0xE7A5", + "0x7EC2": "0xE7A6", + "0x7EC9": "0xE7A7", + "0x7ECB": "0xE7A8", + "0x7ECC": "0xE7A9", + "0x7ED0": "0xE7AA", + "0x7ED4": "0xE7AB", + "0x7ED7": "0xE7AC", + "0x7EDB": "0xE7AD", + "0x7EE0": "0xE7AE", + "0x7EE1": "0xE7AF", + "0x7EE8": "0xE7B0", + "0x7EEB": "0xE7B1", + "0x7EEE": "0xE7B2", + "0x7EEF": "0xE7B3", + "0x7EF1": "0xE7B4", + "0x7EF2": "0xE7B5", + "0x7F0D": "0xE7B6", + "0x7EF6": "0xE7B7", + "0x7EFA": "0xE7B8", + "0x7EFB": "0xE7B9", + "0x7EFE": "0xE7BA", + "0x7F01": "0xE7BB", + "0x7F02": "0xE7BC", + "0x7F03": "0xE7BD", + "0x7F07": "0xE7BE", + "0x7F08": "0xE7BF", + "0x7F0B": "0xE7C0", + "0x7F0C": "0xE7C1", + "0x7F0F": "0xE7C2", + "0x7F11": "0xE7C3", + "0x7F12": "0xE7C4", + "0x7F17": "0xE7C5", + "0x7F19": "0xE7C6", + "0x7F1C": "0xE7C7", + "0x7F1B": "0xE7C8", + "0x7F1F": "0xE7C9", + "0x7F21": "0xE7CA", + "0x7F22": "0xE7CB", + "0x7F23": "0xE7CC", + "0x7F24": "0xE7CD", + "0x7F25": "0xE7CE", + "0x7F26": "0xE7CF", + "0x7F27": "0xE7D0", + "0x7F2A": "0xE7D1", + "0x7F2B": "0xE7D2", + "0x7F2C": "0xE7D3", + "0x7F2D": "0xE7D4", + "0x7F2F": "0xE7D5", + "0x7F30": "0xE7D6", + "0x7F31": "0xE7D7", + "0x7F32": "0xE7D8", + "0x7F33": "0xE7D9", + "0x7F35": "0xE7DA", + "0x5E7A": "0xE7DB", + "0x757F": "0xE7DC", + "0x5DDB": "0xE7DD", + "0x753E": "0xE7DE", + "0x9095": "0xE7DF", + "0x738E": "0xE7E0", + "0x7391": "0xE7E1", + "0x73AE": "0xE7E2", + "0x73A2": "0xE7E3", + "0x739F": "0xE7E4", + "0x73CF": "0xE7E5", + "0x73C2": "0xE7E6", + "0x73D1": "0xE7E7", + "0x73B7": "0xE7E8", + "0x73B3": "0xE7E9", + "0x73C0": "0xE7EA", + "0x73C9": "0xE7EB", + "0x73C8": "0xE7EC", + "0x73E5": "0xE7ED", + "0x73D9": "0xE7EE", + "0x987C": "0xE7EF", + "0x740A": "0xE7F0", + "0x73E9": "0xE7F1", + "0x73E7": "0xE7F2", + "0x73DE": "0xE7F3", + "0x73BA": "0xE7F4", + "0x73F2": "0xE7F5", + "0x740F": "0xE7F6", + "0x742A": "0xE7F7", + "0x745B": "0xE7F8", + "0x7426": "0xE7F9", + "0x7425": "0xE7FA", + "0x7428": "0xE7FB", + "0x7430": "0xE7FC", + "0x742E": "0xE7FD", + "0x742C": "0xE7FE", + "0x741B": "0xE8A1", + "0x741A": "0xE8A2", + "0x7441": "0xE8A3", + "0x745C": "0xE8A4", + "0x7457": "0xE8A5", + "0x7455": "0xE8A6", + "0x7459": "0xE8A7", + "0x7477": "0xE8A8", + "0x746D": "0xE8A9", + "0x747E": "0xE8AA", + "0x749C": "0xE8AB", + "0x748E": "0xE8AC", + "0x7480": "0xE8AD", + "0x7481": "0xE8AE", + "0x7487": "0xE8AF", + "0x748B": "0xE8B0", + "0x749E": "0xE8B1", + "0x74A8": "0xE8B2", + "0x74A9": "0xE8B3", + "0x7490": "0xE8B4", + "0x74A7": "0xE8B5", + "0x74D2": "0xE8B6", + "0x74BA": "0xE8B7", + "0x97EA": "0xE8B8", + "0x97EB": "0xE8B9", + "0x97EC": "0xE8BA", + "0x674C": "0xE8BB", + "0x6753": "0xE8BC", + "0x675E": "0xE8BD", + "0x6748": "0xE8BE", + "0x6769": "0xE8BF", + "0x67A5": "0xE8C0", + "0x6787": "0xE8C1", + "0x676A": "0xE8C2", + "0x6773": "0xE8C3", + "0x6798": "0xE8C4", + "0x67A7": "0xE8C5", + "0x6775": "0xE8C6", + "0x67A8": "0xE8C7", + "0x679E": "0xE8C8", + "0x67AD": "0xE8C9", + "0x678B": "0xE8CA", + "0x6777": "0xE8CB", + "0x677C": "0xE8CC", + "0x67F0": "0xE8CD", + "0x6809": "0xE8CE", + "0x67D8": "0xE8CF", + "0x680A": "0xE8D0", + "0x67E9": "0xE8D1", + "0x67B0": "0xE8D2", + "0x680C": "0xE8D3", + "0x67D9": "0xE8D4", + "0x67B5": "0xE8D5", + "0x67DA": "0xE8D6", + "0x67B3": "0xE8D7", + "0x67DD": "0xE8D8", + "0x6800": "0xE8D9", + "0x67C3": "0xE8DA", + "0x67B8": "0xE8DB", + "0x67E2": "0xE8DC", + "0x680E": "0xE8DD", + "0x67C1": "0xE8DE", + "0x67FD": "0xE8DF", + "0x6832": "0xE8E0", + "0x6833": "0xE8E1", + "0x6860": "0xE8E2", + "0x6861": "0xE8E3", + "0x684E": "0xE8E4", + "0x6862": "0xE8E5", + "0x6844": "0xE8E6", + "0x6864": "0xE8E7", + "0x6883": "0xE8E8", + "0x681D": "0xE8E9", + "0x6855": "0xE8EA", + "0x6866": "0xE8EB", + "0x6841": "0xE8EC", + "0x6867": "0xE8ED", + "0x6840": "0xE8EE", + "0x683E": "0xE8EF", + "0x684A": "0xE8F0", + "0x6849": "0xE8F1", + "0x6829": "0xE8F2", + "0x68B5": "0xE8F3", + "0x688F": "0xE8F4", + "0x6874": "0xE8F5", + "0x6877": "0xE8F6", + "0x6893": "0xE8F7", + "0x686B": "0xE8F8", + "0x68C2": "0xE8F9", + "0x696E": "0xE8FA", + "0x68FC": "0xE8FB", + "0x691F": "0xE8FC", + "0x6920": "0xE8FD", + "0x68F9": "0xE8FE", + "0x6924": "0xE9A1", + "0x68F0": "0xE9A2", + "0x690B": "0xE9A3", + "0x6901": "0xE9A4", + "0x6957": "0xE9A5", + "0x68E3": "0xE9A6", + "0x6910": "0xE9A7", + "0x6971": "0xE9A8", + "0x6939": "0xE9A9", + "0x6960": "0xE9AA", + "0x6942": "0xE9AB", + "0x695D": "0xE9AC", + "0x6984": "0xE9AD", + "0x696B": "0xE9AE", + "0x6980": "0xE9AF", + "0x6998": "0xE9B0", + "0x6978": "0xE9B1", + "0x6934": "0xE9B2", + "0x69CC": "0xE9B3", + "0x6987": "0xE9B4", + "0x6988": "0xE9B5", + "0x69CE": "0xE9B6", + "0x6989": "0xE9B7", + "0x6966": "0xE9B8", + "0x6963": "0xE9B9", + "0x6979": "0xE9BA", + "0x699B": "0xE9BB", + "0x69A7": "0xE9BC", + "0x69BB": "0xE9BD", + "0x69AB": "0xE9BE", + "0x69AD": "0xE9BF", + "0x69D4": "0xE9C0", + "0x69B1": "0xE9C1", + "0x69C1": "0xE9C2", + "0x69CA": "0xE9C3", + "0x69DF": "0xE9C4", + "0x6995": "0xE9C5", + "0x69E0": "0xE9C6", + "0x698D": "0xE9C7", + "0x69FF": "0xE9C8", + "0x6A2F": "0xE9C9", + "0x69ED": "0xE9CA", + "0x6A17": "0xE9CB", + "0x6A18": "0xE9CC", + "0x6A65": "0xE9CD", + "0x69F2": "0xE9CE", + "0x6A44": "0xE9CF", + "0x6A3E": "0xE9D0", + "0x6AA0": "0xE9D1", + "0x6A50": "0xE9D2", + "0x6A5B": "0xE9D3", + "0x6A35": "0xE9D4", + "0x6A8E": "0xE9D5", + "0x6A79": "0xE9D6", + "0x6A3D": "0xE9D7", + "0x6A28": "0xE9D8", + "0x6A58": "0xE9D9", + "0x6A7C": "0xE9DA", + "0x6A91": "0xE9DB", + "0x6A90": "0xE9DC", + "0x6AA9": "0xE9DD", + "0x6A97": "0xE9DE", + "0x6AAB": "0xE9DF", + "0x7337": "0xE9E0", + "0x7352": "0xE9E1", + "0x6B81": "0xE9E2", + "0x6B82": "0xE9E3", + "0x6B87": "0xE9E4", + "0x6B84": "0xE9E5", + "0x6B92": "0xE9E6", + "0x6B93": "0xE9E7", + "0x6B8D": "0xE9E8", + "0x6B9A": "0xE9E9", + "0x6B9B": "0xE9EA", + "0x6BA1": "0xE9EB", + "0x6BAA": "0xE9EC", + "0x8F6B": "0xE9ED", + "0x8F6D": "0xE9EE", + "0x8F71": "0xE9EF", + "0x8F72": "0xE9F0", + "0x8F73": "0xE9F1", + "0x8F75": "0xE9F2", + "0x8F76": "0xE9F3", + "0x8F78": "0xE9F4", + "0x8F77": "0xE9F5", + "0x8F79": "0xE9F6", + "0x8F7A": "0xE9F7", + "0x8F7C": "0xE9F8", + "0x8F7E": "0xE9F9", + "0x8F81": "0xE9FA", + "0x8F82": "0xE9FB", + "0x8F84": "0xE9FC", + "0x8F87": "0xE9FD", + "0x8F8B": "0xE9FE", + "0x8F8D": "0xEAA1", + "0x8F8E": "0xEAA2", + "0x8F8F": "0xEAA3", + "0x8F98": "0xEAA4", + "0x8F9A": "0xEAA5", + "0x8ECE": "0xEAA6", + "0x620B": "0xEAA7", + "0x6217": "0xEAA8", + "0x621B": "0xEAA9", + "0x621F": "0xEAAA", + "0x6222": "0xEAAB", + "0x6221": "0xEAAC", + "0x6225": "0xEAAD", + "0x6224": "0xEAAE", + "0x622C": "0xEAAF", + "0x81E7": "0xEAB0", + "0x74EF": "0xEAB1", + "0x74F4": "0xEAB2", + "0x74FF": "0xEAB3", + "0x750F": "0xEAB4", + "0x7511": "0xEAB5", + "0x7513": "0xEAB6", + "0x6534": "0xEAB7", + "0x65EE": "0xEAB8", + "0x65EF": "0xEAB9", + "0x65F0": "0xEABA", + "0x660A": "0xEABB", + "0x6619": "0xEABC", + "0x6772": "0xEABD", + "0x6603": "0xEABE", + "0x6615": "0xEABF", + "0x6600": "0xEAC0", + "0x7085": "0xEAC1", + "0x66F7": "0xEAC2", + "0x661D": "0xEAC3", + "0x6634": "0xEAC4", + "0x6631": "0xEAC5", + "0x6636": "0xEAC6", + "0x6635": "0xEAC7", + "0x8006": "0xEAC8", + "0x665F": "0xEAC9", + "0x6654": "0xEACA", + "0x6641": "0xEACB", + "0x664F": "0xEACC", + "0x6656": "0xEACD", + "0x6661": "0xEACE", + "0x6657": "0xEACF", + "0x6677": "0xEAD0", + "0x6684": "0xEAD1", + "0x668C": "0xEAD2", + "0x66A7": "0xEAD3", + "0x669D": "0xEAD4", + "0x66BE": "0xEAD5", + "0x66DB": "0xEAD6", + "0x66DC": "0xEAD7", + "0x66E6": "0xEAD8", + "0x66E9": "0xEAD9", + "0x8D32": "0xEADA", + "0x8D33": "0xEADB", + "0x8D36": "0xEADC", + "0x8D3B": "0xEADD", + "0x8D3D": "0xEADE", + "0x8D40": "0xEADF", + "0x8D45": "0xEAE0", + "0x8D46": "0xEAE1", + "0x8D48": "0xEAE2", + "0x8D49": "0xEAE3", + "0x8D47": "0xEAE4", + "0x8D4D": "0xEAE5", + "0x8D55": "0xEAE6", + "0x8D59": "0xEAE7", + "0x89C7": "0xEAE8", + "0x89CA": "0xEAE9", + "0x89CB": "0xEAEA", + "0x89CC": "0xEAEB", + "0x89CE": "0xEAEC", + "0x89CF": "0xEAED", + "0x89D0": "0xEAEE", + "0x89D1": "0xEAEF", + "0x726E": "0xEAF0", + "0x729F": "0xEAF1", + "0x725D": "0xEAF2", + "0x7266": "0xEAF3", + "0x726F": "0xEAF4", + "0x727E": "0xEAF5", + "0x727F": "0xEAF6", + "0x7284": "0xEAF7", + "0x728B": "0xEAF8", + "0x728D": "0xEAF9", + "0x728F": "0xEAFA", + "0x7292": "0xEAFB", + "0x6308": "0xEAFC", + "0x6332": "0xEAFD", + "0x63B0": "0xEAFE", + "0x643F": "0xEBA1", + "0x64D8": "0xEBA2", + "0x8004": "0xEBA3", + "0x6BEA": "0xEBA4", + "0x6BF3": "0xEBA5", + "0x6BFD": "0xEBA6", + "0x6BF5": "0xEBA7", + "0x6BF9": "0xEBA8", + "0x6C05": "0xEBA9", + "0x6C07": "0xEBAA", + "0x6C06": "0xEBAB", + "0x6C0D": "0xEBAC", + "0x6C15": "0xEBAD", + "0x6C18": "0xEBAE", + "0x6C19": "0xEBAF", + "0x6C1A": "0xEBB0", + "0x6C21": "0xEBB1", + "0x6C29": "0xEBB2", + "0x6C24": "0xEBB3", + "0x6C2A": "0xEBB4", + "0x6C32": "0xEBB5", + "0x6535": "0xEBB6", + "0x6555": "0xEBB7", + "0x656B": "0xEBB8", + "0x724D": "0xEBB9", + "0x7252": "0xEBBA", + "0x7256": "0xEBBB", + "0x7230": "0xEBBC", + "0x8662": "0xEBBD", + "0x5216": "0xEBBE", + "0x809F": "0xEBBF", + "0x809C": "0xEBC0", + "0x8093": "0xEBC1", + "0x80BC": "0xEBC2", + "0x670A": "0xEBC3", + "0x80BD": "0xEBC4", + "0x80B1": "0xEBC5", + "0x80AB": "0xEBC6", + "0x80AD": "0xEBC7", + "0x80B4": "0xEBC8", + "0x80B7": "0xEBC9", + "0x80E7": "0xEBCA", + "0x80E8": "0xEBCB", + "0x80E9": "0xEBCC", + "0x80EA": "0xEBCD", + "0x80DB": "0xEBCE", + "0x80C2": "0xEBCF", + "0x80C4": "0xEBD0", + "0x80D9": "0xEBD1", + "0x80CD": "0xEBD2", + "0x80D7": "0xEBD3", + "0x6710": "0xEBD4", + "0x80DD": "0xEBD5", + "0x80EB": "0xEBD6", + "0x80F1": "0xEBD7", + "0x80F4": "0xEBD8", + "0x80ED": "0xEBD9", + "0x810D": "0xEBDA", + "0x810E": "0xEBDB", + "0x80F2": "0xEBDC", + "0x80FC": "0xEBDD", + "0x6715": "0xEBDE", + "0x8112": "0xEBDF", + "0x8C5A": "0xEBE0", + "0x8136": "0xEBE1", + "0x811E": "0xEBE2", + "0x812C": "0xEBE3", + "0x8118": "0xEBE4", + "0x8132": "0xEBE5", + "0x8148": "0xEBE6", + "0x814C": "0xEBE7", + "0x8153": "0xEBE8", + "0x8174": "0xEBE9", + "0x8159": "0xEBEA", + "0x815A": "0xEBEB", + "0x8171": "0xEBEC", + "0x8160": "0xEBED", + "0x8169": "0xEBEE", + "0x817C": "0xEBEF", + "0x817D": "0xEBF0", + "0x816D": "0xEBF1", + "0x8167": "0xEBF2", + "0x584D": "0xEBF3", + "0x5AB5": "0xEBF4", + "0x8188": "0xEBF5", + "0x8182": "0xEBF6", + "0x8191": "0xEBF7", + "0x6ED5": "0xEBF8", + "0x81A3": "0xEBF9", + "0x81AA": "0xEBFA", + "0x81CC": "0xEBFB", + "0x6726": "0xEBFC", + "0x81CA": "0xEBFD", + "0x81BB": "0xEBFE", + "0x81C1": "0xECA1", + "0x81A6": "0xECA2", + "0x6B24": "0xECA3", + "0x6B37": "0xECA4", + "0x6B39": "0xECA5", + "0x6B43": "0xECA6", + "0x6B46": "0xECA7", + "0x6B59": "0xECA8", + "0x98D1": "0xECA9", + "0x98D2": "0xECAA", + "0x98D3": "0xECAB", + "0x98D5": "0xECAC", + "0x98D9": "0xECAD", + "0x98DA": "0xECAE", + "0x6BB3": "0xECAF", + "0x5F40": "0xECB0", + "0x6BC2": "0xECB1", + "0x89F3": "0xECB2", + "0x6590": "0xECB3", + "0x9F51": "0xECB4", + "0x6593": "0xECB5", + "0x65BC": "0xECB6", + "0x65C6": "0xECB7", + "0x65C4": "0xECB8", + "0x65C3": "0xECB9", + "0x65CC": "0xECBA", + "0x65CE": "0xECBB", + "0x65D2": "0xECBC", + "0x65D6": "0xECBD", + "0x7080": "0xECBE", + "0x709C": "0xECBF", + "0x7096": "0xECC0", + "0x709D": "0xECC1", + "0x70BB": "0xECC2", + "0x70C0": "0xECC3", + "0x70B7": "0xECC4", + "0x70AB": "0xECC5", + "0x70B1": "0xECC6", + "0x70E8": "0xECC7", + "0x70CA": "0xECC8", + "0x7110": "0xECC9", + "0x7113": "0xECCA", + "0x7116": "0xECCB", + "0x712F": "0xECCC", + "0x7131": "0xECCD", + "0x7173": "0xECCE", + "0x715C": "0xECCF", + "0x7168": "0xECD0", + "0x7145": "0xECD1", + "0x7172": "0xECD2", + "0x714A": "0xECD3", + "0x7178": "0xECD4", + "0x717A": "0xECD5", + "0x7198": "0xECD6", + "0x71B3": "0xECD7", + "0x71B5": "0xECD8", + "0x71A8": "0xECD9", + "0x71A0": "0xECDA", + "0x71E0": "0xECDB", + "0x71D4": "0xECDC", + "0x71E7": "0xECDD", + "0x71F9": "0xECDE", + "0x721D": "0xECDF", + "0x7228": "0xECE0", + "0x706C": "0xECE1", + "0x7118": "0xECE2", + "0x7166": "0xECE3", + "0x71B9": "0xECE4", + "0x623E": "0xECE5", + "0x623D": "0xECE6", + "0x6243": "0xECE7", + "0x6248": "0xECE8", + "0x6249": "0xECE9", + "0x793B": "0xECEA", + "0x7940": "0xECEB", + "0x7946": "0xECEC", + "0x7949": "0xECED", + "0x795B": "0xECEE", + "0x795C": "0xECEF", + "0x7953": "0xECF0", + "0x795A": "0xECF1", + "0x7962": "0xECF2", + "0x7957": "0xECF3", + "0x7960": "0xECF4", + "0x796F": "0xECF5", + "0x7967": "0xECF6", + "0x797A": "0xECF7", + "0x7985": "0xECF8", + "0x798A": "0xECF9", + "0x799A": "0xECFA", + "0x79A7": "0xECFB", + "0x79B3": "0xECFC", + "0x5FD1": "0xECFD", + "0x5FD0": "0xECFE", + "0x603C": "0xEDA1", + "0x605D": "0xEDA2", + "0x605A": "0xEDA3", + "0x6067": "0xEDA4", + "0x6041": "0xEDA5", + "0x6059": "0xEDA6", + "0x6063": "0xEDA7", + "0x60AB": "0xEDA8", + "0x6106": "0xEDA9", + "0x610D": "0xEDAA", + "0x615D": "0xEDAB", + "0x61A9": "0xEDAC", + "0x619D": "0xEDAD", + "0x61CB": "0xEDAE", + "0x61D1": "0xEDAF", + "0x6206": "0xEDB0", + "0x8080": "0xEDB1", + "0x807F": "0xEDB2", + "0x6C93": "0xEDB3", + "0x6CF6": "0xEDB4", + "0x6DFC": "0xEDB5", + "0x77F6": "0xEDB6", + "0x77F8": "0xEDB7", + "0x7800": "0xEDB8", + "0x7809": "0xEDB9", + "0x7817": "0xEDBA", + "0x7818": "0xEDBB", + "0x7811": "0xEDBC", + "0x65AB": "0xEDBD", + "0x782D": "0xEDBE", + "0x781C": "0xEDBF", + "0x781D": "0xEDC0", + "0x7839": "0xEDC1", + "0x783A": "0xEDC2", + "0x783B": "0xEDC3", + "0x781F": "0xEDC4", + "0x783C": "0xEDC5", + "0x7825": "0xEDC6", + "0x782C": "0xEDC7", + "0x7823": "0xEDC8", + "0x7829": "0xEDC9", + "0x784E": "0xEDCA", + "0x786D": "0xEDCB", + "0x7856": "0xEDCC", + "0x7857": "0xEDCD", + "0x7826": "0xEDCE", + "0x7850": "0xEDCF", + "0x7847": "0xEDD0", + "0x784C": "0xEDD1", + "0x786A": "0xEDD2", + "0x789B": "0xEDD3", + "0x7893": "0xEDD4", + "0x789A": "0xEDD5", + "0x7887": "0xEDD6", + "0x789C": "0xEDD7", + "0x78A1": "0xEDD8", + "0x78A3": "0xEDD9", + "0x78B2": "0xEDDA", + "0x78B9": "0xEDDB", + "0x78A5": "0xEDDC", + "0x78D4": "0xEDDD", + "0x78D9": "0xEDDE", + "0x78C9": "0xEDDF", + "0x78EC": "0xEDE0", + "0x78F2": "0xEDE1", + "0x7905": "0xEDE2", + "0x78F4": "0xEDE3", + "0x7913": "0xEDE4", + "0x7924": "0xEDE5", + "0x791E": "0xEDE6", + "0x7934": "0xEDE7", + "0x9F9B": "0xEDE8", + "0x9EF9": "0xEDE9", + "0x9EFB": "0xEDEA", + "0x9EFC": "0xEDEB", + "0x76F1": "0xEDEC", + "0x7704": "0xEDED", + "0x770D": "0xEDEE", + "0x76F9": "0xEDEF", + "0x7707": "0xEDF0", + "0x7708": "0xEDF1", + "0x771A": "0xEDF2", + "0x7722": "0xEDF3", + "0x7719": "0xEDF4", + "0x772D": "0xEDF5", + "0x7726": "0xEDF6", + "0x7735": "0xEDF7", + "0x7738": "0xEDF8", + "0x7750": "0xEDF9", + "0x7751": "0xEDFA", + "0x7747": "0xEDFB", + "0x7743": "0xEDFC", + "0x775A": "0xEDFD", + "0x7768": "0xEDFE", + "0x7762": "0xEEA1", + "0x7765": "0xEEA2", + "0x777F": "0xEEA3", + "0x778D": "0xEEA4", + "0x777D": "0xEEA5", + "0x7780": "0xEEA6", + "0x778C": "0xEEA7", + "0x7791": "0xEEA8", + "0x779F": "0xEEA9", + "0x77A0": "0xEEAA", + "0x77B0": "0xEEAB", + "0x77B5": "0xEEAC", + "0x77BD": "0xEEAD", + "0x753A": "0xEEAE", + "0x7540": "0xEEAF", + "0x754E": "0xEEB0", + "0x754B": "0xEEB1", + "0x7548": "0xEEB2", + "0x755B": "0xEEB3", + "0x7572": "0xEEB4", + "0x7579": "0xEEB5", + "0x7583": "0xEEB6", + "0x7F58": "0xEEB7", + "0x7F61": "0xEEB8", + "0x7F5F": "0xEEB9", + "0x8A48": "0xEEBA", + "0x7F68": "0xEEBB", + "0x7F74": "0xEEBC", + "0x7F71": "0xEEBD", + "0x7F79": "0xEEBE", + "0x7F81": "0xEEBF", + "0x7F7E": "0xEEC0", + "0x76CD": "0xEEC1", + "0x76E5": "0xEEC2", + "0x8832": "0xEEC3", + "0x9485": "0xEEC4", + "0x9486": "0xEEC5", + "0x9487": "0xEEC6", + "0x948B": "0xEEC7", + "0x948A": "0xEEC8", + "0x948C": "0xEEC9", + "0x948D": "0xEECA", + "0x948F": "0xEECB", + "0x9490": "0xEECC", + "0x9494": "0xEECD", + "0x9497": "0xEECE", + "0x9495": "0xEECF", + "0x949A": "0xEED0", + "0x949B": "0xEED1", + "0x949C": "0xEED2", + "0x94A3": "0xEED3", + "0x94A4": "0xEED4", + "0x94AB": "0xEED5", + "0x94AA": "0xEED6", + "0x94AD": "0xEED7", + "0x94AC": "0xEED8", + "0x94AF": "0xEED9", + "0x94B0": "0xEEDA", + "0x94B2": "0xEEDB", + "0x94B4": "0xEEDC", + "0x94B6": "0xEEDD", + "0x94B7": "0xEEDE", + "0x94B8": "0xEEDF", + "0x94B9": "0xEEE0", + "0x94BA": "0xEEE1", + "0x94BC": "0xEEE2", + "0x94BD": "0xEEE3", + "0x94BF": "0xEEE4", + "0x94C4": "0xEEE5", + "0x94C8": "0xEEE6", + "0x94C9": "0xEEE7", + "0x94CA": "0xEEE8", + "0x94CB": "0xEEE9", + "0x94CC": "0xEEEA", + "0x94CD": "0xEEEB", + "0x94CE": "0xEEEC", + "0x94D0": "0xEEED", + "0x94D1": "0xEEEE", + "0x94D2": "0xEEEF", + "0x94D5": "0xEEF0", + "0x94D6": "0xEEF1", + "0x94D7": "0xEEF2", + "0x94D9": "0xEEF3", + "0x94D8": "0xEEF4", + "0x94DB": "0xEEF5", + "0x94DE": "0xEEF6", + "0x94DF": "0xEEF7", + "0x94E0": "0xEEF8", + "0x94E2": "0xEEF9", + "0x94E4": "0xEEFA", + "0x94E5": "0xEEFB", + "0x94E7": "0xEEFC", + "0x94E8": "0xEEFD", + "0x94EA": "0xEEFE", + "0x94E9": "0xEFA1", + "0x94EB": "0xEFA2", + "0x94EE": "0xEFA3", + "0x94EF": "0xEFA4", + "0x94F3": "0xEFA5", + "0x94F4": "0xEFA6", + "0x94F5": "0xEFA7", + "0x94F7": "0xEFA8", + "0x94F9": "0xEFA9", + "0x94FC": "0xEFAA", + "0x94FD": "0xEFAB", + "0x94FF": "0xEFAC", + "0x9503": "0xEFAD", + "0x9502": "0xEFAE", + "0x9506": "0xEFAF", + "0x9507": "0xEFB0", + "0x9509": "0xEFB1", + "0x950A": "0xEFB2", + "0x950D": "0xEFB3", + "0x950E": "0xEFB4", + "0x950F": "0xEFB5", + "0x9512": "0xEFB6", + "0x9513": "0xEFB7", + "0x9514": "0xEFB8", + "0x9515": "0xEFB9", + "0x9516": "0xEFBA", + "0x9518": "0xEFBB", + "0x951B": "0xEFBC", + "0x951D": "0xEFBD", + "0x951E": "0xEFBE", + "0x951F": "0xEFBF", + "0x9522": "0xEFC0", + "0x952A": "0xEFC1", + "0x952B": "0xEFC2", + "0x9529": "0xEFC3", + "0x952C": "0xEFC4", + "0x9531": "0xEFC5", + "0x9532": "0xEFC6", + "0x9534": "0xEFC7", + "0x9536": "0xEFC8", + "0x9537": "0xEFC9", + "0x9538": "0xEFCA", + "0x953C": "0xEFCB", + "0x953E": "0xEFCC", + "0x953F": "0xEFCD", + "0x9542": "0xEFCE", + "0x9535": "0xEFCF", + "0x9544": "0xEFD0", + "0x9545": "0xEFD1", + "0x9546": "0xEFD2", + "0x9549": "0xEFD3", + "0x954C": "0xEFD4", + "0x954E": "0xEFD5", + "0x954F": "0xEFD6", + "0x9552": "0xEFD7", + "0x9553": "0xEFD8", + "0x9554": "0xEFD9", + "0x9556": "0xEFDA", + "0x9557": "0xEFDB", + "0x9558": "0xEFDC", + "0x9559": "0xEFDD", + "0x955B": "0xEFDE", + "0x955E": "0xEFDF", + "0x955F": "0xEFE0", + "0x955D": "0xEFE1", + "0x9561": "0xEFE2", + "0x9562": "0xEFE3", + "0x9564": "0xEFE4", + "0x9565": "0xEFE5", + "0x9566": "0xEFE6", + "0x9567": "0xEFE7", + "0x9568": "0xEFE8", + "0x9569": "0xEFE9", + "0x956A": "0xEFEA", + "0x956B": "0xEFEB", + "0x956C": "0xEFEC", + "0x956F": "0xEFED", + "0x9571": "0xEFEE", + "0x9572": "0xEFEF", + "0x9573": "0xEFF0", + "0x953A": "0xEFF1", + "0x77E7": "0xEFF2", + "0x77EC": "0xEFF3", + "0x96C9": "0xEFF4", + "0x79D5": "0xEFF5", + "0x79ED": "0xEFF6", + "0x79E3": "0xEFF7", + "0x79EB": "0xEFF8", + "0x7A06": "0xEFF9", + "0x5D47": "0xEFFA", + "0x7A03": "0xEFFB", + "0x7A02": "0xEFFC", + "0x7A1E": "0xEFFD", + "0x7A14": "0xEFFE", + "0x7A39": "0xF0A1", + "0x7A37": "0xF0A2", + "0x7A51": "0xF0A3", + "0x9ECF": "0xF0A4", + "0x99A5": "0xF0A5", + "0x7A70": "0xF0A6", + "0x7688": "0xF0A7", + "0x768E": "0xF0A8", + "0x7693": "0xF0A9", + "0x7699": "0xF0AA", + "0x76A4": "0xF0AB", + "0x74DE": "0xF0AC", + "0x74E0": "0xF0AD", + "0x752C": "0xF0AE", + "0x9E20": "0xF0AF", + "0x9E22": "0xF0B0", + "0x9E28": "0xF0B1", + "0x9E29": "0xF0B2", + "0x9E2A": "0xF0B3", + "0x9E2B": "0xF0B4", + "0x9E2C": "0xF0B5", + "0x9E32": "0xF0B6", + "0x9E31": "0xF0B7", + "0x9E36": "0xF0B8", + "0x9E38": "0xF0B9", + "0x9E37": "0xF0BA", + "0x9E39": "0xF0BB", + "0x9E3A": "0xF0BC", + "0x9E3E": "0xF0BD", + "0x9E41": "0xF0BE", + "0x9E42": "0xF0BF", + "0x9E44": "0xF0C0", + "0x9E46": "0xF0C1", + "0x9E47": "0xF0C2", + "0x9E48": "0xF0C3", + "0x9E49": "0xF0C4", + "0x9E4B": "0xF0C5", + "0x9E4C": "0xF0C6", + "0x9E4E": "0xF0C7", + "0x9E51": "0xF0C8", + "0x9E55": "0xF0C9", + "0x9E57": "0xF0CA", + "0x9E5A": "0xF0CB", + "0x9E5B": "0xF0CC", + "0x9E5C": "0xF0CD", + "0x9E5E": "0xF0CE", + "0x9E63": "0xF0CF", + "0x9E66": "0xF0D0", + "0x9E67": "0xF0D1", + "0x9E68": "0xF0D2", + "0x9E69": "0xF0D3", + "0x9E6A": "0xF0D4", + "0x9E6B": "0xF0D5", + "0x9E6C": "0xF0D6", + "0x9E71": "0xF0D7", + "0x9E6D": "0xF0D8", + "0x9E73": "0xF0D9", + "0x7592": "0xF0DA", + "0x7594": "0xF0DB", + "0x7596": "0xF0DC", + "0x75A0": "0xF0DD", + "0x759D": "0xF0DE", + "0x75AC": "0xF0DF", + "0x75A3": "0xF0E0", + "0x75B3": "0xF0E1", + "0x75B4": "0xF0E2", + "0x75B8": "0xF0E3", + "0x75C4": "0xF0E4", + "0x75B1": "0xF0E5", + "0x75B0": "0xF0E6", + "0x75C3": "0xF0E7", + "0x75C2": "0xF0E8", + "0x75D6": "0xF0E9", + "0x75CD": "0xF0EA", + "0x75E3": "0xF0EB", + "0x75E8": "0xF0EC", + "0x75E6": "0xF0ED", + "0x75E4": "0xF0EE", + "0x75EB": "0xF0EF", + "0x75E7": "0xF0F0", + "0x7603": "0xF0F1", + "0x75F1": "0xF0F2", + "0x75FC": "0xF0F3", + "0x75FF": "0xF0F4", + "0x7610": "0xF0F5", + "0x7600": "0xF0F6", + "0x7605": "0xF0F7", + "0x760C": "0xF0F8", + "0x7617": "0xF0F9", + "0x760A": "0xF0FA", + "0x7625": "0xF0FB", + "0x7618": "0xF0FC", + "0x7615": "0xF0FD", + "0x7619": "0xF0FE", + "0x761B": "0xF1A1", + "0x763C": "0xF1A2", + "0x7622": "0xF1A3", + "0x7620": "0xF1A4", + "0x7640": "0xF1A5", + "0x762D": "0xF1A6", + "0x7630": "0xF1A7", + "0x763F": "0xF1A8", + "0x7635": "0xF1A9", + "0x7643": "0xF1AA", + "0x763E": "0xF1AB", + "0x7633": "0xF1AC", + "0x764D": "0xF1AD", + "0x765E": "0xF1AE", + "0x7654": "0xF1AF", + "0x765C": "0xF1B0", + "0x7656": "0xF1B1", + "0x766B": "0xF1B2", + "0x766F": "0xF1B3", + "0x7FCA": "0xF1B4", + "0x7AE6": "0xF1B5", + "0x7A78": "0xF1B6", + "0x7A79": "0xF1B7", + "0x7A80": "0xF1B8", + "0x7A86": "0xF1B9", + "0x7A88": "0xF1BA", + "0x7A95": "0xF1BB", + "0x7AA6": "0xF1BC", + "0x7AA0": "0xF1BD", + "0x7AAC": "0xF1BE", + "0x7AA8": "0xF1BF", + "0x7AAD": "0xF1C0", + "0x7AB3": "0xF1C1", + "0x8864": "0xF1C2", + "0x8869": "0xF1C3", + "0x8872": "0xF1C4", + "0x887D": "0xF1C5", + "0x887F": "0xF1C6", + "0x8882": "0xF1C7", + "0x88A2": "0xF1C8", + "0x88C6": "0xF1C9", + "0x88B7": "0xF1CA", + "0x88BC": "0xF1CB", + "0x88C9": "0xF1CC", + "0x88E2": "0xF1CD", + "0x88CE": "0xF1CE", + "0x88E3": "0xF1CF", + "0x88E5": "0xF1D0", + "0x88F1": "0xF1D1", + "0x891A": "0xF1D2", + "0x88FC": "0xF1D3", + "0x88E8": "0xF1D4", + "0x88FE": "0xF1D5", + "0x88F0": "0xF1D6", + "0x8921": "0xF1D7", + "0x8919": "0xF1D8", + "0x8913": "0xF1D9", + "0x891B": "0xF1DA", + "0x890A": "0xF1DB", + "0x8934": "0xF1DC", + "0x892B": "0xF1DD", + "0x8936": "0xF1DE", + "0x8941": "0xF1DF", + "0x8966": "0xF1E0", + "0x897B": "0xF1E1", + "0x758B": "0xF1E2", + "0x80E5": "0xF1E3", + "0x76B2": "0xF1E4", + "0x76B4": "0xF1E5", + "0x77DC": "0xF1E6", + "0x8012": "0xF1E7", + "0x8014": "0xF1E8", + "0x8016": "0xF1E9", + "0x801C": "0xF1EA", + "0x8020": "0xF1EB", + "0x8022": "0xF1EC", + "0x8025": "0xF1ED", + "0x8026": "0xF1EE", + "0x8027": "0xF1EF", + "0x8029": "0xF1F0", + "0x8028": "0xF1F1", + "0x8031": "0xF1F2", + "0x800B": "0xF1F3", + "0x8035": "0xF1F4", + "0x8043": "0xF1F5", + "0x8046": "0xF1F6", + "0x804D": "0xF1F7", + "0x8052": "0xF1F8", + "0x8069": "0xF1F9", + "0x8071": "0xF1FA", + "0x8983": "0xF1FB", + "0x9878": "0xF1FC", + "0x9880": "0xF1FD", + "0x9883": "0xF1FE", + "0x9889": "0xF2A1", + "0x988C": "0xF2A2", + "0x988D": "0xF2A3", + "0x988F": "0xF2A4", + "0x9894": "0xF2A5", + "0x989A": "0xF2A6", + "0x989B": "0xF2A7", + "0x989E": "0xF2A8", + "0x989F": "0xF2A9", + "0x98A1": "0xF2AA", + "0x98A2": "0xF2AB", + "0x98A5": "0xF2AC", + "0x98A6": "0xF2AD", + "0x864D": "0xF2AE", + "0x8654": "0xF2AF", + "0x866C": "0xF2B0", + "0x866E": "0xF2B1", + "0x867F": "0xF2B2", + "0x867A": "0xF2B3", + "0x867C": "0xF2B4", + "0x867B": "0xF2B5", + "0x86A8": "0xF2B6", + "0x868D": "0xF2B7", + "0x868B": "0xF2B8", + "0x86AC": "0xF2B9", + "0x869D": "0xF2BA", + "0x86A7": "0xF2BB", + "0x86A3": "0xF2BC", + "0x86AA": "0xF2BD", + "0x8693": "0xF2BE", + "0x86A9": "0xF2BF", + "0x86B6": "0xF2C0", + "0x86C4": "0xF2C1", + "0x86B5": "0xF2C2", + "0x86CE": "0xF2C3", + "0x86B0": "0xF2C4", + "0x86BA": "0xF2C5", + "0x86B1": "0xF2C6", + "0x86AF": "0xF2C7", + "0x86C9": "0xF2C8", + "0x86CF": "0xF2C9", + "0x86B4": "0xF2CA", + "0x86E9": "0xF2CB", + "0x86F1": "0xF2CC", + "0x86F2": "0xF2CD", + "0x86ED": "0xF2CE", + "0x86F3": "0xF2CF", + "0x86D0": "0xF2D0", + "0x8713": "0xF2D1", + "0x86DE": "0xF2D2", + "0x86F4": "0xF2D3", + "0x86DF": "0xF2D4", + "0x86D8": "0xF2D5", + "0x86D1": "0xF2D6", + "0x8703": "0xF2D7", + "0x8707": "0xF2D8", + "0x86F8": "0xF2D9", + "0x8708": "0xF2DA", + "0x870A": "0xF2DB", + "0x870D": "0xF2DC", + "0x8709": "0xF2DD", + "0x8723": "0xF2DE", + "0x873B": "0xF2DF", + "0x871E": "0xF2E0", + "0x8725": "0xF2E1", + "0x872E": "0xF2E2", + "0x871A": "0xF2E3", + "0x873E": "0xF2E4", + "0x8748": "0xF2E5", + "0x8734": "0xF2E6", + "0x8731": "0xF2E7", + "0x8729": "0xF2E8", + "0x8737": "0xF2E9", + "0x873F": "0xF2EA", + "0x8782": "0xF2EB", + "0x8722": "0xF2EC", + "0x877D": "0xF2ED", + "0x877E": "0xF2EE", + "0x877B": "0xF2EF", + "0x8760": "0xF2F0", + "0x8770": "0xF2F1", + "0x874C": "0xF2F2", + "0x876E": "0xF2F3", + "0x878B": "0xF2F4", + "0x8753": "0xF2F5", + "0x8763": "0xF2F6", + "0x877C": "0xF2F7", + "0x8764": "0xF2F8", + "0x8759": "0xF2F9", + "0x8765": "0xF2FA", + "0x8793": "0xF2FB", + "0x87AF": "0xF2FC", + "0x87A8": "0xF2FD", + "0x87D2": "0xF2FE", + "0x87C6": "0xF3A1", + "0x8788": "0xF3A2", + "0x8785": "0xF3A3", + "0x87AD": "0xF3A4", + "0x8797": "0xF3A5", + "0x8783": "0xF3A6", + "0x87AB": "0xF3A7", + "0x87E5": "0xF3A8", + "0x87AC": "0xF3A9", + "0x87B5": "0xF3AA", + "0x87B3": "0xF3AB", + "0x87CB": "0xF3AC", + "0x87D3": "0xF3AD", + "0x87BD": "0xF3AE", + "0x87D1": "0xF3AF", + "0x87C0": "0xF3B0", + "0x87CA": "0xF3B1", + "0x87DB": "0xF3B2", + "0x87EA": "0xF3B3", + "0x87E0": "0xF3B4", + "0x87EE": "0xF3B5", + "0x8816": "0xF3B6", + "0x8813": "0xF3B7", + "0x87FE": "0xF3B8", + "0x880A": "0xF3B9", + "0x881B": "0xF3BA", + "0x8821": "0xF3BB", + "0x8839": "0xF3BC", + "0x883C": "0xF3BD", + "0x7F36": "0xF3BE", + "0x7F42": "0xF3BF", + "0x7F44": "0xF3C0", + "0x7F45": "0xF3C1", + "0x8210": "0xF3C2", + "0x7AFA": "0xF3C3", + "0x7AFD": "0xF3C4", + "0x7B08": "0xF3C5", + "0x7B03": "0xF3C6", + "0x7B04": "0xF3C7", + "0x7B15": "0xF3C8", + "0x7B0A": "0xF3C9", + "0x7B2B": "0xF3CA", + "0x7B0F": "0xF3CB", + "0x7B47": "0xF3CC", + "0x7B38": "0xF3CD", + "0x7B2A": "0xF3CE", + "0x7B19": "0xF3CF", + "0x7B2E": "0xF3D0", + "0x7B31": "0xF3D1", + "0x7B20": "0xF3D2", + "0x7B25": "0xF3D3", + "0x7B24": "0xF3D4", + "0x7B33": "0xF3D5", + "0x7B3E": "0xF3D6", + "0x7B1E": "0xF3D7", + "0x7B58": "0xF3D8", + "0x7B5A": "0xF3D9", + "0x7B45": "0xF3DA", + "0x7B75": "0xF3DB", + "0x7B4C": "0xF3DC", + "0x7B5D": "0xF3DD", + "0x7B60": "0xF3DE", + "0x7B6E": "0xF3DF", + "0x7B7B": "0xF3E0", + "0x7B62": "0xF3E1", + "0x7B72": "0xF3E2", + "0x7B71": "0xF3E3", + "0x7B90": "0xF3E4", + "0x7BA6": "0xF3E5", + "0x7BA7": "0xF3E6", + "0x7BB8": "0xF3E7", + "0x7BAC": "0xF3E8", + "0x7B9D": "0xF3E9", + "0x7BA8": "0xF3EA", + "0x7B85": "0xF3EB", + "0x7BAA": "0xF3EC", + "0x7B9C": "0xF3ED", + "0x7BA2": "0xF3EE", + "0x7BAB": "0xF3EF", + "0x7BB4": "0xF3F0", + "0x7BD1": "0xF3F1", + "0x7BC1": "0xF3F2", + "0x7BCC": "0xF3F3", + "0x7BDD": "0xF3F4", + "0x7BDA": "0xF3F5", + "0x7BE5": "0xF3F6", + "0x7BE6": "0xF3F7", + "0x7BEA": "0xF3F8", + "0x7C0C": "0xF3F9", + "0x7BFE": "0xF3FA", + "0x7BFC": "0xF3FB", + "0x7C0F": "0xF3FC", + "0x7C16": "0xF3FD", + "0x7C0B": "0xF3FE", + "0x7C1F": "0xF4A1", + "0x7C2A": "0xF4A2", + "0x7C26": "0xF4A3", + "0x7C38": "0xF4A4", + "0x7C41": "0xF4A5", + "0x7C40": "0xF4A6", + "0x81FE": "0xF4A7", + "0x8201": "0xF4A8", + "0x8202": "0xF4A9", + "0x8204": "0xF4AA", + "0x81EC": "0xF4AB", + "0x8844": "0xF4AC", + "0x8221": "0xF4AD", + "0x8222": "0xF4AE", + "0x8223": "0xF4AF", + "0x822D": "0xF4B0", + "0x822F": "0xF4B1", + "0x8228": "0xF4B2", + "0x822B": "0xF4B3", + "0x8238": "0xF4B4", + "0x823B": "0xF4B5", + "0x8233": "0xF4B6", + "0x8234": "0xF4B7", + "0x823E": "0xF4B8", + "0x8244": "0xF4B9", + "0x8249": "0xF4BA", + "0x824B": "0xF4BB", + "0x824F": "0xF4BC", + "0x825A": "0xF4BD", + "0x825F": "0xF4BE", + "0x8268": "0xF4BF", + "0x887E": "0xF4C0", + "0x8885": "0xF4C1", + "0x8888": "0xF4C2", + "0x88D8": "0xF4C3", + "0x88DF": "0xF4C4", + "0x895E": "0xF4C5", + "0x7F9D": "0xF4C6", + "0x7F9F": "0xF4C7", + "0x7FA7": "0xF4C8", + "0x7FAF": "0xF4C9", + "0x7FB0": "0xF4CA", + "0x7FB2": "0xF4CB", + "0x7C7C": "0xF4CC", + "0x6549": "0xF4CD", + "0x7C91": "0xF4CE", + "0x7C9D": "0xF4CF", + "0x7C9C": "0xF4D0", + "0x7C9E": "0xF4D1", + "0x7CA2": "0xF4D2", + "0x7CB2": "0xF4D3", + "0x7CBC": "0xF4D4", + "0x7CBD": "0xF4D5", + "0x7CC1": "0xF4D6", + "0x7CC7": "0xF4D7", + "0x7CCC": "0xF4D8", + "0x7CCD": "0xF4D9", + "0x7CC8": "0xF4DA", + "0x7CC5": "0xF4DB", + "0x7CD7": "0xF4DC", + "0x7CE8": "0xF4DD", + "0x826E": "0xF4DE", + "0x66A8": "0xF4DF", + "0x7FBF": "0xF4E0", + "0x7FCE": "0xF4E1", + "0x7FD5": "0xF4E2", + "0x7FE5": "0xF4E3", + "0x7FE1": "0xF4E4", + "0x7FE6": "0xF4E5", + "0x7FE9": "0xF4E6", + "0x7FEE": "0xF4E7", + "0x7FF3": "0xF4E8", + "0x7CF8": "0xF4E9", + "0x7D77": "0xF4EA", + "0x7DA6": "0xF4EB", + "0x7DAE": "0xF4EC", + "0x7E47": "0xF4ED", + "0x7E9B": "0xF4EE", + "0x9EB8": "0xF4EF", + "0x9EB4": "0xF4F0", + "0x8D73": "0xF4F1", + "0x8D84": "0xF4F2", + "0x8D94": "0xF4F3", + "0x8D91": "0xF4F4", + "0x8DB1": "0xF4F5", + "0x8D67": "0xF4F6", + "0x8D6D": "0xF4F7", + "0x8C47": "0xF4F8", + "0x8C49": "0xF4F9", + "0x914A": "0xF4FA", + "0x9150": "0xF4FB", + "0x914E": "0xF4FC", + "0x914F": "0xF4FD", + "0x9164": "0xF4FE", + "0x9162": "0xF5A1", + "0x9161": "0xF5A2", + "0x9170": "0xF5A3", + "0x9169": "0xF5A4", + "0x916F": "0xF5A5", + "0x917D": "0xF5A6", + "0x917E": "0xF5A7", + "0x9172": "0xF5A8", + "0x9174": "0xF5A9", + "0x9179": "0xF5AA", + "0x918C": "0xF5AB", + "0x9185": "0xF5AC", + "0x9190": "0xF5AD", + "0x918D": "0xF5AE", + "0x9191": "0xF5AF", + "0x91A2": "0xF5B0", + "0x91A3": "0xF5B1", + "0x91AA": "0xF5B2", + "0x91AD": "0xF5B3", + "0x91AE": "0xF5B4", + "0x91AF": "0xF5B5", + "0x91B5": "0xF5B6", + "0x91B4": "0xF5B7", + "0x91BA": "0xF5B8", + "0x8C55": "0xF5B9", + "0x9E7E": "0xF5BA", + "0x8DB8": "0xF5BB", + "0x8DEB": "0xF5BC", + "0x8E05": "0xF5BD", + "0x8E59": "0xF5BE", + "0x8E69": "0xF5BF", + "0x8DB5": "0xF5C0", + "0x8DBF": "0xF5C1", + "0x8DBC": "0xF5C2", + "0x8DBA": "0xF5C3", + "0x8DC4": "0xF5C4", + "0x8DD6": "0xF5C5", + "0x8DD7": "0xF5C6", + "0x8DDA": "0xF5C7", + "0x8DDE": "0xF5C8", + "0x8DCE": "0xF5C9", + "0x8DCF": "0xF5CA", + "0x8DDB": "0xF5CB", + "0x8DC6": "0xF5CC", + "0x8DEC": "0xF5CD", + "0x8DF7": "0xF5CE", + "0x8DF8": "0xF5CF", + "0x8DE3": "0xF5D0", + "0x8DF9": "0xF5D1", + "0x8DFB": "0xF5D2", + "0x8DE4": "0xF5D3", + "0x8E09": "0xF5D4", + "0x8DFD": "0xF5D5", + "0x8E14": "0xF5D6", + "0x8E1D": "0xF5D7", + "0x8E1F": "0xF5D8", + "0x8E2C": "0xF5D9", + "0x8E2E": "0xF5DA", + "0x8E23": "0xF5DB", + "0x8E2F": "0xF5DC", + "0x8E3A": "0xF5DD", + "0x8E40": "0xF5DE", + "0x8E39": "0xF5DF", + "0x8E35": "0xF5E0", + "0x8E3D": "0xF5E1", + "0x8E31": "0xF5E2", + "0x8E49": "0xF5E3", + "0x8E41": "0xF5E4", + "0x8E42": "0xF5E5", + "0x8E51": "0xF5E6", + "0x8E52": "0xF5E7", + "0x8E4A": "0xF5E8", + "0x8E70": "0xF5E9", + "0x8E76": "0xF5EA", + "0x8E7C": "0xF5EB", + "0x8E6F": "0xF5EC", + "0x8E74": "0xF5ED", + "0x8E85": "0xF5EE", + "0x8E8F": "0xF5EF", + "0x8E94": "0xF5F0", + "0x8E90": "0xF5F1", + "0x8E9C": "0xF5F2", + "0x8E9E": "0xF5F3", + "0x8C78": "0xF5F4", + "0x8C82": "0xF5F5", + "0x8C8A": "0xF5F6", + "0x8C85": "0xF5F7", + "0x8C98": "0xF5F8", + "0x8C94": "0xF5F9", + "0x659B": "0xF5FA", + "0x89D6": "0xF5FB", + "0x89DE": "0xF5FC", + "0x89DA": "0xF5FD", + "0x89DC": "0xF5FE", + "0x89E5": "0xF6A1", + "0x89EB": "0xF6A2", + "0x89EF": "0xF6A3", + "0x8A3E": "0xF6A4", + "0x8B26": "0xF6A5", + "0x9753": "0xF6A6", + "0x96E9": "0xF6A7", + "0x96F3": "0xF6A8", + "0x96EF": "0xF6A9", + "0x9706": "0xF6AA", + "0x9701": "0xF6AB", + "0x9708": "0xF6AC", + "0x970F": "0xF6AD", + "0x970E": "0xF6AE", + "0x972A": "0xF6AF", + "0x972D": "0xF6B0", + "0x9730": "0xF6B1", + "0x973E": "0xF6B2", + "0x9F80": "0xF6B3", + "0x9F83": "0xF6B4", + "0x9F85": "0xF6B5", + "0x9F86": "0xF6B6", + "0x9F87": "0xF6B7", + "0x9F88": "0xF6B8", + "0x9F89": "0xF6B9", + "0x9F8A": "0xF6BA", + "0x9F8C": "0xF6BB", + "0x9EFE": "0xF6BC", + "0x9F0B": "0xF6BD", + "0x9F0D": "0xF6BE", + "0x96B9": "0xF6BF", + "0x96BC": "0xF6C0", + "0x96BD": "0xF6C1", + "0x96CE": "0xF6C2", + "0x96D2": "0xF6C3", + "0x77BF": "0xF6C4", + "0x96E0": "0xF6C5", + "0x928E": "0xF6C6", + "0x92AE": "0xF6C7", + "0x92C8": "0xF6C8", + "0x933E": "0xF6C9", + "0x936A": "0xF6CA", + "0x93CA": "0xF6CB", + "0x938F": "0xF6CC", + "0x943E": "0xF6CD", + "0x946B": "0xF6CE", + "0x9C7F": "0xF6CF", + "0x9C82": "0xF6D0", + "0x9C85": "0xF6D1", + "0x9C86": "0xF6D2", + "0x9C87": "0xF6D3", + "0x9C88": "0xF6D4", + "0x7A23": "0xF6D5", + "0x9C8B": "0xF6D6", + "0x9C8E": "0xF6D7", + "0x9C90": "0xF6D8", + "0x9C91": "0xF6D9", + "0x9C92": "0xF6DA", + "0x9C94": "0xF6DB", + "0x9C95": "0xF6DC", + "0x9C9A": "0xF6DD", + "0x9C9B": "0xF6DE", + "0x9C9E": "0xF6DF", + "0x9C9F": "0xF6E0", + "0x9CA0": "0xF6E1", + "0x9CA1": "0xF6E2", + "0x9CA2": "0xF6E3", + "0x9CA3": "0xF6E4", + "0x9CA5": "0xF6E5", + "0x9CA6": "0xF6E6", + "0x9CA7": "0xF6E7", + "0x9CA8": "0xF6E8", + "0x9CA9": "0xF6E9", + "0x9CAB": "0xF6EA", + "0x9CAD": "0xF6EB", + "0x9CAE": "0xF6EC", + "0x9CB0": "0xF6ED", + "0x9CB1": "0xF6EE", + "0x9CB2": "0xF6EF", + "0x9CB3": "0xF6F0", + "0x9CB4": "0xF6F1", + "0x9CB5": "0xF6F2", + "0x9CB6": "0xF6F3", + "0x9CB7": "0xF6F4", + "0x9CBA": "0xF6F5", + "0x9CBB": "0xF6F6", + "0x9CBC": "0xF6F7", + "0x9CBD": "0xF6F8", + "0x9CC4": "0xF6F9", + "0x9CC5": "0xF6FA", + "0x9CC6": "0xF6FB", + "0x9CC7": "0xF6FC", + "0x9CCA": "0xF6FD", + "0x9CCB": "0xF6FE", + "0x9CCC": "0xF7A1", + "0x9CCD": "0xF7A2", + "0x9CCE": "0xF7A3", + "0x9CCF": "0xF7A4", + "0x9CD0": "0xF7A5", + "0x9CD3": "0xF7A6", + "0x9CD4": "0xF7A7", + "0x9CD5": "0xF7A8", + "0x9CD7": "0xF7A9", + "0x9CD8": "0xF7AA", + "0x9CD9": "0xF7AB", + "0x9CDC": "0xF7AC", + "0x9CDD": "0xF7AD", + "0x9CDF": "0xF7AE", + "0x9CE2": "0xF7AF", + "0x977C": "0xF7B0", + "0x9785": "0xF7B1", + "0x9791": "0xF7B2", + "0x9792": "0xF7B3", + "0x9794": "0xF7B4", + "0x97AF": "0xF7B5", + "0x97AB": "0xF7B6", + "0x97A3": "0xF7B7", + "0x97B2": "0xF7B8", + "0x97B4": "0xF7B9", + "0x9AB1": "0xF7BA", + "0x9AB0": "0xF7BB", + "0x9AB7": "0xF7BC", + "0x9E58": "0xF7BD", + "0x9AB6": "0xF7BE", + "0x9ABA": "0xF7BF", + "0x9ABC": "0xF7C0", + "0x9AC1": "0xF7C1", + "0x9AC0": "0xF7C2", + "0x9AC5": "0xF7C3", + "0x9AC2": "0xF7C4", + "0x9ACB": "0xF7C5", + "0x9ACC": "0xF7C6", + "0x9AD1": "0xF7C7", + "0x9B45": "0xF7C8", + "0x9B43": "0xF7C9", + "0x9B47": "0xF7CA", + "0x9B49": "0xF7CB", + "0x9B48": "0xF7CC", + "0x9B4D": "0xF7CD", + "0x9B51": "0xF7CE", + "0x98E8": "0xF7CF", + "0x990D": "0xF7D0", + "0x992E": "0xF7D1", + "0x9955": "0xF7D2", + "0x9954": "0xF7D3", + "0x9ADF": "0xF7D4", + "0x9AE1": "0xF7D5", + "0x9AE6": "0xF7D6", + "0x9AEF": "0xF7D7", + "0x9AEB": "0xF7D8", + "0x9AFB": "0xF7D9", + "0x9AED": "0xF7DA", + "0x9AF9": "0xF7DB", + "0x9B08": "0xF7DC", + "0x9B0F": "0xF7DD", + "0x9B13": "0xF7DE", + "0x9B1F": "0xF7DF", + "0x9B23": "0xF7E0", + "0x9EBD": "0xF7E1", + "0x9EBE": "0xF7E2", + "0x7E3B": "0xF7E3", + "0x9E82": "0xF7E4", + "0x9E87": "0xF7E5", + "0x9E88": "0xF7E6", + "0x9E8B": "0xF7E7", + "0x9E92": "0xF7E8", + "0x93D6": "0xF7E9", + "0x9E9D": "0xF7EA", + "0x9E9F": "0xF7EB", + "0x9EDB": "0xF7EC", + "0x9EDC": "0xF7ED", + "0x9EDD": "0xF7EE", + "0x9EE0": "0xF7EF", + "0x9EDF": "0xF7F0", + "0x9EE2": "0xF7F1", + "0x9EE9": "0xF7F2", + "0x9EE7": "0xF7F3", + "0x9EE5": "0xF7F4", + "0x9EEA": "0xF7F5", + "0x9EEF": "0xF7F6", + "0x9F22": "0xF7F7", + "0x9F2C": "0xF7F8", + "0x9F2F": "0xF7F9", + "0x9F39": "0xF7FA", + "0x9F37": "0xF7FB", + "0x9F3D": "0xF7FC", + "0x9F3E": "0xF7FD", + "0x9F44": "0xF7FE" +} diff --git a/tests/dev1/dataHandle/common/bmp.ts b/tests/dev1/dataHandle/common/bmp.ts new file mode 100644 index 0000000..d60ee53 --- /dev/null +++ b/tests/dev1/dataHandle/common/bmp.ts @@ -0,0 +1,176 @@ +const decoder = new TextDecoder() +export class BmpDecoder { + pos: number + buffer: ArrayBuffer + is_with_alpha: boolean + bottom_up: boolean + flag: string + constructor(buffer: ArrayBuffer, is_with_alpha: boolean) { + this.pos = 0 + this.buffer = buffer + this.is_with_alpha = !!is_with_alpha + this.bottom_up = true + + this.flag = decoder.decode(this.buffer.slice(0, (this.pos += 2))) + + if (this.flag != 'BM') + throw new Error('Invalid BMP File') + // this.parseHeader(); + // this.parseRGBA(); + } + + parseHeader() {} + parseRGBA() {} +} + +export function createBmpFile( + imgData: ImageData, + paramDic: { [key: string]: string } = {}, +) { + let dpi = 300 + let bitPP = paramDic.bpp ? Number(paramDic.bpp) : 24 + let printResolution = Math.round(dpi * 39.3701) + + if ([1, 24, 32].includes(bitPP) == false) { + throw new Error(`不支持的${bitPP}bpp BMP`) + } + + let data = imgData.data + + let width = imgData.width + let height = imgData.height + let extraBytes = width % 4 + let rgbSize = data.length + switch (bitPP) { + case 1: + { + let rowSize = width / 8 + (width % 8 > 0 ? 1 : 0) + extraBytes = rowSize % 4 + rgbSize = height * (rowSize + extraBytes) + } + break + case 24: + { + let rowSize = 3 * width + extraBytes = rowSize % 4 + rgbSize = height * (rowSize + extraBytes) + } + break + } + let headerInfoSize = 40 + + /** ****************header */ + let flag = 'BM' + let reserved = 0 + let offset = 54 + let fileSize = rgbSize + offset + let planes = 1 + + let compress = 0 + let hr = printResolution + let vr = printResolution + let colors = 0 + let importantColors = 0 + let colorPaletteSize = 0 + if (bitPP == 1) { + colorPaletteSize = 2 * 4 + } + let buffer = new ArrayBuffer(offset + colorPaletteSize + rgbSize) + let tempBuffer = new DataView(buffer) + let pos = 0 + + // BMP Header + // ID + tempBuffer.setUint8(pos, flag.charCodeAt(0)) + pos += 1 + tempBuffer.setUint8(pos, flag.charCodeAt(1)) + pos += 1 + // size + tempBuffer.setUint32(pos, fileSize, true) + pos += 4 + + // Application specific + tempBuffer.setUint32(pos, reserved, true) + pos += 4 + // Offset + tempBuffer.setUint32(pos, offset, true) + pos += 4 + + // DIB Header + // Number of bytes in the DIB header (from this point) + tempBuffer.setUint32(pos, headerInfoSize, true) + pos += 4 + + tempBuffer.setUint32(pos, width, true) + pos += 4 + tempBuffer.setUint32(pos, height, true) + pos += 4 + + tempBuffer.setUint16(pos, planes, true) + pos += 2 + tempBuffer.setUint16(pos, bitPP, true) + pos += 2 + + tempBuffer.setUint16(pos, compress, true) + pos += 4 + + tempBuffer.setUint16(pos, rgbSize, true) + pos += 4 + + tempBuffer.setUint16(pos, hr, true) + pos += 4 + tempBuffer.setUint16(pos, vr, true) + pos += 4 + + tempBuffer.setUint16(pos, colors, true) + pos += 4 + tempBuffer.setUint16(pos, importantColors, true) + pos += 4 + + if (bitPP == 1) { + tempBuffer.setUint8(pos++, 0) // b + tempBuffer.setUint8(pos++, 0) // g + tempBuffer.setUint8(pos++, 0) // r + tempBuffer.setUint8(pos++, 0) // a + + tempBuffer.setUint8(pos++, 255) // b + tempBuffer.setUint8(pos++, 255) // g + tempBuffer.setUint8(pos++, 255) // r + tempBuffer.setUint8(pos++, 0) // a + } + + let pixData = '' + let writeBinData = () => { + tempBuffer.setUint8(pos++, Number.parseInt(pixData.padEnd(8, '0'), 2)) + pixData = '' + } + for (let y = height - 1; y >= 0; y--) { + let rowIndex = y * width * 4 + for (let x = 0; x < width; x++) { + let p = rowIndex + x * 4 + + if (bitPP == 1) { + if (pixData.length == 8) { + writeBinData() + } + let rgb = [data[p + 0], data[p + 1], data[p + 2]] + pixData += rgb.filter((v, i) => v > 170).length == 3 ? '1' : '0' // 170 以下小板缩略图条会断 + } else { + tempBuffer.setUint8(pos++, data[p + 2]) // b + tempBuffer.setUint8(pos++, data[p + 1]) // g + tempBuffer.setUint8(pos++, data[p + 0]) // r + if (bitPP == 32) { + tempBuffer.setUint8(pos++, data[p + 3]) // a + } + } + } + if (bitPP == 1 && pixData.length > 0) { + writeBinData() + } + for (let index = 0; index < extraBytes; index++) { + tempBuffer.setUint8(pos++, 0) + } + } + + return tempBuffer.buffer +} diff --git a/tests/dev1/dataHandle/common/core/Container.ts b/tests/dev1/dataHandle/common/core/Container.ts new file mode 100644 index 0000000..3b88e77 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/Container.ts @@ -0,0 +1,399 @@ +import { ClipType, PolyFillType } from 'js-angusj-clipper/web' +import type { Paths } from 'js-angusj-clipper/web' +import type { SubjectInput } from 'js-angusj-clipper/web/clipFunctions' +import { Box2 } from '../Box2' +import { clipperCpp } from '../ClipperCpp' +import type { CompareVectorFn } from '../ComparePoint' +import { ComparePoint } from '../ComparePoint' +import { ConvexHull2D } from '../ConvexHull2D' +import type { NestFiler } from '../Filer' +import type { Point } from '../Vector2' +import { equaln } from '../Util' +import { Vector2 } from '../Vector2' +import { PlaceType } from '../../confClass' //'../../vo/enums/PlaceType' +import { NestCache } from './NestCache' +import type { Part } from './Part' +import { PartGroup } from './Part' +import type { Path } from './Path' +import { Area, TranslatePath } from './Path' + +/** + * 当零件尝试放置后容器的状态,用于尝试放置,并且尝试贪心 + */ +interface PartPlacedContainerState +{ + p: Point + area?: number + hull?: Point[] + box?: Box2 +} + +/** + * 排料零件的容器,用来放置零件 + * 它是一块大板 + * 也可以是一个网洞 + * 也可以是余料 + */ +export class Container +{ + ParentId: number = -1// 容器bin来源 -1表示默认的bin,大于等于0表示来自板件网洞 + ChildrenIndex: number = 0// 网洞的索引位置 + ParentM: Point// 在旋转网洞时,和非旋转网洞时表现不一样. (网洞相对于父零件的位置,如果是使用旋转网洞则表示网洞的最下角位置) + + PlacedParts: Part[] = [] + // 放置状态 + PlacedArea = 0 + PlacedBox: Box2 + PlacedHull: Point[] + + StatusKey: string + + CompartePoint: CompareVectorFn + constructor(protected BinPath?: Path, private _PlaceType = PlaceType.Box, compare = 'xy') + { + if (BinPath) + this.StatusKey = `${this.BinPath.Id.toString()},${this._PlaceType}` + + this.CompartePoint = ComparePoint(compare) + } + + get UseRatio(): number + { + return this.PlacedBox.area / this.BinPath.Area + } + + private _NotPuts: Set[] = []// 已经无法放置的pathId + + private PrePut(part: Part): PartPlacedContainerState + { + // 无法容纳 + if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area) + return + + let cacheKey = `${this.StatusKey},${part.State.Contour.Id}` + let cacheP = NestCache.PositionCache[cacheKey] + // 读取缓存位置 + if (cacheP) + { + NestCache.count1++ + return this.Calc(part, cacheP) + } + + let binNfp = this.BinPath.GetInsideNFP(part.State.Contour) + if (!binNfp || binNfp.length === 0) + return + + // 首个 + if (this.PlacedParts.length === 0) + { + let p = this.GetFarLeftP(binNfp) + NestCache.PositionCache[cacheKey] = p + return this.Calc(part, p) + } + + // 快速退出 + let noSet = NestCache.NoPutCache[this.StatusKey] + if (noSet) + this._NotPuts.push(noSet) + for (let set of this._NotPuts) + { + if (set.has(part.State.Contour.Id)) + { + NestCache.count2++ + return + } + } + + let finalNfp = this.GetNFPs(part, binNfp) + if (!finalNfp || finalNfp.length === 0) + { + if (noSet) + noSet.add(part.State.Contour.Id) + else + { + noSet = new Set([part.State.Contour.Id]) + NestCache.NoPutCache[this.StatusKey] = noSet + } + return + } + + // 选择合适的放置点 + let minArea: number = Number.POSITIVE_INFINITY + let translate: Point + let bestBox: Box2 + let bestHull: Point[] + + let tempVec = new Vector2() + for (let nfp of finalNfp) + { + for (let p of nfp) + { + tempVec.set(p.x * 1e-4, p.y * 1e-4) + switch (this._PlaceType) + { + case PlaceType.Box: + { + let box2 = part.State.Contour.BoundingBox.clone() + box2.translate(tempVec) + let rectBox = this.PlacedBox.clone().union(box2) + let size = rectBox.getSize() + let area = size.x * size.y + if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1)) + { + translate = p + minArea = area + bestBox = rectBox + } + break + } + case PlaceType.Hull: + { + let pts = TranslatePath(part.State.Contour.Points, tempVec) + let nhull = ConvexHull2D([...pts, ...this.PlacedHull]) + let area = Math.abs(Area(nhull)) + if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1)) + { + translate = p + minArea = area + bestHull = nhull + } + break + } + case PlaceType.Gravity: + { + if (!translate || this.CompartePoint(p, translate) === -1) + translate = p + + break + } + default: + break + } + } + } + + if (translate) + { + NestCache.PositionCache[cacheKey] = translate + if (!bestBox) + bestBox = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate({ x: translate.x * 1e-4, y: translate.y * 1e-4 })) + return { p: translate, area: minArea, box: bestBox, hull: bestHull } + } + } + + private Calc(part: Part, p: Point): PartPlacedContainerState + { + let d: PartPlacedContainerState = { p } + + let m: Point = { x: p.x * 1e-4, y: p.y * 1e-4 } + + if (this.PlacedBox) + d.box = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate(m)) + else + d.box = part.State.Contour.BoundingBox.clone().translate(m) + + // 凸包 + if (this._PlaceType === PlaceType.Hull) + { + if (!this.PlacedHull) + { + d.hull = TranslatePath(part.State.Contour.Points, m) + d.area = part.State.Contour.Area + } + else + { + d.hull = ConvexHull2D([...this.PlacedHull, ...TranslatePath(part.State.Contour.Points, m)]) + d.area = Area(d.hull) + } + + d.area = Math.abs(d.area) + } + else + { + d.area = d.box.area + } + return d + } + + PutPart(p: Part, greedy = false): boolean + { + let bestD: PartPlacedContainerState + if (greedy) + { + let bestI: number + for (let i = 0; i < p.RotatedStates.length; i++) + { + p.StateIndex = i + let d = this.PrePut(p) + if (d && (!bestD || bestD.area > d.area + || ( + this._PlaceType === PlaceType.Hull + && equaln(bestD.area, d.area, 0.1) + && d.box.area < bestD.box.area + ) + )) + { + bestD = d + bestI = i + } + } + if (bestD) + p.StateIndex = bestI + } + else + bestD = this.PrePut(p) + + if (bestD) + { + p.PlacePosition = bestD.p + this.PlacedBox = bestD.box ?? this.PlacedBox + this.PlacedHull = bestD.hull + this.AppendPart(p, false) + return true + } + return false + } + + protected GetNFPs(part: Part, binNfp: Point[][]): Paths + { + // 合并(零件和所有已经放置零件的NFP) + let nfps: SubjectInput[] = [] + for (let placedPart of this.PlacedParts) + { + let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour) + if (!nfp) + return + for (let n of nfp) + { + let nnfp = TranslatePath(n, placedPart.PlacePosition) + nfps.push({ data: nnfp, closed: true }) + } + } + // 合并nfp + let combinedNfp = clipperCpp.lib.clipToPaths({ + subjectInputs: nfps, + clipType: ClipType.Union, + subjectFillType: PolyFillType.NonZero, + }) + + combinedNfp = clipperCpp.lib.cleanPolygons(combinedNfp, 100) + + if (combinedNfp.length === 0) + return + + // 减去nfp + let finalNfp = clipperCpp.lib.clipToPaths({ + subjectInputs: [{ data: binNfp, closed: true }], + clipInputs: [{ data: combinedNfp }], + clipType: ClipType.Difference, + subjectFillType: PolyFillType.NonZero, + }) + finalNfp = clipperCpp.lib.cleanPolygons(finalNfp, 100) + return finalNfp + } + + /** + * 将Part添加的Placed列表 + * @param part 零件,已经计算了放置状态 + * @param [calc] 是否计算当前的容器状态? + */ + private AppendPart(part: Part, calc = true): void + { + this.StatusKey += `,${part.State.Contour.Id}` + this.PlacedParts.push(part) + this.PlacedArea += part.State.Contour.Area + let m = { x: part.PlacePosition.x * 1e-4, y: part.PlacePosition.y * 1e-4 } + if (calc) + { + // 凸包 + if (this._PlaceType === PlaceType.Hull) + { + if (!this.PlacedHull) + this.PlacedHull = TranslatePath(part.State.Contour.Points, m) + else + this.PlacedHull = ConvexHull2D(this.PlacedHull.concat(TranslatePath(part.State.Contour.Points, m))) + } + } + if (calc || this._PlaceType !== PlaceType.Box) + { + if (this.PlacedBox) + this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(m)) + else + this.PlacedBox = part.State.Contour.BoundingBox.clone().translate(m) + } + } + + /** + * 得到最左边的点 + */ + protected GetFarLeftP(nfp: Point[][]): Point + { + let leftP: Point + for (let path of nfp) + { + for (let p of path) + { + if (!leftP || this.CompartePoint(p, leftP) === -1) + leftP = p + } + } + return leftP + } + + // #region -------------------------File------------------------- + // 对象从文件中读取数据,初始化自身 + ReadFile(file: NestFiler, parts: Part[]) + { + this.ParentId = file.Read() + this.ChildrenIndex = file.Read() + this.ParentM = file.Read() + let count = file.Read() as number + this.PlacedParts = [] + for (let i = 0; i < count; i++) + { + let index = file.Read() as number + let part = parts[index] + part.StateIndex = file.Read() + part.PlacePosition = file.Read() + this.PlacedParts.push(part) + } + + if (!this.PlacedBox) + this.PlacedBox = new Box2() + this.PlacedBox.min.fromArray(file.Read()) + this.PlacedBox.max.fromArray(file.Read()) + + return this + } + + // 对象将自身数据写入到文件. + WriteFile(file: NestFiler) + { + file.Write(this.ParentId) + file.Write(this.ChildrenIndex) + file.Write(this.ParentM) + + let parts: Part[] = [] + for (let p of this.PlacedParts) + { + if (p instanceof PartGroup) + parts.push(...p.Export()) + else + parts.push(p) + } + + file.Write(parts.length) + for (let p of parts) + { + file.Write(p.Id) + file.Write(p.StateIndex) + file.Write(p.PlacePosition) + } + + if (!this.PlacedBox) + this.PlacedBox = new Box2() + file.Write(this.PlacedBox.min.toArray()) + file.Write(this.PlacedBox.max.toArray()) + } + // #endregion +} diff --git a/tests/dev1/dataHandle/common/core/GNestConfig.ts b/tests/dev1/dataHandle/common/core/GNestConfig.ts new file mode 100644 index 0000000..b2b7b71 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/GNestConfig.ts @@ -0,0 +1,5 @@ +export const GNestConfig = { + RotateHole: true, // 在旋转零件的时候旋转网洞 + UsePartGroup: false, // 如果开启这个特性,将在第一次放置零件时,尝试计算完全对插的板件,并且使用它.(基因注册,模范夫妻) + UseOffsetSimplify: true, +} diff --git a/tests/dev1/dataHandle/common/core/Individual.ts b/tests/dev1/dataHandle/common/core/Individual.ts new file mode 100644 index 0000000..877126e --- /dev/null +++ b/tests/dev1/dataHandle/common/core/Individual.ts @@ -0,0 +1,274 @@ +import { arrayLast, arrayRemoveOnce } from '../ArrayExt' +import type { NestFiler } from '../Filer' +import { RandomIndex } from '../Random' +import type { PlaceType } from '../../vo/enums/PlaceType' +import { Container } from './Container' +import { GNestConfig } from './GNestConfig' +import { DefaultComparePointKeys } from './NestDatabase' +import type { Part } from './Part' +import type { Path } from './Path' + +/** + * 个体(表示某一次优化的结果,或者还没开始优化的状态) + * 个体是由一堆零件组成的,零件可以有不同的状态。 + * + * 个体单独变异 + * 可以是某个零件的旋转状态发生改变 + * 可以是零件的顺序发生改变 + * + * 个体交配(感觉暂时不需要) + * 个体和其他个体进行基因交换 + */ +export class Individual { + constructor(public Parts?: Part[], public mutationRate = 0.5, private bin?: Path, private OddmentsBins: Path[] = [], private ComparePointKeys: string[] = DefaultComparePointKeys) { } + + Fitness: number // (评估健康) 大板个数-1 + 最后一块板的利用率 + Containers: Container[] // 大板列表(已排 + HoleContainers: Container[]// 网洞列表 + OddmentsContainers: Container[]// 余料列表 + /** + * 评估健康程度 + */ + Evaluate(bestCount: number, greedy = false, type?: PlaceType) { + if (GNestConfig.RotateHole) + return this.EvaluateOfUseRotateHole(bestCount, greedy, type) + + this.Containers = [] + this.HoleContainers = [] + // 初始化余料列表 + this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])) + this.InitHoleContainers() + + // 余料优先 + let parts = this.Parts.filter(p => !this.OddmentsContainers.some(odd => odd.PutPart(p))) + + // 网洞优先 + parts = parts.filter(p => !this.HoleContainers.some(hole => hole.PutPart(p))) + + while (parts.length > 0) { + if (this.Containers.length > bestCount) { + this.Fitness = Math.ceil(bestCount) + 1 + return + } + const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]) + if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度 + { + const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP) + + const PutGroupF = (): boolean => { + // 基因注册 + const nextPartIndex = parts.findIndex(p => p !== maxP) + if (nextPartIndex !== -1) { + const nextPart = parts[nextPartIndex] + const groups = maxP.ParseGroup(nextPart, this.bin) + for (const group of groups) { + if (container.PutPart(group)) { + parts.splice(nextPartIndex, 1) + arrayRemoveOnce(parts, maxP) + return true + } + } + } + return false + } + if (GNestConfig.UsePartGroup && PutGroupF()) { } + else if (container.PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) } + } + parts = parts.filter(p => !container.PutPart(p, greedy)) + if (!greedy)// 如果没有贪心,排完后在贪心一下 + parts = parts.filter(p => !container.PutPart(p, true)) + this.Containers.push(container) + } + this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers)?.UseRatio ?? 0 + } + + /** + * 在网洞利用时,保持纹路正确 + * + * @param bestCount + * @param greedy + * @param type + */ + private EvaluateOfUseRotateHole(bestCount: number, greedy = false, type?: PlaceType) { + // 初始化余料列表 + this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])) + + this.Containers = [] + this.HoleContainers = [] + let parts = this.Parts.concat() + while (parts.length > 0) { + if (this.Containers.length > bestCount)// 提前结束,已经超过最佳用板量 + { + this.Fitness = Math.ceil(bestCount) + 1 + return + } + + const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]) + const holes: Container[] = [] + const PutPart = (part: Part, greedy: boolean): boolean => { + // 先放置在网洞内 + const isOk + = this.OddmentsContainers.some(oc => oc.PutPart(part, greedy)) // 余料 + || holes.some((h) => { + const isOk = h.PutPart(part, greedy) + return isOk + }) // 网洞 + || container.PutPart(part, greedy) // 大板 + + if (isOk) + this.AppendHoles(part, holes) + + return isOk + } + + if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度 + { + const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP) + + const PutGroupF = (): boolean => { + // 基因注册 + const nextPartIndex = parts.findIndex(p => p !== maxP) + if (nextPartIndex !== -1) { + const nextPart = parts[nextPartIndex] + const groups = maxP.ParseGroup(nextPart, this.bin) + for (const group of groups) { + if (PutPart(group, greedy)) { + parts.splice(nextPartIndex, 1) + arrayRemoveOnce(parts, maxP) + return true + } + } + } + return false + } + if (GNestConfig.UsePartGroup && PutGroupF()) { } + else if (PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) } + } + + parts = parts.filter(p => !PutPart(p, greedy)) + if (!greedy)// 如果没有贪心,排完后在贪心一下 + parts = parts.filter(p => !PutPart(p, true)) + + if (container.PlacedParts.length) + this.Containers.push(container) + } + this.Fitness = this.Containers.length - 1 + (arrayLast(this.Containers)?.UseRatio ?? 0) + } + + private AppendHoles(part: Part, holes: Container[]) { + for (let i = 0; i < part.Holes.length; i++) { + const hole = part.Holes[i] + const container = new Container(hole.Contour) + container.ParentId = part.Id + container.ChildrenIndex = i + container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint + this.HoleContainers.push(container) + holes.push(container) + } + } + + private InitHoleContainers() { + if (GNestConfig.RotateHole) + return + for (const part of this.Parts) { + for (let i = 0; i < part.Holes.length; i++) { + const hole = part.Holes[i] + const container = new Container(hole.Contour) + container.ParentId = part.Id + container.ChildrenIndex = i + container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint + this.HoleContainers.push(container) + } + } + } + + Clone() { + const p = new Individual(this.Parts.map(p => p.Clone()), this.mutationRate, this.bin, this.OddmentsBins, this.ComparePointKeys) + p.Mutate() + return p + } + + /** + * 突变 + */ + Mutate() { + if (this.mutationRate > 0.5) { + for (let i = 0; i < this.Parts.length; i++) { + const rand = Math.random() + if (rand < this.mutationRate * 0.5) { + // 和下一个调换顺序 + const j = i + 1 + if (j < this.Parts.length) + [this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]] + } + if (rand < this.mutationRate) + this.Parts[i].Mutate() + } + } + else { + // 洗牌 + const rand = Math.random() + if (rand < 0.5) { + const index = RandomIndex(this.Parts.length - 2) + const count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2) || 1 + const parts = this.Parts.splice(index, count) + this.Parts.push(...parts) + this.Parts[index].Mutate() + } + else { + for (let i = 0; i < this.Parts.length; i++) { + const rand = Math.random() + if (rand < this.mutationRate) { + this.Parts[i].Mutate() + i += 5 + } + } + } + } + + if (this.mutationRate > 0.2) + this.mutationRate -= 0.004 + return this + } + + // #region -------------------------File------------------------- + + // 对象从文件中读取数据,初始化自身 + ReadFile(file: NestFiler) { + const ver = file.Read()// ver + + this.Fitness = file.Read() + let count = file.Read() as number + this.Containers = [] + for (let i = 0; i < count; i++) + this.Containers.push(new Container().ReadFile(file, this.Parts)) + + count = file.Read() + this.HoleContainers = [] + for (let i = 0; i < count; i++) + this.HoleContainers.push(new Container().ReadFile(file, this.Parts)) + + count = file.Read() + this.OddmentsContainers = [] + for (let i = 0; i < count; i++) + this.OddmentsContainers.push(new Container().ReadFile(file, this.Parts)) + } + + // 对象将自身数据写入到文件. + WriteFile(f: NestFiler) { + f.Write(1)// ver + f.Write(this.Fitness) + f.Write(this.Containers.length) + for (const c of this.Containers) + c.WriteFile(f) + + f.Write(this.HoleContainers.length) + for (const c of this.HoleContainers) + c.WriteFile(f) + + f.Write(this.OddmentsContainers.length) + for (const c of this.OddmentsContainers) + c.WriteFile(f) + } + // #endregion +} diff --git a/tests/dev1/dataHandle/common/core/NestCache.ts b/tests/dev1/dataHandle/common/core/NestCache.ts new file mode 100644 index 0000000..508bba7 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/NestCache.ts @@ -0,0 +1,37 @@ +import type { Point } from '../Vector2' +import { Path } from './Path' + +export class NestCache +{ + static count1 = 0 + static count2 = 0// noset + + static PositionCache: { [key: string]: Point } = {} + static NoPutCache: { [key: string]: Set } = {} + private static CacheRect = new Map() + + /** + * 用于创建原点在0点的矩形路径 + */ + static CreatePath(x: number, y: number, knifRadius = 3.5): Path + { + let minX = -knifRadius + let maxX = x + knifRadius + let minY = -knifRadius + let maxY = y + knifRadius + return new Path([ + { x: minX, y: minY }, + { x: maxX, y: minY }, + { x: maxX, y: maxY }, + { x: minX, y: maxY }, + ]) + } + + static Clear() + { + this.count1 = 0 + this.count2 = 0 + this.CacheRect.clear() + this.PositionCache = {} + } +} diff --git a/tests/dev1/dataHandle/common/core/NestDatabase.ts b/tests/dev1/dataHandle/common/core/NestDatabase.ts new file mode 100644 index 0000000..5321dc9 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/NestDatabase.ts @@ -0,0 +1,82 @@ +import { NestFiler } from '../Filer' +import { Part } from './Part' +import { Path } from './Path' +import { PathGeneratorSingle } from './PathGenerator' + +export const DefaultComparePointKeys = ['xy', 'yx'] + +/** + * 排料数据库,用这个类来序列化需要排料的数据 + * 用于在Work间传输 + */ +export class NestDatabase { + Bin: Path // 默认的容器 + OddmentsBins: Path[]// 余料容器列表 + Paths: Path[] // 所有的Path都在这里 + Parts: Part[] // 所有的零件 + ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式 + + // #region -------------------------File------------------------- + // 对象从文件中读取数据,初始化自身 + ReadFile(file: NestFiler) { + const ver = file.Read() + let count = file.Read() as number + this.Paths = [] + for (let i = 0; i < count; i++) { + const path = new Path() + path.ReadFile(file) + this.Paths.push(path) + } + this.Bin = this.Paths[file.Read()] + PathGeneratorSingle.paths = this.Paths + count = file.Read() + this.Parts = [] + for (let i = 0; i < count; i++) { + const part = new Part() + part.ReadFile(file) + this.Parts.push(part) + } + + count = file.Read() + this.OddmentsBins = [] + for (let i = 0; i < count; i++) { + const path = new Path() + path.ReadFile(file) + this.OddmentsBins.push(path) + } + + if (ver > 1) + this.ComparePointKeys = file.Read() + return this + } + + // 对象将自身数据写入到文件. + WriteFile(file: NestFiler) { + file.Write(2) + file.Write(this.Paths.length) + for (const path of this.Paths) + path.WriteFile(file) + + file.Write(this.Bin.Id) + file.Write(this.Parts.length) + for (const part of this.Parts) + part.WriteFile(file) + + if (!this.OddmentsBins) + this.OddmentsBins = [] + file.Write(this.OddmentsBins.length) + for (const path of this.OddmentsBins) + path.WriteFile(file) + + file.Write(this.ComparePointKeys) + + return this + } + // #endregion + + get File() { + const f = new NestFiler() + this.WriteFile(f) + return f + } +} diff --git a/tests/dev1/dataHandle/common/core/OptimizeMachine.ts b/tests/dev1/dataHandle/common/core/OptimizeMachine.ts new file mode 100644 index 0000000..ac79dd0 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/OptimizeMachine.ts @@ -0,0 +1,163 @@ +import { arrayRemoveIf } from '../ArrayExt' +import { clipperCpp } from '../ClipperCpp' +import { Sleep } from '../Sleep' +import { Individual } from './Individual' +import { NestCache } from './NestCache' +import { DefaultComparePointKeys } from './NestDatabase' +import type { Part } from './Part' +import type { Path } from './Path' + +/** + * 优化器 + * 配置优化器 + * 放入零件 + * 按下启动 + * 按下暂停 + * 清理机台 + */ +export class OptimizeMachine { + // 配置 + Config: { + PopulationCount: number// 种群个数 + } + + Bin: Path // 默认的容器 + OddmentsBins: Path[]// 余料容器列表 + Parts: Part[] // 所有的零件 + ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式 + + // //计算重复的零件 TODO:需要对相同的零件提取出来 + // PartCount: number[] = []; + + private _IsSuspend = false + + protected _Individuals: Individual[]// 个体列表 + + constructor() { + this.Config = { PopulationCount: 50 } + } + + // 放入零件 + PutParts(parts: Part[]) { + if (globalThis.document) + parts = parts.slice() + arrayRemoveIf(parts, p => p.RotatedStates.length === 0) + this.Parts = parts + + // //计算重复的零件(暂时不用) + // for (let part of parts) + // { + // let count = this.PartCount[part.Id]; + // this.PartCount[part.Id] = count === undefined ? 1 : (count + 1); + // } + } + + callBack: (i: Individual) => Promise + + // 启动 + async Start() { + if (this.Parts.length === 0) + return + console.log(this.Parts.length) + this._IsSuspend = false + NestCache.Clear() + this.Parts.sort((p1, p2) => p2.State.Contour.Area - p1.State.Contour.Area) + this._Individuals = [new Individual(this.Parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys)] + for (let i = 1; i < this.Config.PopulationCount; i++) { + const parts = this.Parts.map(p => p.Clone()) + if (i < 3) { + for (let i = parts.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [parts[i], parts[j]] = [parts[j], parts[i]] + parts[i].Mutate() + } + } + this._Individuals.push(new Individual(parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys)) + } + // 2.执行 + await this.Run() + } + + calcCount = 0 + best = Number.POSITIVE_INFINITY + bestP: Individual + bestCount = 0 + private async Run() { + console.time('1') + if (this.Parts.length === 0) + return + // 开始自然选择 + while (!this._IsSuspend) // 实验停止信号 + { + const goBack = this.calcCount - this.bestCount > 8000 + // 1.适应环境(放置零件) + for (let i = 0; i < this._Individuals.length; i++) { + if (globalThis.document || !clipperCpp.lib) + await Sleep(0) + const p = this._Individuals[i] + if (this.calcCount < 1000 || this.calcCount % 1000 === 0) + p.Evaluate(this.best, true, i % 3) + else + p.Evaluate(this.best) + + if (!this.bestP || p.Fitness < this.bestP.Fitness) { + this.bestP = p + this.best = p.Fitness + await this.callBack(p) + } + if (goBack) + p.mutationRate = 0.5 + } + + this.calcCount += this._Individuals.length + if (this.calcCount % 100 === 0) { + await Sleep(0) + } + + this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness) + const bestP = this._Individuals[0] + // //回调最好的 + // if (bestP.Fitness < this.best) + // { + // this.best = bestP.Fitness; + // this.bestP = bestP; + // // console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2); + // if (this.callBack) + // await this.callBack(bestP); + // } + + // 自然选择 + this._Individuals.splice(-10)// 杀死它 + for (let i = 0; i < 4; i++) + this._Individuals.push(bestP.Clone()) + this._Individuals.push(this.bestP.Clone()) + for (let i = 0; i < 3; i++) + this._Individuals.push(this._Individuals[1].Clone()) + for (let i = 0; i < 2; i++) + this._Individuals.push(this._Individuals[2].Clone()) + // 全部突变 + for (const p of this._Individuals) { + if (p.Fitness !== undefined) + p.Mutate() + } + } + } + + // 暂停 + Suspend() { + console.timeEnd('1') + console.log(this.best, this.calcCount, NestCache.count1) + this._IsSuspend = true + console.log('暂停') + } + + // 继续 + Continue() { + this._IsSuspend = false + this.Run() + } + + // 清理机台 + Clear() { + } +} diff --git a/tests/dev1/dataHandle/common/core/ParseOddments.ts b/tests/dev1/dataHandle/common/core/ParseOddments.ts new file mode 100644 index 0000000..fe00cec --- /dev/null +++ b/tests/dev1/dataHandle/common/core/ParseOddments.ts @@ -0,0 +1,238 @@ +import { ClipType, EndType, JoinType, PolyFillType } from 'js-angusj-clipper/web' +import type { ClipInput } from 'js-angusj-clipper/web' +import { Box2 } from '..//Box2' +import { clipperCpp } from '../ClipperCpp' +import type { Container } from './Container' +import { NestCache } from './NestCache' +import { Path, PathScale, TranslatePath, TranslatePath_Self } from './Path' + +const SquarePath = NestCache.CreatePath(60, 60, 0) +const CanPutPaths = [ + NestCache.CreatePath(200, 200, 0), + NestCache.CreatePath(600, 100, 0), + NestCache.CreatePath(100, 600, 0), +] + +/** + * 分析排料结果的余料 + * @param container 排料结果的容器 + * @param binPath 容器的bin + * @param [knifeRadius] 刀半径(以便我们再次偏移) + * @param squarePath 使用一个正方形路径来简化余料轮廓 + * @param canPutPaths 使用可以放置的路径列表来测试余料是否可用,如果可用,则保留 + * @returns Path[] 轮廓的位置存储在OrigionMinPoint中 + */ +export function ParseOddments(container: Container, binPath: Path, knifeRadius: number = 3.5, squarePath: Path = SquarePath, canPutPaths: Path[] = CanPutPaths): Path[] +{ + // 构建轮廓数据 + let partPaths: ClipInput[] = container.PlacedParts.map((part) => + { + // 直接在这里偏移,而不缓存,应该没有性能问题 + let newPts = clipperCpp.lib.offsetToPaths({ + delta: knifeRadius * 1e4, + offsetInputs: [{ data: part.State.Contour.BigIntPoints, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], + })[0] + + let path = TranslatePath(newPts, { x: part.PlacePosition.x - 5e3, y: part.PlacePosition.y - 5e3 })// 因为移动了0.5,0.5,所以这里也要移动0.5 + return { data: path } + }) + // console.log('构建轮廓数据', partPaths) + // 所有的余料(使用布尔差集) + let oddmentsPolygon = clipperCpp.lib.clipToPolyTree({ + subjectInputs: [{ data: binPath.BigIntPoints, closed: true }], + clipInputs: partPaths, + clipType: ClipType.Difference, + subjectFillType: PolyFillType.NonZero, + }) + + // 现在我们用树状结构,应该不会自交了?(文档写了,返回的结果不可能重叠或者自交) + // 简化结果,避免自交 + // oddmentsPolygon = clipperCpp.lib.simplifyPolygons(oddmentsPolygon); + + function CreatePolygon(minx: number, miny: number, maxx: number, maxy: number) + { + return [ + { x: minx, y: miny }, + { x: maxx, y: miny }, + { x: maxx, y: maxy }, + { x: minx, y: maxy }, + ] + } + + let clipedPaths: Path[] = []// 已经减去网洞投影的余料轮廓列表 + + // 由于手动排版可能造成余料网洞,我们将网洞的盒子投影,然后裁剪余料,避免余料有网洞 + for (let node of oddmentsPolygon.childs) + { + let nodePolygon = node.contour + // 减去网洞 + if (node.childs.length) + { + let box = new Box2().setFromPoints(nodePolygon) + + let childBoxPolygon = node.childs.map((cnode) => + { + let cbox = new Box2().setFromPoints(cnode.contour) + let type = 0// 0左1右2上3下 + let minDist = Number.POSITIVE_INFINITY + + let letftDist = cbox.min.x - box.min.x + let rightDist = box.max.x - cbox.max.x + let topDist = box.max.y - cbox.max.y + let downDist = cbox.min.y - box.min.y + + if (rightDist < letftDist) + { + type = 1 + minDist = rightDist + } + + if (topDist < minDist) + { + type = 2 + minDist = topDist + } + + if (downDist < minDist) + type = 3 + + if (type === 0) + return CreatePolygon(box.min.x, cbox.min.y, cbox.max.x, cbox.max.y) + if (type === 1) + return CreatePolygon(cbox.min.x, cbox.min.y, box.max.x, cbox.max.y) + if (type === 2) + return CreatePolygon(cbox.min.x, cbox.min.y, cbox.max.x, box.max.y) + if (type === 3) + return CreatePolygon(cbox.min.x, box.min.y, cbox.max.x, cbox.max.y) + }) + + let splits = clipperCpp.lib.clipToPaths({ + subjectInputs: [{ data: nodePolygon, closed: true }], + clipInputs: childBoxPolygon.map((polygon) => { return { data: polygon } }), + clipType: ClipType.Difference, + subjectFillType: PolyFillType.NonZero, + }) + + for (let p of splits) + clipedPaths.push(new Path(PathScale(p, 1e-4))) + } + else + clipedPaths.push(new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))) + } + + let OddmentsPaths: Path[] = [] + for (let polygonPath of clipedPaths) + { + // 先获取内部的nfp + let insideNFPS = polygonPath.GetInsideNFP(squarePath) + + if (!insideNFPS) + continue + + let beferPolygons: ClipInput[] = [] + + for (let nfp of insideNFPS) + { + let nfpPath = new Path(PathScale(nfp, 1e-4)) + // 通过内部nfp还原实际轮廓 + let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, squarePath.BigIntPoints, true) + sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons) + + for (let poly of sumPolygons) + { + if (clipperCpp.lib.area(poly) < 0) + continue// 移除内部的,无意义的 + + let tempPath = new Path(poly.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))// 这里new一个新的,下面就复用这个 + if (canPutPaths.some(p => tempPath.GetInsideNFP(p)?.length))// 能塞的下指定的轮廓才会被留下 + { + if (beferPolygons.length) + { + // 移动到实际位置 + TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4) + + // 在这里裁剪之前的余料轮廓 + let tree = clipperCpp.lib.clipToPolyTree({ + subjectInputs: [{ data: poly, closed: true }], + clipInputs: beferPolygons, + clipType: ClipType.Difference, + subjectFillType: PolyFillType.NonZero, + }) + + for (let node of tree.childs) + { + if (node.childs.length) + continue + + tempPath = new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } })) + + // 继续简化 + tempPath = SimplifyPathOfSqPath(tempPath, squarePath) + + if (!tempPath) + continue + + OddmentsPaths.push(tempPath) + + // 偏移2把刀 + let offsetedPolygon = clipperCpp.lib.offsetToPaths({ + delta: knifeRadius * 2e4, + offsetInputs: [{ data: node.contour, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], + })[0] + beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料 + } + } + else + { + // 设置轮廓的位置 + tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x + tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y + OddmentsPaths.push(tempPath) + + // 将余料轮廓加入到裁剪轮廓中,用于裁剪后续的余料 + if (insideNFPS.length) + { + // 移动到实际位置 + TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4) + // 偏移2把刀 + let offsetedPolygon = clipperCpp.lib.offsetToPaths({ + delta: knifeRadius * 2e4, + offsetInputs: [{ data: poly, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], + })[0] + beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料 + } + } + } + } + } + } + // console.log('ParseOddments end', OddmentsPaths) + return OddmentsPaths +} + +// 使用矩形轮廓来简化余料轮廓(通常一进一出) +function SimplifyPathOfSqPath(polygonPath: Path, sqPath: Path): Path | undefined +{ + // 先获取内部的nfp + let insideNFPS = polygonPath.GetInsideNFP(sqPath) + if (insideNFPS.length > 0)// 目前一般只有1个,不知道会不会有多个 + { + let nfp = insideNFPS[0] + let nfpPath = new Path(PathScale(nfp, 1e-4)) + // 通过内部nfp还原实际轮廓 + let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, sqPath.BigIntPoints, true) + sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons) + + for (let poly of sumPolygons)// 通常是一个内部的+一个外部的 + { + if (clipperCpp.lib.area(poly) < 0) + continue// 移除内部的,无意义的 + + let tempPath = new Path(PathScale(poly, 1e-4)) + + tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x + tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y + return tempPath + } + } +} diff --git a/tests/dev1/dataHandle/common/core/Part.ts b/tests/dev1/dataHandle/common/core/Part.ts new file mode 100644 index 0000000..e34e12c --- /dev/null +++ b/tests/dev1/dataHandle/common/core/Part.ts @@ -0,0 +1,394 @@ +import type { Box2 } from '../Box2' +import type { NestFiler } from '../Filer' +import type { Point } from '../Vector2' +import { RandomIndex } from '../Random' +import { FixIndex } from '../Util' +import { Vector2 } from '../Vector2' +import { GNestConfig } from './GNestConfig' +import { NestCache } from './NestCache' +import { PartState } from './PartState' +import { Path } from './Path' +import { PathGeneratorSingle } from './PathGenerator' + +const EmptyArray = [] + +/** + * 零件类 + * 零件类可以绑定数据,也存在位置和旋转状态的信息 + * + * 初始化零件: + * 传入零件的轮廓,刀半径,包围容器(或者为空?) + * 初始化用于放置的轮廓。将轮廓首点移动到0点,记录移动的点P。 + * + * 零件放置位置: + * 表示零件轮廓首点的位置。 + * + * 零件的旋转: + * 表示零件轮廓按照首点(0)旋转。 + * + * 还原零件的放置状态: + * 同样将零件移动到0点 + * 同样将零件旋转 + * 同样将零件移动到指定的位置 + * 零件可能处于容器中,变换到容器坐标系 + * + */ +export class Part +{ + Id: number// 用于确定Part的唯一性,并且有助于在其他Work中还原 + private _Holes: PartState[] = [] + _RotateHoles: PartState[][] = [] + // 零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态 + StateIndex = 0 // 旋转状态 + RotatedStates: PartState[] = []// 可能的旋转状态列表 + PlacePosition: Point // 放置位置(相对于容器的位置) + + HolePosition: Point// + + // #region 临时数据(不会被序列化到优化线程) + UserData: T// 只用于还原零件的显示状态(或者关联到实际数据) + Parent: Part// 如果这个零件放置在了网洞中,这个Parent表示这个网洞所属的零件,我们可以得到这个零件的放置信息,并且可以从Container中的ParentM得到网洞相对于零件的位置 + PlaceCS: Matrix// 放置矩阵 Matrix4 + PlaceIndex: number// 放置的大板索引 + // #endregion + + GroupMap: { [key: number]: Part[] } = {} + get State(): PartState // 零件当前的状态 + { + return this.RotatedStates[this.StateIndex] + } + + get Holes(): PartState[] + { + if (GNestConfig.RotateHole) + return this._RotateHoles[this.StateIndex] || EmptyArray + return this._Holes + } + + // 初始化零件的各个状态,按360度旋转个数 + Init(path: Path, bin: Path, rotateCount = 4): this + { + let rotations: number[] = [] + let a = 2 * Math.PI / rotateCount + for (let i = 0; i < rotateCount; i++) + { + rotations.push(a * i) + } + this.Init2(path, bin, rotations) + return this + } + + // 初始化零件的各个状态,按旋转角度表 + Init2(path: Path, bin: Path, rotations: number[] = []): this + { + let pathP = path.OrigionMinPoint + let path_0 = PathGeneratorSingle.Allocate(path) + let pathSet = new Set() + + // 初始化零件的状态集合 + for (let pa of rotations) + { + let partState = new PartState() + partState.Rotation = pa + if (pa === 0) + { + partState.Contour = path_0 + partState.OrigionMinPoint = pathP + partState.MinPoint = path.OrigionMinPoint + } + else + { + let path_r = new Path(path.Points, pa) + partState.Contour = PathGeneratorSingle.Allocate(path_r) + partState.Contour.Area = path_0.Area + // 避免重复的Path进入State + if (pathSet.has(partState.Contour)) + continue + let p0 = path_r.OrigionMinPoint + let c = Math.cos(-pa) + let s = Math.sin(-pa) + let x1 = p0.x * c - p0.y * s + let y1 = p0.x * s + p0.y * c + partState.OrigionMinPoint = new Vector2(pathP.x + x1, pathP.y + y1) + + // 计算正确的最小点 + let tempPath = new Path(path.OrigionPoints, pa) + partState.MinPoint = tempPath.OrigionMinPoint + } + // 记录已有Path + pathSet.add(partState.Contour) + // 必须能放置 + if (bin.GetInsideNFP(partState.Contour)) + { + this.RotatedStates.push(partState) + PathGeneratorSingle.RegisterId(partState.Contour) + } + } + + // 为了复用NFP,不管第0个Path是否可用,都注册它. + if (this.RotatedStates.length > 4) + PathGeneratorSingle.RegisterId(path_0) + return this + } + + ParseGroup(partOther: Part, bin: Path): Part[] + { + let arr = this.GroupMap[partOther.Id] + if (arr) + return arr + + arr = [] + if (this.Holes.length || partOther.Holes.length) + return arr + this.GroupMap[partOther.Id] = arr + if (this.State.Contour.IsRect || partOther.State.Contour.IsRect) + return arr + if (this.State.Contour.Area > this.State.Contour.BoundingBox.area * 0.9) + return arr + if (partOther.State.Contour.Area > partOther.State.Contour.BoundingBox.area * 0.9) + return arr + + for (let i = 0; i < this.RotatedStates.length; i++) + { + let s1 = this.RotatedStates[i] + for (let j = 1; j < partOther.RotatedStates.length; j++) + { + let s2 = partOther.RotatedStates[j] + let nfps = s1.Contour.GetOutsideNFP(s2.Contour) + for (let nfp of nfps) + { + for (let k = 0; k < nfp.length * 2; k++) + { + let p: Point + if (k % 2 === 0) + { + p = { ...nfp[k / 2] } + } + else + { + let p1 = nfp[FixIndex(k / 2 - 0.5, nfp)] + let p2 = nfp[FixIndex(k / 2 + 0.5, nfp)] + p = { x: p1.x + p2.x, y: p1.y + p2.y } + p.x *= 0.5 + p.y *= 0.5 + } + p.x *= 1e-4 + p.y *= 1e-4 + + let newBox = s2.Contour.BoundingBox.clone().translate(p) + newBox.union(s1.Contour.BoundingBox) + + if (newBox.area < (s1.Contour.Area + s2.Contour.Area) * 1.3) + { + let partGroup = new PartGroup(this, partOther, i, j, p, newBox, bin) + if (partGroup.RotatedStates.length > 0 + && !arr.some(p => p.State.Contour === partGroup.State.Contour)// 类似的 + ) + { + arr.push(partGroup) + return arr + } + } + } + } + } + } + return arr + } + + // 添加网洞 + AppendHole(path: Path) + { + let hole = new PartState() + hole.Contour = PathGeneratorSingle.Allocate(path) + PathGeneratorSingle.RegisterId(hole.Contour) + hole.OrigionMinPoint = path.OrigionMinPoint + hole.Rotation = 0 + this._Holes.push(hole) + + if (GNestConfig.RotateHole) + for (let i = 0; i < this.RotatedStates.length; i++) + { + let r = this.RotatedStates[i].Rotation + let arr = this._RotateHoles[i] + if (!arr) + { + arr = [] + this._RotateHoles[i] = arr + } + + if (r === 0) + { + hole.MinPoint = path.OrigionMinPoint + arr.push(hole) + } + else + { + let newPath = new Path(path.Points, r) + let newHole = new PartState() + newHole.Rotation = r + newHole.Contour = PathGeneratorSingle.Allocate(newPath) + PathGeneratorSingle.RegisterId(newHole.Contour) + newHole.OrigionMinPoint = newPath.OrigionMinPoint + + // 计算正确的最小点 + let tempPath = new Path(path.OrigionPoints, r) + newHole.MinPoint = tempPath.OrigionMinPoint + + arr.push(newHole) + } + } + } + + // TODO:因为现在实现的是左右翻转,所以会出现角度匹配不完全的问题(缺失上下翻转) + Mirror(doubleFace: boolean) + { + let states = this.RotatedStates + let holes = this._Holes + let roholes = this._RotateHoles + if (!doubleFace) + { + this.RotatedStates = [] + this._Holes = [] + this._RotateHoles = [] + } + let count = states.length + for (let i = 0; i < count; i++) + { + let s = states[i] + let ns = s.Mirror() + if (ns && !this.RotatedStates.some(state => state.Contour === s.Contour)) + { + this.RotatedStates.push(ns) + + if (this._Holes.length > 0) + this._Holes.push(holes[i].Mirror()) + + if (this._RotateHoles.length > 0) + this._RotateHoles.push(roholes[i].map(s => s.Mirror())) + } + } + } + + // 浅克隆 + Clone() + { + let part = new Part() + part.Id = this.Id + part.UserData = this.UserData + part.RotatedStates = this.RotatedStates + part.StateIndex = this.StateIndex + part._Holes = this._Holes + part._RotateHoles = this._RotateHoles + return part + } + + // 旋转起来,改变自身旋转状态(变异) + Mutate(): this + { + this.StateIndex = RandomIndex(this.RotatedStates.length, this.StateIndex) + return this + } + + // #region -------------------------File------------------------- + ReadFile(file: NestFiler) + { + this.Id = file.Read() + let count = file.Read() as number + this.RotatedStates = [] + for (let i = 0; i < count; i++) + { + let state = new PartState() + state.ReadFile(file) + this.RotatedStates.push(state) + } + + // 无旋转网洞 + count = file.Read() + this._Holes = [] + for (let i = 0; i < count; i++) + { + let state = new PartState() + state.ReadFile(file) + this._Holes.push(state) + } + + // 旋转网洞 + count = file.Read() + this._RotateHoles = [] + for (let i = 0; i < count; i++) + { + let count2 = file.Read() as number + + let holes: PartState[] = [] + for (let j = 0; j < count2; j++) + { + let state = new PartState() + state.ReadFile(file) + holes.push(state) + } + this._RotateHoles.push(holes) + } + } + + WriteFile(file: NestFiler) + { + file.Write(this.Id) + file.Write(this.RotatedStates.length) + for (let state of this.RotatedStates) + state.WriteFile(file) + + // 非旋转网洞 + file.Write(this._Holes.length) + for (let hole of this._Holes) + hole.WriteFile(file) + + // 写入旋转网洞 + file.Write(this._RotateHoles.length) + for (let holes of this._RotateHoles) + { + file.Write(holes.length) + for (let hole of holes) + { + hole.WriteFile(file) + } + } + } + // #endregion +} + +// 零件组合 +export class PartGroup extends Part +{ + constructor(public part1: Part, + public part2: Part, + public index1: number, + public index2: number, + public p: Point, + public box: Box2, + bin: Path, + ) + { + super() + let size = box.getSize(new Vector2()) + this.Init2(NestCache.CreatePath(size.x, size.y, 0), bin, [0]) + } + + Export(): Part[] + { + this.part1.StateIndex = this.index1 + this.part2.StateIndex = this.index2 + + this.part1.PlacePosition = { + x: this.PlacePosition.x - this.box.min.x * 1e4, + y: this.PlacePosition.y - this.box.min.y * 1e4, + } + + this.part2.PlacePosition = { + x: this.PlacePosition.x - this.box.min.x * 1e4 + this.p.x * 1e4, + y: this.PlacePosition.y - this.box.min.y * 1e4 + this.p.y * 1e4, + } + + return [this.part1, this.part2] + } +} diff --git a/tests/dev1/dataHandle/common/core/PartState.ts b/tests/dev1/dataHandle/common/core/PartState.ts new file mode 100644 index 0000000..0275914 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/PartState.ts @@ -0,0 +1,61 @@ +import type { Point } from '../Vector2' +import type { NestFiler } from '../Filer' +import { Path } from './Path' +import { PathGeneratorSingle } from './PathGenerator' + +/** + * 用于存放零件旋转后的状态 + * 记录了用于放置时的轮廓。该轮廓总是首点等于0,便于放置时的计算。 + */ +export class PartState +{ + Rotation: number + OrigionMinPoint: Point// 使用 Rotation(O - OrigionMinPoint) 将零件变换到和排料矩形状态相同的状态,然后使用PlacePoint(O)将零件设置到正确的状态 + + MinPoint: Point// 这个状态下的最小点 + Contour: Path// 轮廓 + + IsMirror: boolean = false + MirrorOriginMinPoint: Point + + Mirror(): PartState + { + if (this.Contour.IsRect) + return + + let mpts = this.Contour.Points.map((p) => { return { x: -p.x, y: p.y } }).reverse() + let path = new Path(mpts, 0) + let partState = new PartState() + partState.Contour = PathGeneratorSingle.Allocate(path) + PathGeneratorSingle.RegisterId(partState.Contour) + partState.Rotation = this.Rotation + partState.OrigionMinPoint = this.OrigionMinPoint + partState.MinPoint = this.MinPoint + partState.IsMirror = true + partState.MirrorOriginMinPoint = path.OrigionMinPoint + return partState + } + + // #region -------------------------File------------------------- + ReadFile(file: NestFiler) + { + this.Rotation = file.Read() + this.OrigionMinPoint = file.Read() + this.MinPoint = file.Read() + + let index = file.Read() as number + this.Contour = PathGeneratorSingle.paths[index] + + if (!this.Contour) + console.error('无法得到PartState的轮廓!') + } + + WriteFile(file: NestFiler) + { + file.Write(this.Rotation) + file.Write(this.OrigionMinPoint) + file.Write(this.MinPoint) + file.Write(this.Contour.Id) + } + // #endregion +} diff --git a/tests/dev1/dataHandle/common/core/Path.ts b/tests/dev1/dataHandle/common/core/Path.ts new file mode 100644 index 0000000..1338038 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/Path.ts @@ -0,0 +1,381 @@ +import { Box2 } from '../Box2.js' +import { clipperCpp } from '../ClipperCpp.js' +import type { NestFiler } from '../Filer.js' +import type { Point } from '../Vector2.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 +} diff --git a/tests/dev1/dataHandle/common/core/PathGenerator.ts b/tests/dev1/dataHandle/common/core/PathGenerator.ts new file mode 100644 index 0000000..7a5381a --- /dev/null +++ b/tests/dev1/dataHandle/common/core/PathGenerator.ts @@ -0,0 +1,87 @@ +import { FixIndex, equaln } from '../Util' +import type { Path } from './Path' + +/** + * 轮廓路径构造器 + * 传递一组简化后的点表过来,如果已经有同样的点表时,返回已经生产的Path,避免重复产生Path。 + * 使用相同的PATH有复用路径缓存。 + * + * 每次进行优化时,必须清理构造器,保证Path生成是对本次优化唯一。 + */ +class PathGenerator +{ + paths: Path[] = [] + pathAreaMap: { [key: string]: Path[] } = {} + + // 缓存命中次数 + cacheCount = 0 + + /** + * 如果存在同样的轮廓,则返回已经构造的轮廓, + * 如果没有,则返回自身,并且注册它。 + * 如果id没有被注册,那么证明它无法放置在bin中 + */ + Allocate(path: Path): Path + { + let area = path.Area.toFixed(0) + let paths = this.pathAreaMap[area] + if (paths) + { + for (let ps of paths) + { + if (EqualPath(ps, path)) + { + this.cacheCount++ + return ps + } + } + paths.push(path) + } + else + this.pathAreaMap[area] = [path] + return path + } + + RegisterId(path: Path) + { + if (path.Id === undefined) + { + path.Id = this.paths.length + this.paths.push(path) + } + } + + Clear() + { + this.paths = [] + this.pathAreaMap = {} + this.cacheCount = 0 + } +} + +/** + * 两路径相等,点表个数相等且每个点都相似 + */ +function EqualPath(path1: Path, path2: Path): boolean +{ + if (path1.Points.length !== path2.Points.length) + return false + + let p0 = path1.Points[0] + let p2Index = path2.Points.findIndex((p) => + { + return equaln(p.x, p0.x, 1e-3) && equaln(p.y, p0.y, 1e-3) + }) + + for (let i = 0; i < path1.Points.length; i++) + { + let p1 = path1.Points[i] + let p2 = path2.Points[FixIndex(p2Index + i, path2.Points)] + + if (!equaln(p1.x, p2.x, 1e-4) || !equaln(p1.y, p2.y, 1e-4)) + return false + } + return true +} + +export const PathGeneratorSingle = new PathGenerator() diff --git a/tests/dev1/dataHandle/common/core/PlaceType.ts b/tests/dev1/dataHandle/common/core/PlaceType.ts new file mode 100644 index 0000000..6322309 --- /dev/null +++ b/tests/dev1/dataHandle/common/core/PlaceType.ts @@ -0,0 +1,11 @@ + +/**排牌类型:Hull=0凸包模式 (凸包面积) Box=1盒子模式 (长x宽) Gravity=2重力模式(重力) */ +export enum PlaceType +{ + /**凸包模式 (凸包面积) */ + Hull = 0, + /**盒子模式 (长乘以宽) */ + Box = 1, + /**重力模式(重力) */ + Gravity = 2 +} diff --git a/tests/dev1/dataHandle/common/cutorder/CutOrder.ts b/tests/dev1/dataHandle/common/cutorder/CutOrder.ts new file mode 100644 index 0000000..ea8a48f --- /dev/null +++ b/tests/dev1/dataHandle/common/cutorder/CutOrder.ts @@ -0,0 +1,387 @@ + +import { Point2d } from '../../common/base/CAD.js'; +import { ArrayExt } from '../../common/base/ArrayExt.js'; +import { BlockRegion, PlaceBlock, PlaceBoard, PlaceMaterial } from '../../confClass.js'; +import { Point } from '../Vector2.js'; +import { BlockPlus } from '../LayoutEngine/BlockPlus.js'; +import { + KLSC, YH_bang + +} from '../../confClass.js'; +import { PlacePosition } from '../PlacePosition.js'; +import { BlockHelper } from '../LayoutEngine/BlockHelper.js'; +/** 开料顺序类 */ +export class CutOrder { + /** 开料顺序 */ + static autoSetCutOrder(materialList: PlaceMaterial[], config, useNewSort = false) { + // let order = PlaceStore.order; + + // console.log(order.materialList); + let newMaterialList = [...materialList] + for (let pm of newMaterialList) { + for (let pb of pm.boardList) { + this.autoCalcCutOrder(pm, pb, config, useNewSort); + } + } + return newMaterialList + } + /** 自动计算开料顺序 useNewSort是否使用新开料顺序算法*/ + static autoCalcCutOrder(pm: PlaceMaterial, pb: PlaceBoard, config, useNewSort = false) { + if (pb.isLocked) return; + let blocks = pb.blockList; + + if (blocks.length == 0) return; + if (blocks.length == 1) { + blocks[0].cutOrder = 1; + } + else { + let isUseNewSort = useNewSort; //使用新开料顺序算法 + if (isUseNewSort && checkBoardCross()) isUseNewSort = false; //异形穿插的大板使用旧的开料顺序算法 + if (isUseNewSort) { + this.autoSortBlockNew(pm, pb); + } + else { + this.autoSortBlock(pm, pb, config); + } + } + + console.log('开料顺序', pm, pb, pb.blockList.map(e => e.cutOrder)); + // //自动设置下刀点 + // CutPointHelper.autoFindCutPoint(pm, pb); + + /** 判断大板是否有交叉的大板(包括、包含) */ + function checkBoardCross() { + for (let i = 0; i < pb.blockList.length; i++) { + for (let j = i + 1; j < pb.blockList.length; j++) { + if (checkCross(pb.blockList[i], pb.blockList[j])) return true; + } + } + return false; + } + + function checkCross(b1: PlaceBlock, b2: PlaceBlock): boolean { + let c1 = { x: b1.placeX + b1.placeWidth / 2, y: b1.placeY + b1.placeLength / 2 }; + let c2 = { x: b2.placeX + b2.placeWidth / 2, y: b2.placeY + b2.placeLength / 2 }; + + return Math.abs(c1.x - c2.x) < (b1.placeWidth / 2 + b2.placeWidth / 2) && Math.abs(c1.y - c2.y) < (b1.placeLength / 2 + b2.placeLength / 2); + } + + return pb + } + + + /** 新开料顺序算法 */ + static autoSortBlockNew(pm: PlaceMaterial, pb: PlaceBoard) { + let selectBs = pb.blockList; + let beginId = 0; + const has0 = pb.blockList.filter(t => t.cutOrder == 0); + const has1 = pb.blockList.filter(t => t.cutOrder > 0); + const has2 = pb.blockList.filter(t => t.cutOrder < 0); + //有手动指定开料顺序的 + if (has0.length > 0 && (has1.length + has2.length) > 0) { + selectBs = has0; + if (has1.length > 0) //开头的 + { + const bs = has1.sort((a, b) => a.cutOrder - b.cutOrder); + for (const b of bs) { + beginId++; + b.cutOrder = beginId; + } + } + if (has2.length > 0) //结尾的 + { + const bs = has2.sort((a, b) => a.cutOrder - b.cutOrder); + let endId = has0.length + has1.length; + for (const b of bs) { + endId++; + b.cutOrder = endId; + } + } + } + + let bangs: YH_bang[] = []; + let blocks = new Array(); + for (let i = 0; i < selectBs.length; i++) { + let block = selectBs[i]; + let bangid = i + 1; + let line = 0; + let x = block.placeX; + let y = block.placeY; + let pbg = block.placeLength; + let pbk = block.placeWidth; + let ishb = false; //是否参与合并的板 + let isbig = false; //是否为合并的大板; + blocks[bangid] = block; + + bangs.push({ + bangid, + line: 0, + pbg, + pbk, + x, + y, + ishb: false, + hb: [], + isbig: false, + isqg: false, + isgr: false, + gr: [], + grid: -1 + }); + } + let dt = pm.diameter + pm.cutKnifeGap; + let k = pb.width; + let g = pb.length; + + let xdsc = new KLSC(bangs, k, g, dt, 0, 0, 1); + let rt = xdsc.SCid; + // let rt = JSXDSC(bangs, dt, k, g); + + if (rt.length < selectBs.length) return; + for (let i = 0; i < rt.length; i++) { + let bid = rt[i]; + beginId++; + blocks[bid].cutOrder = beginId; + } + } + + /** 设置下刀点 */ + static setCutPoint(block: PlaceBlock, cutRegion: BlockRegion) { + block.cutRegion = cutRegion; + let x = 0, + y = 0; + + if (cutRegion == BlockRegion.RIGHT_BOTTOM) + // if (cutRegion == BlockRegion.LEFT_BOTTOM) + { + x = block.placeWidth; + } + if (cutRegion == BlockRegion.RIGHT_TOP) + // if (cutRegion == BlockRegion.RIGHT_BOTTOM) + { + x = block.placeWidth; + y = block.placeLength; + } + if (cutRegion == BlockRegion.LEFT_TOP) + // if (cutRegion == BlockRegion.RIGHT_TOP) + { + y = block.placeLength; + } + + block.cutPointId = CutOrder.findCutPoint(block, x, y); + } + + /**计算下刀点 */ + private static findCutPoint(block: PlaceBlock, x: number, y: number): number { + let list = BlockPlus.getBorder(block); + let p = new Point2d(x, y); + let mV = Number.MAX_VALUE; + let index = 0; + for (let i = 0; i < list.length; i++) { + let v = p.DistensTo(list[i].StartPoint); + if (v < mV) { + mV = v; + index = i; + } + } + return index; + } + + /**获得下刀点坐标 */ + static getCutPointInBloard(block: PlaceBlock): Point { + let curves = BlockPlus.getBorder(block); + while (block.cutPointId >= curves.length) { + block.cutPointId = block.cutPointId - curves.length; + } + while (block.cutPointId < 0) { + block.cutPointId = block.cutPointId + curves.length; + } + let p = curves[block.cutPointId].StartPoint; + //内收 一个刀直径 + let rx = p.m_X; + let ry = p.m_Y; + let offv = + block.blockDetail.offsetKnifeRadius == 0 ? 6 : block.blockDetail.offsetKnifeRadius * 2; + if (p.m_X == 0) rx = offv; + if (p.m_X == block.placeWidth) rx = block.placeWidth - offv; + if (p.m_Y == 0) ry = offv; + if (p.m_Y == block.placeLength) ry = block.placeLength - offv; + + return new Point(block.placeX + rx, block.placeY + ry); + } + + //原开料顺序算法-------------------------------------------------------- + + //原开料顺序算法 + static autoSortBlock(pm: PlaceMaterial, pb: PlaceBoard, config: any) { + let blocks = pb.blockList; + //将小板位置恢复到左下角靠板的状态 + PlacePosition.resetPlacePosition(pb, config); + + //清除 开料顺序 + //如果全部都有开料序号,(重新优化,或移动小板等需要清除开料顺序) + //手动设置开料顺序一半时,不能把设置的开料顺序清空 + if (!blocks.some(t => t.cutOrder == 0)) { + blocks.forEach(t => (t.cutOrder = 0)); + } + + /**板板之间距离 */ + let cutGap = pm.diameter + pm.cutKnifeGap; + let sortId = ArrayExt.max(blocks, t => t.cutOrder) + 1; + + //优先排大板内部的小板 + for (let block of blocks) { + if (block.cutOrder != 0) continue; + let blocks_innner = BlockHelper.getInnerBlock(block, blocks); + if (blocks_innner && blocks_innner.length > 0) { + while (true) { + let topB = this.findTopBlock(blocks_innner); + if (topB == null) break; + let coverBlock = CutOrder.findCoverBlock(blocks_innner, topB); + coverBlock.cutOrder = sortId++; + } + } + } + + //优先排四周(50mm)的小板 + + //优先排左侧小板 + this.autoSortBlockLeft(blocks, cutGap); + //优先排下侧小板 + this.autoSortBlockBottom(blocks, cutGap); + + sortId = ArrayExt.max(blocks, t => t.cutOrder, t => true, 0) + 1; + + while (true) { + let topB = this.findTopBlock(blocks); + if (topB == null) break; + let coverBlock = CutOrder.findCoverBlock(blocks, topB); + coverBlock.cutOrder = sortId++; + CutOrder.setCutPoint(coverBlock, BlockRegion.LEFT_BOTTOM); + } + if (blocks.some(t => t.cutOrder < 0)) //有手工设置倒数开料的 + { + //将开料顺序为负数的设置成最后 + for (let b of blocks) { + if (b.cutOrder < 0) b.cutOrder = b.cutOrder + blocks.length + 1; + } + } + else //全自动 开料顺序,比较最后2片,倒数第二片是否更优 + { + let lastBlock = blocks.find(t => t.cutOrder == blocks.length); + let lastBlock2 = blocks.find(t => t.cutOrder == blocks.length - 1); + + if (lastBlock && lastBlock2) { + if (lastBlock.area < 0.3 && lastBlock2.area - 0.1 > lastBlock.area) { + lastBlock.cutOrder--; + lastBlock2.cutOrder++; + } + } + } + + //将小板位置 恢复到 系统配置的靠板 状态 + PlacePosition.resetPlacePosition(pb, config); + } + + + /**手工排序:如果 cutOrder == 0 按当前板顺序,如果 >0 则设置为值,板上比他的大的设置为0 */ + static resetCutOrder(pb: PlaceBoard, block: PlaceBlock, cutOrder: number = 0) { + if (cutOrder > 0) { + block.cutOrder = cutOrder; + } + else { + for (let block of pb.blockList) { + if (block.cutOrder >= cutOrder) { + block.cutOrder = 0; + } + } + block.cutOrder = cutOrder; + } + } + + /**优先排大板左侧的小板 (细长条) */ + private static autoSortBlockLeft(blocks: PlaceBlock[], curtGap: number) { + // let sysConfig = PlaceStore.sysConfig; + let blockList = ArrayExt.orderBy(blocks, t => t.placeX, t => -t.placeY); + for (const b of blockList) { + // if (b.cutOrder != 0 || b.placeWidth > SysConfig.AutoSortingMinWidth) continue; + if (b.cutOrder != 0) continue; + + //查询挡住的板个数 : 在b左边,且b的前 板件之内 + let count = ArrayExt.count( + blockList, + t => + t.cutOrder == 0 && + b.placeX > t.placeX + t.placeWidth && + t.placeY < b.placeY + b.placeLength && + t.placeY + t.placeLength > b.placeY + ); + if (count == 0) //判断是否在其他板内部 + { + let count2 = ArrayExt.count( + blockList, + t => + t.cutOrder == 0 && + b.placeX >= t.placeX && b.placeX < t.placeX + t.placeWidth && + t.placeY > b.placeY && t.placeY < b.placeY + b.placeLength + ); + count += count2; + } + if (count == 0) { + b.cutOrder = ArrayExt.max(blockList, t => t.cutOrder, t => true, 0) + 1; + CutOrder.setCutPoint(b, BlockRegion.RIGHT_TOP); + } + } + } + + /**优先排大板下侧的小板 (短粗条) */ + private static autoSortBlockBottom(blocks: PlaceBlock[], curtGap: number) { + // let SysConfig = PlaceStore.sysConfig; + //排序, + let list = ArrayExt.orderBy(blocks, t => t.placeY, t => -t.placeX); + for (const b of list) { + // if (b.cutOrder != 0 || b.placeLength > SysConfig.AutoSortingMinWidth) continue; + if (b.cutOrder != 0) continue; + + //查询挡住的板个数 : 在b上面边,且b的前 板件之内 + let count = ArrayExt.count(list, t => + t.cutOrder == 0 && + b.placeY > t.placeY + t.placeLength && + t.placeX < b.placeX + b.placeWidth && + t.placeX + t.placeWidth > b.placeX); + if (count == 0) { + b.cutOrder = ArrayExt.max(list, t => t.cutOrder, t => true, 0) + 1; + CutOrder.setCutPoint(b, BlockRegion.LEFT_TOP); + } + } + } + + //block列表中找到最远的板 + static findTopBlock(blocks: PlaceBlock[]): PlaceBlock { + return ArrayExt.last(blocks, t => t.cutOrder == 0, t => t.placeY + t.placeLength, t => t.placeX + t.placeWidth); + } + + //寻找挡在block之前(x+)的板 + private static findCoverBlock(blocks: PlaceBlock[], block: PlaceBlock): PlaceBlock { + let unSortCount = ArrayExt.count(blocks, t => t.cutOrder == 0); + if (unSortCount == 1) return block; + let blocks_cover: PlaceBlock[] = []; + + //可以挡住block 的板列表: 在block 右边(posx 大) ,顶点在 block 之间 + blocks_cover = ArrayExt.where(blocks, t => + t.cutOrder == 0 + && t != block + && t.placeX > block.placeX + && t.placeY + t.placeLength > block.placeY); + //如果没有挡住的板,则返回最高板 + if (blocks_cover.length == 0) return block; + let nextBlock = ArrayExt.last(blocks_cover, t => true, t => t.placeY + t.placeLength); + return CutOrder.findCoverBlock(blocks, nextBlock); + } + + + + + +} + diff --git a/tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts b/tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts new file mode 100644 index 0000000..6d32a02 --- /dev/null +++ b/tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts @@ -0,0 +1,440 @@ + +import { ArrayExt } from "../../common/base/ArrayExt.js"; +import { Curve2d } from "../../common/base/CAD.js"; +import { BlockBorderPoint, PlaceBlock, PlaceBoard, PlaceMaterial } from "../../confClass.js"; +import { BlockHelper } from "../LayoutEngine/BlockHelper.js"; +import { BlockPlus } from "../LayoutEngine/BlockPlus.js"; +import { Point } from "../Vector2.js"; + +//下刀点的逻辑类 +export class CutPointHelper { + /** 手动 设置下刀点 + * 获得离鼠标最近的顶角 距离都大于50 则返回null */ + static getCutPointWithClick(block: PlaceBlock, mousePos: Point): BlockBorderPoint | null { + let curves = BlockPlus.getBorder(block); + let dis = Number.MAX_VALUE; + let point: Point; + let index = -1; + for (let i = 0; i < curves.length; i++) { + let line = curves[i]; + let d = Math.pow(line.StartPoint.m_X + block.placeX - mousePos.x, 2) + Math.pow(line.StartPoint.m_Y + block.placeY - mousePos.y, 2); + if (d < dis) { + point = new Point(line.StartPoint.m_X, line.StartPoint.m_Y); + dis = d; + index = i; + } + } + if (index == -1) return null; //距离太远,不选择 + let apexId = BlockHelper.getApexAngleNumFromBlock(block, point); + block.cutPointId = index; + return new BlockBorderPoint(block, point.x, point.y, index, apexId); + } + /** 计算所有板材的下刀点 */ + static async autoFindAllCutPoint(pmList: PlaceMaterial[]) { + for (const pm of pmList) { + for (const pb of pm.boardList) { + await this.autoFindCutPoint(pm, pb) + } + } + } + //以下是自动设置 下刀点 + + static gap = 7; + static cutedSpace = 100; //走刀100内 不能算靠板 + static lines_V: any[] = []; //垂直线, x ,y1,y2 + static lines_H: any[] = []; //水平线 x1,x2, y; + /** 计算当前板材的下刀点 + * 若传入 block 则只计算block的下刀点 + */ + static autoFindCutPoint(pm: PlaceMaterial, pb: PlaceBoard, block: PlaceBlock | null = null) { + if (pb.blockList.length == 0) return; + //初始化 + this.gap = pm.diameter + pm.cutKnifeGap; + this.lines_H = []; //开料水平线 + this.lines_V = []; //开料垂直线 + + //大板外轮廓 边缘线 + if (pb.points.length == 0) { + let p0 = { x: 0, y: 0 }; + let p1 = { x: pb.width, y: 0 }; + let p2 = { x: pb.width, y: pb.length }; + let p3 = { x: 0, y: pb.length }; + this.pushLine(p0, p1); + this.pushLine(p1, p2); + this.pushLine(p2, p3); + this.pushLine(p3, p0); + } + else { + for (let i = 0; i < pb.points.length; i++) { + let j = i + 1; + if (j == pb.points.length) j = 0; + this.pushLine(pb.points[i], pb.points[j]); + } + } + + //有板的时候,除了该板计算,其他的板都不计算下刀点 + let hasBlock = (block != null); + + let blocks = ArrayExt.where(pb.blockList, t => t.cutOrder > 0, t => t.cutOrder); + for (let b of blocks) { + let notNeedFind = hasBlock && b.blockId == block?.blockId; + CutPointHelper.findCutPoint(b, !notNeedFind); + } + } + + /**计算下刀点 */ + static findCutPoint(block: PlaceBlock, needFind = true) { + let list = BlockPlus.getBorder(block); + + //计算没边的靠板情况,并自动生成 铣板走线 数据 + let borders_cuting: any[] = []; + for (let i = 0; i < list.length; i++) { + let rt = this.getUnCutedLength(block, list, i); + + borders_cuting.push([i, rt[0], rt[1]]); + + } + + if (needFind == false) return; //不计算下刀点 + + //计算最优下刀点 + let unCutedlength = 0; + let unCutedSize = 0; + let cutPointIndex = -1; + + for (let i = 0; i < borders_cuting.length; i++) { + let data = borders_cuting[i]; + let index = data[0]; + let len = data[1]; + let size = data[2]; + if (isBest(len, size)) { + cutPointIndex = index; + unCutedlength = len; + unCutedSize = size; + } + } + + if (cutPointIndex >= list.length) cutPointIndex -= list.length; + block.cutPointId = cutPointIndex + 1; + + //四周都没有靠的 + if (cutPointIndex == -1) { + this.getCutPointWithClick(block, new Point(block.placeWidth, block.placeLength)); + } + function isBest(len1, size1) //判断那条边 更优 + { + + if (len1 == 0) return false; //没有考的 + let dis_avg = size1 / len1; + if (dis_avg < 50) return false; //跟最近的 平均距离 < 50 ,当作没有考的 + + + + if (cutPointIndex == -1) return true; + + //return len1 > unCutedlength; + if (len1 > 150 && unCutedlength < 150) return true; + if (len1 > unCutedlength * 2) return true; + if (size1 > unCutedSize * 1.2) return true; //未切面积 相差很大. 取 + if (size1 < unCutedSize * 0.8) return false; //小于 以获得的 边 , 不取 + if (len1 > unCutedlength) return true; //面积相差无几, 取边长的 + return false; + } + } + + + /**获取未切边长度 */ + private static getUnCutedLength(block: PlaceBlock, curs: Curve2d[], i): [number, number] { + + let cur = curs[i]; + let p1 = { x: block.placeX + cur.StartPoint.m_X, y: block.placeY + cur.StartPoint.m_Y }; + let p2 = { x: block.placeX + cur.EndPoint.m_X, y: block.placeY + cur.EndPoint.m_Y }; + + let length = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y); //切边总长度 + let cutedLength = 0; + let cutedSize = 0; + let dat_H; + let dat_V; + let isSP = this.qual(p1.y, p2.y); + let isCZ = this.qual(p1.x, p2.x); + if (isSP) //水平 + { + dat_H = this.getcutedLength_h(p1, p2); + } + else if (isCZ) //垂直 + { + + dat_V = this.getcutedLength_v(p1, p2); + } + else if (p2.y > p1.y) { + if (p2.x > p1.x) //右上 + { + let pc = { x: p2.x, y: p1.y }; + dat_H = this.getcutedLength_h(p1, pc); + dat_V = this.getcutedLength_v(pc, p1); + } + else //左上 + { + let pc = { x: p1.x, y: p2.y }; + dat_V = this.getcutedLength_v(p1, pc); + dat_H = this.getcutedLength_h(pc, p2); + } + } + else { + if (p2.x > p1.x) //右下 + { + let pc = { x: p1.x, y: p2.y }; + dat_V = this.getcutedLength_v(p1, pc); + dat_H = this.getcutedLength_h(p1, pc); + } + else //左下 + { + let pc = { x: p2.x, y: p1.y }; + dat_H = this.getcutedLength_h(p1, pc); + dat_V = this.getcutedLength_v(p1, pc); + } + } + + if (dat_H) { + cutedLength += dat_H[0]; + cutedSize += dat_H[4]; + this.pushline_H(dat_H[1], dat_H[2], dat_H[3]); + } + if (dat_V) { + cutedLength += dat_V[0]; + cutedSize += dat_V[4]; + this.pushline_V(dat_V[1], dat_V[2], dat_V[3]); + } + + let unCutedLength = length - cutedLength; + if (unCutedLength < 0) unCutedLength = 0; + if (unCutedLength > 0 && block.isUnRegular) //避免异形板、格子抽、齿的边被选出来 + { + //边缘线不计算 + let isborder = isSP && (this.qual(cur.StartPoint.m_Y, 0) || this.qual(cur.StartPoint.m_Y, block.placeLength)); + if (isborder == false) { + isborder = isCZ && (this.qual(cur.StartPoint.m_X, 0) || this.qual(cur.StartPoint.m_X, block.placeWidth)); + } + + if (isborder == false) { + for (let j = 0; j < curs.length; j++) { + if (i == j) continue; + let other = curs[j]; + if (isSP && this.qual(other.StartPoint.m_Y, other.EndPoint.m_Y)) //水平,line 在板内 + { + if (this.qual(other.StartPoint.m_Y, 0)) continue; //边缘线,不计算 + if (this.qual(other.StartPoint.m_Y, block.placeLength)) continue; + + if (this.qual(cur.StartPoint.m_Y, other.StartPoint.m_Y, 100)) { + unCutedLength = 0; + break; + } + } + else { + if (isCZ && this.qual(other.StartPoint.m_X, other.EndPoint.m_X)) { + if (this.qual(other.StartPoint.m_X, 0)) continue; + if (this.qual(other.StartPoint.m_X, block.placeWidth)) continue; + + if (this.qual(cur.StartPoint.m_X, other.StartPoint.m_X, 100)) { + unCutedLength = 0; + break; + } + } + } + } + + } + + + } + return [unCutedLength, cutedSize]; + } + + /**获取水平开料边长度 */ + private static getcutedLength_h(p1, p2): [number, number, number, number, number] //cutedLength,x1,x2,y + { + //水平线 + let x1; + let x2; + + let y = p1.y; + + //切片法 + let cell = 5; + let cutedLength = 0; + let cutedSize = 0; + + if (p2.x > p1.x) // 向右,往下面100内找有没有切割的走刀 + { + x1 = p1.x; + x2 = p2.x; + let cutedLines = this.lines_H.filter(t => t.y < y && t.x1 < x2 && t.x2 > x1); //向右,下面找 + + for (let pos = x1; pos < x2 + 5; pos += cell) { + let lines = ArrayExt.where(cutedLines, t => t.x1 <= pos && t.x2 > pos, t => -t.y); //按y降序排序 ,找最高的 + if (lines.length > 0) { + let line = lines[0]; + let disY = Math.abs(line.y - y); + if (disY < 100) cutedLength = cutedLength + cell; + cutedSize += disY * cell; + } + } + } + else //往左 + { + x1 = p2.x; + x2 = p1.x; + let cutedLines = this.lines_H.filter(t => t.y > y && t.x1 < x2 && t.x2 > x1); //向左,上面找 + for (let pos = x1; pos < x2 + 5; pos += cell) { + let lines = ArrayExt.where(cutedLines, t => t.x1 <= pos && t.x2 > pos, t => t.y); //按y上线排序 ,找最底的 + if (lines.length > 0) { + let line = lines[0]; + let disY = Math.abs(line.y - y); + if (disY < 100) cutedLength = cutedLength + cell; + cutedSize += disY * cell; + } + } + } + return [cutedLength, x1, x2, p1.y, cutedSize]; + } + + /**获取竖直方向开料边长度 */ + private static getcutedLength_v(p1, p2): [number, number, number, number, number] //cutedLength,x,y1,y2 + { + // let x = p1.x; + // let x1; + // let x2; + + // let y1; + // let y2; + + // if (p2.y > p1.y) // 向上,往右面100内找有没有切割的走刀 + // { + // y1 = p1.y; + // y2 = p2.y; + // x1 = p1.x; + // x2 = p1.x + this.cutedSpace;;; + // } + // else //往下 + // { + // y1 = p2.y; + // y2 = p1.y; + // x1 = p1.x - this.cutedSpace; + // x2 = p1.x; + // } + + + // let cutedLines = this.lines_V.filter(t => t.x > x1 && t.x < x2 && t.y1 < y2 && t.y2 > y1); + // //切片法 + // let cell = 5; + // let cutedLength = 0; + // let cutedSize = 0; + // if (cutedLines.length > 0) + // { + // for (let y = y1; y < y2 + 5; y += cell) + // { + // let line = cutedLines.find(t => t.y1 <= y && t.y2 > y); + // if (line) + // { + // cutedLength += cell; + // cutedSize += Math.abs(line.x - p1.x) * cell; + // } + // } + // } + // return [cutedLength, p1.x, y1, y2, cutedSize]; + + let x = p1.x; + + let y1; + let y2; + let cell = 5; + let cutedLength = 0; + let cutedSize = 0; + if (p2.y > p1.y) // 向上,往右面100内找有没有切割的走刀 + { + y1 = p1.y; + y2 = p2.y; + let cutedLines = this.lines_V.filter(t => t.x > x && t.y1 < y2 && t.y2 > y1); //向上,右面找 + for (let y = y1; y < y2 + 5; y += cell) { + let lines = ArrayExt.where(cutedLines, t => t.y1 <= y && t.y2 > y, t => t.x); + if (lines.length > 0) { + let line = lines[0]; + let dis = Math.abs(line.x - x); + if (dis < 100) cutedLength = cutedLength + cell; + cutedSize += dis * cell; + } + } + } + else //往下 + { + y1 = p2.y; + y2 = p1.y; + let cutedLines = this.lines_V.filter(t => t.x < x && t.y1 < y2 && t.y2 > y1); //向下,左面找 + for (let y = y1; y < y2 + 5; y += cell) { + let lines = ArrayExt.where(cutedLines, t => t.y1 <= y && t.y2 > y, t => -t.x); + if (lines.length > 0) { + let line = lines[0]; + let dis = Math.abs(line.x - x); + if (dis < 100) cutedLength = cutedLength + cell; + cutedSize += dis * cell; + } + } + } + + return [cutedLength, p1.x, y1, y2, cutedSize]; + + + } + + /**保存刀路 */ + static pushLine(p1, p2) { + if (this.qual(p1.y, p2.y)) //水平 + { + this.pushline_H(p1.x, p2.x, p1.y); + } + else if (this.qual(p1.x, p2.y)) //垂直 + { + this.pushline_V(p1.x, p1.y, p2.y); + } + else if (p2.y > p1.y) { + if (p2.x > p1.x) //右上 + { + this.pushline_H(p1.x, p2.x, p1.y); + this.pushline_V(p2.x, p1.y, p2.y); + } + else //左上 + { + + this.pushline_V(p1.x, p1.y, p2.y); + this.pushline_H(p2.x, p1.x, p2.y); + } + } + else { + if (p2.x > p1.x) //右下 + { + this.pushline_V(p1.x, p1.y, p2.y); + this.pushline_H(p1.x, p2.x, p2.y); + } + else //左下 + { + this.pushline_H(p2.x, p1.x, p1.y); + this.pushline_V(p2.x, p1.y, p2.y); + } + } + } + + /**保存水平刀路 */ + private static pushline_H(x1, x2, y) { + this.lines_H.push({ x1: Math.min(x1, x2), x2: Math.max(x1, x2), y: y }); + } + + /**保存竖直刀路 */ + private static pushline_V(x, y1, y2) { + this.lines_V.push({ x: x, y1: Math.min(y1, y2), y2: Math.max(y1, y2) }); + } + + /**判断是否相等 */ + static qual(x, y, off = 0.005): boolean { + return Math.abs(x - y) < off; + } +} diff --git a/tests/dev1/dataHandle/common/decorators/json.ts b/tests/dev1/dataHandle/common/decorators/json.ts new file mode 100644 index 0000000..0f27000 --- /dev/null +++ b/tests/dev1/dataHandle/common/decorators/json.ts @@ -0,0 +1,31 @@ +import 'reflect-metadata' + +export const jsonOptionMataDataKey = Symbol('JsonOptions') + +export enum JsonOptionType + { + Ignore = 0, + Property = 1, +} + +export class JsonOption +{ + constructor(type: JsonOptionType, data: any = null) + { + this.type = type + this.data = data + } + + type: JsonOptionType + data: any +} + +export function jsonProperty(key: string | number) +{ + return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Property, { key })) +} + +export function jsonIgnore() +{ + return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Ignore)) +} diff --git a/tests/dev1/dataHandle/common/decorators/model.ts b/tests/dev1/dataHandle/common/decorators/model.ts new file mode 100644 index 0000000..a6d99de --- /dev/null +++ b/tests/dev1/dataHandle/common/decorators/model.ts @@ -0,0 +1,39 @@ +import 'reflect-metadata' + +const displayMetadataKey = Symbol('display') + +export function display( + name: string, + group: string = '', + type: string = 'color', +) +{ + return Reflect.metadata(displayMetadataKey, { + name, + group, + type, + }) +} + +export function getDisplayInfo(target: any) +{ + let keys = Object.getOwnPropertyNames(target) + let list = [] + keys.forEach((key) => + { + let info = <{ name: string; group: string; type: number }>( + Reflect.getMetadata(displayMetadataKey, target, key) + ) + if (info) + { + info + list.push({ + prop: key, + name: info.name, + group: info.group, + type: info.type, + }) + } + }) + return list +} diff --git a/tests/dev1/dataHandle/common/drawing/base.ts b/tests/dev1/dataHandle/common/drawing/base.ts new file mode 100644 index 0000000..7194193 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/base.ts @@ -0,0 +1,98 @@ +export abstract class Drawing +{ + width: number + height: number + + scale: number = 1 + abstract drawPath(path: DrawPathContext, option: DrawLineOption) + + abstract drawLine( + x0: number, + y0: number, + x1: number, + y1: number, + option: DrawLineOption, + ) + abstract drawRect( + x: number, + y: number, + width: number, + height: number, + option: DrawRectOption, + ) + abstract drawLinePolygon(points: Array, option: DrawLinePolygonOption) + abstract drawText(text: string, x: number, y: number, option: DrawTextOption) + abstract drawForeignObjectText( + text: string, + x: number, + y: number, + option: DrawTextOption, + ) + abstract drawImage( + source: CanvasImageSource, + x: number, + y: number, + width: number, + height: number, + option: DrawImageOption, + border: number, + ): Promise + + abstract clearAll() + + abstract toHTML(): string +} + +export class BaseDrawOption +{ + lineWidth: number = 1 + strokeStyle: string +} + +interface IDrawPath +{ + method: string + args: number[] +} +export class DrawPathContext +{ + data: Array = [] + + moveTo(x: number, y: number) + { + this.data.push({ method: 'M', args: [x, y] }) + + return this + } + + lineTo(x: number, y: number) + { + this.data.push({ method: 'L', args: [x, y] }) + return this + } +} + +export class DrawLineOption extends BaseDrawOption { } +export class DrawRectOption extends BaseDrawOption +{ + fillStyle: string +} +export class DrawImageOption extends BaseDrawOption { } +export class DrawLinePolygonOption extends BaseDrawOption +{ + isFill: boolean = true + fillStyle: string = 'rgb(0,0,0)' +} + +export type DrawTextBaseLnie = 'alphabetic' | 'bottom' | 'hanging' | 'ideographic' | 'middle' | 'top' +export type DrawTextAlign = 'start' | 'center' | 'right' +export class DrawTextOption +{ + maxWidth?: number + textAlign: DrawTextAlign = 'center' + textBaseline: DrawTextBaseLnie = 'middle' + fillStyle: string = 'rgb(0,0,0)' + fontSize: string = '12' + fontWeight: string = '400' + fontFamily: string = '宋体' +} diff --git a/tests/dev1/dataHandle/common/drawing/canvasDrawing.ts b/tests/dev1/dataHandle/common/drawing/canvasDrawing.ts new file mode 100644 index 0000000..f2f39f8 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/canvasDrawing.ts @@ -0,0 +1,197 @@ +import { createBmpFile } from '../bmp.js' +import { getFileExt } from '../file.js' +import type { + DrawImageOption, + DrawLineOption, + DrawLinePolygonOption, + DrawPathContext, + DrawRectOption, + DrawTextOption } from './base.js' +import +{ + Drawing, +} from './base.js' + +export class CanvasDrawing extends Drawing { + canvas: HTMLCanvasElement + ctx: CanvasRenderingContext2D + // isCaching: boolean; + outputType: string = 'png' + orgWidth: number + orgHeight: number + toHTML(): string { + let img = new Image(this.canvas.width, this.canvas.height) + img.src = this.canvas.toDataURL(`image/${this.outputType}`) + return img.outerHTML + } + + clearAll() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) + } + + /** + * + */ + constructor( + canvas: HTMLCanvasElement, + // isCaching: boolean = false, + ) { + super() + this.canvas = canvas + this.width = this.orgWidth = canvas.width = canvas.width + this.height = this.orgHeight = canvas.height = canvas.height + // this.isCaching = isCaching; + this.ctx = this.canvas.getContext('2d') + } + + drawPath(path: DrawPathContext, option: DrawLineOption) { + this.ctx.beginPath() + for (const item of path.data) { + switch (item.method) { + case 'M': + this.ctx.moveTo(item.args[0], item.args[1]) + break + case 'L': + this.ctx.lineTo(item.args[0], item.args[1]) + break + } + } + this.ctx.lineWidth = option.lineWidth + this.ctx.strokeStyle = option.strokeStyle + this.ctx.stroke() + } + + drawLine( + x0: number, + y0: number, + x1: number, + y1: number, + option: DrawLineOption, + ) { + this.ctx.lineWidth = option.lineWidth + this.ctx.strokeStyle = option.strokeStyle + this.ctx.beginPath() + this.ctx.moveTo(x0, y0) + this.ctx.lineTo(x1, y1) + this.ctx.stroke() + } + + drawRect( + x: number, + y: number, + width: number, + height: number, + option: DrawRectOption, + ) { + this.ctx.lineWidth = option.lineWidth + this.ctx.strokeStyle = option.strokeStyle + this.ctx.fillStyle = option.fillStyle + if (option.fillStyle) { + this.ctx.fillRect(x, y, width, height) + } else { + this.ctx.strokeRect(x, y, width, height) + } + } + + drawText(text: string, x: number, y: number, option: DrawTextOption) { + this.ctx.textAlign = option.textAlign as CanvasTextAlign + this.ctx.textBaseline = option.textBaseline as CanvasTextBaseline + this.ctx.fillStyle = option.fillStyle + this.ctx.font + = `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}` + this.ctx.fillText(text, x, y, option.maxWidth) + } + + drawForeignObjectText( + text: string, + x: number, + y: number, + option: DrawTextOption, + ) {} + + imageCache: Map = new Map() + drawImage( + source: CanvasImageSource, + x: number, + y: number, + width: number, + height: number, + option: DrawImageOption, + border: number, + ) { + return new Promise((resolve, reject) => { + this.ctx.drawImage(source, x + border, y + border, width, height) + resolve(null) + }) + } + + drawLinePolygon(points: Array, option: DrawLinePolygonOption) { + this.ctx.beginPath() + this.ctx.moveTo(points[0].x, points[0].y) + for (let i = 1; i < points.length; i++) { + this.ctx.lineTo(points[i].x, points[i].y) + } + this.ctx.closePath() + + this.ctx.stroke() + if (option.isFill) + { + this.ctx.fillStyle = option.fillStyle + this.ctx.fill() + } + } + + setScale(value: number) { + this.scale = value + this.canvas.width = this.width = this.orgWidth * value + this.canvas.height = this.height = this.orgHeight * value + this.ctx.scale(value, value) + } + // resize(width: number, height: number) + // { + // let oldWidth = this.width; + // let oldHeight = this.height; + // if (oldWidth != width || oldHeight != height) + // { + // let newCanvas = document.createElement('canvas'); + // this.width = newCanvas.width = width; + // this.height = newCanvas.height = height; + // let newCtx = newCanvas.getContext('2d'); + // newCtx.drawImage(this.canvas, 0, 0, oldWidth, oldHeight, 0, 0, width, height); + // this.canvas = newCanvas; + // this.ctx = newCtx; + // } + // } + + exportFile(nameOrType: string, param: string): Promise { + let mimeType = 'image/png' + let ext = getFileExt(nameOrType) + if (ext === null) { + ext = nameOrType + } + let paramDic: { [key: string]: string } = {} + if (param != null) { + for (const item of param.split('&')) { + let kv = item.split('=') + paramDic[kv[0]] = kv[1] + } + } + switch (ext) { + case 'bmp': + let data = createBmpFile( + this.ctx.getImageData(0, 0, this.width, this.height), + paramDic, + ) + return Promise.resolve(new Uint8Array(data)) + case 'jpg': + case 'jpeg': + mimeType = 'image/jpeg' + break + } + return new Promise((resolve) => { + this.canvas.toBlob((data: Blob) => { + resolve(data) + }, mimeType) + }) + } +} diff --git a/tests/dev1/dataHandle/common/drawing/canvasUtil.ts b/tests/dev1/dataHandle/common/drawing/canvasUtil.ts new file mode 100644 index 0000000..2c4de26 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/canvasUtil.ts @@ -0,0 +1,39 @@ +import type { DrawTextOption } from './base' + +// const canvas = new OffscreenCanvas(0, 0); +const canvas = document.createElement('canvas') +const canvasCtx = canvas.getContext('2d') + +export function blobToDataURL(data: Blob): Promise +{ + return new Promise((resolve) => + { + let reader = new FileReader() + reader.onload = (response) => + { + resolve(response.target.result as string) + } + reader.readAsDataURL(data) + }) +} + +export function getStringWidth( + text: string, + width: number, + height: number, + option: DrawTextOption, +): number +{ + canvasCtx.clearRect( + 0, + 0, + width, + height, + ) + canvasCtx.textAlign = option.textAlign as CanvasTextAlign + canvasCtx.textBaseline = option.textBaseline as CanvasTextBaseline + canvasCtx.fillStyle = option.fillStyle + canvasCtx.font + = `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}` + return canvasCtx.measureText(text).width +} diff --git a/tests/dev1/dataHandle/common/drawing/imageCode.ts b/tests/dev1/dataHandle/common/drawing/imageCode.ts new file mode 100644 index 0000000..8d584b0 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/imageCode.ts @@ -0,0 +1,33 @@ +import jsbarcode from 'jsbarcode' +import type { QRCodeRenderersOptions } from 'qrcode' +import { toCanvas } from 'qrcode' + +// const canvas = new OffscreenCanvas(0, 0); +const canvas = document.createElement('canvas') +const canvasCtx = canvas.getContext('2d') + +export async function genBarcode(text: string, options: jsbarcode.Options) +{ + canvas.width = options.width + canvas.height = options.height + canvasCtx.clearRect(0, 0, options.width, options.height) + try + { + jsbarcode(canvas, text, options) + // return await canvas.transferToImageBitmap(); + return await createImageBitmap(canvas) + } catch + { + return null + } +} +export async function genQrcode(text: string, options: QRCodeRenderersOptions) +{ + canvas.width = options.width + canvas.height = options.width + canvasCtx.clearRect(0, 0, options.width, options.width) + await toCanvas(canvas, text, options) + + // return await canvas.transferToImageBitmap(); + return await createImageBitmap(canvas) +} diff --git a/tests/dev1/dataHandle/common/drawing/imageUtil.ts b/tests/dev1/dataHandle/common/drawing/imageUtil.ts new file mode 100644 index 0000000..9097202 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/imageUtil.ts @@ -0,0 +1,12 @@ +export function UrlToBitmap(src: string) +{ + return new Promise((resolve) => + { + let img = new Image() + img.src = src + img.onload = () => + { + resolve(img) + } + }) +} diff --git a/tests/dev1/dataHandle/common/drawing/index.ts b/tests/dev1/dataHandle/common/drawing/index.ts new file mode 100644 index 0000000..9d0f88a --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/index.ts @@ -0,0 +1,3 @@ +export * from './base' +export * from './canvasDrawing' +export * from './svgDrawing' diff --git a/tests/dev1/dataHandle/common/drawing/svgDrawing.ts b/tests/dev1/dataHandle/common/drawing/svgDrawing.ts new file mode 100644 index 0000000..f1a3026 --- /dev/null +++ b/tests/dev1/dataHandle/common/drawing/svgDrawing.ts @@ -0,0 +1,251 @@ +import type { + DrawImageOption, DrawLineOption, DrawLinePolygonOption, DrawPathContext, DrawRectOption, + DrawTextOption, +} from './base.js' +import +{ Drawing, +} from './base.js' + +const canvas = document.createElement('canvas') +const canvasCtx = canvas.getContext('2d') +export class SvgDrawing extends Drawing +{ + svg: SVGElement + /** + * + */ + constructor( + width: number | string, + height: number | string, + scale: number = 0.8, + ) + { + super() + this.scale = scale + this.width = Number(width) + this.height = Number(height) + this.svg = this.createElement('svg') + // this.svg.setAttribute('width', this.width.toString());//(Number(width) * this.scale) + // this.svg.setAttribute('height', this.height.toString());//(Number(height) * this.scale) + // this.svg.style.transform = 'scale(' + this.scale + ')'; + // this.svg.style.transformOrigin = 'left top'; + this.svg.setAttribute('viewBox', `0 0 ${width} ${height}`) + } + + private createElement(name) + { + return document.createElementNS('http://www.w3.org/2000/svg', name) + } + + toHTML(): string + { + return this.svg.outerHTML + } + + clearAll() + { + this.svg.innerHTML = '' + } + + drawPath(path: DrawPathContext, option: DrawLineOption) + { + let el = this.createElement('path') + + let pathStr = '' + for (const item of path.data) + { + switch (item.method) + { + case 'M': + pathStr += `M ${item.args.join(' ')} ` + break + case 'L': + pathStr += `L ${item.args.join(' ')} ` + break + } + } + el.setAttribute('d', pathStr) + el.setAttribute('stroke-width', option.lineWidth.toString()) + el.setAttribute('stroke', option.strokeStyle) + this.svg.appendChild(el) + } + + drawLine( + x0: number, + y0: number, + x1: number, + y1: number, + option: DrawLineOption, + ) + { + let el = this.createElement('line') + el.setAttribute('x1', x0.toString()) // (Number(x0) * this.scale) + el.setAttribute('y1', y0.toString()) // (Number(y0) * this.scale) + el.setAttribute('x2', x1.toString()) // (Number(x1) * this.scale) + el.setAttribute('y2', y1.toString()) // (Number(y1) * this.scale) + el.setAttribute('stroke', option.strokeStyle) + el.setAttribute('fill', 'rgba(0,0,0,0)') + el.setAttribute('stroke-width', option.lineWidth.toString()) + this.svg.appendChild(el) + } + + drawRect( + x: number, + y: number, + width: number, + height: number, + option: DrawRectOption, + ) + { + let el = this.createElement('rect') + el.setAttribute('x', x.toString()) // ((x + option.baseLine / 2) * this.scale) + el.setAttribute('y', y.toString()) // ((y + option.baseLine) * this.scale) + el.setAttribute('width', width.toString()) // (Number(width) * this.scale) + el.setAttribute('height', height.toString()) // (Number(height) * this.scale) + if (option.fillStyle) + { + el.setAttribute('fill', option.fillStyle) + el.setAttribute('stroke', option.strokeStyle) + } else + { + el.setAttribute('fill', 'rgba(1,1,1,0)') + el.setAttribute('stroke', option.strokeStyle) + el.setAttribute( + 'stroke-width', + (Math.floor(option.lineWidth) + 0.5).toString(), + ) + } + this.svg.appendChild(el) + } + + drawText(text: string, x: number, y: number, option: DrawTextOption) + { + let el = this.createElement('text') + el.setAttribute('x', x.toString()) // (Number(x) * this.scale) option['CncDict'] ? (x - Number(option.fontSize) / 6).toString() : + el.setAttribute('y', y.toString()) // (Number(y) * this.scale) + el.style.dominantBaseline = 'text-before-edge' + if (option.resetFont) + { + el.style.dominantBaseline = 'middle' + el.setAttribute('text-anchor', 'middle') + } + el.setAttribute('fill', option.fillStyle) + el.setAttribute('font-size', `${option.fontSize}px`) + el.setAttribute('font-weight', option.fontWeight) + el.setAttribute('font-family', option.fontFamily) + if (option.overflowText) + { + el.setAttribute('textLength', option.maxWidth.toString()) + el.setAttribute('lengthAdjust', 'spacingAndGlyphs') + } + el.setAttribute('alignment-baseline', option.textBaseline) + switch (option.textAlign) + { + case 'start': + el.setAttribute('text-anchor', 'left') + break + case 'center': + el.setAttribute('text-anchor', 'middle') + break + case 'right': + el.setAttribute('text-anchor', 'end') + break + } + + if (typeof text === 'string') + { + el.innerHTML = text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') + } else + { + el.innerHTML = text + } + + this.svg.appendChild(el) + } + + drawForeignObjectText( + text: string, + x: number, + y: number, + option: DrawTextOption, + ) + { + // let el = this.createElement('foreignObject'); + // el.setAttribute('x',x.toString()); + // el.setAttribute('y',y.toString()); + // el.setAttribute('width',option.maxWidth.toString()); + // el.setAttribute('height',option['maxHeight'].toString()); + // let span = this.createElement('span'); + // span.style.dominantBaseline = 'text-before-edge'; + // if(option['resetFont']){ + // span.style.dominantBaseline = 'middle'; + // span.style.textAnchor = 'middle'; + // } + // span.style.fontSize = option.fontSize + 'px'; + // span.style.fontWeight = option.fontWeight; + // span.style.fontFamily = option.fontFamily; + // span.style.display = 'inline-block'; + // span.style.width = option.maxWidth + 'px'; + // span.style.height = option['maxHeight'] + 'px'; + // span.style.overflow = 'hidden'; + // span.innerHTML = text; + // el.appendChild(span); + // this.svg.appendChild(el); + } + + async drawImage( + source: CanvasImageSource, + x: number, + y: number, + width: number, + height: number, + option: DrawImageOption, + ) + { + let src = '' + if (source instanceof ImageBitmap) + { + canvas.width = source.width + canvas.height = source.height + canvasCtx.clearRect(0, 0, source.width, source.height) + canvasCtx.drawImage(source, 0, 0, source.width, source.height) + src = canvas.toDataURL() + } else if (source instanceof HTMLImageElement) + { + src = source.src + } + return new Promise((resolve, reject) => + { + let el = this.createElement('image') + el.setAttribute('x', x.toString()) // (Number(x) * this.scale) + el.setAttribute('y', y.toString()) // (Number(y) * this.scale) + el.setAttribute('width', width.toString()) // (Number(width) * this.scale) + el.setAttribute('height', height.toString()) // (Number(height) * this.scale) + el.setAttribute('xlink:href', src) + el.setAttribute('preserveAspectRatio', 'none meet') + this.svg.appendChild(el) + resolve(null) + }) + } + + drawLinePolygon(points: Array, option: DrawLinePolygonOption) + { + let el = this.createElement('polygon') + let pointsStr = '' + for (let i = 0; i < points.length; i++) + { + if (i == points.length - 1) + { + pointsStr += `${points[i].x} ${points[i].y}` + } else + { + pointsStr += `${points[i].x} ${points[i].y},` + } + } + el.setAttribute('points', pointsStr) + el.setAttribute('fill', option.isFill ? option.fillStyle : 'none') + el.setAttribute('stroke', option.strokeStyle) + el.setAttribute('stroke-width', option.lineWidth.toString())// 添加多段线的线宽--xyh + this.svg.appendChild(el) + } +} diff --git a/tests/dev1/dataHandle/common/file.ts b/tests/dev1/dataHandle/common/file.ts new file mode 100644 index 0000000..53b8945 --- /dev/null +++ b/tests/dev1/dataHandle/common/file.ts @@ -0,0 +1,22 @@ +export async function readText(file: Blob, encoding?: string): Promise +{ + return new Promise((resolve) => + { + let fileReader = new FileReader() + fileReader.onload = function (e) + { + resolve( this.result) + } + fileReader.readAsText(file, encoding) + }) +} + +export function getFileExt(name: string) +{ + let index = name.lastIndexOf('.') + if (index != -1) + { + return name.substring(index + 1, name.length) + } + return null +} diff --git a/tests/dev1/dataHandle/common/log.ts b/tests/dev1/dataHandle/common/log.ts new file mode 100644 index 0000000..4df5969 --- /dev/null +++ b/tests/dev1/dataHandle/common/log.ts @@ -0,0 +1,33 @@ +export class Logger +{ + name: string + static interceptors: LogInterceptor[] = [] + constructor(name: string) + { + this.name = name + } + + static AddInterceptor(interceptor: LogInterceptor) + { + Logger.interceptors.push(interceptor) + } + + error(error: Error, payload: any = null) + { + console.error(error, payload) + Logger.interceptors.forEach((i) => + { + i.call(this, error, payload) + }) + } + + info(message: string, payload: any = null) + { + console.info(message, payload) + Logger.interceptors.forEach((i) => + { + i.call(this, message, payload) + }) + } +} +export type LogInterceptor = (this: Logger, info: string | Error, payload: any) => void diff --git a/tests/dev1/dataHandle/common/zip.ts b/tests/dev1/dataHandle/common/zip.ts new file mode 100644 index 0000000..88cb1b1 --- /dev/null +++ b/tests/dev1/dataHandle/common/zip.ts @@ -0,0 +1,151 @@ +// import JsZip from 'jszip'; +import type { DeflateOptions } from 'fflate' +import { Zip, ZipDeflate, strToU8 } from 'fflate' +import { getFileExt } from './file' + +export class FileInfo +{ + name: string + content: string | Blob | Uint8Array + encode: string = 'UTF-8' + isBase64 = false + binary = false + constructor(name, text, isBase64 = false) + { + this.name = name + this.content = text + this.isBase64 = isBase64 + } +} + +export abstract class ZipProvider +{ + // abstract loadAsync(file: File): Promise; + protected files: FileInfo[] = [] + + addFile(file: FileInfo) + { + this.files.push(file) + } + + abstract saveAsync(): Promise +} + +// export class JsZipProvider extends ZipProvider +// { + +// private ctx = new JsZip(); + +// // async loadAsync(file: File): Promise +// // { +// // await this.ctx.loadAsync(file); +// // } + +// async saveAsync(): Promise +// { +// for (const file of this.files) +// { +// this.ctx.file(file.name, file.content, { +// createFolders: true, +// base64: file.isBase64, +// binary: file.binary, +// compression: "DEFLATE", +// }); +// } +// return await this.ctx.generateAsync({ type: 'blob' }); +// } +// } + +export class FflateZipProvider extends ZipProvider +{ + private ctx = new Zip() + + private task: Promise + + constructor() + { + super() + this.task = new Promise((resolve, reject) => + { + let result = [] + this.ctx.ondata = (err, data, final) => + { + if (!err) + { + result.push(data) + if (final) + { + resolve(new Blob(result, { type: 'application/zip' })) + } + } else + { + reject() + } + } + }) + } + + async saveAsync(): Promise + { + for (const file of this.files) + { + let data: Uint8Array + if (file.content instanceof Blob) + { + // console.log(file.name); + let buffer = await file.content.arrayBuffer() + // console.log('buffer', buffer); + data = new Uint8Array(buffer) + } else if (file.content instanceof Uint8Array) + { + data = file.content + } + else if (file.isBase64) + { + data = new Uint8Array(atob(file.content).split('').map((c) => + { + return c.charCodeAt(0) + })) + } else + { + data = strToU8(file.content) + } + + let zipInput = new ZipDeflate(file.name, this.getOptionByExt(file.name)) + this.ctx.add(zipInput) + zipInput.push(data, true) + } + this.ctx.end() + return this.task + } + + getOptionByExt(fileName: string) + { + let option: DeflateOptions = { + level: 6, + // mem: 12 + } + let ext = getFileExt(fileName) + if (ext !== null) + { + switch (ext) + { + case 'bmp': + option.level = 1 + option.mem = 0 + break + case 'jpg': + case 'jpeg': + option.level = 1 + break + case 'png': + option.level = 0 + break + } + } + + return option + } +} + +export const DefaultZipProvider = () => new FflateZipProvider() diff --git a/tests/dev1/dataHandle/confClass.ts b/tests/dev1/dataHandle/confClass.ts new file mode 100644 index 0000000..75d72c7 --- /dev/null +++ b/tests/dev1/dataHandle/confClass.ts @@ -0,0 +1,4814 @@ +import { StringFormat } from "./StringFormat" +import type * as CAD from './common/base/CAD' +import { Polyline } from "cadapi" +import { ArrayExt } from "./ArrayExt" +import { BlockHelper } from "./common/LayoutEngine/BlockHelper" +import { BlockSizePlus } from "./common/LayoutEngine/BlockSizePlus" +import { Point } from "./common/Vector2" + + +/**配置项属性 */ +export class confItem { + /** 属性key */ + key: string = '' + /** 说明 */ + label: string = '' + /** 属性value 值 */ + value: Array | any +} +/** 内部指令 接收 */ +export class CodeAction { + code?: String + order?: String + codeParams?: any +} +/**内部响应类 */ +export class GCodeResType { + code: number = -1 + data: any = null + msg: string = '' + logError(msg) { + console.error(msg) + } +} + +/** 排孔类型 */ +export enum HoleArrange { + /** 正面 */ + FRONT = 0, + /** 反面 */ + BACK = 1, + /** 随意面 */ + RANDOM_FACE = 2, +} + + +// 加工项 点数据 +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 type _knifeType = Partial + +/** + * 板面类型 + * 0正面 1反面 2侧面 21左侧面 22右侧面 23上侧面 24下侧面 29 弧形侧面 30异形侧面 + */ +export enum FaceType { + /** 正面 */ + FRONT = 0, + /** 反面 */ + BACK = 1, + /** 侧面 */ + SIDE = 2, + /** 左侧面 */ + LEFT_SIDE = 21, + /** 右侧面 */ + RIGHT_SIDE = 22, + /** 上侧面 */ + TOP_SIDE = 23, + /** 下侧面 */ + BOTTOM_SIDE = 24, + /** 弧形侧面 */ + CURVED_SIDE = 29, + /** 异形侧面 */ + SPECIAL_SHAPED_SIDE = 30, + /** 未设置 */ + UNSET = -1 +} + +/** 开料刀 */ +export class Knife { + /** 是否启用 */ + isEnabled = true; + /** 轴号 */ + axleId = 0; + /** 开料刀ID号 */ + knifeId = 1; + /** 加工面(0正面 1反面 2左侧面 3右侧面 4上侧面 5下侧面 6任意) */ + processFace = 0; + /** 刀名称 */ + knifeName = ''; + /** 刀具类型(1铣刀 2成型刀 3钻头 4锯 5刀片) */ + knifeType = KnifeType.MILLING_CUTTER; + /** 功能属性(数组): 1开料/切割 2拉槽 3铣型 4铣孔 5钻孔 6拉米诺 7乐扣 8T型 */ + ability: any = []; + + // /** 默认开料刀 */ + // isDefaultCutKnife = false; + // /** 是否可用于开料切割 */ + // isCuttingKnife = false; + // /** 是否允许铣孔(knifeType为铣刀生效) */ + // isMillingAllowed = false; + /** 刀直径mm */ + diameter = 6; + /** 刀长(最大深度) */ + length = 20; + /** 锯齿厚度,单次加工切缝宽度 */ + sawThiness = 0; + /**锯向: 0横向(或长边) 1纵向(短边) 2自由角度*/ + sawDirection = 0; + /**切向加工方向: 0横向左往右 2横向右往左 3纵向上往下 4纵向下往上 5随意 */ + processDirection = 0; + /**进给速度, 0取系统默认 */ + speed = 0; + /** 进给深度 */ + stepDepth = 0; + // /** 组号 */ + // groupNo = 0; + // /** 主刀 */ + // isMainKnife = false; + // /** 是否高级加工 */ + // isAdvancedProcessEnabled = false; + // /** 是否集合加工 */ + // isBatchProcessEnabled = false; + // /** 副刀偏置长边偏移(Y轴) */ + // auxKnifeOffsetY = 0; + // /** 副刀偏置短边偏移(X轴) */ + // auxKnifeOffsetX = 0; + /** 偏置短边偏移(X轴)-相对于工件原点位置XY坐标的短边方向偏移量 */ + offsetX = 0; + /** 偏置长边偏移(Y轴) -相对于工件原点位置XY坐标的长边方向偏移量*/ + offsetY = 0; + /** 偏置垂直偏移(Z轴) -相对于工件原点位置XY坐标的垂直方向偏移量*/ + offsetZ = 0; + /** 基准坐标-短边(X轴) -钻头在钻包中短边方向(纵向)的相对位置 */ + baseX = 0; + /** 基准坐标-长边(Y轴) -钻头在钻包中长边方向(横向)的相对位置 */ + baseY = 0; + /**组合钻 */ + isModularDrill = false; + /** 是否预启动 */ + isPreStartEnabled = false; + /** 预启动提前动作数 */ + preStartAheadActionCount = 5; + /** 预启动延迟换刀 */ + isPreStartToolChangeDelay = false; + /** 预启动延迟换刀指令 */ + preStartToolChangeDelayCode = ''; + /** 轴启动指令后置 */ + isAxisStartCodePostpost = false; + /** 轴停止指令前置 */ + isAxisStopCodePrepose = false; + /** 钻组独立指令(启用后,刀起始指令、刀结束指令使用钻组起始指令、钻组结束指令替换) */ + drillGroupCode = ''; + + /** 轴启动代码*/ + axisStartCode = ""; + /** 刀启动代码 */ + knifeStartCode = ''; + /** 钻组起始指令 */ + drillGroupStartCode = ''; + /** 钻组结束指令 */ + drillGroupEndCode = ''; + /** 刀停止代码 */ + knifeStopCode = ''; + /** 轴停止指令 */ + axisStopCode = ''; + // /** 高级加工指令 */ + // advancedCode = ''; + /** 开料刀 */ + isCuttingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.CUT); + } + /** 造型刀 */ + isModelingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.MILLING_MODEL); + } + /** 钻刀 */ + isDrillingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.DRILL_HOLE); + } + /** 铣孔 */ + isCutting4HoleKnife() { + return this.isEnabled && this.ability.includes(AbilityType.MILLING_HOLE); + } + /** 设置刀具(轴号, 刀名, 刀直径, 是否主刀, 是否铣孔, 是否开料刀) */ + // set(axleId: number, name: string, diameter: number, isMainKnife: boolean, isMillingAllowed: boolean, isCuttingKnife: boolean) + /** 设置刀具(轴号, 刀名, 刀类型, 刀直径, 刀长, 是否默认刀) */ +} + + +/** 刀类型: MILLING_CUTTER铣刀 FORMING_CUTTER成型刀 DRILL钻头 SAW锯 BLADE刀片 */ +export enum KnifeType { + /** 铣刀 */ + MILLING_CUTTER = 1, + /** 成型刀 */ + FORMING_CUTTER = 2, + /** 钻头 */ + DRILL = 3, + /** 锯 */ + SAW = 4, + /** 刀片 */ + BLADE = 5 +} + +/** 刀功能: + ** 1-CUT开料/切割 2-PULLING_GROOVE拉槽 3-MILLING_MODEL铣型 4-MILLING_HOLE铣孔 + ** 5-DRILL_HOLE钻孔 6-RAMINO拉米诺 7-EASY_FASTEN乐扣 8-T_TYPE T型 */ +export enum AbilityType { + /** 1开料/切割 */ + CUT = 1, + /** 2拉槽 */ + PULLING_GROOVE = 2, + /** 3铣型 */ + MILLING_MODEL = 3, + /** 4铣孔 */ + MILLING_HOLE = 4, + /** 5钻孔 */ + DRILL_HOLE = 5, + /** 6拉米诺 */ + RAMINO = 6, + /** 7乐扣 */ + EASY_FASTEN = 7, + /** 8T型 */ + T_TYPE = 8 +} + +/** 枚举 坐标轴类型 */ +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 enum OriginZPosition { + /** 台面向上Z轴正 */ + WorkTop = 0, + /** 板面向上Z轴正 */ + BoardFace = 1, +} + +// nc文件编码 +export const ncEncodeMap = { + 'UTF-8': 'UTF-8', + 'GB2312': 'GB2312', + 'UTF-8-BOM': 'UTF-8-BOM', + 'GBK': 'GBK', + 'Big-5': 'Big-5', + 'GB18030': 'GB18030', + 'Unicode': 'Unicode' +} + +// 小板文件名选项 +export const smallPlateOptionsMap = { + 板宽: '{0}', + 板长: '{1}', + 颜色: '{2}', + 材质: '{3}', + 大板号: '{4}', + 工位号: '{5}', + 板厚: '{6}', + 开料顺序: '{7}', + 批次识别: '{8}', + 小板编号: '{9}', + 全局大板号: '{10}', + 品牌: '{11}', +} + +// zip压缩包文件名选项 +export const zipFileOptionsMap = { + '板宽': '{0}', + '板长': '{1}', + '颜色': '{2}', + '材质': '{3}', + '大板号': '{4}', + '工位号': '{5}', + '板厚': '{6}', + '开料顺序': '{7}', + '批次识别': '{8}', + '小板编号': '{9}', + '全局大板号': '{10}', + '品牌': '11', +} + +/** 枚举 大板边角位置 */ +export enum BoardPosition { + /** 左上角 */ + // LEFT_TOP = 0, + // /** 左下角 */ + // LEFT_BOTTOM = 1, + // /** 右下角 */ + // RIGHT_BOTTOM = 2, + // /** 右上角 */ + // RIGHT_TOP = 3, + /** 左上角 */ + LEFT_TOP = 3, + /** 左下角 */ + LEFT_BOTTOM = 0, + /** 右下角 */ + RIGHT_BOTTOM = 1, + /** 右上角 */ + RIGHT_TOP = 2, + /** + * 左下角 = 0, + 右下角 = 1, + 右上角 = 2, + 左上角 = 3 + */ +} + +/** 排版样式 */ +export enum PlaceStyle { + /** 正面 */ + FRONT = 0, + /** 正面右转 */ + FRONT_TURN_RIGHT = 1, + /** 正面后转 */ + FRONT_TURN_BACK = 2, + /** 正面左转 */ + FRONT_TURN_LEFT = 3, + /** 反面 */ + BACK = 4, + /** 反面右转 */ + BACK_TURN_RIGHT = 5, + /** 反面后转 */ + BACK_TURN_BACK = 6, + /** 反面左转 */ + BACK_TURN_LEFT = 7, +} + +/** 小板边的位置类型 */ +export enum EdgeType { + /** 下=0 */ + BOTTOM = 0, + /** 右=1 */ + RIGHT = 1, + /** 上=2 */ + TOP = 2, + /** 左=3 */ + LEFT = 3, +} + +/** 开料小板 */ +export class PlaceBlock { + /** 小板明细(异形,孔,造型) */ + blockDetail?: PlaceBlockDetail | null = null + // 订单信息---------------------------------------------------------- + + /** 订单号 */ + orderId: number = 0 + /** 板材ID */ + goodsId = '' + // 小板属性----------------------------------------------------------- + // 房间名, 柜体名, 小板ID, 小板编号,小板名称, 板材名称, 备注 + // 房间ID + roomId = '' + /** 房间名 */ + roomName = '' + /** 柜体号 */ + bodyId = '' + /** 柜体名 */ + bodyName = '' + /** 小板ID */ + blockId = 0 + /** 原小板ID */ + oldBlockId: number = 0 + /** 板编号 */ + blockNo: number = 0 + /** 自定义板编号 */ + customPlateNo: any = '' + /** 明细ID */ + itemId: number = 0 + /** 标签板号 */ + labelNo = '' + /** 板名称 */ + blockName = '' + /** 板件备注 */ + plateRemark = '' + /** 备注1 */ + remark1 = '' + /** 备注2 */ + remark2 = '' + /** 备注3 */ + remark3 = '' + /** 备注4 */ + remark4 = '' + /** 备注5 */ + remark5 = '' + /** 铰链备注 */ + extraRemark: object = {} + + // 尺寸,封边,开料尺寸----------------------------------------------------- + + /** 板宽 */ + width = 0 + /** 板长 */ + length = 0 + /** 板厚 */ + thickness = 0 + /** 面积 Size */ + area = 0 + + /** 左封边 BorderLeft */ + sealLeft = 0 + /** 右封边 BorderRight */ + sealRight = 0 + /** 上封边 BorderUpper */ + sealTop = 0 + /** 下封边 BorderUnder */ + sealBottom = 0 + + /** 无封边板宽 开料板宽 CuttingWidth */ + cutWidth = 0 + /** 无封边板长 开料板长 CuttingLength */ + cutLength = 0 + /** 无封边面积 开料面积 CuttingSize */ + cutArea = 0 + + // 高级------------------------------------------------------------- + + /** 自增小板 */ + isAdditionalBlock = false + /** 余料板 IsScrapBlock */ + isRemainBlock = false + /** 开料纹路 */ + texture = TextureType.ROTATABLE_TEXTURE // 可翻转 + /** 排版面 PaiKong */ + placeHole = HoleArrange.RANDOM_FACE // 随意面 + /** 异型偏移x BorderLengthHeavy */ + abnormalOffsetX = 0 + /** 异型偏移y BorderLengthLight */ + abnormalOffsetY = 0 + + /** 正面有大孔 */ + bigHoleInFaceA(): boolean { + let res = false + if (this.blockDetail && this.blockDetail.bigHoleInFaceA) { + res = true + } + return res + } + + /** 是否异形 */ + isUnRegular = false + // isUnRegular(): boolean { + // return this.blockDetail && this.blockDetail.isUnRegular || false + // } + + /** 正面孔数 */ + holeCountFront(): number { + return this.blockDetail && this.blockDetail.holeCountFront || 0 + } + + /** 反面孔数 */ + holeCountBack(): number { + return this.blockDetail && this.blockDetail.holeCountBack || 0 + } + + /** 通孔数 */ + holeCountThrough(): number { + return this.blockDetail && this.blockDetail.holeCountThrough || 0 + } + + /** 侧孔数 HoleCount_Side */ + holeCountSide(): number { + return this.blockDetail && this.blockDetail.holeCountSide || 0 + } + + /** 左侧孔数 HoleCount_Left */ + holeCountLeft() { return this?.blockDetail?.holeCountLeft || 0 } + /** 右侧孔数 HoleCount_Right */ + holeCountRight() { + return this?.blockDetail?.holeCountRight || 0 + } + /** 侧孔数 HoleCount_Upper */ + holeCountTop() { return this?.blockDetail?.holeCountTop || 0 } + /** 侧孔数 HoleCount_Under */ + holeCountBottom() { return this?.blockDetail?.holeCountBottom || 0 } + /** 正面造型数 */ + modelCountFront(): number { + return this.blockDetail && this.blockDetail.modelCountFront || 0 + } + + /** 反面造型数 */ + modelCountBack(): number { + return this.blockDetail && this.blockDetail.modelCountBack || 0 + } + + /** 打穿造型数 */ + modelCountThrough(): number { + return (this.blockDetail && this.blockDetail.modelCountThrough) || 0 + } + + /** 有挖穿造型 */ + hasModelThrough(): boolean { + return (this.blockDetail && this.blockDetail.hasModelThrogh) || false + } + + /** 需双面处理 */ + isDoubleFaceProcess(): boolean { + if (this.placeHole == HoleArrange.RANDOM_FACE) // 随意面 + { + return (this.blockDetail && this.blockDetail.isTwoFaceProcess) || false + } + else if (this.placeHole == HoleArrange.FRONT) // 正面 + { + return this.holeCountBack() + this.modelCountBack() > 0 + } + else { + return this.holeCountFront() + this.modelCountFront() > 0 + } + } // (根据排版规格计算) + + /** 优化排版时,需反面(不是真正的反面开料) */ + isTurnFaceToPlace = false + + /** 优化排版时,可双面排版 */ + isTwoFaceToPlace = false + /** 是否弧形地脚,不处理异形 IsHXDJX */ + isCurvedGroundLine = false + /** 封边偏移值x */ + offsetX(): number { + return (this.blockDetail && this.blockDetail.offsetX) || 0 + } + + /** 封边偏移值y */ + offsetY(): number { + return (this.blockDetail && this.blockDetail.offsetY) || 0 + } + + // 异形、孔、造型明细列表 + points(): BlockPoint[] { + return (this.blockDetail && this.blockDetail.points) || [] + } + + orgPoints(): BlockPoint[] { + return (this.blockDetail && this.blockDetail.orgPoints) || [] + } + + holes(): BlockHole[] { + + return (this.blockDetail && this.blockDetail.holes) || [] + } + + models(): BlockModel[] { + return (this.blockDetail && this.blockDetail.models) || [] + } + + holeListOrgFaceA(): BlockHole[] { + return (this.blockDetail && this.blockDetail.holeListFaceA) || [] + } + + holeListOrgFaceB(): BlockHole[] { + return (this.blockDetail && this.blockDetail.holeListFaceB) || [] + } + + holeListOrgThrough(): BlockHole[] { + return (this.blockDetail && this.blockDetail.holeListThrough) || [] + } + + holeListOrgFaceSide(): SideHole[] { + return (this.blockDetail && this.blockDetail.holeListSide) || [] + } + + modelListOrgFaceA(): BlockModel[] { + return (this.blockDetail && this.blockDetail.modelListFaceA) || [] + } + + modelListOrgFaceB(): BlockModel[] { + return (this.blockDetail && this.blockDetail.modelListFaceB) || [] + } + + modelListOrgTrough(): BlockModel[] { + + return (this.blockDetail && this.blockDetail?.modelListThrough) || [] + } + + // 排版情况 + /** 已排版 */ + isPlaced = false + /** 全局大板Id */ + fullBoardId = 0 + /** 大板ID */ + boardId = 0 + /** 排单ID */ + placeId = 0 + /** 坐标X */ + placeX = 0 + /** 坐标Y */ + placeY = 0 + /** 排版方式 */ + placeStyle: PlaceStyle = PlaceStyle.FRONT // 正面 + /** 下刀点区域 */ + cutRegion = BlockRegion.LEFT_BOTTOM // 左下 + /** 开料顺序 */ + cutOrder = 0 + /** 下刀点位 */ + cutPointId = 0 + /** 开料宽 */ + placeWidth = 0 + /** 开料长 */ + placeLength = 0 + /** 开料左封边 */ + placeSealLeft = 0 + /** 开料右封边 */ + placeSealRight = 0 + /** 开料上封边 */ + placeSealTop = 0 + /** 开料下封边 */ + placeSealBottom = 0 + /** 原造型下刀超出板外情况 OrgSizeOutOff */ + orgSizeExpand: SizeExpand = new SizeExpand() + /** 造型下刀超出板外情况 尺寸扩展 SizeOutOff */ + sizeExpand() { return this.blockDetail && this.blockDetail.currentSizeExpand ? this.blockDetail.currentSizeExpand : new SizeExpand() }; + /** 排版宽 加上预铣等外括 ,开料刀半径 ,缝隙 */ + placeFullWidth() { return this.cutWidth + (this.blockDetail ? this.blockDetail?.currentSizeExpand?.width || 0 : 0) } + /** 排版长 加上预铣等外括 ,开料刀半径 ,缝隙 */ + placeFullLength() { return this.cutLength + (this.blockDetail ? this.blockDetail?.currentSizeExpand?.length || 0 : 0) } + /** 坐标偏移情况 */ + placeOffX = 0 + placeOffY = 0 + /** 原始排版坐标 */ + placeOrgX() { return this.placeX - this.placeOffX } + placeOrgY() { return this.placeY - this.placeOffY } + /** 反面开料 */ + isTurnOver(): boolean { + return this.placeStyle > 3 + } + + /** 后转180 */ + isTurnBack(): boolean { + return ( + this.placeStyle == PlaceStyle.BACK_TURN_BACK + || this.placeStyle == PlaceStyle.FRONT_TURN_BACK + ) + } + + /** 反纹开料 */ + isTurnDown(): boolean { + return this.placeStyle % 2 == 1 + } + + /** 自动排版 */ + isAutoPlaced = true + /** 超出板外 */ + isOutBoard = false + /** 已重叠 */ + isOverlap = false + /** 高方向 */ + placeDirection = '→' + /** 长边方向 */ + placeDirection_Length = '→' + + // 开料情况 + + // isUseSameKnifeToHelpCut() { return this.blockDetail ? this.blockDetail.isHelpCut : false; }; + /** 是否排钻 IsDoHoling */ + isDrilling = true + /** 是否造型 IsDoModeling */ + isModeling = true + /** 超限板标识 : -1=不是; 0=不判断 1=是 */ + overBlockFlag = 0 + /** 开料时需翻面 IsDoOtherFace */ + isCutOtherFace = false + /** 开料反面要处理 NeedTurnFaceToDo */ + isCutTurnOver = false + + /** 开料面孔数 HoleCount_DoFaceA */ + holeCountFaceA(): number { + return this.holeListFaceA.length + } + + /** 开料反面孔数 HoleCount_DoFaceB */ + holeCountFaceB(): number { + return this.holeListFaceB.length + } + + /** 开料左侧孔数 HoleCount_DoLeft */ + holeCountSideLeft = 0 + /** 开料右侧孔数 HoleCount_DoRight */ + holeCountSideRight = 0 + /** 开料上侧孔数 HoleCount_DoUpper */ + holeCountSideTop = 0 + /** 开料下侧孔数 HoleCount_DoUnder */ + holeCountSideBottom = 0 + /** 开料面造型数 ModelCount_DoFaceA */ + modelCountFaceA(): number { + return this.modelListFaceA.length + } + + /** 开料反面造型数 ModelCount_DoFaceB */ + modelCountFaceB(): number { + return this.modelListFaceB.length + } // 不显示 + + /** 开料面孔 Holes_DoFaceA */ + holeListFaceA: BlockHole[] = [] // 不显示 + /** 开料反面孔 Holes_DoFaceB */ + holeListFaceB: BlockHole[] = [] // 不显示 + /** 开料面造型 Models_DoFaceA */ + modelListFaceA: BlockModel[] = [] // 不显示 + /** 开料反面造型 Models_DoFaceB */ + modelListFaceB: BlockModel[] = [] + + /** 贴标位置x NotePosX */ + labelPosX = 0 + /** 贴标位置y NotePosY */ + labelPosY = 0 + + // CNC高级对接-------------------------------------------------------- + + /** CNC正面条码 CNC_NoteA */ + isCncLabelA = false + /** CNC反面条码 CNC_NoteB */ + isCncLabelB = false + /** CNC翻面条码 CNC_NoteT */ + isCncLabelTurnOver = false + /** 类型 */ + type = '' + /** 加工组 */ + processGroupName = '' + /** 开门类型 */ + openDoorType = OpenDoorType.NONE // 无 + /** 自增小板的id接口获取 */ + _blockId = '' + + constructor(data: any = null) { + if (data != null) { + + for (const key in data) { + if (Reflect.has(this, key)) { + if (['goodsId'].includes(key)) { + this[key] = data[key].toString() + } else if (['width', 'length', 'thickness', 'sealLeft', 'sealRight', 'sealTop', 'sealBottom'].includes(key)) { + this[key] = StringFormat.toFixed(data[key], 3) + } else if (key == 'texture') { + this.texture = data.texture + } else if (key == 'placeHole') { + this.placeHole = data.placeHole + } else if (key == 'remarkJson') { + let remarkList = JSON.parse(data.remarkJson == null || data.remarkJson == '' ? '[]' : data.remarkJson) + this.remark1 = remarkList[0] == null ? '' : remarkList[0] + this.remark2 = remarkList[1] == null ? '' : remarkList[1] + this.remark3 = remarkList[2] == null ? '' : remarkList[2] + this.remark4 = remarkList[3] == null ? '' : remarkList[3] + this.remark5 = remarkList[4] == null ? '' : remarkList[4] + } + else { + try { + if (Object.getOwnPropertyDescriptor(this, key)?.writable == true) { + this[key] = data[key] + } + + } catch (error) { + console.log(error) + } + + } + } else { + // console.log('PlaceBlock Init', key) + } + } + this.cutWidth = this.width - this.sealLeft - this.sealRight + this.cutLength = this.length - this.sealTop - this.sealBottom + this.cutArea = this.cutWidth * this.cutLength * 0.000001 + this._blockId = data.blockId + + } + } + + getRemark(rname, st = '', ed = '') { + if (!this.extraRemark) + return '' + if (!this.extraRemark[rname]) + return '' + let text: string = this.extraRemark[rname] + if (st != '') { + let index = text.indexOf(st) + if (index > -1) { + // text = text.substr(index + st.length, 99999); + text = text.substring(index + st.length, 99999) + } + } + if (ed != '') { + let index = text.indexOf(ed) + if (index > -1) { + text = text.substring(0, index) + } + } + return text + } +} +/** 小板明细(异形,造型,孔) */ +export class PlaceBlockDetail { + // 数据库定义列 + /** 用户ID */ + organId: number = 0 + /** 数据类型 */ + dataType: number = 0 + /** 订单号 */ + orderId: number = 0 + /** 小板ID */ + blockId: number = 0 + /** 原柜体编号rawBoxNo */ + rawBoxNo: number = 0 + /** 异形定义明细 */ + pointDetail: any = [] + /** 造型定义明细 */ + modelDetail: any = [] + /** 排钻定义明细 */ + holeDetail: any = [] + + /** 封边偏移值x */ + offsetX = 0 + /** 封边偏移值y */ + offsetY = 0 + /** 开料宽度 KaiLiaoWidth */ + cutWidth = 0 + /** 开料长度 KaiLiaoLength */ + cutLength = 0 + + /** 异形点阵 */ + points: BlockPoint[] = [] + /** 异形点阵含封边 */ + orgPoints: BlockPoint[] = [] + /** 原轮廓数据 */ + orgContourData: any = null + + /** 排钻 */ + holes: BlockHole[] = [] + /** 正面孔列表(不包含通孔和孔深大于板厚) Holes_FaceA */ + holeListFaceA: BlockHole[] = [] + /** 反面孔列表(不包含通孔和孔深大于板厚) Holes_FaceB */ + holeListFaceB: BlockHole[] = [] + /** 穿孔列表(通孔和孔深大于板厚) Holes_Through */ + holeListThrough: BlockHole[] = [] + /** 侧孔列表 Holes_FaceSide */ + holeListSide: SideHole[] = [] + + /** 造型点阵 */ + models: BlockModel[] = [] + /** 正面造型列表(不包含打穿) Models_FaceA */ + modelListFaceA: BlockModel[] = [] + /** 反面造型列表(不包含打穿) Models_FaceB */ + modelListFaceB: BlockModel[] = [] + /** 打穿造型列表 Models_Through */ + modelListThrough: BlockModel[] = [] + /** 侧面造型 BlockModel SideModels 20241111 改为 BlockModel */ + modelListSide: SideModel[] = [] + + + // /** 切割板宽 */ + // cuttingWidth = 0; + // /** 切割板厚 */ + // cuttingLength = 0; + /** 板厚 */ + thickness = 18 + /** 是否异形 */ + isUnRegular = false + /** 正面有打孔 */ + bigHoleInFaceA = false + /** 正面孔数 */ + holeCountFront = 0 + /** 反面孔数 */ + holeCountBack = 0 + /** 穿孔数 */ + holeCountThrough = 0 + /** 侧孔数 */ + holeCountSide = 0 + /** 左侧孔数 */ + holeCountLeft = 0 + /** 右侧孔数 */ + holeCountRight = 0 + /** 上侧孔数 */ + holeCountTop = 0 + /** 下侧孔数 */ + holeCountBottom = 0 + + /** 斜孔数量 */ + holeCountBevelled = 0 + + + /** 正面造型数 */ + modelCountFront = 0 + /** 反面造型数 */ + modelCountBack = 0 + /** 打穿造型数 */ + modelCountThrough = 0 + + /** 侧面造型数量 */ + modelCountSide(): number { + return this.modelListSide.length + } + /** 下侧造型数量 */ + modelCountBottom(): number { + return this.modelListSide.filter(sideModel => sideModel.face == 0).length + } + /** 右侧造型数量 */ + modelCountRight(): number { + return this.modelListSide.filter(sideModel => sideModel.face == 1).length + } + /** 下侧造型数量 */ + modelCountTop(): number { + return this.modelListSide.filter(sideModel => sideModel.face == 2).length + } + /** 下侧造型数量 */ + modelCountLeft(): number { + return this.modelListSide.filter(sideModel => sideModel.face == 3).length + } + + /** 是否有挖穿的造型 */ + hasModelThrogh = false + + /** 双面处理 Do2Face */ + isTwoFaceProcess = false + // isTwoFaceProcess(): boolean + // { + // return this.blockDetail && this.blockDetail.isTwoFaceProcess + // } + // 为了缺角空间 放在右边 + /** 当需要正面正纹的,最佳放置方式: 正面或正面后转 Ps00 */ + placeStyleFront = PlaceStyle.FRONT // 正面 + /** 正面反纹放置方式 Ps01 */ + placeStyleFrontReverse = PlaceStyle.FRONT_TURN_RIGHT // 正面右转 + /** 反面正纹放置方式 Ps10 */ + placeStyleBack = PlaceStyle.BACK // 反面 + /** 反面反纹放置方式 Ps11 */ + placeStyleBackReverse = PlaceStyle.BACK_TURN_LEFT // 反面左转 + + /** 开料序号贴标位置x */ + labelPosX = -1 + /** 开料序号贴标位置y */ + labelPosY = -1 + + /** 是否初始化 */ + isInited = false + /** 是否已检查 */ + isChecked = false + + /** 全部二维刀路径集合 { model, modelID, lineID, isFaceB, name, value, knife, knifeRadius, depth, points, offset} */ + // vKnifeModels: any[]; + + /** 是否偏移倒角 IsOffsetRounding */ + isOffsetRounding = false + /** 偏移值刀半径 CutR */ + offsetKnifeRadius = 0 + + /** 当前的预铣值 preCutValue */ + preMillingSize = 0 + /** 该板需要辅助开料 isNeedHelpCut */ + isUseSameKnifeToHelpCut = false + /** 同刀辅助值 */ + useSameKnifeToHelpCutGap = 2 + + /** 预铣外扩尺寸 preCutSizeOutOff */ + preMillingExpandSize?: SizeExpand | null = null + /** 板外造型外扩尺寸 modelKnifeOutOff */ + modelExpandSize?: SizeExpand | null = null + /** 2V刀路V外扩尺寸 */ + vKnifeModelExpandSize?: SizeExpand | null = null + /** 同刀辅助外扩尺寸 sameKnfieHelpOutOff */ + sameKnifeHelpExpandSize?: SizeExpand | null = null + /** 当前外扩 currentOutOff */ + currentSizeExpand?: SizeExpand | null = null + /** 边框轮廓,走刀路径等(包含偏移后的轮廓结合) borderUnit */ + borderContour?: PlaceBorderContour | null = null + + // 板件扩展尺寸 数据集 + plateExtraRemark: any + + /** 优化前的配置及其轮廓等用于恢复 */ + savedSetting + + /** 备注 数据集 */ + remarkParams: any + constructor(data: any = null, sourceType: number = 2) { + if (data != null) { + this.remarkParams = data.remarkParams + this.orderId = data.orderId + this.dataType = data.dataType + this.orderId = data.orderId + this.blockId = data.blockId + this.rawBoxNo = data.rawBoxNo + + // 封边偏移值 + this.offsetX = data.offSet && data.offSet.x ? StringFormat.toFixed(data.offSet.x, 3) : 0 + this.offsetY = data.offSet && data.offSet.y ? StringFormat.toFixed(data.offSet.y, 3) : 0 + // 开料长宽(不含封边) + this.cutWidth = data.cutSize ? StringFormat.toFixed(data.cutSize.width, 3) : 0 + this.cutLength = data.cutSize ? StringFormat.toFixed(data.cutSize.height, 3) : 0 + // 初始化 异形点阵 + data.pointDetail.forEach((p) => { this.points.push(new BlockPoint(p)) }) + // 初始化 异形点阵 含封边 + if (!data.orgPointDetail) { + data.orgPointDetail = [] + } + data.orgPointDetail.forEach((p) => { this.orgPoints.push(new BlockPoint(p)) }) + + if (!data.holes) { + data.holes = data.holeDetail || [] + } + // 初始化 排钻列表 + data.holes.forEach((h) => { this.holes.push(new BlockHole(h)) }) + // 初始化 侧孔列表 + if (!data.holeListSide) { + data.holeListSide = data.sideHoleDetail || [] + } + data.holeListSide.forEach((sh) => { this.holeListSide.push(new SideHole(sh)) }) + // 初始化 造型列表 + data.modelDetail.forEach((t) => { this.models.push(new BlockModel(t)) }) + // 初始化 侧面造型列表 + if (!data.modelListSide) { + data.modelListSide = [] + } + + this.plateExtraRemark = data.plateExtraRemark + + data.modelListSide.forEach((t) => { this.modelListSide.push(new SideModel(t)) }) // 侧面 + + this.holeListFaceA = this.holes.filter(t => t.face == FaceType.FRONT) // 正面 + this.holeListFaceB = this.holes.filter(t => t.face == FaceType.BACK) // 反面 + this.holeListThrough = [] + + this.modelListFaceA = this.models.filter(t => t.face == FaceType.FRONT && (t.depth < this.thickness || t.isVKnifeModel())) + this.modelListFaceB = this.models.filter(t => t.face == FaceType.BACK && (t.depth < this.thickness || t.isVKnifeModel())) + this.modelListThrough = this.models.filter(t => t.depth > this.thickness - 0.01 && t.isVKnifeModel() == false) + + this.holeCountFront = this.holeListFaceA.length + this.holeCountBack = this.holeListFaceB.length + this.holeCountThrough = this.holeListThrough.length + this.holeCountSide = this.holeListSide.length + + this.modelCountFront = this.modelListFaceA.length + this.modelCountBack = this.modelListFaceB.length + this.modelCountThrough = this.modelListThrough.length + + this.hasModelThrogh = this.modelListThrough.length > 0 + this.bigHoleInFaceA = this.holeListFaceA.some(t => t.holeType == HoleType.BIG_HOLE) // 大孔 + + } + + this.isUnRegular = this.points.length > 0 + + } + + /** 保存 开料配置 */ + saveSetting() { + this.savedSetting = {} + + this.savedSetting.offsetKnifeRadius = this.offsetKnifeRadius + this.savedSetting.preMillingSize = this.preMillingSize + this.savedSetting.isUseSameKnifeToHelpCut = this.isUseSameKnifeToHelpCut + this.savedSetting.useSameKnifeToHelpCutGap = this.useSameKnifeToHelpCutGap + // SizeExpand + this.savedSetting.preMillingExpandSize = this.preMillingExpandSize + this.savedSetting.modelExpandSize = this.modelExpandSize + this.savedSetting.vKnifeModelExpandSize = this.vKnifeModelExpandSize + this.savedSetting.sameKnifeHelpExpandSize = this.sameKnifeHelpExpandSize + this.savedSetting.currentExpandSize = this.currentSizeExpand + // 轮廓,铣刀路径 + if (this.borderContour != null) { + this.savedSetting.borderContour = this.borderContour.clone() + } + + } + + loadSetting() { + if (!this.savedSetting) + return + this.offsetKnifeRadius = this.savedSetting.offsetKnifeRadius + this.preMillingSize = this.savedSetting.preMillingSize + this.isUseSameKnifeToHelpCut = this.savedSetting.isUseSameKnifeToHelpCut + this.useSameKnifeToHelpCutGap = this.savedSetting.useSameKnifeToHelpCutGap + // SizeOutOff + this.preMillingExpandSize = this.savedSetting.preMillingExpandSize + this.modelExpandSize = this.savedSetting.modelExpandSize + this.vKnifeModelExpandSize = this.savedSetting.vKnifeModelExpandSize + this.sameKnifeHelpExpandSize = this.savedSetting.sameKnifeHelpExpandSize + this.currentSizeExpand = this.savedSetting.currentSizeExpand + // 轮廓,铣刀路径 + this.borderContour = this.savedSetting.borderContour + } +} +/** 开门类型(0无 1左 2右 3上 4下) */ +export enum OpenDoorType { + /** 无 */ + NONE = 0, + /** 左 */ + LEFT = 1, + /** 右 */ + RIGHT = 2, + /** 上 */ + TOP = 3, + /** 下 */ + BOTTOM = 4, +} + +/** 纹理类型 */ +export enum TextureType { + /** 正纹 */ + NORMAL_TEXTURE = 0, + /** 可翻转 */ + ROTATABLE_TEXTURE = 1, + /** 反纹 */ + REVERSE_TEXTURE = 2, +} + +/** 排钻 */ +export class BlockHole { + /** 孔类别 */ + holeType: HoleType = 0 + /** 面 */ + face: FaceType = 0 + /** 侧孔所在面 */ + sideFace: FaceType = FaceType.FRONT // 正面 + /** 坐标x */ + pointX: number = 0 + /** 坐标y */ + pointY: number = 0 + /** 坐标z */ + pointZ: number = 0 + /** 半径 */ + radius: number = 5 + /** 深度 */ + depth: number = 12 + /** 终点坐标 */ + endPoint: string = '' + /** 角度 */ + angle: number = 0 + /** CNC不能加工 cncUndo */ + isCncCanNotProcess: boolean = false + /** 不能加工类型 unDoType */ + cncCanNotProcessType = '' + /** 是否在开料机上处理 IsDo */ + isCutting: boolean = true + + knife?: Knife | null = null + blockNo?: string | number + + constructor(data: any = null) { + if (data != null) { + this.holeType = data.holeType + this.face = data.face + this.pointX = StringFormat.toFixed(data.pointX, 3) + this.pointY = StringFormat.toFixed(data.pointY, 3) + this.pointZ = StringFormat.toFixed(data.pointZ, 3) + this.radius = StringFormat.toFixed(data.radius, 3) + this.depth = StringFormat.toFixed(data.depth, 3) + this.angle = data.angle ? data.angle : 0 + this.endPoint = data.endPoint + } + } + + ToSideHole(block: PlaceBlock): SideHole { + let newH = new SideHole() + newH.holeType = this.holeType + newH.faceId = 0 + newH.sideFace = SideFaceType.BOTTOM // 下侧面 + newH.pointX = this.pointX + newH.pointY = this.pointY + newH.pointZ = this.pointZ + + newH.radius = 4 + let diameter = this.radius * 2 + if (Math.abs(diameter - 8.3) < 0.1 || Math.abs(diameter - 6.3) < 0.1 || Math.abs(diameter - 11.5) < 0.1 || Math.abs(diameter - 12) < 0.1) { + newH.radius = this.radius + } + newH.depth = this.depth + + let cells = this.endPoint.split(',') + if (cells.length >= 3) { + // 起点坐标 + let bx = Number.parseFloat(cells[0]) + let by = Number.parseFloat(cells[1]) + + // x 相等 , 侧孔方向 上下 + if (Math.abs(bx - this.pointX) < 0.1) { + let toUpper = this.isToTop(block, by) + if (toUpper) // y+ 向上 + { + newH.pointX2 = bx + newH.pointY2 = this.pointY + this.depth + newH.direct = 0 + newH.faceId = 0 + newH.sideFace = SideFaceType.BOTTOM_SIDE // 下侧面 + } // y- 向下 + else { + newH.pointX2 = bx + newH.pointY2 = this.pointY - this.depth + newH.direct = 2 + newH.faceId = 2 + newH.sideFace = SideFaceType.TOP_SIDE // 上侧面 + } + } + else if (Math.abs(by - this.pointY) < 0.1) { + let toRight = this.isToRight(block, bx) + // y相等, 侧孔方向左右 + if (toRight) { + // x+ 向右 + newH.pointX2 = this.pointX + this.depth + newH.pointY2 = by + newH.direct = 3 + newH.faceId = 3 + newH.sideFace = SideFaceType.LEFT_SIDE // 左侧面 + } // x- 向左 + else { + newH.pointX2 = this.pointX - this.depth + newH.pointY2 = by + newH.direct = 1 + newH.faceId = 1 + newH.sideFace = SideFaceType.RIGHT_SIDE // 右侧面 + } + } // x ,y 都不相等 + else { + newH.pointX2 = bx + newH.pointY2 = by + newH.direct = -1 + newH.faceId = 4 + newH.sideFace = SideFaceType.SPECIAL_SHAPED_SIDE // 异形侧面 + } + } + + return newH + } + + /** 向右(bx: 起点坐标X) */ + private isToRight(block: PlaceBlock, bx: number): boolean { + // 在左侧外 + if (bx < 0) + return true + // 在板内 靠左侧 + if (bx > 22 && bx < 40) + return true + + // 在右侧外 + if (bx > block.width) + return false + // 在右侧外 + if (bx > block.width - 40 && bx < block.width - 22) + return false + + for (let i = 0; i < block.points.length; i++) { + let j = i + 1 + if (j == block.points.length) + j = 0 + let p1 = block.points[i] + let p2 = block.points[j] + if (Math.abs(p1.pointX - this.pointX) < 3 && Math.abs(p2.pointX - this.pointX) < 3 && (p1.pointY > this.pointY != p2.pointY > this.pointY)) { + return p1.pointY > p2.pointY + } + } + + return false + } + + /** 向上(起点坐标Y) */ + private isToTop(block: PlaceBlock, by: number): boolean { + // 在下侧外 + if (by < 0) + return true + // 靠近下侧 + if (by > 22 && by < 40) + return true + + if (by > block.length) + return false + if (by > block.length - 40 && by < block.length - 20) + return false + + for (let i = 0; i < block.points.length; i++) { + let j = i + 1 + if (j == block.points.length) + j = 0 + let p1 = block.points[i] + let p2 = block.points[j] + if (Math.abs(p1.pointY - this.pointY) < 3 && Math.abs(p2.pointY - this.pointY) < 3 && (p1.pointX > this.pointX != p2.pointX > this.pointX)) { + return p1.pointX < p2.pointX + } + } + + return false + } +} +/** 异形点 */ +export class BlockPoint { + /** 坐标x */ + pointX: number = 0 + /** 坐标y */ + pointY: number = 0 + /** 凹凸度 */ + curve: number = 0 + /** 半径 */ + radius: number = 0 + /** 封边 */ + sealSize: number = 0 + /** 是否这条边需预洗 */ + isPreCutRequired = false + + constructor(data: any = null) { + if (data != null) { + let x = data.PointX != undefined ? data.PointX : data.pointX + let y = data.PointY != undefined ? data.PointY : data.pointY + let curve = data.Curve != undefined ? data.Curve : data.curve + let radius = 1 / curve + this.pointX = StringFormat.toFixed(x) + this.pointY = StringFormat.toFixed(y) + this.curve = curve + this.radius = StringFormat.toFixed(Math.abs(radius)) + if (Math.abs(this.curve) < 1e-5) { + this.curve = 0 + this.radius = 0 + } + this.sealSize = data.SealSize != undefined ? data.SealSize : data.sealSize || 0 + } + } + + copy(): BlockPoint { + let nw = new BlockPoint() + nw.pointX = this.pointX + nw.pointY = this.pointY + nw.curve = this.curve + nw.radius = this.radius + nw.sealSize = this.sealSize + return nw + } +} + +/** 侧孔 */ +export class SideHole { + /** 孔类别 */ + holeType: HoleType = 0 + /** 面 */ + faceId: number = 0 + /** 侧孔所在面 */ + sideFace: SideFaceType = SideFaceType.BOTTOM // 正面 + /** 方向 0向上 1向左 2向下 3向右 -1斜 */ + direct: number = 0 + /** 坐标x */ + pointX: number = 0 + /** 坐标y */ + pointY: number = 0 + /** 坐标z */ + pointZ: number = 0 + /** 半径 */ + radius: number = 5 + /** 深度 */ + depth: number = 12 + + /** 坐标x */ + pointX2: number = 0 + /** 坐标y */ + pointY2: number = 0 + + // cncUndo: boolean = true; //cnc不能做. + /** CNC不能处理 */ + isCncCanNotProcess: boolean = true + + /** 是否可处理 IsDo */ + isProcess: boolean = true + + knife?: Knife | null = null + + constructor(data: any = null) { + if (data != null) { + this.holeType = data.holeType + this.faceId = data.face + this.sideFace = data.sideFace + this.direct = data.sideFace + this.pointX = StringFormat.toFixed(data.pointX, 3) + this.pointY = StringFormat.toFixed(data.pointY, 3) + this.pointZ = StringFormat.toFixed(data.pointZ, 3) + this.radius = StringFormat.toFixed(data.radius, 3) + this.depth = StringFormat.toFixed(data.depth, 3) + this.pointX2 = StringFormat.toFixed(data.pointX2, 3) + this.pointY2 = StringFormat.toFixed(data.pointY2, 3) + } + } +} + +/** 孔类型 */ +export enum HoleType { + /** 大孔 */ + BIG_HOLE = 0, + /** 小孔 */ + TINY_HOLE = 10, + /** 木削 */ + WOOD_DUST = 20, + /** 木削大孔 */ + WOOD_DUST_BIG_HOLE = 21, + /** 层板钉 */ + LAMINATED_NAIL = 30, + /** 通孔 */ + THROUGH_HOLE = 40, + /** 造型孔 */ + MODELING_HOLE = -10, + /** 连接杆 */ + CONNECTING_ROD = 50, +} + +/** 板件造型 */ +export class BlockModel { + /** 订单号 */ + orderId: string = '' + /** 小板号 */ + blockId: number = 0 + /** 造型Id */ + modelId: number = 0 + /**线ID */ + lineId: number = 0; + /** 纹路Id */ + textureId: number = 0 + /** 板面 */ + face: FaceType = FaceType.UNSET + /** 刀号 */ + knifeName: string = '' + /** 刀半径 */ + knifeRadius: number = 0 + /** 深度 */ + depth: number = 0 + /** 是否可以挖空后排版 */ + canSpace = false + /** 是否是矩形 */ + isRect = false + /** 是否倾斜 至少有一条边是斜的 */ + isTilt = false + + type = '' + + knife: Knife | null | undefined = null + + blockNo?: string| number + /** 有轮廓 */ + hasContour() { + let val = false + try { + if (this.originModeling != null && this.originModeling.outline != 0 && this.originModeling?.outline.map(e => e?.pts).length > 0) { + val = true + } + } catch (error) { + console.log('handle hasContour error', error) + val = false + } + + return val // this.originModeling?.outline.map(e=>e?.pts).length >0 //this.originModeling?.outline?.pts?.length > 0 + } + + modelWidth: any = 0 + modelLength: any = 0 + + modelStartPoint: any = null + modelEndPoint: any = null + /** + * 造型轮廓(孔转的造型,没有造型轮廓) + * OriginModeling.outline.pts.x | y ; outline.buls + */ + originModeling: any = null + + /** 点列表 */ + pointList: BlockModelPoint[] = [] + /** 实际替代刀直径 */ + realKnifeRadius = 0 + /** 实际替代刀点阵 */ + realPointList: BlockModelPoint[] = [] + /** 实际刀编号 */ + realKnifeId = -1 + + /** cnc不能加工 cncUndo */ + isCncCanNotProcess: boolean = true + /** 超限类型(cnc为何不能加工) unDoType */ + cncCanNotProcessType = '' + + /** 是否在开料机加工 IsDo */ + isCutting: boolean = true + + /** 二维造型的偏移数组 */ + offsetList: ModelOffsetData[] = [] + + /** 转换过的二维刀路路径{isFaceB, name刀名称, offset偏移, knife刀, knifeId刀Id, knifeRadius刀半径, depth深度, points{x,y,z,bul,r}, orgOffset(源偏移)} */ + VLines: any[] = [] + + offsetInfo = '' + /** 是否二维造型 */ + isVKnifeModel(): boolean { + return this.offsetList.length > 0 + } + + /** 是否三维造型 */ + is3VModel(): boolean { + return this.offsetList.length == 0 && this.pointList.some(t => Math.abs(t.depth - this.depth) > 0.01) + } + + constructor(data: any = null) { + if (data != null) { + this.modelId = data.modelId + this.lineId = data.lineId + this.textureId = data.textureId + this.face = data.face + this.knifeName = data.knifeName ? data.knifeName.trim() : '' + this.knifeRadius = StringFormat.toFixed(data.knifeRadius, 3) + this.depth = StringFormat.toFixed(data.depth, 3) + + if (data.modelPoint) + data.modelPoint.forEach(pt => { + + this.pointList.push(new BlockModelPoint(pt)) + }) + if (data.modelOffSet) + data.modelOffSet.forEach(os => this.offsetList.push(new ModelOffsetData(os))) + + if (this.offsetList.length > 0) { + let names: any = [] + for (let os of this.offsetList) { + if (os.name == this.knifeName) + continue + if (names.includes(os.name)) + continue + names.push(name) + } + if (names.length == 0) + this.offsetInfo = `${this.offsetList.length - 1}` + else + this.offsetInfo = `${this.offsetList.length - 1}(${names.join(' ')})` + } + + if (data.originModeling) { + this.originModeling = JSON.parse(JSON.stringify(data.originModeling)) + // 计算造型 + // this.countModelWidthAndLength(this) + } + } + } +} + +/** 造型点 */ +export class BlockModelPoint { + /** 坐标X */ + pointX: number = 2 + /** 坐标Y */ + pointY: number = 2 + /** 半径 */ + radius: number = 0 + /** 深度 */ + depth: number = 6 + /** 凹凸度 */ + curve: number = 0 + + constructor(data: any = null) { + if (data != null) { + this.pointX = StringFormat.toFixed(data.PointX || data.pointX) + this.pointY = StringFormat.toFixed(data.PointY || data.pointY) + this.radius = StringFormat.toFixed(Math.abs(data.Radius || data.radius)) + this.depth = StringFormat.toFixed(data.Depth || data.depth) + this.curve = data.Curve || data.curve + } + } + + copy(): BlockModelPoint { + let obj = new BlockModelPoint() + obj.pointX = this.pointX + obj.pointY = this.pointY + obj.radius = this.radius + obj.depth = this.depth + obj.curve = this.curve + return obj + } +} + +/** 二维造型的偏移数据 */ +export class ModelOffsetData { + /** 刀名称 */ + name: string = '' + /** 偏移 */ + offset: number = 0 + /** 刀半径 */ + radius: number = 0 + /** 深度 */ + depth: number = 0 + /** 角度 */ + angle: number = 0 + /** 板面类型 */ + face: FaceType = FaceType.UNSET + + fullName() { + return `偏移${this.offset} 深${this.depth} 刀${this.name}` + } + + constructor(data: any = null) { + if (data != null) { + let off = 0 + if (data.offset != undefined) { + off = data.offset + } else if (data.value != undefined) { + off = data.value + } else if (data.value != undefined) { + off = data.value + } + this.name = data.Name != undefined ? data.Name : data.name + this.offset = StringFormat.toFixed(data.offset || data.value || data.value) + this.radius = StringFormat.toFixed(data.Radius || data.radius) + this.depth = StringFormat.toFixed(data.Deep || data.depth) + this.face = data.Face != undefined ? data.Face : data.face + this.angle = StringFormat.toFixed(data.Angle || data.angle) + } + } +} + +/** + * 侧面造型 所在的面 0 下 1 右 2上 3 左 其它待测试 + */ +export enum SideFaceType { + /** 下 */ + BOTTOM = 0, + /** 右 */ + RIGHT = 1, + /** 上 */ + TOP = 2, + /** 左 */ + LEFT = 3, + + /** 左侧面 */ + LEFT_SIDE = 21, + /** 右侧面 */ + RIGHT_SIDE = 22, + /** 上侧面 */ + TOP_SIDE = 23, + /** 下侧面 */ + BOTTOM_SIDE = 24, + /** 弧形侧面 */ + CURVED_SIDE = 29, + /** 异形侧面 */ + SPECIAL_SHAPED_SIDE = 30, +} + +/** 侧边造型 + * + * 侧面造型的点阵判断逻辑 + * + * 提要: + * 1、 face 为 该造型在该板件的第几条边上 + * 2、 originModeling 内的造型轮廓点阵的 X Y轴 为 : + * 以该造型为正面 且小板板面朝上 造型为正面的左下角为原点 板厚 为 Y 横象为X + * 注:最终使用的时候 要得到该造型 对应机台的 轮廓数据和刀路数据 + * + * 平行判断 参考 checkIsTilt 修改一个新的方法 + * 是否在板内 可使用 isPointInBlock + * + * 要求:要得到 + * + * 转换逻辑 + * 1、通过face 获取 该造型所在的边 + * 2、将这条边 与 板件的坐标轴做比较 判断平行 + * 情况1:与板件的X轴 平行 则造型 可能为 上 || 下 ,使用 isPointInBlock 判断 具体是上 还是下 + * 情况2:与板件的Y轴 平行 则造型 可能为 左 || 右 , 使用 isPointInBlock 判断 具体是左 还是右 + * 情况3:都不平行 则 朝向的值为斜边 + * 最终得到造型的朝向 + * 3、依据边的坐标和造型的朝向 可以 根据造型的轮廓数据转为 对应机台的轮廓数据 和刀路数据 +*/ +export class SideModel { + /** 订单号 */ + orderId: string = '' + /** 小板号 */ + blockId: number = 0 + /** 造型Id */ + modelId: number = 0 + /**线ID */ + lineId: number = 0; + /** 纹路Id */ + textureId: number = 0 + /** 板面 造型所在的边 第几条边 */ + face: SideFaceType = 0 + /** 刀号 */ + knifeName: string = '' + /** 刀半径 */ + knifeRadius: number = 0 + /** 深度 */ + depth: number = 0 + /** 是否可以挖空后排版 */ + canSpace = false + /** 是否是矩形 */ + isRect = false + /** 是否倾斜 至少有一条边是斜的 */ + isTilt = false + /** 暂时没用 */ + type = '' + + /** 造型方向 -2 未知(是异常情况 要排查) -1 斜的 0 下 1右 2上 3左 10 左下斜 11 右下斜 12 右上斜 13 左上斜 */ + + direction = 0 + + + knife?: Knife | null = null + /** 有轮廓 */ + hasContour() { return this.originModeling != null && this.originModeling.outline != 0 && this.originModeling?.outline?.pts?.length > 0 } + + /** 侧面造型 宽 */ + modelWidth: any = 0 + + /** 侧面造型 长 */ + modelLength: any = 0 + + /** 侧面造型 起点 */ + modelStartPoint: any = null + + /** 侧面造型 终点 */ + modelEndPoint: any = null + /** + * 造型轮廓(孔转的造型,没有造型轮廓) + * OriginModeling.outline.pts.x | y ; outline.buls + */ + originModeling: any = null + + /** 点列表 */ + pointList: BlockSideModelPoint[] = [] + /** 实际替代刀直径 */ + realKnifeRadius = 0 + /** 实际替代刀点阵 */ + realPointList: BlockSideModelPoint[] = [] + /** 实际刀编号 */ + realKnifeId = -1 + + /** cnc不能加工 cncUndo */ + isCncCanNotProcess: boolean = true + /** 超限类型(cnc为何不能加工) unDoType */ + cncCanNotProcessType = '' + + /** 是否在开料机加工 IsDo */ + isCutting: boolean = true + + /** 二维造型的偏移数组 */ + offsetList: ModelOffsetData[] = [] + + /** 转换过的二维刀路路径{isFaceB, name刀名称, offset偏移, knife刀, knifeId刀Id, knifeRadius刀半径, depth深度, points{x,y,z,bul,r}, orgOffset(源偏移)} */ + VLines = [] + + offsetInfo = '' + /** 是否二维造型 */ + isVKnifeModel(): boolean { + return this.offsetList.length > 0 + } + + /** 是否三维造型 */ + is3VModel(): boolean { + return this.offsetList.length == 0 && this.pointList.some(t => Math.abs(t.depth - this.depth) > 0.01) + } + + constructor(data: any = null) { + if (data != null) { + this.modelId = data.modelId + this.lineId = data.lineId + this.textureId = data.textureId + this.face = data.face + this.knifeName = data.knifeName ? data.knifeName.trim() : '' + this.knifeRadius = StringFormat.toFixed(data.knifeRadius, 3) + this.depth = StringFormat.toFixed(data.depth, 3) + + + // if (data.originModeling) + // { + // this.originModeling = JSON.parse(JSON.stringify(data.originModeling)) + // MachineHelper.countModelWidthAndLength(this) + // } + } + } + +} + +/** 小板区域 */ +export enum BlockRegion { + /** 左下 = 0 */ + LEFT_BOTTOM = 0, + /** 右下 = 1 */ + RIGHT_BOTTOM = 1, + /** 右上 = 2 */ + RIGHT_TOP = 2, + /** 左上 = 3 */ + LEFT_TOP = 3, +} + +/** 造型偏移 */ +export class SizeExpand { + /** 左 */ + left: number = 0 + /** 右 */ + right: number = 0 + /** 上 */ + top: number = 0 + /** 下 */ + bottom: number = 0 + /** 板外扩宽 */ + width: number = 0 + /** 板外扩长 */ + length: number = 0 + /** 排版外扩宽 */ + outWidth: boolean = false + /** 排版外扩长 */ + outLength: boolean = false + /** 是否完成 */ + hasDone: boolean = false + + constructor(data: any = null) { + if (data != null) { + this.left = data.left || 0 + this.right = data.right || 0 + this.bottom = data.bottom || 0 + this.top = data.top || 0 + this.width = (this.left + this.right) || 0 + this.length = (this.top + this.bottom) || 0 + } + } + + /** 设置长宽 */ + setSize() { + this.width = this.left + this.right + this.length = this.top + this.bottom + } + + toString() { + return `左:${this.left.toFixed(2)} 右:${this.right.toFixed(2)} 上:${this.top.toFixed(2)} 下:${this.bottom.toFixed(2)}` + } +} + +/** + * 板件侧面早些点阵 + */ +export class BlockSideModelPoint { + /** 坐标X */ + pointX: number = 2 + /** 坐标Y */ + pointY: number = 2 + /** 坐标Z */ + pointZ: number = 2 + /** 半径 */ + radius: number = 0 + /** 深度 */ + depth: number = 6 + /** 凹凸度 */ + curve: number = 0 + + constructor(data: any = null) { + if (data != null) { + this.pointX = StringFormat.toFixed(data.pointX || data.x) + this.pointY = StringFormat.toFixed(data.pointY || data.y) + this.pointZ = StringFormat.toFixed(data.pointZ || data.z) + this.radius = StringFormat.toFixed(Math.abs(data.radius || 0)) + this.depth = StringFormat.toFixed(data.depth) + this.curve = data.curve || data.buls || 0 + + if (this.radius == 0 && this.curve != 0) { + this.radius = 1 / this.curve + } + } + } + + copy(): BlockSideModelPoint { + let obj = new BlockSideModelPoint() + obj.pointX = this.pointX + obj.pointY = this.pointY + obj.radius = this.radius + obj.depth = this.depth + obj.curve = this.curve + return obj + } +} + + +/** 放置板的边框集合,内部轮廓,偏移后轮廓 */ +export class PlaceBorderContour { + /** 放置方式 */ + placeStyle: PlaceStyle = PlaceStyle.FRONT // 正面 + /** 1成品轮廓 必须提前生成 */ + borderFinal: CAD.Curve2d[] + /** 2原始轮廓 <不含预铣> 必须提前生成 */ + borderOrg: CAD.Curve2d[] + /** 2.原始轮廓含预铣 border_preCut */ + borderPreMilling: CAD.Curve2d[] = [] + + /** 2原始多段线 《不含预铣》 必须提前生成,用于 计算造型,2v板外干涉轮廓 */ + polylineOrg?: Polyline | null = null + + /** 3.1开料轮廓 <含预铣> */ + border: CAD.Curve2d[] = [] + + /** 3.2真.开料轮廓.同刀辅助._有预铣带预铣) border_tdfz */ + borderSameKnifeHelpCut: CAD.Curve2d[] = [] + /** 走刀路径(预铣) */ + cutLines: CAD.Curve2d[] = [] + + /** 走刀路径2(预铣 同刀辅助) cutLines_tdfz */ + cutLinesSameKnifeHelpCut: CAD.Curve2d[] = [] + + /** 排版轮廓+预铣+同刀辅助+刀半径+缝隙/2 再与造型外扩并集 border_moving */ + borderMoving: CAD.Curve2d[] = [] + /** 优化轮廓,用于王者优化 border_wxyh */ + borderKingOptimize: CAD.Curve2d[] = [] + + /** 板内轮廓挖穿造型(borders_inner) */ + borderModelThrough: CAD.Curve2d[][] = [] + /** 板内轮廓挖穿r(borders_inner_r) */ + borderModelThroughR: number[] = [] + /** 板内挖穿造型走刀路径,内部走刀轮廓(cutLines_inner) */ + cutLinesModelThrough: CAD.Curve2d[][] = [] + /** 板内排版轮廓,用于手动排版定位(borders_inner_place) */ + borderInnerPlace: CAD.Curve2d[][] = [] + /** 板内空间:挖穿造型(spaces_inner) */ + blockInnerSpace: PlaceSpace[] = [] + /** 板外空间:缺角(spaces_outer) */ + blockOuterSpace: PlaceSpace[] = [] + + /** 空间,包括造型矩形与缺角空间 */ + spaces: PlaceSpace[] = [] + + /** 2v刀路,板外轮廓 */ + polylines2vModel: Polyline[] = [] + + /** 造型,板外轮廓 */ + polylinesOutModel: Polyline[] = [] + + /** 翻转后的这些轮廓集合 */ + placeContours: PlaceBorderContour[] + + constructor(placeStyle: PlaceStyle, borderFinal: CAD.Curve2d[], borderOrg: CAD.Curve2d[]) { + this.placeStyle = placeStyle + this.borderFinal = borderFinal + this.borderOrg = borderOrg + this.placeContours = [] + } + + /** 开料刀 半径变化了 */ + cutKnifeChanged() { + this.cutLines = [] + this.cutLinesSameKnifeHelpCut = [] + this.borderMoving = [] + // this.borderKingOptimize = null; + + this.borderModelThrough = [] + this.borderInnerPlace = [] + this.blockInnerSpace = [] + this.blockOuterSpace = [] + if (this.placeContours) + this.placeContours.forEach(t => t.cutKnifeChanged()) + } + + /** 预铣值变化, 将有关轮廓全部清理 */ + preValueChanged() { + // console.log('预铣值变化, 将有关轮廓全部清理') + this.border = [] + this.borderSameKnifeHelpCut = [] + this.cutLines = [] + this.cutLinesSameKnifeHelpCut = [] + this.borderMoving = [] + // this.borderKingOptimize = null; + this.blockOuterSpace = [] + if (this.placeContours) + this.placeContours.forEach(t => t.preValueChanged()) + } + + /** 同刀辅助 变化了 */ + sameKnifeGapChanged() { + this.borderSameKnifeHelpCut = [] + this.cutLinesSameKnifeHelpCut = [] + this.borderMoving = [] + // this.borderKingOptimize = null; + this.blockOuterSpace = [] + if (this.placeContours) + this.placeContours.forEach(t => t.sameKnifeGapChanged()) + } + + /** 克隆 */ + clone(): PlaceBorderContour { + let newObj = new PlaceBorderContour(this.placeStyle, this.borderFinal, this.borderOrg) + newObj.borderPreMilling = this.borderPreMilling + newObj.polylineOrg = this.polylineOrg + newObj.border = this.border + newObj.borderSameKnifeHelpCut = this.borderSameKnifeHelpCut + newObj.cutLines = this.cutLines + newObj.cutLinesSameKnifeHelpCut = this.cutLinesSameKnifeHelpCut + newObj.borderMoving = this.borderMoving + // newObj.borderKingOptimize = this.borderKingOptimize; + newObj.borderModelThrough = this.borderModelThrough + newObj.borderModelThroughR = this.borderModelThroughR + newObj.cutLinesModelThrough = this.cutLinesModelThrough + newObj.borderInnerPlace = this.borderInnerPlace + newObj.blockInnerSpace = this.blockInnerSpace + newObj.blockOuterSpace = this.blockOuterSpace + newObj.spaces = this.spaces + newObj.polylines2vModel = this.polylines2vModel + newObj.polylinesOutModel = this.polylinesOutModel + newObj.placeContours = [] + for (let pc of this.placeContours) { + if (pc) { + let npc = pc.clone() + newObj.placeContours.push(npc) + } + } + console.log('克隆', newObj) + return newObj + } +} + + +/** 优化空间 */ +export class PlaceSpace { + /** 大板ID */ + boardId: number + /** 空间ID */ + spaceId: number + /** 坐标x */ + x: number + /** 坐标y */ + y: number + /** 宽 */ + width: number + /** 长 */ + length: number + /** 是否是小板内部造型空间 IsInner */ + isBlockInnerSpace = false + /** 顶点Y */ + topY(): number { return this.y + this.length } + /** 右边X */ + topX(): number { return this.x + this.width }; + + constructor(x: number, y: number, width: number, length: number) { + this.boardId = 0 + this.spaceId = 0 + this.x = x + this.y = y + this.width = width + this.length = length + } + + /** 克隆 */ + clone(): PlaceSpace { + let obj = new PlaceSpace(this.x, this.y, this.width, this.length) + return obj + } + + static create(x1, y1, x2, y2): PlaceSpace { + return new PlaceSpace(x1, y1, x2 - x1, y2 - y1) + } +} + +/** 开料板材 */ +export class PlaceMaterial { + /** 订单号(*) */ + orderId = '' + /** 板材ID(*) */ + goodsId = '' + /** 板材名称(*) */ + goodsName = '' + /** 规格(*) */ + spec = '' + /** 材质(*) */ + material = '' + /** 颜色(*) */ + color = '' + /** 品牌(*) */ + brand = '' + /** 有纹路 */ + hasTexture = false + /** 最后保存时间 */ + updateTime = '' + /** 纹路标识 */ + get textureFlag(): string { return this.hasTexture ? '有' : '无' } + /** 板材全名 */ + get fullName(): string { return `${this.hasTexture ? '' : '【无纹】'}${this.thickness} ${this.goodsName} ${this.material} ${this.color}` } + /** 板材原宽 */ + orgWidth = 1220 + /** 板材原长 */ + orgLength = 2440 + /** 板宽,用于生产 */ + width = 1220 + /** 板长 */ + length = 2440 + /** 板厚 */ + thickness = 18 + /** 全部尺寸 */ + get fullSize(): string { return `${this.length}*${this.width}*${this.thickness}` } + + /** 修边值(*) Border */ + cutBorder = 3 + /** 开料刀直径(*) CutDia */ + diameter = 6 + /** 开料刀路间隙(*) CutGap */ + cutKnifeGap = 1 + /** 预铣值 PreCutValue */ + preMillingSize = 0 + /** 是否辅助开料 HelpCut */ + isHelpCut = false + /** 辅助开料标识 */ + get strHelpCut() { return this.isHelpCut ? '✔' : '✘' } + /** 同刀辅助开料偏移 SameKnifeHelpCutGap */ + helpCutGap = 0 + + /** 开料刀ID */ + cutKnifeId = -1 + /** 开料刀名称 */ + cutKnifeName = '' + /** 辅助刀ID */ + helpKnifeId = -1 + /** 辅助刀名称 */ + helpKnifeName = '' + get strHelpKnifeName() { return this.isHelpCut ? this.helpKnifeName : '' } + + /** 是否已优化(*) isSorted */ + isOptimized = false + get optimizedFlag() { return this.isOptimized ? '是' : '-' } + /** 大板数(*) */ + boardCount = 0 + /** 余料板数 */ + remainBoardCount = 0 + /** 最小板号(*) */ + minBoardId = 0 + /** 最大板号(*) */ + maxBoardId = 0 + /** 当前板号 */ + currentBoardId = 0 + + /** 总平均利用率(*) AvgLyr_All */ + avgUsageRateAll = 0 + /** 前N块板(不包含最后一块板)的平均利用率(*) AvgLyr_NoLastOne */ + avgUsageRateExcludeLastBoard = 0 + /** 最后一块板的利用率(*) Lyr_LastOne */ + usageRateLastBoard = 0 + /** 分配到该板材的 小板数量 */ + allBlockCount = 0 + /**分配到该板材的优化后小板数量 -- 选择优化的板件数目 */ + selectedBlockCount = 0 + /** 分配到该板材的优化后 未被优化的 小板数量 */ + noPlaceBlockCount = 0 + /** 优化时大板数(*) */ + optimizingBoardCount = 0 + /** 优化时余料板数 */ + optimizingRemainBoardCount = 0 + /** 优化时不含余料的大板数 */ + get OptimizingNoRemainBoardCount() { return this.optimizingBoardCount - this.optimizingRemainBoardCount } + /** 优化时总平均利用率(*) */ + optimizingAvgUsageRateAll = 0 + /** 优化时前N块板(不包含最后一块板)的平均利用率(*) */ + optimizingAvgUsageRateExcludeLastBoard = 0 + /** 优化时最后一块板的利用率(*) */ + optimizingUsageRateLastBoard = 0 + + /** 优化时间 */ + + placeTime + /** 使用大板情况(*) */ + usedBoardInfo = '' + /** 小板排版情况(*) */ + blockPlaceInfo = '' + /** 余料板情况 */ + remainBoardInfo = '' + + /** 翻面开料的大板数 BoardCount_do2Face */ + boardCountFlipFace = 0 + /** 小板数 */ + blockCount = 0 + /** 小板总面积 */ + blockArea = 0 + + /** 封边长度 [{t:1,l:233}, {t:2,l:100}] */ + edgeSealLengthList: any = [] + edgeSealLengthStr() { + let s = '' + for (let rt of this.edgeSealLengthList) { + s += `[${rt.t}]${rt.l}米;` + } + return s + } + + /** 余料前利用(异形优化) */ + // ScrapBoardList: ScrapBoard[] = []; + remainBoardList: RemainBoard[] = [] + /** 后余料板 */ + remainList: any[] = [] + + /** 大板(优化)列表 */ + boardList: PlaceBoard[] = [] + /** 小板(优化)列表 */ + blockList: PlaceBlock[] = [] + /** 是否有锁定的大板 */ + hasBoardLocked() { return this.boardList.some(t => t.isLocked) } + + /** 最优优化结果(临时) */ + tempBestPlaceResult?: MaterialPlaceResult | null = null + tempBestPlaceResultLast?: MaterialPlaceResult | null = null + tempBestPlaceResultLastTime = 0 + /** 需要最后一片重新计算 */ + rePlaceLastBoardRequired = false + tempPlaceUnregular: boolean = false + /** 最优优化结果(临时)标识 */ + tempPlaceResultFlag() { return (this.tempBestPlaceResult) ? '有' : '-' } + /** 最优优化结果是否只使用没锁定的 */ + tempPlaceResultOnyUnlockedBoard = false + /** 最后优化错误信息 */ + tempPlaceResultError: string = '' + /** 移动小板临时仓 */ + moveBlockList: PlaceBlock[] = [] + + /** 新优化前原配置,用于取消优化后还原到原先的配置 */ + savedSetting + + constructor(data: any = null) { + if (data != null) { + this.boardList = data?.boardList || [] + this.boardCount = data?.boardCount || 0 + this.orderId = data.orderId + this.goodsId = data.goodsId + this.goodsName = data.goodsName + this.spec = data.spec + this.material = data.material + this.color = data.color + this.brand = data.brand + this.hasTexture = true + if (!(data.hasTexture == null || data.hasTexture == undefined)) + this.hasTexture = data.hasTexture + this.orgWidth = StringFormat.toFixed(Number(data.orgWidth || data.width)) + this.orgLength = StringFormat.toFixed(Number(data.orgLength || data.length)) + this.width = StringFormat.toFixed(Number(data.width)) + this.length = StringFormat.toFixed(Number(data.length)) + this.thickness = StringFormat.toFixed(Number(data.thickness)) + this.cutBorder = StringFormat.toFixed(Number(data.cutBorder)) + this.diameter = StringFormat.toFixed(Number(data.diameter)) + this.cutKnifeGap = StringFormat.toFixed(Number(data.cutKnifeGap)) + this.preMillingSize = data.preMillingSize || 0 + this.isHelpCut = data.isHelpCut || false + this.helpCutGap = data.helpCutGap || 0 + this.cutKnifeId = data.cutKnifeId || -1 + this.helpKnifeId = data.helpKnifeId || -1 + this.isOptimized = Boolean(data.isOptimized) + this.boardCount = StringFormat.toFixed(Number(data.boardCount)) + this.remainBoardCount = StringFormat.toFixed(data.remainBoardCount ? Number(data.remainBoardCount) : 0) + this.minBoardId = Number(data.minBoardId) + this.maxBoardId = Number(data.maxBoardId) + this.avgUsageRateAll = StringFormat.toFixed(Number(data.avgUsageRateAll)) + this.avgUsageRateExcludeLastBoard = StringFormat.toFixed(Number(data.avgUsageRateExcludeLastBoard)) + this.usageRateLastBoard = StringFormat.toFixed(Number(data.usageRateLastBoard)) + this.usedBoardInfo = data.usedBoardInfo + this.blockPlaceInfo = data.blockPlaceInfo + this.remainBoardInfo = data.remainBoardInfo ? data.remainBoardInfo : '' + // this.remainBoardList = data.remainBoardList ? data.remainBoardList : [] + + if (Array.isArray(data.remainBoardList)) { + this.remainBoardList = data.remainBoardList + } else { + this.remainBoardList = data.remainBoardInfo ? JSON.parse(this.remainBoardInfo) : '' + } + + this.updateTime = data['this.updateTime'] || '' + } + } + + /** 保存 原尺寸,开料刀,预铣,辅助开料 */ + saveSetting() { + let setting: any = {} + // 尺寸, + setting.orgWidth = this.orgWidth + setting.orgLength = this.orgLength + setting.width = this.width + setting.length = this.length + setting.thickness = this.thickness + + setting.preMillingSize = this.preMillingSize + setting.isHelpCut = this.isHelpCut + setting.helpCutGap = this.helpCutGap + + setting.diameter = this.diameter + setting.cutKnifeId = this.cutKnifeId + setting.cutKnifeName = this.cutKnifeName + setting.helpKnifeId = this.helpKnifeId + setting.helpKnifeName = this.helpKnifeName + this.savedSetting = setting + + // 小板备份资料 + let createTime = Date.now() + for (let block of this.blockList) { + if (block.blockDetail) { + let savedObj = block.blockDetail.savedSetting + if (savedObj && savedObj.createTime == createTime) + break // 已备份 + + + block.blockDetail.saveSetting() + block.blockDetail.savedSetting.createTime = createTime + } + + } + } + + loadSetting() { + if (this.savedSetting == null) + return + // 尺寸, + this.orgWidth = this.savedSetting.orgWidth + this.orgLength = this.savedSetting.orgLength + this.width = this.savedSetting.width + this.length = this.savedSetting.length + this.thickness = this.savedSetting.thickness + + this.preMillingSize = this.savedSetting.preMillingSize + this.isHelpCut = this.savedSetting.isHelpCut + this.helpCutGap = this.savedSetting.helpCutGap + this.diameter = this.savedSetting.diameter + this.cutKnifeId = this.savedSetting.cutKnifeId + this.cutKnifeName = this.savedSetting.cutKnifeName + this.helpKnifeId = this.savedSetting.helpKnifeId + this.helpKnifeName = this.savedSetting.helpKnifeName + + for (let block of this.blockList) { + if (block.blockDetail) { + block.blockDetail.loadSetting() + block.blockDetail.savedSetting = null + } + + } + } +} + +/** 排单大板 */ +export class PlaceBoard { + /** 数据源ID */ + sourceId = 0 + + /** 大板编码(余料板号) */ + boardNo = '' + /** 大板ID */ + boardId = 0 + /** 大板ID(按全排单) */ + fullBoardId = 0 + /** 大板宽 */ + width = 0 + /** 大板长/高 */ + length = 0 + /** 面积 */ + area = 0 + + /** 小板数 */ + blockCount = 0 + /** 利用面积 BlockSize */ + blockArea = 0 + /** 利用率 */ + usageRate = 0 + /** 余料板异形 IsOddmengt */ + isAdnormal() { return this.points && this.points.length > 0 } + /** 余料板异形点{x,y,bul} */ + points: any[] = [] + + /** 是否加工2面, 需反面 IsDo2Face */ + isTwoFaceProcessing() { + let res = this.blockList.some(e => e.isDoubleFaceProcess()) + return res + } + /** 是否锁定 */ + isLocked = false + + /** 是否开料 */ + isCuted = false + /** 开料状态 + * + * 0 未开料 1 开料中 2 已开料 + */ + cutedType = 0 + /** 小板列表 */ + blockList: PlaceBlock[] = [] + /** 余料板列表 ScrapBlockList */ + remainBlockList: RemainBlock[] = [] + /** 需要生成余料空间 needToScrapSpace */ + isCreateRemainSpace = false + /** 异形大板的轮廓线 */ + polyline?: Polyline + /** 无法加工的左边出现造型 */ + hasModelOnLeft = false + /** 无法加工的右边出现造型 */ + hasModelOnRight = false + + /** 构造函数 */ + constructor(boardId: number, width: number, length: number, sourceId = 0, boardNo = '') { + this.sourceId = sourceId + this.boardId = boardId + this.width = width + this.length = length + this.boardNo = boardNo + this.area = width * length * 0.000001 + this.blockList = [] + } + + reset() { + this.blockCount = this.blockList.length + this.blockArea = ArrayExt.sum(this.blockList, t => t.area) + this.usageRate = (100 * this.blockArea) / this.area + // this.isTwoFaceProcessing = this.blockList.some(t => t.isCutOtherFace) + } + + addBlock(block: PlaceBlock) { + if (this.blockList.includes(block)) + return + this.blockList.push(block) + this.reset() + } + + removeBlock(block: PlaceBlock) { + let index = this.blockList.findIndex(t => t == block) + if (index == -1) + return + this.blockList.splice(index, 1) + this.reset() + } + + getSize(): string { + return `${this.width}*${this.length}` + } +} + +/** 实际开料的板材 || 余料板材 */ +export class RemainBoard { + /** 余料板ID */ + id: number | string = '' + /** 源排单ID */ + orgPlanId: number | string = '' + /** 排单ID */ + planId: number | string = '' + /** 状态(未使用 = 0, 已使用 = 1) */ + status: number = 0 + /** 商品ID */ + goodsId: number | string = '' + /** 商品名 */ + goodsName: string = '' + /** 材质 */ + material: string = '' + // /** 材料名 */ + // materialName: string = ''; + /** 颜色 */ + color: string = '' + /** 宽 */ + width: number = 0 + /** 长 */ + length: number = 0 + /** 品牌 */ + brand: string = '' + // /** 保存仓库 */ + // storeHouse: string = ''; + /** 纹路 */ + texture?: string + /** 排单号 */ + planCode: string = '' + /** 数量 */ + count: number = 0 + /** 排版宽 */ + placeWidth() { return this.placeStyle % 2 == 0 ? this.width : this.length } + /** 排版长 */ + placeLength() { return this.placeStyle % 2 == 0 ? this.length : this.width } + /** 厚度 */ + thickness: number | undefined + /** 轮廓 */ + outLineJson: string = '{}' + /** 备注 */ + remark: string = '' + /** */ + basePolyline: Polyline | undefined + /** 排版样式 */ + placeStyle: PlaceStyle = PlaceStyle.FRONT + /** 排版多段线 */ + placePolyline: Polyline | undefined + /** 是否已使用 */ + isUsed: boolean = false + /** 是否已入库 */ + isStored = false + + /** 是否开料 */ + isCuted = false + + /**是否开料 */ + cutedType = 0 + /** 实际优化板材的优化数据 */ + // placeBoardList: BoardPlaceResult[] = [] + /** 实际开料大板的数量 */ + usedCount = 0 + + /** 总利用率 */ + avgUsageRateAll = 0 + /** 前N块板利用率 */ + avgUsageRateExcludeLastBoard = 0 + /** 最后一块板利用率 */ + usageRateLastBoard = 0 + copy(): RemainBoard { + let obj = new RemainBoard() + obj.id = this.id + obj.planId = this.planId + obj.status = this.status + obj.goodsId = this.goodsId + obj.goodsName = this.goodsName + obj.material = this.material + obj.color = this.color + obj.width = this.width + obj.length = this.length + obj.thickness = this.thickness + obj.outLineJson = this.outLineJson + obj.remark = this.remark + obj.placeStyle = this.placeStyle + obj.isUsed = this.isUsed + obj.texture = this.texture + return obj + } +} + +/** 余料小板 */ +export class RemainBlock { + /** 余料板ID */ + remainId: number = 0 + /** 排版坐标x */ + placeX: number + /** 排版坐标y */ + placeY: number + /** 开料宽 */ + placeWidth: number + /** 开料长 */ + placeLength: number + /** 是否不规则 */ + isUnRegular = false + /** 是否重叠 */ + isOverlap = false + /** 是否已录入余料库 */ + isStored = false + + private pointlist: any = [] + points() { + if (this.pointlist.length == 0) { + this.pointlist.push({ x: 0, y: 0 }) + this.pointlist.push({ x: this.placeWidth, y: 0 }) + this.pointlist.push({ x: this.placeWidth, y: this.placeLength }) + this.pointlist.push({ x: 0, y: this.placeLength }) + } + return this.pointlist + } + + private pl: Polyline | null = null + get polyline() { return this.pl } + set polyline(v) { this.pl = v; this.polylineOffset = null } + /** 多段线偏移 */ + polylineOffset?: Polyline | null + + constructor(x, y, width, length) { + this.placeX = x + this.placeY = y + this.placeWidth = width + this.placeLength = length + } + + /** 修改大小 */ + reSize(width, length) { + if (this.isUnRegular) + return + this.placeWidth = width + this.placeLength = length + this.pointlist = [] + this.pl = null + this.polylineOffset = null + } + + setPoints(pts) { + this.pointlist = pts + if (this.pointlist.length == 0 || this.pointlist.length == 4) + this.isUnRegular = false + + else + this.isUnRegular = true + + this.polyline = null + this.polylineOffset = null + } + + load(data) { + this.remainId = data.remainId + this.placeX = data.placeX + this.placeY = data.placeY + this.placeWidth = data.placeWidth + this.placeLength = data.placeLength + this.isUnRegular = data.isUnRegular + this.pointlist = data.pointlist + this.isStored = data.isStored || false + } +} + +/** 板材优化结果 */ +export class MaterialPlaceResult { + /** 线程ID */ + threadId = 0 + /** 使用时间 */ + usedTime = 0 + /** 优化次数 */ + placeCount = 0 + /** 大板数 */ + boardCount = 0 + /** 总平均利用率 */ + avgUsageRateAll = 0 + /** 前N块板(不包含最后一块板)的平均利用率 */ + avgUsageRateExcludeLastBoard = 0 + /** 最后一块板的利用率 */ + usageRateLastBoard = 0 + /** 余料板数 */ + remainBoardCount = 0 + /** 大板列表 */ + boards: BoardPlaceResult[] = [] + /** 小板优化数据 */ + results: BlockPlaceResult[] = [] + /** 优化是否全部完成 */ + placeStoped = false + /** 余料板利用情况 */ + remailBoardList: any[] = [] + + constructor() { + + } + + /** 比other 更好 */ + isBetterThan(other: MaterialPlaceResult): boolean { + if (this.boardCount < other.boardCount) + return true + if (this.usageRateLastBoard < other.usageRateLastBoard) + return true + return false + } +} + + +export class BoardPlaceResult { + /** 大板ID */ + boardId = 0 + /** 宽 */ + width = 0 + /** 长 */ + length = 0 + /** 是否余料板 */ + isRemainBoard = false + /** 余料板编码 */ + remainNo = '' + /** 余料板ID */ + remainId = 0 + /** 是否废弃物 */ + isScrap = false + /** 点列表 */ + points: any[] = [] + /** 小板排版结果 */ + blocks: BlockPlaceResult[] = [] + /** 小板排版 */ + objects: any[] = [] + /** 面积 */ + area = 0 + /** 是否双面加工 */ + isTwoFaceProcess = false + + cutedType = 0 + isLock = false + /**当前大板的利用率 */ + // avgUsageRate = 0 + + constructor() { + + } + + /** 是否有异形板重叠 */ + hasOverLapBlock() { + for (let i = 0; i < this.blocks.length; i++) { + let b1 = this.blocks[i] + for (let j = i + 1; j < this.blocks.length; j++) { + let b2 = this.blocks[j] + if (this.isOverLap(b1, b2)) + return true + } + } + return false + } + + /** 两块板是否是否重叠 */ + isOverLap(b1: BlockPlaceResult, b2: BlockPlaceResult): boolean { + let l1 = { x: b1.placeX, y: b1.placeY + b1.length } + let r1 = { x: b1.placeX + b1.width, y: b1.placeY } + let l2 = { x: b2.placeX, y: b2.placeY + b2.length } + let r2 = { x: b2.placeX + b2.width, y: b2.placeY } + + if (l1.x > r2.x || l2.x > r1.x) + return false + if (l1.y < r2.y || l2.y < r1.y) + return false + return true + } +} + +/** 小板优化结果 */ +export class BlockPlaceResult { + /** 小板号 */ + blockId = 0 + + /** 小板编号 */ + blockNo = '' + /** 大板ID */ + boardId: number + /** 排版情况: 排版ID */ + placeId: number + /** 排版情况: 大板坐标X */ + placeX: number // 坐标 + /** 排版情况: 大板坐标Y */ + placeY: number + /** 宽 */ + width: number + /** 长 */ + length: number + /** 排版情况正纹 */ + placeStyle: PlaceStyle + /** 面积 */ + area: number + + constructor(blockId: number, boardId: number, placeId: number, x: number, y: number, width: number, length: number, pstyle: PlaceStyle, area: number) { + this.blockId = blockId + this.boardId = boardId + this.placeId = placeId + this.placeX = x + this.placeY = y + this.placeStyle = pstyle + this.width = width + this.length = length + this.area = area + } +} + +export enum PlaceType { + /** 凸包模式 (凸包面积) */ + Hull = 0, + /** 盒子模式 (长乘以宽) */ + Box = 1, + /** 重力模式(重力) */ + Gravity = 2, +} + +export class BlockBorderPoint { + /** 板 */ + block: PlaceBlock + /** 原坐标x */ + x: number + /** 原坐标y */ + y: number + /** 大板坐标X */ + placeX: number + /** 大板坐标Y */ + placeY: number + + /** 拖拉点距切割后板的坐标偏移x */ + bx = 0 + /** 拖拉点距切割后板的坐标偏移x */ + by = 0 + /** 在placed线框中的序号 */ + curveIndex: number + /** 在板的位置: 10:基点(左下点);1:右下角;2:右上角;3:左上角; -1:表示异形非顶点 areaID */ + posId: number + + constructor(block: PlaceBlock, x: number, y: number, index: number, posId: number, bx = 0, by = 0) { + this.block = block + this.x = x + this.y = y + this.placeX = (block ? block.placeX : 0) + x + this.placeY = (block ? block.placeY : 0) + y + this.curveIndex = index + this.posId = posId + this.bx = bx + this.by = by + } +} + +/** + * 放置位置类 + */ +export class PlacePositionClass { + // 4. 放置配置映射表 + private placementConfigs: Record; + + constructor() { + this.placementConfigs = { + // 正面放置配置 + [PlaceStyle.FRONT]: { + widthSource: 'cutWidth', + lengthSource: 'cutLength', + sealMap: { + left: 'sealLeft', + right: 'sealRight', + top: 'sealTop', + bottom: 'sealBottom' + }, + holeCountMap: { + left: 'holeCountLeft', + right: 'holeCountRight', + top: 'holeCountTop', + bottom: 'holeCountBottom' + }, + direction: Direction.RIGHT, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.RIGHT : Direction.DOWN + }, + + // 正面右转配置 + [PlaceStyle.FRONT_TURN_RIGHT]: { + widthSource: 'cutLength', + lengthSource: 'cutWidth', + sealMap: { + left: 'sealBottom', + right: 'sealTop', + top: 'sealLeft', + bottom: 'sealRight' + }, + holeCountMap: { + left: 'holeCountBottom', + right: 'holeCountTop', + top: 'holeCountLeft', + bottom: 'holeCountRight' + }, + direction: Direction.DOWN, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.DOWN : Direction.LEFT + }, + + // 正面后转配置 + [PlaceStyle.FRONT_TURN_BACK]: { + widthSource: 'cutWidth', + lengthSource: 'cutLength', + sealMap: { + left: 'sealRight', + right: 'sealLeft', + top: 'sealBottom', + bottom: 'sealTop' + }, + holeCountMap: { + left: 'holeCountRight', + right: 'holeCountLeft', + top: 'holeCountBottom', + bottom: 'holeCountTop' + }, + direction: Direction.LEFT, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.LEFT : Direction.UP + }, + + // 正面左转配置 + [PlaceStyle.FRONT_TURN_LEFT]: { + widthSource: 'cutLength', + lengthSource: 'cutWidth', + sealMap: { + left: 'sealTop', + right: 'sealBottom', + top: 'sealRight', + bottom: 'sealLeft' + }, + holeCountMap: { + left: 'holeCountTop', + right: 'holeCountBottom', + top: 'holeCountRight', + bottom: 'holeCountLeft' + }, + direction: Direction.UP, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.UP : Direction.RIGHT + }, + + // 反面配置 + [PlaceStyle.BACK]: { + widthSource: 'cutWidth', + lengthSource: 'cutLength', + sealMap: { + left: 'sealRight', + right: 'sealLeft', + top: 'sealTop', + bottom: 'sealBottom' + }, + holeCountMap: { + left: 'holeCountRight', + right: 'holeCountLeft', + top: 'holeCountTop', + bottom: 'holeCountBottom' + }, + direction: Direction.RIGHT, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.RIGHT : Direction.UP + }, + + // 反面右转配置 + [PlaceStyle.BACK_TURN_RIGHT]: { + widthSource: 'cutLength', + lengthSource: 'cutWidth', + sealMap: { + left: 'sealBottom', + right: 'sealTop', + top: 'sealRight', + bottom: 'sealLeft' + }, + holeCountMap: { + left: 'holeCountBottom', + right: 'holeCountTop', + top: 'holeCountRight', + bottom: 'holeCountLeft' + }, + direction: Direction.DOWN, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.DOWN : Direction.RIGHT + }, + + // 反面后转配置 + [PlaceStyle.BACK_TURN_BACK]: { + widthSource: 'cutWidth', + lengthSource: 'cutLength', + sealMap: { + left: 'sealLeft', + right: 'sealRight', + top: 'sealTop', + bottom: 'sealBottom' + }, + holeCountMap: { + left: 'holeCountLeft', + right: 'holeCountRight', + top: 'holeCountTop', + bottom: 'holeCountBottom' + }, + direction: Direction.LEFT, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.LEFT : Direction.DOWN + }, + + // 反面左转配置 + [PlaceStyle.BACK_TURN_LEFT]: { + widthSource: 'cutLength', + lengthSource: 'cutWidth', + sealMap: { + left: 'sealTop', + right: 'sealBottom', + top: 'sealLeft', + bottom: 'sealRight' + }, + holeCountMap: { + left: 'holeCountTop', + right: 'holeCountBottom', + top: 'holeCountLeft', + bottom: 'holeCountRight' + }, + direction: Direction.UP, + lengthDirection: (block) => + block.length > block.width - 0.001 ? Direction.UP : Direction.LEFT + } + }; + } + + /** + * 设置块的放置属性 + * @param block 块对象 + * @param newStyle 新的放置样式 + */ + static setBlockPlacementProperties(block: Block, newStyle: PlaceStyle): Block { + // 创建一个临时实例来访问 placementConfigs + const instance = new PlacePositionClass(); + const config = instance.placementConfigs[newStyle]; + if (!config) return block; + + // 设置尺寸 + block.placeWidth = block[config.widthSource] as number; + block.placeLength = block[config.lengthSource] as number; + + // 设置封边 + block.placeSealLeft = block[config.sealMap.left]; + block.placeSealRight = block[config.sealMap.right]; + block.placeSealTop = block[config.sealMap.top]; + block.placeSealBottom = block[config.sealMap.bottom]; + + // 设置孔位计数 + block.holeCountSideLeft = block[config.holeCountMap.left] as number; + block.holeCountSideRight = block[config.holeCountMap.right] as number; + block.holeCountSideTop = block[config.holeCountMap.top] as number; + block.holeCountSideBottom = block[config.holeCountMap.bottom] as number; + + // 设置方向,严格参照 BlockHelper.resetPlaceStyle + switch (newStyle) { + case PlaceStyle.FRONT: + block.placeDirection = '→'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↓'; + break; + case PlaceStyle.FRONT_TURN_RIGHT: + block.placeDirection = '↓'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '←'; + break; + case PlaceStyle.FRONT_TURN_BACK: + block.placeDirection = '←'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↑'; + break; + case PlaceStyle.FRONT_TURN_LEFT: + block.placeDirection = '↑'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '→'; + break; + case PlaceStyle.BACK: + block.placeDirection = '→'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '→' : '↑'; + break; + case PlaceStyle.BACK_TURN_RIGHT: + block.placeDirection = '↓'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '↓' : '→'; + break; + case PlaceStyle.BACK_TURN_BACK: + block.placeDirection = '←'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '←' : '↓'; + break; + case PlaceStyle.BACK_TURN_LEFT: + block.placeDirection = '↑'; + block.placeDirection_Length = block.length > block.width - 0.001 ? '↑' : '←'; + break; + default: + block.placeDirection = ''; + block.placeDirection_Length = ''; + break; + } + + return block; + } + + static getOriginalSides(placeStyle: PlaceStyle): number[] { + // let orgSides = [0, 1, 2, 3]; + let orgSides = [EdgeType.BOTTOM, EdgeType.RIGHT, EdgeType.TOP, EdgeType.LEFT] + switch (placeStyle) { + case PlaceStyle.FRONT: // 正面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + case PlaceStyle.BACK: // 反面 + orgSides[EdgeType.BOTTOM] = EdgeType.BOTTOM + orgSides[EdgeType.RIGHT] = EdgeType.LEFT + orgSides[EdgeType.TOP] = EdgeType.TOP + orgSides[EdgeType.LEFT] = EdgeType.RIGHT + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + orgSides[EdgeType.BOTTOM] = EdgeType.LEFT + orgSides[EdgeType.RIGHT] = EdgeType.TOP + orgSides[EdgeType.TOP] = EdgeType.RIGHT + orgSides[EdgeType.LEFT] = EdgeType.BOTTOM + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + orgSides[EdgeType.BOTTOM] = EdgeType.TOP + orgSides[EdgeType.RIGHT] = EdgeType.RIGHT + orgSides[EdgeType.TOP] = EdgeType.BOTTOM + orgSides[EdgeType.LEFT] = EdgeType.LEFT + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + orgSides[EdgeType.BOTTOM] = EdgeType.RIGHT + orgSides[EdgeType.RIGHT] = EdgeType.BOTTOM + orgSides[EdgeType.TOP] = EdgeType.LEFT + orgSides[EdgeType.LEFT] = EdgeType.TOP + break + default: + break + } + return orgSides + } + /** 翻转小板,翻转动作opType: 0翻面 1右转 2后转 3左转 */ + static turnBlock(block: PlaceBlock, pm: PlaceMaterial, opType: number) { + let orgStyle = block.placeStyle + let newStyle = BlockHelper.getTurnedPlaceStyle(orgStyle, opType) + + let orgPlaceX = block.placeX - block.placeOffX + let orgPlaceY = block.placeY - block.placeOffY + + let offset = BlockSizePlus.getOffDis(block, newStyle) + let newPlaceX = orgPlaceX + offset.x + let newPlaceY = orgPlaceY + offset.y + block.placeOffX = offset.x + block.placeOffY = offset.y + // 修改小板的放置方式 + BlockHelper.resetPlaceStyle(block, newStyle) + // 重置放置,检查冲突 + BlockHelper.replaceBlock(pm, block, block.boardId, new Point(newPlaceX, newPlaceY)) + } + /** 获得翻转后的新放置方式, 翻转动作opType: 0翻面 1右转 2后转 3左转 */ + static getTurnedPlaceStyle(orgStyle: PlaceStyle, opType: number): PlaceStyle { + + let newStyle: number = orgStyle + if (opType == 0) // 翻面 + { + switch (orgStyle) { + case PlaceStyle.FRONT: // 正面 + newStyle = PlaceStyle.BACK // 反面 + break + case PlaceStyle.FRONT_TURN_RIGHT: // 正面右转 + newStyle = PlaceStyle.BACK_TURN_LEFT // 反面左转 + break + case PlaceStyle.FRONT_TURN_BACK: // 正面后转 + newStyle = PlaceStyle.BACK_TURN_BACK // 反面后转 + break + case PlaceStyle.FRONT_TURN_LEFT: // 正面左转 + newStyle = PlaceStyle.BACK_TURN_RIGHT // 反面右转 + break + case PlaceStyle.BACK: // 反面 + newStyle = PlaceStyle.FRONT // 正面 + break + case PlaceStyle.BACK_TURN_RIGHT: // 反面右转 + newStyle = PlaceStyle.FRONT_TURN_LEFT // 正面左转 + break + case PlaceStyle.BACK_TURN_BACK: // 反面后转 + newStyle = PlaceStyle.FRONT_TURN_BACK // 正面后转 + break + case PlaceStyle.BACK_TURN_LEFT: // 反面左转 + newStyle = PlaceStyle.FRONT_TURN_RIGHT // 正面右转 + break + default: + break + } + } + else if (opType == 1) // 右转 + { + newStyle = orgStyle + 1 + if (newStyle == 4) + newStyle = 0 + if (newStyle == 8) + newStyle = 4 + } + else if (opType == 2) // 后转 + { + newStyle = orgStyle + 2 + if (orgStyle < 4 && newStyle >= 4) + newStyle = newStyle - 4 + if (orgStyle >= 4 && newStyle >= 8) + newStyle = newStyle - 4 + } + else if (opType == 3) // 左转 + { + newStyle = orgStyle - 1 + if (newStyle == 3) + newStyle = 7 + if (newStyle == -1) + newStyle = 3 + } + return newStyle + } + static resetPlaceStyle(block: PlaceBlock, newStyle: PlaceStyle) { + block.placeStyle = newStyle + let _width = block.cutWidth + let _lenth = block.cutLength + if (block.width > block.length) { + block.cutWidth = Math.max(_width, _lenth) + block.cutLength = Math.min(_width, _lenth) + } else { + block.cutWidth = Math.min(_width, _lenth) + block.cutLength = Math.max(_width, _lenth) + } + let _block: Block = { + placeWidth: block.placeWidth, + placeLength: block.placeLength, + cutWidth: block.cutWidth, + cutLength: block.cutLength, + width: block.width, + length: block.length, + sealLeft: block.sealLeft, + sealRight: block.sealRight, + sealTop: block.sealTop, + sealBottom: block.sealBottom, + placeSealLeft: block.placeSealLeft, + placeSealRight: block.placeSealRight, + placeSealTop: block.placeSealTop, + placeSealBottom: block.placeSealBottom, + holeCountLeft: block.holeCountLeft(), + holeCountRight: block.holeCountRight(), + holeCountTop: block.holeCountTop(), + holeCountBottom: block.holeCountBottom(), + holeCountSideLeft: block.holeCountSideLeft, + holeCountSideRight: block.holeCountSideRight, + holeCountSideTop: block.holeCountSideTop, + holeCountSideBottom: block.holeCountSideBottom, + placeDirection: block.placeDirection, + placeDirection_Length: block.placeDirection_Length + } + this.setBlockPlacementProperties(_block, newStyle) + } +} + +// 1. 定义Block接口 +interface Block { + // 尺寸相关 + placeWidth: number; + placeLength: number; + cutWidth: number; + cutLength: number; + width: number; + length: number; + + // 封边相关 + sealLeft: any; + sealRight: any; + sealTop: any; + sealBottom: any; + placeSealLeft: any; + placeSealRight: any; + placeSealTop: any; + placeSealBottom: any; + + // 孔位计数相关 + holeCountLeft: number; + holeCountRight: number; + holeCountTop: number; + holeCountBottom: number; + holeCountSideLeft: number; + holeCountSideRight: number; + holeCountSideTop: number; + holeCountSideBottom: number; + + // 方向相关 + placeDirection: string; + placeDirection_Length: string; +} + +// 2. 定义方向枚举(增强类型安全) +enum Direction { + RIGHT = '→', + LEFT = '←', + UP = '↑', + DOWN = '↓' +} + +// 3. 定义放置配置接口 +interface PlacementConfig { + widthSource: keyof Block; // 取宽度的来源 + lengthSource: keyof Block; // 取长度的来源 + sealMap: { // 封边映射 + left: keyof Block; + right: keyof Block; + top: keyof Block; + bottom: keyof Block; + }; + holeCountMap: { // 孔位计数映射 + left: keyof Block; + right: keyof Block; + top: keyof Block; + bottom: keyof Block; + }; + direction: Direction; // 基础方向 + lengthDirection: (block: Block) => Direction; // 长度方向计算函数 +} + + +//import { DrawRect } from "../DrawRect"; + +//开料生产 +export class KLSC +{ + xbang: YH_bang[]; //小板集合 + Bakbang: YH_bang[]; //备份小板集合 + HB_bang: number[] = []; //合并的板 + HB: number[][] = []; //合并板的数组 + B_k: number; //大板宽 + B_g: number; //大板高 + dt: number; //刀头大小(含修边) + wzx: number; //临时用于打印 + wzy: number; //临时用于打印 + jl_mz: number; //用于测试距离或者面积优选 + + SCid: number[] = []; //用于存化顺序的板的bangid + //f = () => 5; + constructor(xbang: YH_bang[], Bang_k: number, Bang_g: number, dt: number, wzx: number, wzy: number, JL_MZ: number) //false JL ture MZ + { + this.xbang = JSON.parse(JSON.stringify(xbang)); + //this.xbang = [...xbang]; + this.Bakbang = JSON.parse(JSON.stringify(xbang)); + this.B_g = Bang_g; + this.B_k = Bang_k; + this.dt = dt; + this.wzx = wzx; + this.wzy = wzy; + this.jl_mz = JL_MZ; + this.XDscjs(); + }; + + //查找距离中心最近,且跟最大的板相差不大于容差面积 rcmz 的板 返加YH_bang[].bangid + MaxMZ = (rcmz: number): number => + { + let tmepckb: number; + let tmepckb1: number; + let tempxbang: YH_bang[] = []; + this.xbang.sort((b, a) => a.pbg * a.pbk - b.pbg * b.pbk); + let maxmz = this.xbang[0].pbg * this.xbang[0].pbk / 1000000; + let maxbangid = this.xbang[0].bangid; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].pbg > this.xbang[i].pbk) + { + tmepckb = this.xbang[i].pbg / this.xbang[i].pbk; + } + else + { + tmepckb = this.xbang[i].pbk / this.xbang[i].pbg; + } + if (this.xbang[0].pbg > this.xbang[0].pbk) + { + tmepckb1 = this.xbang[0].pbg / this.xbang[0].pbk; + } + else + { + tmepckb1 = this.xbang[0].pbk / this.xbang[0].pbg; + } + + if (equaln(this.xbang[i].pbg * this.xbang[i].pbk / 1000000, maxmz, rcmz) && equaln(tmepckb, tmepckb1, 2)) + { + tempxbang.push(this.xbang[i]); + } + } + if (tempxbang.length > 0) + { + return this.minJL(tempxbang); + } + else + { + return maxbangid; + } + + }; + + //查找指定Bangid的板 返回在数组中的位置ID f = () => 5; + getID = (bangid: number): number => this.xbang.findIndex((n) => n.bangid == bangid); + + //查找备份板Bangid的板 返回在数组中的位置ID f = () => 5; + getID_Bkb = (bangid: number): number => this.Bakbang.findIndex((n) => n.bangid == bangid); + + + //查找距离最近的板 返加YH_bang[].bangid + minJL = (xbang: YH_bang[]): number => { xbang.sort((b, a) => Math.hypot(this.B_k / 2 - b.x - b.pbk / 2, this.B_g / 2 - b.y - b.pbg / 2) - Math.hypot(this.B_k / 2 - a.x - a.pbk / 2, this.B_g / 2 - a.y - a.pbg / 2)); return xbang[0].bangid; }; + + //查找左边且Y位置一样的板,, 返加YH_bang[].bangid + F_Left = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.x + n.pbk + this.dt, this.xbang[this.getID(bangid)].x, 0.001) + && equaln(n.y, this.xbang[this.getID(bangid)].y, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + //查找左边关连的板,并写入 + F_GL_LR = () => + { + let temp: number; + let bangIndex: number; + let maxkd: number = 0; + let maxid: number; //bangid + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + bangIndex = i; + while (1) //左边 + { + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbk < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.x + n.pbk < this.xbang[bangIndex].x && n.y <= this.xbang[bangIndex].y && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[1] > this.xbang[bangIndex].pbk); + if (temp != -1) + { + if (this.xbang[temp].pbk > maxkd) + { + maxkd = this.xbang[temp].pbk; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbk > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + bangIndex = i; + while (1) //右边 + { + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbk < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.x > this.xbang[bangIndex].x + this.xbang[bangIndex].pbk + && this.LR_is(this.xbang[bangIndex].bangid, n.bangid) == false + && n.pbg > 300 + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[1] > this.xbang[bangIndex].pbk); + if (temp != -1) + { + if (this.xbang[temp].pbk > maxkd) + { + maxkd = this.xbang[temp].pbk; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbk > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + + + } + }; + }; + + //查找上下边关连的板,并写入 + F_GL_TD = () => + { + let temp: number; + let bangIndex: number; + let maxkd: number = 0; + let maxid: number; //bangid + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + bangIndex = i; + while (1) //上面 + { + if (this.xbang[bangIndex].pbg < this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbg < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.y > this.xbang[bangIndex].y + this.xbang[bangIndex].pbg && n.x + 0.01 <= this.xbang[bangIndex].x + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[0] > this.xbang[bangIndex].pbg); + if (temp != -1) + { + if (this.xbang[temp].pbg > maxkd) + { + maxkd = this.xbang[temp].pbg; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbg > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + bangIndex = i; + while (1) //下面 + { + + if (this.xbang[bangIndex].pbg < this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbg < 200 && this.xbang[bangIndex].ishb == false) + { + + temp = this.xbang.findIndex(n => n.y + n.pbg < this.xbang[bangIndex].y + this.xbang[bangIndex].pbg && n.x - 0.01 <= this.xbang[bangIndex].x + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[0] > this.xbang[bangIndex].pbg); + if (temp != -1) + { + if (this.xbang[temp].pbg > maxkd) + { + maxkd = this.xbang[temp].pbg; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbg > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + + + } + }; + }; + + //查找有异形交集关连的板,并写入 + F_GL_JJB = () => + { + let temp: number; + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + temp = this.xbang.findIndex(n => this.XJcd(n.bangid, this.xbang[i].bangid)[0] > 50 + && this.XJcd(n.bangid, this.xbang[i].bangid)[1] > 50 && n.bangid != this.xbang[i].bangid); + if (temp != -1) + { + if (this.xbang[i].pbg * this.xbang[i].pbk > this.xbang[temp].pbg * this.xbang[temp].pbk) + { + this.xbang[i].isgr = true; + this.xbang[i].gr.push(this.xbang[temp].bangid); + this.xbang[temp].grid = this.xbang[i].bangid; + } + else if (equaln(this.xbang[i].pbg * this.xbang[i].pbk, this.xbang[temp].pbg * this.xbang[temp].pbk, 0.01)) + { + if (this.xbang[temp].x > this.xbang[i].x) + { + + this.xbang[i].isgr = true; + this.xbang[i].gr.push(this.xbang[temp].bangid); + this.xbang[temp].grid = this.xbang[i].bangid; + } + else + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[i].bangid); + this.xbang[i].grid = this.xbang[temp].bangid; + } + } + else + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[i].bangid); + this.xbang[i].grid = this.xbang[temp].bangid; + } + + } + } + }; + for (let k = 0; k < this.xbang.length; k++) + { + let newgr = [... new Set(this.xbang[k].gr)]; + this.xbang[k].gr = newgr; + } + }; + + //判断有关联的板跟大板之间是否有交集,如果有取消这块板的关联 + Is_big_gr = () => + { + for (let k = 0; k < this.xbang.length; k++) + { + let newgr = [... new Set(this.xbang[k].gr)]; + this.xbang[k].gr = newgr; + } + let tempx: number; + let tempy: number; + let bangIndex1 = this.xbang[this.getID(this.HB[0][0])]; + let bangIndex2: number; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].isgr == true) + { + tempx = 0; + tempy = 0; + for (let j = 0; j < this.xbang[i].gr.length; j++) + { + bangIndex2 = this.getID(this.xbang[i].gr[j]); + if (this.xbang[i].bangid == 10)////////////////////用于调试 + { + console.log(this.xbang[i].bangid); + } + if (this.xbang[bangIndex2].pbg > this.xbang[bangIndex2].pbk) + { + tempy = this.Jcxj(bangIndex1.y, bangIndex1.pbg, this.xbang[bangIndex2].y, this.xbang[bangIndex2].pbg); + } + else + { + tempx = this.Jcxj(bangIndex1.x, bangIndex1.pbk, this.xbang[bangIndex2].x, this.xbang[bangIndex2].pbk); + } + } + if (tempx > 50 || tempy > 50) + { + this.xbang[i].isgr = false; + } + } + } + + }; + + + //检测两块板之间的右上角是否有板 false 没有 true 有 + LR_is = (bangid1: number, bangid2: number): boolean => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (tb[0].pbg + tb[0].y > tb[1].pbg + tb[1].y)//右上空间 左边高 + { + return this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt); + } + else + { + return false; + } + }; + + //检测两块板之间的右上角是否有板 false 没有 true 有 + TD_is = (bangid1: number, bangid2: number): boolean => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (tb[0].pbk + tb[0].x > tb[1].pbk + tb[1].x)//右下空间 左边高 + { + return this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt); + } + else + { + return false; + } + }; + + //查找左边且Y位置一样的板,, 返加YH_bang[].bangid + F_Left_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].x + this.xbang[i].pbk < this.xbang[this.getID(bangid)].x && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[1] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + //找到左右靠边最长的高度 返回 [0]长度 [1] y位置 [2] 0左边 1 右边 + L_R_kbcd = (bangid: number): [number, number, number] => + { + let l_b: YH_bang[] = []; + let cd_l = 0; let cd_r: number = 0; let wzl: number; let wzr: number; + let tb = [...this.xbang]; + let tempx: number = this.xbang[this.getID(bangid)].x; + let tempy: number = this.xbang[this.getID(bangid)].y; + let tempk: number = this.xbang[this.getID(bangid)].pbk; + let tempid: number; + let isend: boolean = true; + for (let i = 0; i < 2; i++) + { + l_b = []; + tb = [...this.xbang]; + isend = true; + while (isend == true) + { + if (i == 0) { tempid = tb.findIndex((n) => equaln(n.x + n.pbk + this.dt, tempx, 0.001) && (n.y <= tempy)); } + else { tempid = tb.findIndex((n) => equaln(n.x, tempx + tempk + this.dt, 0.001) && (n.y <= tempy)); } + + if (tempid != -1) + { + l_b.push(tb[tempid]); + tb.splice(tempid, 1); + } + else + { + isend = false; + } + } + if (l_b.length > 0) + { + l_b.sort((a, b) => b.pbg - a.pbg); + if (i == 0) { cd_l = l_b[0].pbg; wzl = l_b[0].y; } else { cd_r = l_b[0].pbg; wzr = l_b[0].y; } + } + } + if (cd_l > cd_r || cd_l == cd_r && cd_l > 0) + { + return [cd_l, wzl, 0]; + } + else if (cd_l < cd_r) + { + return [cd_r, wzr, 1]; + } + else + { + return [0, 0, 0]; + } + + }; + + //查找右边且Y位置一样的板,, 返加YH_bang[].bangid + F_Right = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.x, this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk + this.dt, 0.001) + && equaln(n.y, this.xbang[this.getID(bangid)].y, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + //查找右边且Y位置一样的板,, 返加YH_bang[].bangid + F_Right_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].x > this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[1] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + //查找下边且X位置一样的板,, 返加YH_bang[].bangid + F_Down = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.y + n.pbg + this.dt, this.xbang[this.getID(bangid)].y, 0.001) + // && n.x > 10 + && equaln(n.x, this.xbang[this.getID(bangid)].x, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + //查找上边且X位置一样的板,, 返加YH_bang[].bangid + F_TOP = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.y, this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg + this.dt, 0.001) + //&& n.x > 10 + && equaln(n.x, this.xbang[this.getID(bangid)].x, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + + //查找上边且X位置一样的板,, 返加YH_bang[].bangid hbcd为大于共边的长度 + F_Top_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].y > this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg + && this.xbang[i].x + this.xbang[i].pbk <= this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk + 0.1 + && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[0] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + + //查找下边且X位置一样的板,, 返加YH_bang[].bangid + F_Down_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].y + this.xbang[i].pbg < this.xbang[this.getID(bangid)].y + && this.xbang[i].x >= this.xbang[this.getID(bangid)].x && this.xbang[i].pbg > 300 + && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[0] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + + //合并同高相邻的板 isbig: true 为第一回合并的大板 wz为方位 L 左 R右 T上 D下 返回是否有合并过 false 为没有合并过 true 有合并过 + HB_LR = (bangid: number, isbig: boolean): boolean => + { + let isend: boolean = true; + let tempbangid: number; + let ishb: boolean = false; + + while (isend == true) + { + tempbangid = this.F_Left(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + isend = true; + while (isend == true) + { + tempbangid = this.F_Right(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + return ishb; + }; + + //合并同宽相邻的板 isbig: true 为第一回合并的大板 wz为方位 L 左 R右 T上 D下 返回是否有合并过 false 为没有合并过 true 有合并过 + HB_TD = (bangid: number, isbig: boolean): boolean => + { + let isend: boolean = true; + let tempbangid: number; + let ishb: boolean = false; + while (isend == true) + { + tempbangid = this.F_Down(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + + } + else + { + isend = false; + } + } + isend = true; + while (isend == true) + { + tempbangid = this.F_TOP(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + return ishb; + }; + + addbang = (id1: number, id2: number, x: number, y: number, k: number, g: number, isbig: boolean) => //把合并的板写入,并改板的大小 + { + let tempid1 = this.getID(id1); + let tempid2 = this.getID(id2); + if (this.xbang[tempid1].ishb == false) + { + this.HB_bang.push(id1); + } + this.HB_bang.push(id2); + this.xbang[tempid1].x = x; + this.xbang[tempid1].y = y; + this.xbang[tempid1].pbk = k; + this.xbang[tempid1].pbg = g; + this.xbang[tempid1].ishb = true; + this.xbang[tempid2].ishb = true; + if (isbig == true) { this.xbang[tempid1].isbig = true; this.xbang[tempid2].isbig = true; } + }; + + //计算相邻两块板中有空位的地方是否有其它小板 返回 false 为没有其它板,true 有其它板 x,y,k,g + JC_is_bang = (bangid1: number, bangid2: number): [boolean, number, number, number, number] => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (equaln(tb[0].y, tb[1].y, 0.01)) //左右相邻 + { + tb.sort((a, b) => a.x - b.x); + if (tb[0].pbg < tb[1].pbg)//左上空间 右边高 + { + return [this.JCQY_is_bang(tb[0].x, tb[0].y + tb[0].pbg + this.dt, tb[0].pbk, tb[1].pbg - tb[0].pbg - this.dt), + tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[1].pbg]; + } + else if (tb[0].pbg > tb[1].pbg)//右上空间 左边高 + { + return [this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt), + tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[0].pbg]; + } + else //一样高 + { + return [false, tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[0].pbg]; + } + } + else //上下空间 + { + tb.sort((b, a) => a.y - b.y); + if (tb[0].pbk < tb[1].pbk)//右上空间 上边短 + { + return [this.JCQY_is_bang(tb[0].x + tb[0].pbk + this.dt, tb[0].y, tb[1].pbk - tb[0].pbk - this.dt, tb[0].pbg), + tb[1].x, tb[1].y, tb[1].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + else if (tb[0].pbk > tb[1].pbk)//右下空间 下边短 + { + return [this.JCQY_is_bang(tb[1].x + tb[1].pbk + this.dt, tb[1].y, tb[0].pbk - tb[1].pbk - this.dt, tb[1].pbg), + tb[1].x, tb[1].y, tb[0].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + else //一样高 + { + return [false, tb[1].x, tb[1].y, tb[0].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + } + }; + + //检测两块板之间相交的长度且两块板之间没有其它板 ,返回长度,第一个为X相交长度 第二个为Y相交长度 + XJcd = (bangid1: number, bangid2: number): [number, number] => + { + let tempbang: YH_bang[] = []; + tempbang.push(this.xbang[this.getID(bangid1)]); + tempbang.push(this.xbang[this.getID(bangid2)]); + + let tempx = this.Jcxj(tempbang[0].x, tempbang[0].pbk, tempbang[1].x, tempbang[1].pbk); + let tempy = this.Jcxj(tempbang[0].y, tempbang[0].pbg, tempbang[1].y, tempbang[1].pbg); + if (tempx > 0 && tempy > 0) + { + return [tempx, tempy]; + } + else if (tempx > 0) + { + tempbang.sort((a, b) => a.x - b.x); + if (tempbang[0].y > tempbang[1].y) + { + if (this.JCQY_is_bang(tempbang[1].x, tempbang[1].y + tempbang[1].pbg, tempx, tempbang[0].y - tempbang[1].y - tempbang[1].pbg) == false) + { + return [tempx, 0]; + } + else + { + return [0, 0]; + } + } + else + { + if (this.JCQY_is_bang(tempbang[1].x, tempbang[0].y + tempbang[0].pbg, tempx, tempbang[1].y - tempbang[0].y - tempbang[0].pbg) == false) + { + return [tempx, 0]; + } + else + { + return [0, 0]; + } + } + + } + else if (tempy > 0) + { + tempbang.sort((a, b) => a.x - b.x); + if (tempbang[0].y > tempbang[1].y) + { + if (this.JCQY_is_bang(tempbang[0].x + tempbang[0].pbk, tempbang[0].y, tempbang[1].x - tempbang[0].x - tempbang[0].pbk, tempy) == false) + { + return [0, tempy]; + } + else + { + return [0, 0]; + } + } + else + { + if (this.JCQY_is_bang(tempbang[0].x + tempbang[0].pbk, tempbang[1].y, tempbang[1].x - tempbang[0].x - tempbang[0].pbk, tempy) == false) + { + return [0, tempy]; + } + else + { + return [0, 0]; + } + } + } + else + { + return [0, 0]; + } + }; + + //检测指定区域内否有其它小板 返回 false 为没有其它板,true 有其它板 + JCQY_is_bang = (x: number, y: number, k: number, g: number): boolean => + { + let result = this.xbang.findIndex((n) => (k + n.pbk - Math.abs(x - n.x) - Math.abs(x - n.x + k - n.pbk)) / 2 > 0.01 && + (g + n.pbg - Math.abs(y - n.y) - Math.abs(y - n.y + g - n.pbg)) / 2 > 0.01); + if (result == -1) { return false; } else { return true; } + }; + + //计算两边相交长度/ + Jcxj = (wz1: number, l1: number, wz2: number, l2: number): number => { return (l1 + l2 - Math.abs(wz1 - wz2) - Math.abs(wz1 - wz2 + l1 - l2)) / 2; }; + + Find_BS = (): number[] => + { + let temp = this.Find_BS_gr(); + if (temp[0] > 2) + { + return this.Find_BS_wgr()[1]; + } + else + { + if (temp[1][0] == 0) + { + return this.Find_BS_wgr()[1]; + } + else + { + return temp[1]; + } + + + } + }; + //找到共边最少的板的集合 订算有关系的板 + Find_BS_gr = (): [number, number[]] => + { + let bs: number = 6; + let id: number[] = []; + let tempsl: number[]; + if (this.xbang.length == 1) + { + return [0, [0]]; + } + else + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].isgr == false && this.xbang[i].ishb == false && this.xbang[i].isqg == false) + { + tempsl = this.JSgbsl(this.xbang[i].bangid); + if (bs == tempsl[0]) + { + id.push(this.xbang[i].bangid); + } + else if (bs > tempsl[0]) + { + bs = tempsl[0]; + id = []; + id.push(this.xbang[i].bangid); + } + + } + } + if (id.length > 0) + { return [bs, id]; } + else + { + //console.log("没找到最少边"); + return [0, [0]]; + } + + } + }; + + //找到共边最少的板的集合 订算没有算关系的板 + Find_BS_wgr = (): [number, number[]] => + { + let bs: number = 6; + let id: number[] = []; + let tempsl: number[]; + if (this.xbang.length == 1) + { + return [0, [0]]; + } + else + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].ishb == false && this.xbang[i].isqg == false) + { + tempsl = this.JSgbsl(this.xbang[i].bangid); + if (bs == tempsl[0]) + { + id.push(this.xbang[i].bangid); + } + else if (bs > tempsl[0]) + { + bs = tempsl[0]; + id = []; + id.push(this.xbang[i].bangid); + } + } + } + if (id.length > 0) { return [bs, id]; } else { return [0, [0]]; } + + } + }; + + + //计算这块板的与其它板的相交的边数,及每条边数跟权重 0数量 1左 2右 3上 4下 5权重 + JSgbsl = (bangid: number): [number, number, number, number, number, number] => + { + let left = 0, right = 0, top = 0, down = 0, qz = 0; + let bangIndex = this.getID(bangid); + for (let j = 0; j < this.xbang.length; j++) + { + if (bangIndex != j && this.xbang[j].isqg == false) + { + let jjy = this.Jcxj(this.xbang[bangIndex].y, this.xbang[bangIndex].pbg, this.xbang[j].y, this.xbang[j].pbg); + let jjx = this.Jcxj(this.xbang[bangIndex].x, this.xbang[bangIndex].pbk, this.xbang[j].x, this.xbang[j].pbk); + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk) + { + if (jjy < this.xbang[bangIndex].pbk && jjy < 50) { jjy = 0; } + } + if (this.xbang[bangIndex].pbk > this.xbang[bangIndex].pbg) + { + if (jjx < this.xbang[bangIndex].pbg && jjx < 50) { jjx = 0; } + } + if (jjy > 0 && this.xbang[bangIndex].x > this.xbang[j].x + this.xbang[j].pbk) { left = 1; } + if (jjy > 0 && this.xbang[j].x > this.xbang[bangIndex].x + this.xbang[bangIndex].pbk) { right = 1; } + if (jjx > 0 && this.xbang[bangIndex].y > this.xbang[j].y + this.xbang[j].pbg) { down = 1; } + if (jjx > 0 && this.xbang[j].y > this.xbang[bangIndex].y + this.xbang[bangIndex].pbg) { top = 1; } + if ((left == 1 && right == 1) || (down == 1 && top == 1)) { qz = 1; } + } + } + return [left + right + top + down + qz, left, right, top, down, qz]; + }; + Jcsb = (bangid1: number, bangid2: number): boolean => // 把bang1 要去掉, bang2 是否少边 返加True 有少边 False 没有少边 + { + let xj = this.XJcd(bangid1, bangid2); + let jjx = xj[0]; let jjy = xj[1]; + + + if (jjx > 0 && this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbg) + { + return true; + } + else if (jjx > 0 && jjx > 0 && this.xbang[this.getID(bangid1)].pbk * this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbk * this.xbang[this.getID(bangid2)].pbg) + { + return false; + } + else if (jjy > 0 && this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbg) + { + return false; + } + else if (jjy > 0 && this.xbang[this.getID(bangid1)].pbg > this.xbang[this.getID(bangid2)].pbg) + { + return true; + } + // else if (jjy > 0 && this.xbang[this.getID(bangid2)].pbk > this.xbang[this.getID(bangid2)].pbg + // && this.xbang[this.getID(bangid2)].pbk * this.xbang[this.getID(bangid2)].pbg < this.xbang[this.getID(bangid1)].pbk * this.xbang[this.getID(bangid1)].pbg) + // { + // return true; + // } + else + { + return false; + } + + + + // if (bangid1 == 2 && bangid2 == 10) + // { + // console.log(bangid1); + // } + // let bs1 = this.JSgbsl(bangid1)[0]; + // let bs2 = this.JSgbsl(bangid2)[0]; + // let sb2 = this.is_sb(bangid1, bangid2)[0]; + // let sb1 = this.is_sb(bangid2, bangid1)[0]; + + // if (sb2 < bs1 && sb1 < bs2) + // { + // return true; + // } + // else + // { + // return false; + // } + + }; + + //检测所有会让其它板少条且最小的的板 + JS_sb_minmz = (): number => + { + let Bangidzh = this.Find_BS(); + if (Bangidzh[0] != 0) + { + let tempbang: YH_bang[] = []; + for (let m = 0; m < Bangidzh.length; m++) + { + tempbang.push(this.xbang[this.getID(Bangidzh[m])]); + } + if (tempbang.length > 0) + { + tempbang.sort((a, b) => a.pbg * a.pbk - b.pbg * b.pbk); + return tempbang[0].bangid; + } + else if (tempbang.length == 1) + { + return tempbang[0].bangid; + } + else + { + return -1; + } + } + else + { + return -1; + } + }; + //合并所有的板 + HB_b = () => + { + let jshb_lr: boolean = true; + let jshb_td: boolean = true; + let jshb: boolean; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].ishb == false) { jshb = true; } else { jshb = false; } + while (jshb == true) + { + if (this.xbang[i].pbg > this.xbang[i].pbk && this.xbang[i].pbk > 100) //左右 + { + jshb_lr = this.HB_LR(this.xbang[i].bangid, false); + } + else + { + jshb_lr = false; + } + + if ((this.xbang[i].pbg < this.xbang[i].pbk && this.xbang[i].pbg > 100)) //上下合并 + { + jshb_td = this.HB_TD(this.xbang[i].bangid, false); + } + else + { + jshb_td = false; + } + if (jshb_lr == true || jshb_td == true) + { + jshb = true; + } + else if (jshb_lr == false || jshb_td == false) + { + if ((this.xbang[i].pbg > 100 && this.xbang[i].pbk > 100)) //上下合并 + { + jshb_lr = this.HB_LR(this.xbang[i].bangid, false); + jshb_td = this.HB_TD(this.xbang[i].bangid, false); + if (jshb_lr == true || jshb_td == true) { jshb = true; } else { jshb = false; } + } + else + { + jshb = false; + } + } + else + { + jshb = true; + } + } + if (this.HB_bang.length > 0) + { + this.HB.push(this.HB_bang); + this.HB_bang = []; + } + } + }; + + //合并指定的板 第一步 + HB_Max_bang = (Bangid: number) => + { + if (Bangid > 0) + { + let jshb_lr: boolean = true; + let jshb_td: boolean = true; + let jshb: boolean; + let bangid = this.getID(Bangid); + if (this.xbang[bangid].ishb == false) { jshb = true; } else { jshb = false; } + while (jshb == true) + { + if (this.xbang[bangid].pbg > this.xbang[bangid].pbk && this.xbang[bangid].pbk > 100) //左右 + { + jshb_lr = this.HB_LR(this.xbang[bangid].bangid, false); + } + else + { + jshb_lr = false; + } + + if ((this.xbang[bangid].pbg < this.xbang[bangid].pbk && this.xbang[bangid].pbg > 100)) //上下合并 + { + jshb_td = this.HB_TD(this.xbang[bangid].bangid, false); + } + else + { + jshb_td = false; + } + if (jshb_lr == true || jshb_td == true) + { + jshb = true; + } + else if (jshb_lr == false || jshb_td == false) + { + if ((this.xbang[bangid].pbg > 100 && this.xbang[bangid].pbk > 100)) //上下合并 + { + jshb_lr = this.HB_LR(this.xbang[bangid].bangid, false); + jshb_td = this.HB_TD(this.xbang[bangid].bangid, false); + if (jshb_lr == true || jshb_td == true) { jshb = true; } else { jshb = false; } + } + else + { + jshb = false; + } + } + else + { + jshb = true; + } + } + if (this.HB_bang.length > 0) + { + this.HB.push(this.HB_bang); + this.HB_bang = []; + } + else + { + this.HB.push([Bangid]); + this.xbang[bangid].ishb = true; + } + } + }; + + + //合并上面的大板 cd为共边长度 + HB_top = (bangid: number, cd: number) => + { + let hbbang: number[] = []; + let fid: number = bangid; + while (fid > -1) + { + fid = this.F_Top_Big(fid, cd); + if (fid > -1) + { + this.xbang[this.getID(fid)].ishb = true; + hbbang.push(fid); + //this.printstr(fid, 1, "大合", -30, 80, 50); + } + } + if (hbbang.length > 0) { this.HB.push(hbbang); } + }; + //合并下面的大板 cd为共边长度 + HB_down = (bangid: number, cd: number) => + { + let hbbang: number[] = []; + let fid: number = bangid; + while (fid > -1) + { + fid = this.F_Down_Big(fid, cd); + if (fid > -1) + { + this.xbang[this.getID(fid)].ishb = true; + hbbang.push(fid); + // this.printstr(fid, 1, "大合", 10, 80, 50); + } + } + if (hbbang.length > 0) { this.HB.push(hbbang); } + }; + + //寻找最后一块要切割的板 + F_last_mz = (jlID: number): number => + { + let bangid: number; + let temp: number[]; + let index: number = 1; + let lastId: number; + if (jlID == 0) { lastId = this.F_minMZ(); } else { lastId = jlID; } + while (true) + { + if (lastId == -1) { break; } + bangid = this.JS_sb_Bxl(lastId); //检测所有会让其它板少条且有两块或者多块边相邻的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.JS_sb_minmz(); //检测所有会让其它板少条且最小的的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + if (lastId == -1) { break; } + bangid = this.F_minJL(lastId); + if (bangid > -1) { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.F_minMZ(); //找到面积最小的板 + this.add_last(bangid, index); + index++; + lastId = bangid; + } + } + } + // bangid = this.F_minJL(bangid); + //if (bangid > -1) { this.add_last(bangid, index); index++; } + + temp = this.Find_BS(); + + if (temp[0] == 0) + { + break; + } + } + return index; + + }; + + + //寻找最后一块要切割的板 + F_last_jl = (jlID: number): number => + { + let bangid: number; + let temp: number[]; + let index: number = 1; + let lastId: number; + if (jlID == 0) { lastId = this.F_minMZ(); } else { lastId = jlID; } + let tempend: boolean = false; + while (true) + { + if (lastId == -1) { break; } + bangid = this.JS_sb_Bxl(lastId); //检测所有会让其它板少条且有两块或者多块边相邻的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + if (lastId == -1) { break; } + bangid = this.F_minJL(lastId); + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + //bangid = this.F_minMZ(); //找到面积最小的板 + // if (lastId == -1) { break; } + // bangid = this.F_minJL(lastId); + bangid = this.JS_sb_minmz(); //检测所有会让其它板少条且最小的的板 + + if (bangid > -1) { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.F_minMZ(); //找到面积最小的板 + this.add_last(bangid, index); + index++; + lastId = bangid; + } + } + } + // bangid = this.F_minJL(bangid); + //if (bangid > -1) { this.add_last(bangid, index); index++; } + + temp = this.Find_BS(); + + if (temp[0] == 0) + { + break; + } + + } + return index; + + }; + + //打印并添加最后一块 + add_last = (bangid: number, index: number) => + { + let tempx: number; + //if (index > 9) { tempx = 27; } else { tempx = 10; } 这两行用于测试打印 + //this.printstr(bangid, 1, index.toString(), this.xbang[this.getID(bangid)].pbk / 2 - tempx, this.xbang[this.getID(bangid)].pbg / 2 - 20, 50); + this.SCid.push(bangid); + this.xbang[this.getID(bangid)].isqg = true; + let tempid = this.xbang[this.getID(bangid)].grid; + if (this.xbang[this.getID(bangid)].grid > -1) + { + let tempin = this.xbang[this.getID(tempid)].gr.findIndex((n) => n == bangid); + if (tempin > -1) + { + this.xbang[this.getID(tempid)].gr.splice(tempin, 1); + if (this.xbang[this.getID(tempid)].gr.length == 0) { this.xbang[this.getID(tempid)].isgr = false; } + } + } + }; + + //找到面积最小的板 + F_minMZ = (): number => + { + let jhb = this.Find_BS(); + if (jhb[0] != 0) + { + let tempBang: YH_bang[] = []; + if (jhb.length == 1) + { + return jhb[0]; + } + else + { + for (let i = 0; i < jhb.length; i++) + { + tempBang.push(this.xbang[this.getID(jhb[i])]); + } + tempBang = tempBang.sort((a, b) => + { + if (a.pbg * a.pbk == b.pbg * b.pbk) + { + return Math.hypot(this.B_k - a.x - a.pbk / 2, this.B_g - a.y - a.pbg / 2) - Math.hypot(this.B_k - b.x - b.pbk / 2, this.B_g - b.y - b.pbg / 2); + } + else + { + return a.pbg * a.pbk - b.pbg * b.pbk ? -1 : 1; + } + }); + + return tempBang[0].bangid; + } + } + else + { + return -1; + } + }; + + //按位置最近排序 + F_minJL = (bangid: number): number => + { + let jhb = this.Find_BS(); + if (jhb.length[0] != 0) + { + let tempBang: YH_bang[] = []; + if (jhb.length > 0) + { + for (let i = 0; i < jhb.length; i++) + { + tempBang.push(this.xbang[this.getID(jhb[i])]); + } + let wzx = this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk / 2; + let wzy = this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg / 2; + tempBang.sort((a, b) => a.pbk * a.pbg - b.pbk * b.pbg); + if (tempBang[tempBang.length - 1].pbg * tempBang[tempBang.length - 1].pbk / 1000000 - tempBang[0].pbg * tempBang[0].pbk / 1000000 > 0.2) + { + return tempBang[0].bangid; + } + else + { + tempBang.sort((a, b) => Math.hypot(wzx - a.x - a.pbk / 2, wzy - a.y - a.pbg / 2) - Math.hypot(wzx - b.x - b.pbk / 2, wzy - b.y - b.pbg / 2)); + return tempBang[0].bangid; + } + } + else if (jhb.length == 1) + { + return jhb[0]; + } + else + { + return -1; + } + } + else + { + return -1; + } + }; + //检测所有会让其它板少条且有两块或者多块边相邻的板 + JS_sb_Bxl = (bangid: number): number => + { + let Bangidzh = this.Find_BS(); + if (Bangidzh[0] != 0) + { + let tempbang: YH_bang[] = []; + let tempxj: YH_bang[] = []; + let temp: YH_bang; + for (let m = 0; m < Bangidzh.length; m++) + { + for (let n = 0; n < Bangidzh.length; n++) + { + if (m != n) + { + if (this.Jcsb(Bangidzh[m], Bangidzh[n]) == true) + { + tempbang.push(this.xbang[this.getID(Bangidzh[m])]); + } + } + } + } + if (tempbang.length == 1) + { + return tempbang[0].bangid; + } + else if (tempbang.length == 0) + { + return -1; + } + else //(tempbang.length > 1); + { + let wzx = this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk / 2; + let wzy = this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg / 2; + tempbang.sort((a, b) => Math.hypot(wzx - a.x - a.pbk / 2, wzy - a.y - a.pbg / 2) - Math.hypot(wzx - b.x - b.pbk / 2, wzy - b.y - b.pbg / 2)); + return tempbang[0].bangid; + + } + } + else + { + return -1; + } + + }; + + //用于计算下刀顺序 + XDscjs = () => + { + let bangid = this.MaxMZ(0.05); + this.HB_Max_bang(bangid); + this.HB_top(bangid, 200); + this.HB_down(bangid, 200); + this.F_GL_LR(); + this.F_GL_TD(); + this.Is_big_gr(); + this.F_GL_JJB(); + + let index: number; + + if (this.jl_mz == 1) + { + index = this.F_last_jl(0); + } else + { + index = this.F_last_mz(0); + } + this.printHBCS(index); + + + }; + printHBCS = (index: number) => + { + for (let m = this.HB.length - 1; m >= 0; m--) + { + for (let n = this.HB[m].length - 1; n >= 0; n--) + { + //this.printstr(this.HB[m][n], 1, index.toString(), this.Bakbang[this.getID_Bkb(this.HB[m][n])].pbk / 2, this.Bakbang[this.getID_Bkb(this.HB[m][n])].pbg / 2, 50); + this.SCid.push(this.HB[m][n]); + index++; + }; + } + }; +}; + + + +function equaln(v1: number, v2: number, fuzz = 1e-5) +{ + return Math.abs(v1 - v2) <= fuzz; +} + + + + +export interface YH_bang +{ + bangid: number; + line: number ; + x: number; + y: number; + pbg: number; + pbk: number; + ishb?: boolean;//是否参与合并的板 + hb?: number[]; //合在并的板 + isgr?: boolean; //是否关连 + gr?: number[];//关联的板的集合 + grid: number; //跟别的板关联的ID + isbig?: boolean;//是否为合并的大板 + isqg?: boolean;//是否被切掉的板 + +} diff --git a/tests/dev1/dataHandle/models/config.ts b/tests/dev1/dataHandle/models/config.ts new file mode 100644 index 0000000..ee9b495 --- /dev/null +++ b/tests/dev1/dataHandle/models/config.ts @@ -0,0 +1,28 @@ +/** + * 配置基类,下划线开头的变量不会被序列化 + */ +export class ConfigBase { + name: string = ''; + version:string = '1.0.0'; + [key: string]: any; + + /** + * 加载反序列化数据 + * @param data + */ + load(data:Record){ + for (const key of Object.getOwnPropertyNames(this).filter(i=>i[0]!=='_')) { + if(data[key]!=undefined){ + this[key] = data[key]; + } + } + } + + /** + * 序列化json方法 + * @returns + */ + toJson(){ + return JSON.stringify(this,(k,v)=>k[0]=='_'?undefined:v); + } +} \ No newline at end of file diff --git a/tests/dev1/dataHandle/models/file.ts b/tests/dev1/dataHandle/models/file.ts new file mode 100644 index 0000000..322b403 --- /dev/null +++ b/tests/dev1/dataHandle/models/file.ts @@ -0,0 +1,8 @@ +export interface FileOptions { + encode?: string; + addBOM?: boolean; +} +export interface FileInfo extends FileOptions { + name: string, + content: string | Blob | Uint8Array, +} \ No newline at end of file diff --git a/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeMachine.ts b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeMachine.ts new file mode 100644 index 0000000..d2cc07c --- /dev/null +++ b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeMachine.ts @@ -0,0 +1,1745 @@ +import type { Big_bang, Con, Inv, xbang } from './bang' +import { LineType } from './bang' + +export class RectOptimizeMachine { + private _Stop = false + + public CallBack: (best: Inv, yl: Big_bang[], fit: number, info: any) => void | Promise + + Stop(d?:any) { + this._Stop = true + } + + /** + * @param xbangs 小板 + * @param bigBang 大板(N个元素,前N-1个元素表示余料板且余料板须为矩形,第N个元素表示大板) + * @param bigBangSL 余料板数量(bigBang中前N-1个元素对应的数量,如果bigBang中只有一个元素即只有大板没有余料板,则为空数组) + * @param xyhcs 新优化次数 + * @param isdtwosided 双面加工的小板是否优先排入 + * @param gap 排版缝隙 = 开料刀直径 + 缝隙 + * @param hssf 规则排版 + * @param YuLiaoBoardDo2FaceBlock 余料板是否排入双面加工的小板 + */ + async Start(xbangs: xbang[], bigBang: Big_bang[], bigBangSL: number[], xyhcs: number, isdtwosided: boolean, gap: number, hssf: boolean, YuLiaoBoardDo2FaceBlock: boolean) { + this._Stop = false + + // let bestFit = Infinity; //最佳的利用率 35*10 + 最后一张板 + let bestFit = Number.POSITIVE_INFINITY // 最佳的利用率 35*10 + 最后一张板 + let bestInv: Inv + + let i = 0 + let inv; let re_ylb; let fit: any = null + + while (!this._Stop) { + + const qfsh = hssf ? false : i % 2 === 0 // 电子锯算法, 永远不合并空间 + + let tempArr = SnameYH(xbangs.slice(), bigBang, bigBangSL, xyhcs, qfsh, isdtwosided, gap, YuLiaoBoardDo2FaceBlock) + + inv = tempArr[0] + re_ylb = tempArr[1] + if (xbangs.length > 1000) { + // debugger + } + fit = GetInvFit(inv) + let info = { + times: i, + type: 'loop' + } + if (fit < bestFit) { + bestFit = fit + bestInv = inv + this.CallBack(inv, re_ylb, fit, info) + } + /** 优化次数限制 逻辑 */ + if (xyhcs == i) { + await this.Stop(info); + info.type = 'stop' + this.CallBack(inv, re_ylb, fit, info) + } + await Sleep(Math.max(5, 20 - Math.floor(xbangs.length / 10000))) + i++ + } + } +} + +// 优化所有余料 +function YLYH_all(xbangs: xbang[], yuliao_bang: Big_bang[], ylzs: number[], xyhcs: number, qfsh: boolean, isdtwosided: boolean, gap: number, YuLiaoBoardDo2FaceBlock: boolean): [Inv, xbang[], Big_bang[]] // 余料优化 +{ + let get_regelt: Inv = [] + let temp_yan_bang: Big_bang[] = [] + let re_Bigbang: Big_bang[] = [] + temp_yan_bang = [] + // console.log('余料总数:', ylzs) + for (let i = 0; i < ylzs.length; i++) { + if (xbangs.length == 0) { + break + } + else { + temp_yan_bang = [] + for (let sl = 0; sl < ylzs[i]; sl++) { + temp_yan_bang.push(yuliao_bang[i]) + } + let temp:any = [] + temp = ylyh(xbangs, temp_yan_bang, xyhcs, qfsh, isdtwosided, gap, YuLiaoBoardDo2FaceBlock) + for (let yhsl = 0; yhsl < temp[0].length; yhsl++) { + get_regelt.push(temp[0][yhsl]) + } + for (let ylsl = 0; ylsl < temp[2].length; ylsl++) { + re_Bigbang.push(temp[2][ylsl]) + } + xbangs = temp[1].slice() + // re_Bigbang.push(temp[2].slice()); + } + } + return [get_regelt, xbangs, re_Bigbang] +} + +// 优化余料中的一个尺寸的小板 +function ylyh(xbangs: xbang[], yuliao_bang: Big_bang[], xyhcs: number, qfsh: boolean, isdtwosided: boolean, gap: number, YuLiaoBoardDo2FaceBlock: boolean): [Inv, xbang[], Big_bang[]] // 余料优化 +{ + let finbang: xbang[] + let yh_result: Inv = [] + let l: number + let w: number + let ylzs: number// 余料张数 + let temp_yan_bang: Big_bang[] = [] + let re_yan_bang: Big_bang[] = [] + let temp_max_YH: Inv = [] + temp_yan_bang = [] + temp_yan_bang.push(yuliao_bang[0]) + l = yuliao_bang[0].l + w = yuliao_bang[0].w + + if (YuLiaoBoardDo2FaceBlock) // 余料板允许加入双面小板 + { + finbang = find_bang_k_g(l, w, xbangs, false).slice() + } + else { + finbang = find_bang_k_g(l, w, xbangs, true).slice() + } + + if (finbang.length > 0) { + if (YuLiaoBoardDo2FaceBlock) // 余料板允许加入双面小板 + { + temp_max_YH = YHJS(finbang, temp_yan_bang, xyhcs, qfsh, isdtwosided, gap) // 余料板 不优先 双面加工板 + } + else { + temp_max_YH = YHJS(finbang, temp_yan_bang, xyhcs, qfsh, false, gap) + } + + if (yuliao_bang.length > temp_max_YH.length) { + ylzs = temp_max_YH.length + } + else { + ylzs = yuliao_bang.length + } + for (let i = 0; i < ylzs; i++) { + yh_result.push(temp_max_YH[i]) + re_yan_bang.push(yuliao_bang[0]) + for (let n = 0; n < temp_max_YH[i].length; n++) { + for (let bsl = xbangs.length - 1; bsl >= 0; bsl--) + if (temp_max_YH[i][n].bangid == xbangs[bsl].id) { + xbangs.splice(bsl, 1) + } + } + } + } + return [yh_result, xbangs, re_yan_bang] +} +// 相同板材优化 +function SnameYH(xbangs: xbang[], B_bang: Big_bang[], ylsl: number[], xyhcs: number, qfsh: boolean, isdtwosided: boolean, gap: number, YuLiaoBoardDo2FaceBlock: boolean): [Inv, Big_bang[]] { + let same_yh_bang: Inv = [] + let temp_max_YH: Inv = [] + let ylbang_max_yh: Big_bang[] = [] + let temp_same_yh: [Inv, xbang[]] + let isend: boolean = false + let Bigbang: Big_bang[] = [] + Bigbang.push(B_bang[B_bang.length - 1]) + + let ylbang: Big_bang[] = B_bang.slice() + ylbang.pop() + + let tempresult = [] + + tempresult = YLYH_all(xbangs, ylbang, ylsl, xyhcs, qfsh, isdtwosided, gap, YuLiaoBoardDo2FaceBlock) // 余料优化 + + for (let i = 0; i < tempresult[0].length; i++) { + same_yh_bang.push(tempresult[0][i]) + } + xbangs = tempresult[1].slice() + ylbang_max_yh = tempresult[2].slice() + + while (isend === false) { + if (xbangs.length == 0) { break } + temp_max_YH = YHJS(xbangs, Bigbang, xyhcs, qfsh, isdtwosided, gap) + temp_same_yh = Get_bs_bang(temp_max_YH, xbangs, isdtwosided) + for (let i = 0; i < temp_same_yh[0].length; i++) { + same_yh_bang.push(temp_same_yh[0][i]) + xbangs = temp_same_yh[1].slice() + } + } + + return [same_yh_bang, ylbang_max_yh] +} + +// 优化计算. +function YHJS(xbangs: xbang[], B_bang: Big_bang[], cs: number, qfsh: boolean, isdtwosided: boolean, gap: number): Inv { + let xbangs_bak = xbangs.slice() + let bestFit = Number.POSITIVE_INFINITY + // let bestFit = Infinity; + let bestInv: Inv + for (let i = 0; i < cs; i++) { + xbangs = xbangs_bak.slice() + + let result_yh: Inv + if (qfsh) + result_yh = CBJS(xbangs, B_bang, gap, isdtwosided) + else + result_yh = CBJS1(xbangs, B_bang, gap, isdtwosided) + + let fit = GetInvFit(result_yh) + if (fit < bestFit) { + bestFit = fit + bestInv = result_yh + } + } + return bestInv +} + +// 得到利用率最高板材,及返回倍数板及剩下的小板 +function Get_bs_bang(inv: Inv, listbang: xbang[], isdtwosided: boolean): [Inv, xbang[]] { + let [bestCon] = arrayMax(inv, GetConFit)// 最优大板 + + let temp_yh_bang: Con = [] + let temp_list_yh_bang: Inv = [] + let isfindend: boolean = false + + while (isfindend == false) { + if (listbang.length == 0) { break } + + let unPlacelist = listbang.slice() + for (let br of bestCon) { + let temp_result = find_same_bang(br.pbg, br.pbk, unPlacelist, isdtwosided) + if (temp_result.findid > -1) // 找到合适的板 + { + temp_yh_bang.push( + { + bangid: temp_result.id, + line: temp_result.line, + x: br.x, + y: br.y, + pbg: temp_result.pbg, + pbk: temp_result.pbk, + }, + ) + unPlacelist.splice(temp_result.findid, 1)// 这里不会错误的原因是 必然会有一次成功 并且下面会备份这个数组 + } + else // 没找到 合适的板 退出 + { + isfindend = true + break + } + } + if (isfindend == false) // 找到合适的 + { + temp_list_yh_bang.push(temp_yh_bang) + temp_yh_bang = [] + listbang = unPlacelist.slice()// 备份数组 + } + } + return [temp_list_yh_bang, listbang] +} + +function GetInvFit(inv: Inv) { + let fit = (inv.length - 1) * 10 + fit += GetConFit(arrayLast(inv)) + return fit +} + +function GetConFit(con: Con) { + let fit = 0 + for (let p of con) + fit += p.pbg * p.pbk * 1e-6 + return fit +} + +// 初步优化计算 横竖算法,无空间合并 +function CBJS1(Listbang: xbang[], yanbang: Big_bang[], dt: number, isdtwosided: boolean): Inv // [(YHB_LST)[], (Big_bang[])[], (Big_bang[])[]] +{ + let yanbang_bk: Big_bang[] = yanbang.slice() + // let zfkj: Big_bang[] = []; + // let list_zfkj: (Big_bang[])[] = []; + let list_yh_bng: Inv = [] + // let list_yang_bang: (Big_bang[])[] = []; + let result_yh: Con = [] + + while (Listbang.length > 0) { + // zfkj = []; + yanbang = yanbang_bk.slice() + result_yh = [] + while (yanbang.length > 0) { + let lx: number = Math.floor(Math.random() * 3) // lx:0 面积最大 1 最高 2 最宽 + let fs: boolean // fs:留板方式,0:左右 1:上下 + if (Math.floor(Math.random() * 2) == 0) { fs = false } else { fs = true } + let get_result = [] + get_result = jsyb(yanbang[0], lx, fs, dt, Listbang, isdtwosided) // lx:0面积最大 1最高 2最宽 fs:取板方式,false:左右(取同宽) true:上下(取同高) dt:刀头间隙 + yanbang.shift() + if (get_result[0] > 0) { + for (let i = 0; i < get_result[0]; i++) + yanbang.push(get_result[2][i]) + } + if (get_result[1] > 0) { + for (let i = 0; i < get_result[1]; i++) + result_yh.push(get_result[3][i]) + } + Listbang = get_result[4].slice() + if (Listbang.length == 0) { break } + } + // list_zfkj.push(zfkj); + list_yh_bng.push(result_yh) + // list_yang_bang.push(yanbang); + // list_zfkj.push(zfkj); + // Draw_Yan_bang(yanbang, 3, 0); + // Draw_Yan_bang(zfkj, 5, 0); + } + + // return [list_yh_bng, list_zfkj, list_yang_bang]; + return list_yh_bng +} + +// 初步优化计算 交叉算法,有空间合并 +function CBJS(Listbang: xbang[], yanbang: Big_bang[], dt: number, isdtwosided: boolean): Inv // [(YHB_LST)[], (Big_bang[])[], (Big_bang[])[]] +{ + let yanbang_bk: Big_bang[] = yanbang.slice() + // let zfkj: Big_bang[] = []; + // let list_zfkj: (Big_bang[])[] = []; + let list_yh_bng: Inv = [] + // let list_yang_bang: (Big_bang[])[] = []; + let result_yh: Con = [] + + // let temp: number = 0; + while (Listbang.length > 0)// while (temp < 2)// + { + // temp++; + // zfkj = []; + yanbang = yanbang_bk.slice() + result_yh = [] + while (yanbang.length > 0) { + let lx: number = Math.floor(Math.random() * 3) // lx:0 面积最大 1 最高 2 最宽 + let fs: boolean // fs:留板方式,0:左右 1:上下 + if (Math.floor(Math.random() * 2) == 0) { fs = false } else { fs = true } + let get_result = [] + let temp_yanbang = [] + temp_yanbang = HBKJ(yanbang, Listbang, dt) + yanbang = temp_yanbang[0] + get_result = jsyb1(yanbang[temp_yanbang[1]], lx, fs, dt, Listbang, isdtwosided) // lx:0面积最大 1最高 2最宽 fs:取板方式,false:取同宽) true:取同高) dt:刀头间隙 + yanbang.splice(temp_yanbang[1], 1) + if (get_result[0] > 0) { + for (let i = 0; i < get_result[0]; i++) + yanbang.push(get_result[2][i]) + } + if (get_result[1] > 0) { + for (let i = 0; i < get_result[1]; i++) + result_yh.push(get_result[3][i]) + } + Listbang = get_result[4].slice() + if (Listbang.length == 0) { break } + } + // list_zfkj.push(zfkj); + list_yh_bng.push(result_yh) + // list_yang_bang.push(yanbang); + // list_zfkj.push(zfkj); + // Draw_Yan_bang(yanbang, 3, 0); + // Draw_Yan_bang(zfkj, 5, 0); + } + + // return [list_yh_bng, list_zfkj, list_yang_bang]; + return list_yh_bng +} + +// 空间合并(相加) +function hb(yanbang: Big_bang[], dt: number): Big_bang[] { + let i: number = -1 + yanbang.sort((a1, a2) => a1.x - a2.x) + while (true) { + i++ + + if (yanbang.length < 2 || i === yanbang.length - 1) + break + + if (yanbang[i].y === yanbang[i + 1].y + && yanbang[i].x + yanbang[i].w + dt === yanbang[i + 1].x + && yanbang[i].l === yanbang[i + 1].l) { + yanbang[i].w = yanbang[i].w + yanbang[i + 1].w + dt + yanbang.splice(i + 1, 1) + i = -1 + } + } + return yanbang +} + +// 整理空间 +function zlkj(yanbang: Big_bang[], listbang: xbang[], dt: number): [Big_bang[], boolean] // false 继续合并 ture 不在合并 +{ + let k: number + let g: number + let isjshb: boolean = true// false 继续合并 ture 不在合并 + let minY: number = 100000 + let yanindex: number + if (listbang.length == 0) + return [yanbang, true] + + for (let i = 0; i < yanbang.length; i++) { + if (yanbang[i].y < minY) { + minY = yanbang[i].y + yanindex = i + } + } + k = yanbang[yanindex].w + g = yanbang[yanindex].l + if (yanindex == 0 && yanbang.length > 1) { + if (yanbang[yanindex].x + yanbang[yanindex].w + dt == yanbang[yanindex + 1].x)// && yanbang[i].y + yanbang[i].l == yanbang[i + 1].y + yanbang[i + 1].l) + { + if (find_max_mz(g, k, listbang, false).findid == -1) { + yanbang[yanindex].y = yanbang[yanindex + 1].y + yanbang[yanindex].l = yanbang[yanindex + 1].l + isjshb = false + } + } + } + else if (yanindex == yanbang.length - 1 && yanbang.length > 1) { + if (yanbang[yanindex - 1].x + yanbang[yanindex - 1].w + dt == yanbang[yanindex].x && yanbang.length > 1)// && yanbang[i].y + yanbang[i].l == yanbang[i - 1].y + yanbang[i - 1].l && (yanbang[i - 1].x + yanbang[i - 1].w + dt == yanbang[i].x)) + { + if (find_max_mz(g, k, listbang, false).findid == -1) { + yanbang[yanindex].y = yanbang[yanindex - 1].y + yanbang[yanindex].l = yanbang[yanindex - 1].l + isjshb = false + } + } + } + else if (yanbang.length > 1) { + if (yanbang[yanindex - 1].y > yanbang[yanindex + 1].y) { + if (yanbang[yanindex].x + yanbang[yanindex].w + dt == yanbang[yanindex + 1].x)// && yanbang[i].y + yanbang[i].l == yanbang[i + 1].y + yanbang[i + 1].l) + { + if (find_max_mz(g, k, listbang, false).findid == -1) { + yanbang[yanindex].y = yanbang[yanindex + 1].y + yanbang[yanindex].l = yanbang[yanindex + 1].l + isjshb = false + } + } + } + else { + if (yanbang[yanindex - 1].x + yanbang[yanindex - 1].w + dt == yanbang[yanindex].x)// && yanbang.length > 1 && yanbang[i].y + yanbang[i].l == yanbang[i - 1].y + yanbang[i - 1].l && (yanbang[i - 1].x + yanbang[i - 1].w + dt == yanbang[i].x)) + { + if (find_max_mz(g, k, listbang, false).findid == -1) { + yanbang[yanindex].y = yanbang[yanindex - 1].y + yanbang[yanindex].l = yanbang[yanindex - 1].l + isjshb = false + } + } + } + } + // console.log("zlkj"); + // console.log(g, k); + // console.log(yanbang); + // console.log(listbang); + + // Sleep(0); + return [yanbang, isjshb] +} + +// 合并空间 +function HBKJ(yanbang: Big_bang[], listbang: xbang[], dt: number): [Big_bang[], number] { + let get_result: Big_bang[] + let minY: number = 10000 + let yanindex: number + + get_result = hb(yanbang, dt) + yanbang = get_result + + let isend: boolean = false + while (isend == false) { + if (yanbang.length == 1) { break } + for (let i = 0; i < yanbang.length; i++) { + let temp_result = [] + temp_result = zlkj(yanbang, listbang, dt) + if (temp_result[1] == false) { + yanbang = temp_result[0] + get_result = hb(yanbang, dt) + yanbang = get_result + } + else + isend = true + } + } + for (let i = 0; i < yanbang.length; i++) { + if (yanbang[i].y < minY) { + minY = yanbang[i].y + yanindex = i + } + } + return [yanbang, yanindex] +} + +// 寻找符合面积的板 +function find_bang_k_g(g: number, k: number, Listbang: xbang[], oneFaceBlock: boolean): xbang[] // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 isdtwosided: boolean true 翻板提前 +{ + let finbang: xbang[] = [] + if (oneFaceBlock == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && Listbang[i].isdtwosided == false) { + finbang.push(Listbang[i]) + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && Listbang[i].isdtwosided == false) { + finbang.push(Listbang[i]) + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && Listbang[i].isdtwosided == false) { + finbang.push(Listbang[i]) + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && Listbang[i].isdtwosided == false) { + finbang.push(Listbang[i]) + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k) { + finbang.push(Listbang[i]) + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g) { + finbang.push(Listbang[i]) + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k) { + finbang.push(Listbang[i]) + } + else if (Listbang[i].l <= k && Listbang[i].w <= g) { + finbang.push(Listbang[i]) + } + } + } + } + + return finbang +} // 寻找面积最的板 + +// 寻找面积最大的板 +function find_max_mz(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 isdtwosided: boolean true 翻板提前 +{ + let max: number = 0 + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + let temp_max: number + + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + temp_max = Listbang[i].l * Listbang[i].w / 1000000 + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max && Listbang[i].isdtwosided == true) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max && Listbang[i].isdtwosided == true) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max && Listbang[i].isdtwosided == true) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max && Listbang[i].isdtwosided == true) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + temp_max = Listbang[i].l * Listbang[i].w / 1000000 + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + temp_max = Listbang[i].l * Listbang[i].w / 1000000 + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < temp_max) { + max = temp_max + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < temp_max) { + max = temp_max + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +} // 寻找面积最的板 + +// 寻找最高的板 +function find_max_g(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 +{ + let max: number = 0 + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l && Listbang[i].isdtwosided == true) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w && Listbang[i].isdtwosided == true) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l && Listbang[i].isdtwosided == true) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w && Listbang[i].isdtwosided == true) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +} + +// 寻找同高的板 +function find_same_g(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 +{ + let max: number = g + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].l) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].w) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +}// 寻找同高的板 + +// 寻找最宽的板 +function find_max_k(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 +{ + let max: number = 0 + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w && Listbang[i].isdtwosided == true) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l && Listbang[i].isdtwosided == true) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w && Listbang[i].isdtwosided == true) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l && Listbang[i].isdtwosided == true) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max < Listbang[i].w) { + max = Listbang[i].w + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max < Listbang[i].l) { + max = Listbang[i].l + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +} // 寻找最高的板 + +// 寻同宽的板 +function find_same_k(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 lx:0 面积最大 1 最高 2 最宽 +{ + let max: number = k + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l <= g && Listbang[i].w <= k && max == Listbang[i].w) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l <= k && Listbang[i].w <= g && max == Listbang[i].l) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +} // 寻同宽的板 + +// 找到同样大小尺寸的板材 && Listbang[i].isdtwosided == true) +function find_same_bang(g: number, k: number, Listbang: xbang[], isdtwosided: boolean): findbang // g 高 k 宽 listbang:小板列表 1 同高 2 同宽 +{ + let bangindex: number = -1 + let line: LineType = LineType.Positive + let pbg: number = 0 + let pbk: number = 0 + let bangid: number = 0 + if (isdtwosided == true) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l == g && Listbang[i].w == k && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l == k && Listbang[i].w == g && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l == g && Listbang[i].w == k && Listbang[i].isdtwosided == true) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l == k && Listbang[i].w == g && Listbang[i].isdtwosided == true) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + if (bangindex == -1) { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l == g && Listbang[i].w == k) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l == k && Listbang[i].w == g) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l == g && Listbang[i].w == k) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l == k && Listbang[i].w == g) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + } + else { + for (let i = 0; i < Listbang.length; i++) { + if (Listbang[i].line == 0) { + if (Listbang[i].l == g && Listbang[i].w == k) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + } + else if (Listbang[i].line == 1) { + if (Listbang[i].l == k && Listbang[i].w == g) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + else { + if (Listbang[i].l == g && Listbang[i].w == k) { + bangindex = i + line = 0 + pbg = Listbang[i].l + pbk = Listbang[i].w + bangid = Listbang[i].id + break + } + else if (Listbang[i].l == k && Listbang[i].w == g) { + bangindex = i + line = 1 + pbg = Listbang[i].w + pbk = Listbang[i].l + bangid = Listbang[i].id + break + } + } + } + } + return { + findid: bangindex, + line, + pbg, + pbk, + id: bangid, + } +} // 寻找同高或同宽的板 + +// 计算生成的余板 返回 余板数组,连带的板 小板数组 +// lx:0面积最大 1最高 2最宽 fs:取板方式,false:左右(取同宽) true:上下(取同高) dt:刀头间隙 +function jsyb(bigbang: Big_bang, lx: number, fs: boolean, dt: number, listbang: xbang[], isdtwosided: boolean): [number, number, Big_bang[], Con, xbang[], number, Big_bang[]] { + let x: number = bigbang.x + let y: number = bigbang.y + let k: number = bigbang.w + let g: number = bigbang.l + let isend: boolean = false + let ybg: number = g + let ybk: number = k + let x1: number = x + let y1: number = y + let yb: Big_bang[] = [] // #余板 坐标跟高宽; + let yhxbang: Con = [] // #连带小板的Id,及坐标 + let zfkj: Big_bang[] = [] // 作废的空间 + let l: number + let w: number + + let get_result: findbang + let temp_get: findbang + if (lx === 0) { + // get_result = find_max_mz(g, k, listbang); + get_result = find_max_mz(g, k, listbang, isdtwosided) + } + else if (lx == 1) { + // get_result = find_max_g(g, k, listbang); + get_result = find_max_g(g, k, listbang, isdtwosided) + } + else { + // get_result = find_max_k(g, k, listbang); + get_result = find_max_k(g, k, listbang, isdtwosided) + } + + if (get_result.findid > -1) { + yhxbang.push({ + bangid: get_result.id, + line: get_result.line, + x, + y, + pbg: get_result.pbg, + pbk: get_result.pbk, + }) + listbang.splice(get_result.findid, 1) + l = get_result.pbg + w = get_result.pbk + if (fs == false) // false:左右(取同宽) true:上下(取同高) + { + ybg = g - l - dt + y1 = y + l + dt + while (isend == false) { + // temp_get = find_same_k(ybg, w, listbang); + temp_get = find_same_k(ybg, w, listbang, isdtwosided) + if (temp_get.findid > -1) { + yhxbang.push({ + bangid: temp_get.id, + line: temp_get.line, + x, + y: y1, + pbg: temp_get.pbg, + pbk: temp_get.pbk, + }) + y1 = y1 + temp_get.pbg + dt + ybg = ybg - temp_get.pbg - dt + listbang.splice(temp_get.findid, 1) + } + else { + if (ybg > 0) { + yb.push({ + l: ybg, + w, + x, + y: y1, + }) + } + if (k - w - dt > 0) { + yb.push({ + l: g, + w: k - w - dt, + x: x + w + dt, + y, + }) + } + break + } + } + } + else // true:上下(取同高) + { + ybk = k - w - dt + x1 = x + w + dt + while (isend == false) { + temp_get = find_same_g(l, ybk, listbang, isdtwosided) + if (temp_get.findid > -1) { + yhxbang.push({ + bangid: temp_get.id, + line: temp_get.line, + x: x1, + y, + pbg: temp_get.pbg, + pbk: temp_get.pbk, + }) + x1 = x1 + temp_get.pbk + dt + ybk = ybk - temp_get.pbk - dt + listbang.splice(temp_get.findid, 1) + } + else { + if (ybk > 0) { + yb.push({ + l, + w: ybk, + x: x1, + y, + }) + } + if (g - l - dt > 0) { + yb.push({ + l: g - l - dt, + w: k, + x, + y: y + l + dt, + }) + } + break + } + } + } // false:左右(取同宽) true:上下(取同高) + let ybsl: number = 0 + let yhsl: number = 0 + if (yb.length > 0) { ybsl = yb.length } + if (yhxbang.length > 0) { yhsl = yhxbang.length } + return [ybsl, yhsl, yb, yhxbang, listbang, 0, zfkj] + } + else { + zfkj.push({ + l: g, + w: k, + x, + y, + }) + let ybsl: number = 0 + let yhsl: number = 0 + if (yb.length > 0) { ybsl = yb.length } + if (yhxbang.length > 0) { yhsl = yhxbang.length } + return [ybsl, yhsl, yb, yhxbang, listbang, 1, zfkj] + } +} + +// 计算生成的余板 返回 余板数组,连带的板 小板数组 都是左右分隔空间,后面再来空间合并 +// lx:0面积最大 1最高 2最宽 fs:取板方式,false:取同宽 true:取同高 dt:刀头间隙 +function jsyb1(bigbang: Big_bang, lx: number, fs: boolean, dt: number, listbang: xbang[], isdtwosided: boolean): [number, number, Big_bang[], Con, xbang[], number, Big_bang[]] { + let x: number = bigbang.x + let y: number = bigbang.y + let k: number = bigbang.w + let g: number = bigbang.l + let isend: boolean = false + let ybg: number = g + let ybk: number = k + let x1: number = x + let y1: number = y + let yb: Big_bang[] = [] // #余板 坐标跟高宽; + let yhxbang: Con = []// #连带小板的Id,及坐标 + let zfkj: Big_bang[] = [] // 作废的空间 + let l: number + let w: number + + let get_result: findbang + let temp_get: findbang + if (lx == 0) { + // get_result = find_max_mz(g, k, listbang); + get_result = find_max_mz(g, k, listbang, isdtwosided) + } + else if (lx == 1) { + // get_result = find_max_g(g, k, listbang); + get_result = find_max_g(g, k, listbang, isdtwosided) + } + else { + // get_result = find_max_k(g, k, listbang); + get_result = find_max_k(g, k, listbang, isdtwosided) + } + + if (get_result.findid > -1) { + yhxbang.push({ + bangid: get_result.id, + line: get_result.line, + x, + y, + pbg: get_result.pbg, + pbk: get_result.pbk, + }) + listbang.splice(get_result.findid, 1) + ////////////// + l = get_result.pbg + w = get_result.pbk + if (fs == false) // false:左右(取同宽) true:上下(取同高) + { + ybg = g - l - dt + y1 = y + l + dt + while (isend == false) { + temp_get = find_same_k(ybg, w, listbang, isdtwosided) + if (temp_get.findid > -1) { + yhxbang.push({ + bangid: temp_get.id, + line: temp_get.line, + x, + y: y1, + pbg: temp_get.pbg, + pbk: temp_get.pbk, + }) + y1 = y1 + temp_get.pbg + dt + ybg = ybg - temp_get.pbg - dt + listbang.splice(temp_get.findid, 1) + } + else { + if (ybg > 0) { + yb.push({ + l: ybg, + w, + x, + y: y1, + }) + } + if (k - w - dt > 0) { + yb.push({ + l: g, + w: k - w - dt, + x: x + w + dt, + y, + }) + } + break + } + } + } + else // true:上下(取同高) + { + ybk = k - w - dt + x1 = x + w + dt + while (isend == false) { + temp_get = find_same_g(l, ybk, listbang, isdtwosided) + if (temp_get.findid > -1) { + yhxbang.push({ + bangid: temp_get.id, + line: temp_get.line, + x: x1, + y, + pbg: temp_get.pbg, + pbk: temp_get.pbk, + }) + x1 = x1 + temp_get.pbk + dt + ybk = ybk - temp_get.pbk - dt + listbang.splice(temp_get.findid, 1) + } + else { + if (ybk > 0) { + yb.push({ + l: g, + w: ybk, + x: x1, + y, + }) + } + if (g - l - dt > 0) { + yb.push({ + l: g - l - dt, + w: k - ybk - dt, + x, + y: y + l + dt, + }) + } + break + } + } + } // false:左右(取同宽) true:上下(取同高) + + //////////////////////////////////////////////////////////////////////////// + + let ybsl: number = 0 + let yhsl: number = 0 + if (yb.length > 0) { ybsl = yb.length } + if (yhxbang.length > 0) { yhsl = yhxbang.length } + return [ybsl, yhsl, yb, yhxbang, listbang, 0, zfkj] + } + else { + zfkj.push({ + l: g, + w: k, + x, + y, + }) + let ybsl: number = 0 + let yhsl: number = 0 + if (yb.length > 0) { ybsl = yb.length } + if (yhxbang.length > 0) { yhsl = yhxbang.length } + return [ybsl, yhsl, yb, yhxbang, listbang, 1, zfkj] + } +} + +interface findbang { + findid: number + line: LineType + pbg: number + pbk: number + id: number +} + +interface Lyr { + pfs: number// 平方数 + lry: number// 利用率 + ys: number// 页数 +} + +/** 获取数组最后一个元素 */ +function arrayLast(arr: { [key: number]: T; length: number }): T +{ + return arr[arr.length - 1] +} + +/** 查找数组中最大的元素 */ +function arrayMax(arr: T[], f: (item: T) => number = a => (a as unknown as number)): [T, number] +{ + let max = Number.NEGATIVE_INFINITY + let maxIndex = -1 + for (let i = 0; i < arr.length; i++) + { + let item = arr[i] + let v = f(item) + if (v > max) + { + maxIndex = i + max = v + } + } + return [arr[maxIndex], maxIndex] +} +async function Sleep(time: number) +{ + return new Promise((res) => + { + setTimeout(res, time) + }) +} diff --git a/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeWorkerWorker.ts b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeWorkerWorker.ts new file mode 100644 index 0000000..a051cbe --- /dev/null +++ b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/RectOptimizeWorkerWorker.ts @@ -0,0 +1,43 @@ +import type { Big_bang, xbang } from './bang' +import { RectOptimizeMachine } from './RectOptimizeMachine' +// import {Worker} from "worker_threads" +// import { Worker as NodeWorker } from 'worker_threads'; +const ctx: Worker = self as any + +// if (typeof window !== 'undefined' && 'Worker' in window) { + ctx.addEventListener('message', async (event) => { + let m = new RectOptimizeMachine() + m.CallBack = async (best, fit, arg, info) => { + + ctx.postMessage([best, fit, arg, info]) + } + if (event.data.type == 'start') { + /** + * blockList 小板列表 + * boardList 大板(N个元素,前N-1个元素表示余料板且余料板须为矩形,第N个元素表示大板) + * boardCount 余料板数量(bigBang中前N-1个元素对应的数量,如果bigBang中只有一个元素即只有大板没有余料板,则为空数组) + * optimizeTimes 新优化次数 + * isDoubleFaceBlockFirst 双面加工的小板是否优先排入 + * gap 排版缝隙 = 开料刀直径 + 缝隙 + * gzpb 规则排版 + * isDoubleFaceBlockInRemain 余料板是否排入双面加工的小板 + */ + let [blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain] = (event.data.data) as [xbang[], Big_bang[], number[], number, boolean, number, boolean, boolean] + + m.Start(blockList, boardList, boardCount, optimizeTimes, isDoubleFaceBlockFirst, gap, gzpb, isDoubleFaceBlockInRemain) + } else { + const info = { + type: 'isStop', + } + await m.Stop(info) + ctx.postMessage([[], null, null, info]) + console.log('RectOptimizeWorkerWorker.ts: 关闭线程') + ctx?.terminate() + } + }) +// } else { +// debugger +// } + + +export default {} as typeof Worker & (new () => Worker) diff --git a/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts new file mode 100644 index 0000000..facec27 --- /dev/null +++ b/tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts @@ -0,0 +1,1605 @@ + +export type Con = YH_bang[];//单块1220*2440的板结果 Container +export type Inv = Con[]; //个体:优化结果 包含多个大板 Individual + +/**纹路类型 Positive=0正纹 Reverse=1反纹 CanReversal=2可翻转 */ +export enum LineType +{ + /**正纹 */ + Positive = 0, + /**反纹 */ + Reverse = 1, + /**可翻转 */ + CanReversal = 2, +} + +/**优化的大板 */ +export interface YH_bang +{ + /**板ID */ + bangid: number; + /**纹路 */ + line: LineType; + x: number; + y: number; + /**排版高 */ + pbg: number; + /**排版宽 */ + pbk: number; + + ishb?: boolean;//是否参与合并的板 + hb?: number[]; //合在并的板 + isgr?: boolean; //是否关连 + gr?: number[];//关联的板的集合 + grid?: number; //跟别的板关联的ID + isbig?: boolean;//是否为合并的大板 + isqg?: boolean;//是否被切掉的板 +} + +/**版面类型: Positive=0正面 Reverse=1反面 Arbitrary=2任意面 */ +export enum ComposingType +{ + /**正面 */ + Positive = 0, + /**反面 */ + Reverse = 1, + /**任意面 */ + Arbitrary = 2 +} + +/**孔类型 None=0无 Positive=1正面 Reverse=2反面 Two=3正反 */ +export enum HoleType +{ + /**无 */ + None = 0, + /**正面 */ + Positive = 1, + /**反面 */ + Reverse = 2, + /**正反 */ + Two = 3 +} + +/** 小板 */ +export interface xbang +{ + /**长 */ + l: number; + /**宽 */ + w: number; + /**纹路 */ + line: LineType; + /**排版面 */ + face: ComposingType; + /**小板ID */ + id: number; + /**小板编号 */ + bno: string; + /**孔面: 0无孔 1正面有孔 2反面有孔 3正反面都有孔 */ + holeFaceCount: HoleType; + /**是矩形 */ + isRect?: boolean; + /**有孔 */ + hasHole?: boolean; + /**false单面 true双面 */ + isdtwosided?: boolean; +} + +/** 大板 */ +export class Big_bang //待优化的板 +{ + l: number; //长 + w: number; //宽 + x: number; //x + y: number; //y +} + + +export enum BlockRegion + { + /** 左下 = 0 */ + LEFT_BOTTOM = 0, + /** 右下 = 1 */ + RIGHT_BOTTOM = 1, + /** 右上 = 2 */ + RIGHT_TOP = 2, + /** 左上 = 3 */ + LEFT_TOP = 3, +} + +export class WorkerItemType { + w?: Worker + goodsId?: string | number + pm?: any + status?: 'start' | 'stop' +} + +//开料生产 +export class KLSC +{ + xbang: YH_bang[]; //小板集合 + Bakbang: YH_bang[]; //备份小板集合 + HB_bang: number[] = []; //合并的板 + HB: number[][] = []; //合并板的数组 + B_k: number; //大板宽 + B_g: number; //大板高 + dt: number; //刀头大小(含修边) + wzx: number; //临时用于打印 + wzy: number; //临时用于打印 + jl_mz: number; //用于测试距离或者面积优选 + + SCid: number[] = []; //用于存化顺序的板的bangid + //f = () => 5; + constructor(xbang: YH_bang[], Bang_k: number, Bang_g: number, dt: number, wzx: number, wzy: number, JL_MZ: number) //false JL ture MZ + { + this.xbang = JSON.parse(JSON.stringify(xbang)); + //this.xbang = [...xbang]; + this.Bakbang = JSON.parse(JSON.stringify(xbang)); + this.B_g = Bang_g; + this.B_k = Bang_k; + this.dt = dt; + this.wzx = wzx; + this.wzy = wzy; + this.jl_mz = JL_MZ; + this.XDscjs(); + }; + + //查找距离中心最近,且跟最大的板相差不大于容差面积 rcmz 的板 返加YH_bang[].bangid + MaxMZ = (rcmz: number): number => + { + let tmepckb: number; + let tmepckb1: number; + let tempxbang: YH_bang[] = []; + this.xbang.sort((b, a) => a.pbg * a.pbk - b.pbg * b.pbk); + let maxmz = this.xbang[0].pbg * this.xbang[0].pbk / 1000000; + let maxbangid = this.xbang[0].bangid; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].pbg > this.xbang[i].pbk) + { + tmepckb = this.xbang[i].pbg / this.xbang[i].pbk; + } + else + { + tmepckb = this.xbang[i].pbk / this.xbang[i].pbg; + } + if (this.xbang[0].pbg > this.xbang[0].pbk) + { + tmepckb1 = this.xbang[0].pbg / this.xbang[0].pbk; + } + else + { + tmepckb1 = this.xbang[0].pbk / this.xbang[0].pbg; + } + + if (equaln(this.xbang[i].pbg * this.xbang[i].pbk / 1000000, maxmz, rcmz) && equaln(tmepckb, tmepckb1, 2)) + { + tempxbang.push(this.xbang[i]); + } + } + if (tempxbang.length > 0) + { + return this.minJL(tempxbang); + } + else + { + return maxbangid; + } + + }; + + //查找指定Bangid的板 返回在数组中的位置ID f = () => 5; + getID = (bangid: number): number => this.xbang.findIndex((n) => n.bangid == bangid); + + //查找备份板Bangid的板 返回在数组中的位置ID f = () => 5; + getID_Bkb = (bangid: number): number => this.Bakbang.findIndex((n) => n.bangid == bangid); + + + //查找距离最近的板 返加YH_bang[].bangid + minJL = (xbang: YH_bang[]): number => { xbang.sort((b, a) => Math.hypot(this.B_k / 2 - b.x - b.pbk / 2, this.B_g / 2 - b.y - b.pbg / 2) - Math.hypot(this.B_k / 2 - a.x - a.pbk / 2, this.B_g / 2 - a.y - a.pbg / 2)); return xbang[0].bangid; }; + + //查找左边且Y位置一样的板,, 返加YH_bang[].bangid + F_Left = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.x + n.pbk + this.dt, this.xbang[this.getID(bangid)].x, 0.001) + && equaln(n.y, this.xbang[this.getID(bangid)].y, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + //查找左边关连的板,并写入 + F_GL_LR = () => + { + let temp: number; + let bangIndex: number; + let maxkd: number = 0; + let maxid: number; //bangid + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + bangIndex = i; + while (1) //左边 + { + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbk < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.x + n.pbk < this.xbang[bangIndex].x && n.y <= this.xbang[bangIndex].y && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[1] > this.xbang[bangIndex].pbk); + if (temp != -1) + { + if (this.xbang[temp].pbk > maxkd) + { + maxkd = this.xbang[temp].pbk; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbk > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + bangIndex = i; + while (1) //右边 + { + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbk < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.x > this.xbang[bangIndex].x + this.xbang[bangIndex].pbk + && this.LR_is(this.xbang[bangIndex].bangid, n.bangid) == false + && n.pbg > 300 + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[1] > this.xbang[bangIndex].pbk); + if (temp != -1) + { + if (this.xbang[temp].pbk > maxkd) + { + maxkd = this.xbang[temp].pbk; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbk > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + + + } + }; + }; + + //查找上下边关连的板,并写入 + F_GL_TD = () => + { + let temp: number; + let bangIndex: number; + let maxkd: number = 0; + let maxid: number; //bangid + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + bangIndex = i; + while (1) //上面 + { + if (this.xbang[bangIndex].pbg < this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbg < 200 && this.xbang[bangIndex].ishb == false) + { + temp = this.xbang.findIndex(n => n.y > this.xbang[bangIndex].y + this.xbang[bangIndex].pbg && n.x + 0.01 <= this.xbang[bangIndex].x + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[0] > this.xbang[bangIndex].pbg); + if (temp != -1) + { + if (this.xbang[temp].pbg > maxkd) + { + maxkd = this.xbang[temp].pbg; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbg > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + bangIndex = i; + while (1) //下面 + { + + if (this.xbang[bangIndex].pbg < this.xbang[bangIndex].pbk && this.xbang[bangIndex].pbg < 200 && this.xbang[bangIndex].ishb == false) + { + + temp = this.xbang.findIndex(n => n.y + n.pbg < this.xbang[bangIndex].y + this.xbang[bangIndex].pbg && n.x - 0.01 <= this.xbang[bangIndex].x + && this.XJcd(n.bangid, this.xbang[bangIndex].bangid)[0] > this.xbang[bangIndex].pbg); + if (temp != -1) + { + if (this.xbang[temp].pbg > maxkd) + { + maxkd = this.xbang[temp].pbg; + maxid = this.xbang[temp].bangid; + } + if (this.xbang[temp].pbg > 200) + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[bangIndex].bangid); + this.xbang[bangIndex].grid = this.xbang[temp].bangid; + break; + } + else + { + bangIndex = temp; + } + + } + else + { + break; + } + } + else + { + break; + } + + } + + + } + }; + }; + + //查找有异形交集关连的板,并写入 + F_GL_JJB = () => + { + let temp: number; + if (this.xbang.length > 1) + { + for (let i = 0; i < this.xbang.length; i++) + { + temp = this.xbang.findIndex(n => this.XJcd(n.bangid, this.xbang[i].bangid)[0] > 50 + && this.XJcd(n.bangid, this.xbang[i].bangid)[1] > 50 && n.bangid != this.xbang[i].bangid); + if (temp != -1) + { + if (this.xbang[i].pbg * this.xbang[i].pbk > this.xbang[temp].pbg * this.xbang[temp].pbk) + { + this.xbang[i].isgr = true; + this.xbang[i].gr.push(this.xbang[temp].bangid); + this.xbang[temp].grid = this.xbang[i].bangid; + } + else if (equaln(this.xbang[i].pbg * this.xbang[i].pbk, this.xbang[temp].pbg * this.xbang[temp].pbk, 0.01)) + { + if (this.xbang[temp].x > this.xbang[i].x) + { + + this.xbang[i].isgr = true; + this.xbang[i].gr.push(this.xbang[temp].bangid); + this.xbang[temp].grid = this.xbang[i].bangid; + } + else + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[i].bangid); + this.xbang[i].grid = this.xbang[temp].bangid; + } + } + else + { + this.xbang[temp].isgr = true; + this.xbang[temp].gr.push(this.xbang[i].bangid); + this.xbang[i].grid = this.xbang[temp].bangid; + } + + } + } + }; + for (let k = 0; k < this.xbang.length; k++) + { + let newgr = [... new Set(this.xbang[k].gr)]; + this.xbang[k].gr = newgr; + } + }; + + //判断有关联的板跟大板之间是否有交集,如果有取消这块板的关联 + Is_big_gr = () => + { + for (let k = 0; k < this.xbang.length; k++) + { + let newgr = [... new Set(this.xbang[k].gr)]; + this.xbang[k].gr = newgr; + } + let tempx: number; + let tempy: number; + let bangIndex1 = this.xbang[this.getID(this.HB[0][0])]; + let bangIndex2: number; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].isgr == true) + { + tempx = 0; + tempy = 0; + for (let j = 0; j < this.xbang[i].gr.length; j++) + { + bangIndex2 = this.getID(this.xbang[i].gr[j]); + if (this.xbang[i].bangid == 10)////////////////////用于调试 + { + console.log(this.xbang[i].bangid); + } + if (this.xbang[bangIndex2].pbg > this.xbang[bangIndex2].pbk) + { + tempy = this.Jcxj(bangIndex1.y, bangIndex1.pbg, this.xbang[bangIndex2].y, this.xbang[bangIndex2].pbg); + } + else + { + tempx = this.Jcxj(bangIndex1.x, bangIndex1.pbk, this.xbang[bangIndex2].x, this.xbang[bangIndex2].pbk); + } + } + if (tempx > 50 || tempy > 50) + { + this.xbang[i].isgr = false; + } + } + } + + }; + + + //检测两块板之间的右上角是否有板 false 没有 true 有 + LR_is = (bangid1: number, bangid2: number): boolean => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (tb[0].pbg + tb[0].y > tb[1].pbg + tb[1].y)//右上空间 左边高 + { + return this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt); + } + else + { + return false; + } + }; + + //检测两块板之间的右上角是否有板 false 没有 true 有 + TD_is = (bangid1: number, bangid2: number): boolean => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (tb[0].pbk + tb[0].x > tb[1].pbk + tb[1].x)//右下空间 左边高 + { + return this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt); + } + else + { + return false; + } + }; + + //查找左边且Y位置一样的板,, 返加YH_bang[].bangid + F_Left_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].x + this.xbang[i].pbk < this.xbang[this.getID(bangid)].x && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[1] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + //找到左右靠边最长的高度 返回 [0]长度 [1] y位置 [2] 0左边 1 右边 + L_R_kbcd = (bangid: number): [number, number, number] => + { + let l_b: YH_bang[] = []; + let cd_l = 0; let cd_r: number = 0; let wzl: number; let wzr: number; + let tb = [...this.xbang]; + let tempx: number = this.xbang[this.getID(bangid)].x; + let tempy: number = this.xbang[this.getID(bangid)].y; + let tempk: number = this.xbang[this.getID(bangid)].pbk; + let tempid: number; + let isend: boolean = true; + for (let i = 0; i < 2; i++) + { + l_b = []; + tb = [...this.xbang]; + isend = true; + while (isend == true) + { + if (i == 0) { tempid = tb.findIndex((n) => equaln(n.x + n.pbk + this.dt, tempx, 0.001) && (n.y <= tempy)); } + else { tempid = tb.findIndex((n) => equaln(n.x, tempx + tempk + this.dt, 0.001) && (n.y <= tempy)); } + + if (tempid != -1) + { + l_b.push(tb[tempid]); + tb.splice(tempid, 1); + } + else + { + isend = false; + } + } + if (l_b.length > 0) + { + l_b.sort((a, b) => b.pbg - a.pbg); + if (i == 0) { cd_l = l_b[0].pbg; wzl = l_b[0].y; } else { cd_r = l_b[0].pbg; wzr = l_b[0].y; } + } + } + if (cd_l > cd_r || cd_l == cd_r && cd_l > 0) + { + return [cd_l, wzl, 0]; + } + else if (cd_l < cd_r) + { + return [cd_r, wzr, 1]; + } + else + { + return [0, 0, 0]; + } + + }; + + //查找右边且Y位置一样的板,, 返加YH_bang[].bangid + F_Right = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.x, this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk + this.dt, 0.001) + && equaln(n.y, this.xbang[this.getID(bangid)].y, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + //查找右边且Y位置一样的板,, 返加YH_bang[].bangid + F_Right_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].x > this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[1] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + //查找下边且X位置一样的板,, 返加YH_bang[].bangid + F_Down = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.y + n.pbg + this.dt, this.xbang[this.getID(bangid)].y, 0.001) + // && n.x > 10 + && equaln(n.x, this.xbang[this.getID(bangid)].x, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + //查找上边且X位置一样的板,, 返加YH_bang[].bangid + F_TOP = (bangid: number): number => + { + let a = this.xbang.find((n) => equaln(n.y, this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg + this.dt, 0.001) + //&& n.x > 10 + && equaln(n.x, this.xbang[this.getID(bangid)].x, 0.001) && n.ishb == false); + if (a == undefined) { return; } return a.bangid; + }; + + + //查找上边且X位置一样的板,, 返加YH_bang[].bangid hbcd为大于共边的长度 + F_Top_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].y > this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg + && this.xbang[i].x + this.xbang[i].pbk <= this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk + 0.1 + && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[0] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + + //查找下边且X位置一样的板,, 返加YH_bang[].bangid + F_Down_Big = (bangid: number, gbcd: number): number => + { + let tempjh: YH_bang[] = []; + for (let i = 0; i < this.xbang.length; i++) + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].y + this.xbang[i].pbg < this.xbang[this.getID(bangid)].y + && this.xbang[i].x >= this.xbang[this.getID(bangid)].x && this.xbang[i].pbg > 300 + && this.XJcd(this.xbang[i].bangid, this.xbang[this.getID(bangid)].bangid)[0] > gbcd) + { + tempjh.push(this.xbang[i]); + } + } + } + if (tempjh.length > 0) + { + tempjh.sort((a, b) => a.x - b.x); + return tempjh[0].bangid; + } + else + { + return -1; + } + }; + + + //合并同高相邻的板 isbig: true 为第一回合并的大板 wz为方位 L 左 R右 T上 D下 返回是否有合并过 false 为没有合并过 true 有合并过 + HB_LR = (bangid: number, isbig: boolean): boolean => + { + let isend: boolean = true; + let tempbangid: number; + let ishb: boolean = false; + + while (isend == true) + { + tempbangid = this.F_Left(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + isend = true; + while (isend == true) + { + tempbangid = this.F_Right(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + return ishb; + }; + + //合并同宽相邻的板 isbig: true 为第一回合并的大板 wz为方位 L 左 R右 T上 D下 返回是否有合并过 false 为没有合并过 true 有合并过 + HB_TD = (bangid: number, isbig: boolean): boolean => + { + let isend: boolean = true; + let tempbangid: number; + let ishb: boolean = false; + while (isend == true) + { + tempbangid = this.F_Down(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + + } + else + { + isend = false; + } + } + isend = true; + while (isend == true) + { + tempbangid = this.F_TOP(bangid); + if (tempbangid != undefined) + { + let temp = this.JC_is_bang(bangid, tempbangid); //[f/t,1:x,2:y,3:k,4:g] + if (temp[0] == false) + { + this.addbang(bangid, tempbangid, temp[1], temp[2], temp[3], temp[4], isbig); + ishb = true; + } + else + { + isend = false; + } + } + else + { + isend = false; + } + } + return ishb; + }; + + addbang = (id1: number, id2: number, x: number, y: number, k: number, g: number, isbig: boolean) => //把合并的板写入,并改板的大小 + { + let tempid1 = this.getID(id1); + let tempid2 = this.getID(id2); + if (this.xbang[tempid1].ishb == false) + { + this.HB_bang.push(id1); + } + this.HB_bang.push(id2); + this.xbang[tempid1].x = x; + this.xbang[tempid1].y = y; + this.xbang[tempid1].pbk = k; + this.xbang[tempid1].pbg = g; + this.xbang[tempid1].ishb = true; + this.xbang[tempid2].ishb = true; + if (isbig == true) { this.xbang[tempid1].isbig = true; this.xbang[tempid2].isbig = true; } + }; + + //计算相邻两块板中有空位的地方是否有其它小板 返回 false 为没有其它板,true 有其它板 x,y,k,g + JC_is_bang = (bangid1: number, bangid2: number): [boolean, number, number, number, number] => + { + let tb: YH_bang[] = []; + tb.push(this.xbang[this.getID(bangid1)]); + tb.push(this.xbang[this.getID(bangid2)]); + if (equaln(tb[0].y, tb[1].y, 0.01)) //左右相邻 + { + tb.sort((a, b) => a.x - b.x); + if (tb[0].pbg < tb[1].pbg)//左上空间 右边高 + { + return [this.JCQY_is_bang(tb[0].x, tb[0].y + tb[0].pbg + this.dt, tb[0].pbk, tb[1].pbg - tb[0].pbg - this.dt), + tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[1].pbg]; + } + else if (tb[0].pbg > tb[1].pbg)//右上空间 左边高 + { + return [this.JCQY_is_bang(tb[1].x, tb[1].y + tb[1].pbg + this.dt, tb[1].pbk, tb[0].pbg - tb[1].pbg - this.dt), + tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[0].pbg]; + } + else //一样高 + { + return [false, tb[0].x, tb[0].y, tb[0].pbk + tb[1].pbk + this.dt, tb[0].pbg]; + } + } + else //上下空间 + { + tb.sort((b, a) => a.y - b.y); + if (tb[0].pbk < tb[1].pbk)//右上空间 上边短 + { + return [this.JCQY_is_bang(tb[0].x + tb[0].pbk + this.dt, tb[0].y, tb[1].pbk - tb[0].pbk - this.dt, tb[0].pbg), + tb[1].x, tb[1].y, tb[1].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + else if (tb[0].pbk > tb[1].pbk)//右下空间 下边短 + { + return [this.JCQY_is_bang(tb[1].x + tb[1].pbk + this.dt, tb[1].y, tb[0].pbk - tb[1].pbk - this.dt, tb[1].pbg), + tb[1].x, tb[1].y, tb[0].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + else //一样高 + { + return [false, tb[1].x, tb[1].y, tb[0].pbk, tb[0].pbg + tb[1].pbg + this.dt]; + } + } + }; + + //检测两块板之间相交的长度且两块板之间没有其它板 ,返回长度,第一个为X相交长度 第二个为Y相交长度 + XJcd = (bangid1: number, bangid2: number): [number, number] => + { + let tempbang: YH_bang[] = []; + tempbang.push(this.xbang[this.getID(bangid1)]); + tempbang.push(this.xbang[this.getID(bangid2)]); + + let tempx = this.Jcxj(tempbang[0].x, tempbang[0].pbk, tempbang[1].x, tempbang[1].pbk); + let tempy = this.Jcxj(tempbang[0].y, tempbang[0].pbg, tempbang[1].y, tempbang[1].pbg); + if (tempx > 0 && tempy > 0) + { + return [tempx, tempy]; + } + else if (tempx > 0) + { + tempbang.sort((a, b) => a.x - b.x); + if (tempbang[0].y > tempbang[1].y) + { + if (this.JCQY_is_bang(tempbang[1].x, tempbang[1].y + tempbang[1].pbg, tempx, tempbang[0].y - tempbang[1].y - tempbang[1].pbg) == false) + { + return [tempx, 0]; + } + else + { + return [0, 0]; + } + } + else + { + if (this.JCQY_is_bang(tempbang[1].x, tempbang[0].y + tempbang[0].pbg, tempx, tempbang[1].y - tempbang[0].y - tempbang[0].pbg) == false) + { + return [tempx, 0]; + } + else + { + return [0, 0]; + } + } + + } + else if (tempy > 0) + { + tempbang.sort((a, b) => a.x - b.x); + if (tempbang[0].y > tempbang[1].y) + { + if (this.JCQY_is_bang(tempbang[0].x + tempbang[0].pbk, tempbang[0].y, tempbang[1].x - tempbang[0].x - tempbang[0].pbk, tempy) == false) + { + return [0, tempy]; + } + else + { + return [0, 0]; + } + } + else + { + if (this.JCQY_is_bang(tempbang[0].x + tempbang[0].pbk, tempbang[1].y, tempbang[1].x - tempbang[0].x - tempbang[0].pbk, tempy) == false) + { + return [0, tempy]; + } + else + { + return [0, 0]; + } + } + } + else + { + return [0, 0]; + } + }; + + //检测指定区域内否有其它小板 返回 false 为没有其它板,true 有其它板 + JCQY_is_bang = (x: number, y: number, k: number, g: number): boolean => + { + let result = this.xbang.findIndex((n) => (k + n.pbk - Math.abs(x - n.x) - Math.abs(x - n.x + k - n.pbk)) / 2 > 0.01 && + (g + n.pbg - Math.abs(y - n.y) - Math.abs(y - n.y + g - n.pbg)) / 2 > 0.01); + if (result == -1) { return false; } else { return true; } + }; + + //计算两边相交长度/ + Jcxj = (wz1: number, l1: number, wz2: number, l2: number): number => { return (l1 + l2 - Math.abs(wz1 - wz2) - Math.abs(wz1 - wz2 + l1 - l2)) / 2; }; + + Find_BS = (): number[] => + { + let temp = this.Find_BS_gr(); + if (temp[0] > 2) + { + return this.Find_BS_wgr()[1]; + } + else + { + if (temp[1][0] == 0) + { + return this.Find_BS_wgr()[1]; + } + else + { + return temp[1]; + } + + + } + }; + //找到共边最少的板的集合 订算有关系的板 + Find_BS_gr = (): [number, number[]] => + { + let bs: number = 6; + let id: number[] = []; + let tempsl: number[]; + if (this.xbang.length == 1) + { + return [0, [0]]; + } + else + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].isgr == false && this.xbang[i].ishb == false && this.xbang[i].isqg == false) + { + tempsl = this.JSgbsl(this.xbang[i].bangid); + if (bs == tempsl[0]) + { + id.push(this.xbang[i].bangid); + } + else if (bs > tempsl[0]) + { + bs = tempsl[0]; + id = []; + id.push(this.xbang[i].bangid); + } + + } + } + if (id.length > 0) + { return [bs, id]; } + else + { + //console.log("没找到最少边"); + return [0, [0]]; + } + + } + }; + + //找到共边最少的板的集合 订算没有算关系的板 + Find_BS_wgr = (): [number, number[]] => + { + let bs: number = 6; + let id: number[] = []; + let tempsl: number[]; + if (this.xbang.length == 1) + { + return [0, [0]]; + } + else + { + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].ishb == false && this.xbang[i].isqg == false) + { + tempsl = this.JSgbsl(this.xbang[i].bangid); + if (bs == tempsl[0]) + { + id.push(this.xbang[i].bangid); + } + else if (bs > tempsl[0]) + { + bs = tempsl[0]; + id = []; + id.push(this.xbang[i].bangid); + } + } + } + if (id.length > 0) { return [bs, id]; } else { return [0, [0]]; } + + } + }; + + + //计算这块板的与其它板的相交的边数,及每条边数跟权重 0数量 1左 2右 3上 4下 5权重 + JSgbsl = (bangid: number): [number, number, number, number, number, number] => + { + let left = 0, right = 0, top = 0, down = 0, qz = 0; + let bangIndex = this.getID(bangid); + for (let j = 0; j < this.xbang.length; j++) + { + if (bangIndex != j && this.xbang[j].isqg == false) + { + let jjy = this.Jcxj(this.xbang[bangIndex].y, this.xbang[bangIndex].pbg, this.xbang[j].y, this.xbang[j].pbg); + let jjx = this.Jcxj(this.xbang[bangIndex].x, this.xbang[bangIndex].pbk, this.xbang[j].x, this.xbang[j].pbk); + if (this.xbang[bangIndex].pbg > this.xbang[bangIndex].pbk) + { + if (jjy < this.xbang[bangIndex].pbk && jjy < 50) { jjy = 0; } + } + if (this.xbang[bangIndex].pbk > this.xbang[bangIndex].pbg) + { + if (jjx < this.xbang[bangIndex].pbg && jjx < 50) { jjx = 0; } + } + if (jjy > 0 && this.xbang[bangIndex].x > this.xbang[j].x + this.xbang[j].pbk) { left = 1; } + if (jjy > 0 && this.xbang[j].x > this.xbang[bangIndex].x + this.xbang[bangIndex].pbk) { right = 1; } + if (jjx > 0 && this.xbang[bangIndex].y > this.xbang[j].y + this.xbang[j].pbg) { down = 1; } + if (jjx > 0 && this.xbang[j].y > this.xbang[bangIndex].y + this.xbang[bangIndex].pbg) { top = 1; } + if ((left == 1 && right == 1) || (down == 1 && top == 1)) { qz = 1; } + } + } + return [left + right + top + down + qz, left, right, top, down, qz]; + }; + Jcsb = (bangid1: number, bangid2: number): boolean => // 把bang1 要去掉, bang2 是否少边 返加True 有少边 False 没有少边 + { + let xj = this.XJcd(bangid1, bangid2); + let jjx = xj[0]; let jjy = xj[1]; + + + if (jjx > 0 && this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbg) + { + return true; + } + else if (jjx > 0 && jjx > 0 && this.xbang[this.getID(bangid1)].pbk * this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbk * this.xbang[this.getID(bangid2)].pbg) + { + return false; + } + else if (jjy > 0 && this.xbang[this.getID(bangid1)].pbg < this.xbang[this.getID(bangid2)].pbg) + { + return false; + } + else if (jjy > 0 && this.xbang[this.getID(bangid1)].pbg > this.xbang[this.getID(bangid2)].pbg) + { + return true; + } + // else if (jjy > 0 && this.xbang[this.getID(bangid2)].pbk > this.xbang[this.getID(bangid2)].pbg + // && this.xbang[this.getID(bangid2)].pbk * this.xbang[this.getID(bangid2)].pbg < this.xbang[this.getID(bangid1)].pbk * this.xbang[this.getID(bangid1)].pbg) + // { + // return true; + // } + else + { + return false; + } + + + + // if (bangid1 == 2 && bangid2 == 10) + // { + // console.log(bangid1); + // } + // let bs1 = this.JSgbsl(bangid1)[0]; + // let bs2 = this.JSgbsl(bangid2)[0]; + // let sb2 = this.is_sb(bangid1, bangid2)[0]; + // let sb1 = this.is_sb(bangid2, bangid1)[0]; + + // if (sb2 < bs1 && sb1 < bs2) + // { + // return true; + // } + // else + // { + // return false; + // } + + }; + + //检测所有会让其它板少条且最小的的板 + JS_sb_minmz = (): number => + { + let Bangidzh = this.Find_BS(); + if (Bangidzh[0] != 0) + { + let tempbang: YH_bang[] = []; + for (let m = 0; m < Bangidzh.length; m++) + { + tempbang.push(this.xbang[this.getID(Bangidzh[m])]); + } + if (tempbang.length > 0) + { + tempbang.sort((a, b) => a.pbg * a.pbk - b.pbg * b.pbk); + return tempbang[0].bangid; + } + else if (tempbang.length == 1) + { + return tempbang[0].bangid; + } + else + { + return -1; + } + } + else + { + return -1; + } + }; + //合并所有的板 + HB_b = () => + { + let jshb_lr: boolean = true; + let jshb_td: boolean = true; + let jshb: boolean; + for (let i = 0; i < this.xbang.length; i++) + { + if (this.xbang[i].ishb == false) { jshb = true; } else { jshb = false; } + while (jshb == true) + { + if (this.xbang[i].pbg > this.xbang[i].pbk && this.xbang[i].pbk > 100) //左右 + { + jshb_lr = this.HB_LR(this.xbang[i].bangid, false); + } + else + { + jshb_lr = false; + } + + if ((this.xbang[i].pbg < this.xbang[i].pbk && this.xbang[i].pbg > 100)) //上下合并 + { + jshb_td = this.HB_TD(this.xbang[i].bangid, false); + } + else + { + jshb_td = false; + } + if (jshb_lr == true || jshb_td == true) + { + jshb = true; + } + else if (jshb_lr == false || jshb_td == false) + { + if ((this.xbang[i].pbg > 100 && this.xbang[i].pbk > 100)) //上下合并 + { + jshb_lr = this.HB_LR(this.xbang[i].bangid, false); + jshb_td = this.HB_TD(this.xbang[i].bangid, false); + if (jshb_lr == true || jshb_td == true) { jshb = true; } else { jshb = false; } + } + else + { + jshb = false; + } + } + else + { + jshb = true; + } + } + if (this.HB_bang.length > 0) + { + this.HB.push(this.HB_bang); + this.HB_bang = []; + } + } + }; + + //合并指定的板 第一步 + HB_Max_bang = (Bangid: number) => + { + if (Bangid > 0) + { + let jshb_lr: boolean = true; + let jshb_td: boolean = true; + let jshb: boolean; + let bangid = this.getID(Bangid); + if (this.xbang[bangid].ishb == false) { jshb = true; } else { jshb = false; } + while (jshb == true) + { + if (this.xbang[bangid].pbg > this.xbang[bangid].pbk && this.xbang[bangid].pbk > 100) //左右 + { + jshb_lr = this.HB_LR(this.xbang[bangid].bangid, false); + } + else + { + jshb_lr = false; + } + + if ((this.xbang[bangid].pbg < this.xbang[bangid].pbk && this.xbang[bangid].pbg > 100)) //上下合并 + { + jshb_td = this.HB_TD(this.xbang[bangid].bangid, false); + } + else + { + jshb_td = false; + } + if (jshb_lr == true || jshb_td == true) + { + jshb = true; + } + else if (jshb_lr == false || jshb_td == false) + { + if ((this.xbang[bangid].pbg > 100 && this.xbang[bangid].pbk > 100)) //上下合并 + { + jshb_lr = this.HB_LR(this.xbang[bangid].bangid, false); + jshb_td = this.HB_TD(this.xbang[bangid].bangid, false); + if (jshb_lr == true || jshb_td == true) { jshb = true; } else { jshb = false; } + } + else + { + jshb = false; + } + } + else + { + jshb = true; + } + } + if (this.HB_bang.length > 0) + { + this.HB.push(this.HB_bang); + this.HB_bang = []; + } + else + { + this.HB.push([Bangid]); + this.xbang[bangid].ishb = true; + } + } + }; + + + //合并上面的大板 cd为共边长度 + HB_top = (bangid: number, cd: number) => + { + let hbbang: number[] = []; + let fid: number = bangid; + while (fid > -1) + { + fid = this.F_Top_Big(fid, cd); + if (fid > -1) + { + this.xbang[this.getID(fid)].ishb = true; + hbbang.push(fid); + //this.printstr(fid, 1, "大合", -30, 80, 50); + } + } + if (hbbang.length > 0) { this.HB.push(hbbang); } + }; + //合并下面的大板 cd为共边长度 + HB_down = (bangid: number, cd: number) => + { + let hbbang: number[] = []; + let fid: number = bangid; + while (fid > -1) + { + fid = this.F_Down_Big(fid, cd); + if (fid > -1) + { + this.xbang[this.getID(fid)].ishb = true; + hbbang.push(fid); + // this.printstr(fid, 1, "大合", 10, 80, 50); + } + } + if (hbbang.length > 0) { this.HB.push(hbbang); } + }; + + //寻找最后一块要切割的板 + F_last_mz = (jlID: number): number => + { + let bangid: number; + let temp: number[]; + let index: number = 1; + let lastId: number; + if (jlID == 0) { lastId = this.F_minMZ(); } else { lastId = jlID; } + while (true) + { + if (lastId == -1) { break; } + bangid = this.JS_sb_Bxl(lastId); //检测所有会让其它板少条且有两块或者多块边相邻的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.JS_sb_minmz(); //检测所有会让其它板少条且最小的的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + if (lastId == -1) { break; } + bangid = this.F_minJL(lastId); + if (bangid > -1) { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.F_minMZ(); //找到面积最小的板 + this.add_last(bangid, index); + index++; + lastId = bangid; + } + } + } + // bangid = this.F_minJL(bangid); + //if (bangid > -1) { this.add_last(bangid, index); index++; } + + temp = this.Find_BS(); + + if (temp[0] == 0) + { + break; + } + } + return index; + + }; + + + //寻找最后一块要切割的板 + F_last_jl = (jlID: number): number => + { + let bangid: number; + let temp: number[]; + let index: number = 1; + let lastId: number; + if (jlID == 0) { lastId = this.F_minMZ(); } else { lastId = jlID; } + let tempend: boolean = false; + while (true) + { + if (lastId == -1) { break; } + bangid = this.JS_sb_Bxl(lastId); //检测所有会让其它板少条且有两块或者多块边相邻的板 + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + if (lastId == -1) { break; } + bangid = this.F_minJL(lastId); + if (bangid > -1) + { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + //bangid = this.F_minMZ(); //找到面积最小的板 + // if (lastId == -1) { break; } + // bangid = this.F_minJL(lastId); + bangid = this.JS_sb_minmz(); //检测所有会让其它板少条且最小的的板 + + if (bangid > -1) { this.add_last(bangid, index); index++; lastId = bangid; } + else + { + bangid = this.F_minMZ(); //找到面积最小的板 + this.add_last(bangid, index); + index++; + lastId = bangid; + } + } + } + // bangid = this.F_minJL(bangid); + //if (bangid > -1) { this.add_last(bangid, index); index++; } + + temp = this.Find_BS(); + + if (temp[0] == 0) + { + break; + } + + } + return index; + + }; + + //打印并添加最后一块 + add_last = (bangid: number, index: number) => + { + let tempx: number; + //if (index > 9) { tempx = 27; } else { tempx = 10; } 这两行用于测试打印 + //this.printstr(bangid, 1, index.toString(), this.xbang[this.getID(bangid)].pbk / 2 - tempx, this.xbang[this.getID(bangid)].pbg / 2 - 20, 50); + this.SCid.push(bangid); + this.xbang[this.getID(bangid)].isqg = true; + let tempid = this.xbang[this.getID(bangid)].grid; + if (this.xbang[this.getID(bangid)].grid > -1) + { + let tempin = this.xbang[this.getID(tempid)].gr.findIndex((n) => n == bangid); + if (tempin > -1) + { + this.xbang[this.getID(tempid)].gr.splice(tempin, 1); + if (this.xbang[this.getID(tempid)].gr.length == 0) { this.xbang[this.getID(tempid)].isgr = false; } + } + } + }; + + //找到面积最小的板 + F_minMZ = (): number => + { + let jhb = this.Find_BS(); + if (jhb[0] != 0) + { + let tempBang: YH_bang[] = []; + if (jhb.length == 1) + { + return jhb[0]; + } + else + { + for (let i = 0; i < jhb.length; i++) + { + tempBang.push(this.xbang[this.getID(jhb[i])]); + } + tempBang = tempBang.sort((a, b) => + { + if (a.pbg * a.pbk == b.pbg * b.pbk) + { + return Math.hypot(this.B_k - a.x - a.pbk / 2, this.B_g - a.y - a.pbg / 2) - Math.hypot(this.B_k - b.x - b.pbk / 2, this.B_g - b.y - b.pbg / 2); + } + else + { + return a.pbg * a.pbk - b.pbg * b.pbk ? -1 : 1; + } + }); + + return tempBang[0].bangid; + } + } + else + { + return -1; + } + }; + + //按位置最近排序 + F_minJL = (bangid: number): number => + { + let jhb = this.Find_BS(); + if (jhb.length[0] != 0) + { + let tempBang: YH_bang[] = []; + if (jhb.length > 0) + { + for (let i = 0; i < jhb.length; i++) + { + tempBang.push(this.xbang[this.getID(jhb[i])]); + } + let wzx = this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk / 2; + let wzy = this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg / 2; + tempBang.sort((a, b) => a.pbk * a.pbg - b.pbk * b.pbg); + if (tempBang[tempBang.length - 1].pbg * tempBang[tempBang.length - 1].pbk / 1000000 - tempBang[0].pbg * tempBang[0].pbk / 1000000 > 0.2) + { + return tempBang[0].bangid; + } + else + { + tempBang.sort((a, b) => Math.hypot(wzx - a.x - a.pbk / 2, wzy - a.y - a.pbg / 2) - Math.hypot(wzx - b.x - b.pbk / 2, wzy - b.y - b.pbg / 2)); + return tempBang[0].bangid; + } + } + else if (jhb.length == 1) + { + return jhb[0]; + } + else + { + return -1; + } + } + else + { + return -1; + } + }; + //检测所有会让其它板少条且有两块或者多块边相邻的板 + JS_sb_Bxl = (bangid: number): number => + { + let Bangidzh = this.Find_BS(); + if (Bangidzh[0] != 0) + { + let tempbang: YH_bang[] = []; + let tempxj: YH_bang[] = []; + let temp: YH_bang; + for (let m = 0; m < Bangidzh.length; m++) + { + for (let n = 0; n < Bangidzh.length; n++) + { + if (m != n) + { + if (this.Jcsb(Bangidzh[m], Bangidzh[n]) == true) + { + tempbang.push(this.xbang[this.getID(Bangidzh[m])]); + } + } + } + } + if (tempbang.length == 1) + { + return tempbang[0].bangid; + } + else if (tempbang.length == 0) + { + return -1; + } + else //(tempbang.length > 1); + { + let wzx = this.xbang[this.getID(bangid)].x + this.xbang[this.getID(bangid)].pbk / 2; + let wzy = this.xbang[this.getID(bangid)].y + this.xbang[this.getID(bangid)].pbg / 2; + tempbang.sort((a, b) => Math.hypot(wzx - a.x - a.pbk / 2, wzy - a.y - a.pbg / 2) - Math.hypot(wzx - b.x - b.pbk / 2, wzy - b.y - b.pbg / 2)); + return tempbang[0].bangid; + + } + } + else + { + return -1; + } + + }; + + //用于计算下刀顺序 + XDscjs = () => + { + let bangid = this.MaxMZ(0.05); + this.HB_Max_bang(bangid); + this.HB_top(bangid, 200); + this.HB_down(bangid, 200); + this.F_GL_LR(); + this.F_GL_TD(); + this.Is_big_gr(); + this.F_GL_JJB(); + + let index: number; + + if (this.jl_mz == 1) + { + index = this.F_last_jl(0); + } else + { + index = this.F_last_mz(0); + } + this.printHBCS(index); + + + }; + printHBCS = (index: number) => + { + for (let m = this.HB.length - 1; m >= 0; m--) + { + for (let n = this.HB[m].length - 1; n >= 0; n--) + { + //this.printstr(this.HB[m][n], 1, index.toString(), this.Bakbang[this.getID_Bkb(this.HB[m][n])].pbk / 2, this.Bakbang[this.getID_Bkb(this.HB[m][n])].pbg / 2, 50); + this.SCid.push(this.HB[m][n]); + index++; + }; + } + }; +}; + +function equaln(v1: number, v2: number, fuzz = 1e-5) +{ + return Math.abs(v1 - v2) <= fuzz; +} diff --git a/tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts b/tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts new file mode 100644 index 0000000..f29af02 --- /dev/null +++ b/tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts @@ -0,0 +1,400 @@ +import { ProcessorBase, ProcessorContext, ResCodeType, resultInfo } from "../base"; +import { init2VModel, resetModelContour, resetModelKnife } from "../common/LayoutEngine/BlockDataPlus"; +import { getDoFace } from "../common/LayoutEngine/BlockDoFace"; +import { BlockPlus } from "../common/LayoutEngine/BlockPlus"; +import { PlaceBlock, PlaceBlockDetail, PlaceMaterial } from "../confClass"; +import { ConfigBase } from "../models/config"; +import { Big_bang, ComposingType, LineType, WorkerItemType, xbang } from "./RectOptimizeWorker/bang"; +/** 优化排版 */ +export class OptimizeLayoutProcessor extends ProcessorBase { + public get name(): string { + return "OptimizeLayoutProcessor_优化排版"; + } + + public get version(): string { + return "1.0.0"; + } + + private _output?: Function; + private _source: any; + private _params: ConfigBase = new ConfigBase(); + /** 多线程数据集 */ + private workerList: WorkerItemType[] = [] + constructor() { + super(); + this._params.name = "优化排版配置"; + this._params.version = "1.0.0"; + /** 初始化配置 这里 如果不初始化 load 的时候 不会加载上 */ + /**板最小宽度 */ + this._params.minBlockWidth = 5; + /** 板最小厚度 */ + this._params.minBlockThickness = 2; + /** 排版方式(0按机台尺寸排版 1按板材规格排版) */ + this._params.placeStyle = 1; + /** 机台长度 */ + this._params.boardLength = 2440; + /** 机台宽度 */ + this._params.boardWidth = 1220; + /** 刀路间距 --板件开料轮廓的间距 */ + this._params.blockKnifeLineSpacing = 0 + /** 排版跟随大板定位,小板需要反面优化 */ + this._params.isTurnFaceToPlace = false + /** 双面加工板件优先集中排版AllowDoubleHoleFirstSort */ + this._params.isDoubleFaceBlockFirst = true; + /** 是否是矩形优化? */ + this._params.isRectPlace = true + /** 余料板允许排入双面加工的小板yuLiaoBoardDo2FaceBlock */ + this._params.isDoubleFaceBlockInRemain = true; + } + public async exec(context: ProcessorContext): Promise { + // 输入数据 + this._source = context.input + // 输出函数 + this._output = context.output; + // 加载配置 + // this._params = context.params; + this._params?.load(context.params); + + // this._output?.('初始化完成,开始进行优化!') + this.optimizeLayout() + + } + /** 开始优化主程序 */ + private async optimizeLayout() { + + + let { materialList, blockList, blockDetailList, toolsHelper } = this._source + + if (!Array.isArray(toolsHelper.knifeList) || toolsHelper.knifeList.length == 0) { + console.error('optimizeLayout: 刀库位置异常,请先确认刀库配置!') + return + } + + let checkRes = this.checkPlaceBefore(materialList, blockList) + + if (checkRes.length > 0) { + let res: resultInfo = { + code: ResCodeType.ERROR, + message: '优化前检测未通过', + data: checkRes + }; + this.callBack(res); + } else { + let _blockList: PlaceBlock[] = []; + let _blockDetailList: PlaceBlockDetail[] = []; + for (const b of blockList) { + let block = new PlaceBlock(b); + _blockList.push(block); + } + + for (const bd of blockDetailList) { + + let blockDetail = new PlaceBlockDetail(bd); + await resetModelContour(blockDetail); + await init2VModel(blockDetail, toolsHelper, false); + + _blockDetailList.push(blockDetail); + + let i = blockList.findIndex(e => e.blockId == blockDetail.blockId); + _blockList[i].blockDetail = blockDetail; + + } + + for (const block of _blockList) { + let pm = materialList.find(e => e.goodsId == block.goodsId); + + let knife = toolsHelper.getKnifeByParams({ length: pm?.thickness }); + + pm.diameter = knife?.diameter; + pm.cutKnifeGap = this._params.blockKnifeLineSpacing || 0.01; + + await BlockPlus.initBlock(block, pm, this._params) + await BlockPlus.getBorder(block, false, block.placeStyle) + // await blockBoardClass.getBorder(block); + await resetModelKnife(block, toolsHelper) + } + this._source.blockList = _blockList + materialList.forEach(m => { + let pm = new PlaceMaterial(m); + this.startPlaceThreed(pm) + }); + + } + + + + } + /**开始优化线程 输入的数据没有初始化 */ + startPlaceThreed(material: PlaceMaterial) { + const { materialList, blockList, blockDetailList, toolsHelper } = this._source + const { processMode = 0, cutBoardBorder = 3, blockKnifeLineSpacing = 0 } = this._params + let bList: any = [] + + + let pm: PlaceMaterial = new PlaceMaterial(material) + + 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 = getDoFace(bList[e.blockNo], processMode) + + pm.blockList.push(e) + } + }) + pm.cutBorder = cutBoardBorder + pm.cutKnifeGap = blockKnifeLineSpacing + let knife = toolsHelper.getKnifeByParams({ length: pm.thickness }) + if (!knife) { + console.log('没有开料刀') + return '没有开料刀' + } + pm.diameter = knife?.diameter + pm.cutKnifeName = knife.knifeName + /** 小板 */ + let bans: xbang[] = [] + + // 实际开料大板的列表 + let big_Bang: Big_bang[] = [] + let big_BangSL: number[] = [] + + + let border = cutBoardBorder + let borderOff = (pm.diameter + pm.cutKnifeGap) / 2 + + // 余料板 以及实际开料大板 + for (const cuttingBoard of material.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.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._params.isDoubleFaceBlockFirst // 双面加工排前面 + let gap = this._params.blockKnifeLineSpacing + // this.bestfit = 0 + + for (let j = 0; j < 1; j++) { + + let w = new Worker(new URL('./RectOptimizeWorker/RectOptimizeWorkerWorker.ts', import.meta.url), { type: 'module' }) + // this.optimizeEngines.push(w) + const data = { + type: 'start', + data: [bans, big_Bang, big_BangSL, xyhcs, isDoubleFaceBlockFirst, gap, this._params.isRectPlace, this._params.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.callBack(resObj) + break; + case 'stop': + console.log('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) + } + } + + private callBack(data: any) { + this._output?.(data); + } + /** 关闭线程 */ + async terminateWorker(params) { + const { key, goodsId } = params + let data = { + type: 'stop', + msg: 'byKey' + } + if (key) { + const worker = this.workerList[key] + + await worker.w?.postMessage(data) + worker.w?.terminate() + this.workerList[key].status = 'stop' + } + + if (goodsId) { + let i = this.workerList.findIndex(e => e.goodsId == goodsId) + if (i != -1) { + data.msg = 'byGoodsId' + const worker = this.workerList[i] + + await worker.w?.postMessage(data) + worker.w?.terminate() + this.workerList[i].status = 'stop' + } + } + } + + async terminateWorkerAll() { + let data = { + type: 'stop', + msg: 'allStop' + } + + for (const i in this.workerList) { + let worker = this.workerList[i] + await worker.w?.postMessage(data) + worker.w?.terminate() + this.workerList[i].status = 'stop' + } + } + + /** 优化前检测 */ + checkPlaceBefore(materialList, blockList) { + const arr = this.checkMetrial(materialList) + const arr1 = this.checkBlocks(materialList, blockList) + let errMsg = '' + if (arr.length > 0 || arr1.length > 0) { + + errMsg = errMsg + (arr.length > 0 ? '该订单内大板有异常' : '') + errMsg = errMsg + (arr1.length > 0 ? '该订单内小板有异常' : '') + + } + + return errMsg + } + /** 检查是否 板材尺寸大于机台尺寸 */ + checkMetrial(materialList): any[] { + + let errPMs: any = [] + + if (this._params.placeStyle !== 1) { + for (let pm of materialList) { + if (pm.orgWidth > this._params.boardWidth || pm.orgLength > this._params.boardLength) { + + errPMs.push(pm) + } + } + } + return errPMs + } + /** + * 正纹 = 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._params.isTurnFaceToPlace) + turnF = !turnF + if (!turnF) + return ComposingType.Positive + return ComposingType.Reverse + } + /** 检查异常板 */ + checkBlocks(materialList, blockList): any[] { + + let allBlocks: any[] = [] + //**20250327 li: 现有的需求情况下 这里去验证异常板 不合理 大板有 没有尺寸的情况 二优化尺寸 根据 优化的弹窗去选择尺寸 做优化 */ + /** 有个能参与优化小板的最小尺寸限制 在这里做 */ + for (let pm of materialList) { + let bList = blockList.filter(e => e.goodsId == pm.goodsId) + for (const pb of bList) { + if (pb.width < this._params.minBlockWidth) { + allBlocks.push(pb) + } + if (pb.thickness < this._params.minBlockThickness) { + allBlocks.push(pb) + } + } + } + return allBlocks + } + + + + +} \ No newline at end of file diff --git a/tests/dev1/dataHandle/optimizeLayout/优化排版.md b/tests/dev1/dataHandle/optimizeLayout/优化排版.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/dev1/dataHandle/tools/tool.ts b/tests/dev1/dataHandle/tools/tool.ts new file mode 100644 index 0000000..a5cddac --- /dev/null +++ b/tests/dev1/dataHandle/tools/tool.ts @@ -0,0 +1,253 @@ +import { ProcessorBase, ProcessorContext } from "../base"; +import { ConfigBase } from "../models/config"; + + + +/** 刀具工具类 + * 通过上下文的形式使用 +*/ +export class ToolsHelper extends ProcessorBase { + public get name(): string { + return "刀库模块"; + } + + public get version(): string { + return "1.0.0"; + } + + + private _params: ConfigBase = new ConfigBase(); + + constructor() { + super(); + this._params.name = "刀库配置"; + this._params.version = "1.0.0"; + this._params.knifeList = []; + + } + + public exec(context: ProcessorContext) { + + this._params.knifeList = context.input + context.output = { + knifeList: context.input, + getKnifeByParams: this.getKnifeByParams, + KnifeIsInKnifeList: this.KnifeIsInKnifeList, + _params: context.params + }; + // context.output?.(res); + return context.output; + } + /** + * + * @param params 查询条件 + * @param knifeList 刀具列表 + * @returns 返回符合条件的刀具 + */ + getKnifeByParams(params: _knifeType, k_List: Knife[] = []) { + let knife: Knife | null = null + + if (params) { + + let _List = [...this._params.knifeList, ...k_List] + + let tempKnifeList: Knife[] = [..._List] // [] + 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 = _List.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 + } + /** + * + * @param _knife 刀具 + * @param _knifeList 刀具列表 + * @returns 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 + } + } +} + +/** 刀类型: MILLING_CUTTER铣刀 FORMING_CUTTER成型刀 DRILL钻头 SAW锯 BLADE刀片 */ +export enum KnifeType { + /** 铣刀 */ + MILLING_CUTTER = 1, + /** 成型刀 */ + FORMING_CUTTER = 2, + /** 钻头 */ + DRILL = 3, + /** 锯 */ + SAW = 4, + /** 刀片 */ + BLADE = 5 +} + +export class Knife { + /** 是否启用 */ + isEnabled = true; + /** 轴号 */ + axleId = 0; + /** 开料刀ID号 */ + knifeId = 1; + /** 加工面(0正面 1反面 2左侧面 3右侧面 4上侧面 5下侧面 6任意) */ + processFace = 0; + /** 刀名称 */ + knifeName = ''; + /** 刀具类型(1铣刀 2成型刀 3钻头 4锯 5刀片) */ + knifeType = KnifeType.MILLING_CUTTER; + /** 功能属性(数组): 1开料/切割 2拉槽 3铣型 4铣孔 5钻孔 6拉米诺 7乐扣 8T型 */ + ability: any = []; + + // /** 默认开料刀 */ + // isDefaultCutKnife = false; + // /** 是否可用于开料切割 */ + // isCuttingKnife = false; + // /** 是否允许铣孔(knifeType为铣刀生效) */ + // isMillingAllowed = false; + /** 刀直径mm */ + diameter = 6; + /** 刀长(最大深度) */ + length = 20; + /** 锯齿厚度,单次加工切缝宽度 */ + sawThiness = 0; + /**锯向: 0横向(或长边) 1纵向(短边) 2自由角度*/ + sawDirection = 0; + /**切向加工方向: 0横向左往右 2横向右往左 3纵向上往下 4纵向下往上 5随意 */ + processDirection = 0; + /**进给速度, 0取系统默认 */ + speed = 0; + /** 进给深度 */ + stepDepth = 0; + // /** 组号 */ + // groupNo = 0; + // /** 主刀 */ + // isMainKnife = false; + // /** 是否高级加工 */ + // isAdvancedProcessEnabled = false; + // /** 是否集合加工 */ + // isBatchProcessEnabled = false; + // /** 副刀偏置长边偏移(Y轴) */ + // auxKnifeOffsetY = 0; + // /** 副刀偏置短边偏移(X轴) */ + // auxKnifeOffsetX = 0; + /** 偏置短边偏移(X轴)-相对于工件原点位置XY坐标的短边方向偏移量 */ + offsetX = 0; + /** 偏置长边偏移(Y轴) -相对于工件原点位置XY坐标的长边方向偏移量*/ + offsetY = 0; + /** 偏置垂直偏移(Z轴) -相对于工件原点位置XY坐标的垂直方向偏移量*/ + offsetZ = 0; + /** 基准坐标-短边(X轴) -钻头在钻包中短边方向(纵向)的相对位置 */ + baseX = 0; + /** 基准坐标-长边(Y轴) -钻头在钻包中长边方向(横向)的相对位置 */ + baseY = 0; + /**组合钻 */ + isModularDrill = false; + /** 是否预启动 */ + isPreStartEnabled = false; + /** 预启动提前动作数 */ + preStartAheadActionCount = 5; + /** 预启动延迟换刀 */ + isPreStartToolChangeDelay = false; + /** 预启动延迟换刀指令 */ + preStartToolChangeDelayCode = ''; + /** 轴启动指令后置 */ + isAxisStartCodePostpost = false; + /** 轴停止指令前置 */ + isAxisStopCodePrepose = false; + /** 钻组独立指令(启用后,刀起始指令、刀结束指令使用钻组起始指令、钻组结束指令替换) */ + drillGroupCode = ''; + + /** 轴启动代码*/ + axisStartCode = ""; + /** 刀启动代码 */ + knifeStartCode = ''; + /** 钻组起始指令 */ + drillGroupStartCode = ''; + /** 钻组结束指令 */ + drillGroupEndCode = ''; + /** 刀停止代码 */ + knifeStopCode = ''; + /** 轴停止指令 */ + axisStopCode = ''; + // /** 高级加工指令 */ + // advancedCode = ''; + /** 开料刀 */ + isCuttingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.CUT); + } + /** 造型刀 */ + isModelingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.MILLING_MODEL); + } + /** 钻刀 */ + isDrillingKnife() { + return this.isEnabled && this.ability.includes(AbilityType.DRILL_HOLE); + } + /** 铣孔 */ + isCutting4HoleKnife() { + return this.isEnabled && this.ability.includes(AbilityType.MILLING_HOLE); + } + /** 设置刀具(轴号, 刀名, 刀直径, 是否主刀, 是否铣孔, 是否开料刀) */ + // set(axleId: number, name: string, diameter: number, isMainKnife: boolean, isMillingAllowed: boolean, isCuttingKnife: boolean) + /** 设置刀具(轴号, 刀名, 刀类型, 刀直径, 刀长, 是否默认刀) */ +} +export type _knifeType = Partial + +/** 刀功能: + ** 1-CUT开料/切割 2-PULLING_GROOVE拉槽 3-MILLING_MODEL铣型 4-MILLING_HOLE铣孔 + ** 5-DRILL_HOLE钻孔 6-RAMINO拉米诺 7-EASY_FASTEN乐扣 8-T_TYPE T型 */ +export enum AbilityType { + /** 1开料/切割 */ + CUT = 1, + /** 2拉槽 */ + PULLING_GROOVE = 2, + /** 3铣型 */ + MILLING_MODEL = 3, + /** 4铣孔 */ + MILLING_HOLE = 4, + /** 5钻孔 */ + DRILL_HOLE = 5, + /** 6拉米诺 */ + RAMINO = 6, + /** 7乐扣 */ + EASY_FASTEN = 7, + /** 8T型 */ + T_TYPE = 8 +} \ No newline at end of file diff --git a/tests/dev1/dataHandle/tools/刀库.md b/tests/dev1/dataHandle/tools/刀库.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/dev1/index3.vue b/tests/dev1/index3.vue new file mode 100644 index 0000000..db178ad --- /dev/null +++ b/tests/dev1/index3.vue @@ -0,0 +1,1513 @@ + + + + +