feat:提交
This commit is contained in:
		
							
								
								
									
										392
									
								
								tests/dev1/dataHandle/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								tests/dev1/dataHandle/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,392 @@
 | 
			
		||||
export class List<Item> extends Array<Item>
 | 
			
		||||
{
 | 
			
		||||
  /** 返回符合条件的第一个元素 */
 | 
			
		||||
  first(fn: (item: Item) => boolean): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    for (const item of this)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        return item
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的最后一元素 */
 | 
			
		||||
  last(fn: (t: Item) => boolean): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    for (let i = this.length - 1; i >= 0; i--)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(this[i]))
 | 
			
		||||
        return this[i]
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最大值 */
 | 
			
		||||
  max<T>(fn: (item: Item) => T): T
 | 
			
		||||
  {
 | 
			
		||||
    let maxV: T
 | 
			
		||||
    for (const i of this)
 | 
			
		||||
    {
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (maxV == null)
 | 
			
		||||
      {
 | 
			
		||||
        maxV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v > maxV)
 | 
			
		||||
        {
 | 
			
		||||
          maxV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return maxV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最小值 */
 | 
			
		||||
  min<T>(fn: (item: Item) => T): T
 | 
			
		||||
  {
 | 
			
		||||
    let minV: T
 | 
			
		||||
    for (const i of this)
 | 
			
		||||
    {
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (minV == null)
 | 
			
		||||
      {
 | 
			
		||||
        minV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v < minV)
 | 
			
		||||
        {
 | 
			
		||||
          minV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return minV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 累加 */
 | 
			
		||||
  sum(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    let v: number = 0
 | 
			
		||||
    for (const t of this)
 | 
			
		||||
    {
 | 
			
		||||
      v = v + fn(t)
 | 
			
		||||
    }
 | 
			
		||||
    return v
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 平均值 */
 | 
			
		||||
  avg(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let sum = this.sum(fn)
 | 
			
		||||
    return sum / this.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 满足条件的元素数量 */
 | 
			
		||||
  count(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let c = 0
 | 
			
		||||
    for (const item of this)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        c = c + 1
 | 
			
		||||
    }
 | 
			
		||||
    return c
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FindMax<T>(fn: (item: Item) => T): Item
 | 
			
		||||
  {
 | 
			
		||||
    return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ArrayExt
 | 
			
		||||
{
 | 
			
		||||
  /** 返回满足条件的元素数量 */
 | 
			
		||||
  static count<Item>(list: Item[], fn: (item: Item) => boolean): number
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let c = 0
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        c = c + 1
 | 
			
		||||
    }
 | 
			
		||||
    return c
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 移除 */
 | 
			
		||||
  static remove<Item>(list: Item[], obj: Item)
 | 
			
		||||
  {
 | 
			
		||||
    let index = list.findIndex(t => t == obj)
 | 
			
		||||
    if (index == -1)
 | 
			
		||||
      return
 | 
			
		||||
    list.splice(index, 1)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的第一个元素 */
 | 
			
		||||
  static first<Item>(list: Item[], fn: (item: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    if (orderFn1 == null)
 | 
			
		||||
    {
 | 
			
		||||
      for (const item of list)
 | 
			
		||||
      {
 | 
			
		||||
        if (fn(item))
 | 
			
		||||
          return item
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let minValue1: number
 | 
			
		||||
    let minValue2: number
 | 
			
		||||
    let minItem: Item
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v1 = orderFn1(item)
 | 
			
		||||
      let v2 = orderFn2 != null ? orderFn2(item) : 0
 | 
			
		||||
      if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2))
 | 
			
		||||
      {
 | 
			
		||||
        minValue1 = v1
 | 
			
		||||
        minValue2 = v2
 | 
			
		||||
        minItem = item
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return minItem
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的最后一元素 */
 | 
			
		||||
  static last<Item>(list: Item[], fn: (t: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    if (orderFn1 == null)
 | 
			
		||||
    {
 | 
			
		||||
      for (let i = list.length - 1; i >= 0; i--)
 | 
			
		||||
      {
 | 
			
		||||
        if (fn(list[i]))
 | 
			
		||||
          return list[i]
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    let maxValue1: number
 | 
			
		||||
    let maxValue2: number
 | 
			
		||||
    let maxItem: Item
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v1 = orderFn1(item)
 | 
			
		||||
      let v2 = orderFn2 ? orderFn2(item) : 0
 | 
			
		||||
      if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2))
 | 
			
		||||
      {
 | 
			
		||||
        maxValue1 = v1
 | 
			
		||||
        maxValue2 = v2
 | 
			
		||||
        maxItem = item
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return maxItem
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 取最大值 */
 | 
			
		||||
  static max<T1, T2>(list: T1[], fn: (item: T1) => T2, whereF: (item: T1) => boolean = null, defaultV: T2 = null): T2
 | 
			
		||||
  {
 | 
			
		||||
    let maxV: T2
 | 
			
		||||
    for (const i of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (whereF && whereF(i) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (maxV == undefined)
 | 
			
		||||
      {
 | 
			
		||||
        maxV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v > maxV)
 | 
			
		||||
        {
 | 
			
		||||
          maxV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (maxV != undefined)
 | 
			
		||||
      return maxV
 | 
			
		||||
    return defaultV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最小值 */
 | 
			
		||||
  static min<Item, T>(list: Item[], fn: (item: Item) => T, whereF: (item: Item) => boolean = null, defaultV: T = null): T
 | 
			
		||||
  {
 | 
			
		||||
    let minV: T
 | 
			
		||||
    for (const i of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (whereF && whereF(i) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (minV == undefined)
 | 
			
		||||
      {
 | 
			
		||||
        minV = v
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v < minV)
 | 
			
		||||
        {
 | 
			
		||||
          minV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (minV != undefined)
 | 
			
		||||
      return minV
 | 
			
		||||
    return defaultV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 累加 */
 | 
			
		||||
  static sum<Item>(list: Item[], fn: (item: Item) => number, wn?: (item: Item) => boolean): number
 | 
			
		||||
  {
 | 
			
		||||
    let v: number = 0
 | 
			
		||||
    for (const t of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (wn && wn(t) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      v = v + fn(t)
 | 
			
		||||
    }
 | 
			
		||||
    return v
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 平均值 */
 | 
			
		||||
  static avg<Item>(list: Item[], fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let sum = ArrayExt.sum(list, fn)
 | 
			
		||||
    return sum / this.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 排序 */
 | 
			
		||||
  static sortBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null)
 | 
			
		||||
  {
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) - fn(b))
 | 
			
		||||
    else
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 降序 排序 */
 | 
			
		||||
  static sortByDescending<Item>(list: Item[], fn: (item: Item) => number)
 | 
			
		||||
  {
 | 
			
		||||
    list.sort((a: Item, b: Item): number => fn(b) - fn(a))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 排序成新的数组 */
 | 
			
		||||
  static orderBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.concat([])
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return newList.sort((a: Item, b: Item): number => fn(a) - fn(b))
 | 
			
		||||
    else
 | 
			
		||||
      return newList.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 降序成新的数组 */
 | 
			
		||||
  static orderByDescending<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.concat([])
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(b) - fn(a))
 | 
			
		||||
    else
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 分组 */
 | 
			
		||||
  static groupBy<Item, gT>(list: Item[], fn: (item: Item) => gT): GroupItem<gT, Item>[]
 | 
			
		||||
  {
 | 
			
		||||
    let groups = new Array<GroupItem<gT, Item>>()
 | 
			
		||||
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      let key = fn(item)
 | 
			
		||||
      let group = groups.find(t => t.key == key)
 | 
			
		||||
      if (group == null)
 | 
			
		||||
      {
 | 
			
		||||
        group = new GroupItem(key)
 | 
			
		||||
        groups.push(group)
 | 
			
		||||
      }
 | 
			
		||||
      group.push(item)
 | 
			
		||||
    }
 | 
			
		||||
    return groups
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 选择
 | 
			
		||||
   * let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
 | 
			
		||||
   */
 | 
			
		||||
  static Select<Item, rT>(list: Item[], fn: (item: Item) => rT): rT[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = new Array<rT>()
 | 
			
		||||
    for (const t of list)
 | 
			
		||||
    {
 | 
			
		||||
      newList.push(fn(t))
 | 
			
		||||
    }
 | 
			
		||||
    return newList
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 过来,并按顺序排序 */
 | 
			
		||||
  static where<Item>(list: Item[], whereFn: (item: Item) => boolean, orderfn1: (item: Item) => number = null, orderfn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.filter(whereFn)
 | 
			
		||||
    if (orderfn1 == null && orderfn2 == null)
 | 
			
		||||
      return newList
 | 
			
		||||
    return ArrayExt.sortBy(newList, orderfn1, orderfn2)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class GroupItem<gT, Item>
 | 
			
		||||
{
 | 
			
		||||
  key: gT
 | 
			
		||||
  get count() { return this.list.length }
 | 
			
		||||
  list: Item[]
 | 
			
		||||
 | 
			
		||||
  constructor(k: gT)
 | 
			
		||||
  {
 | 
			
		||||
    this.key = k
 | 
			
		||||
    this.list = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  push(d: Item)
 | 
			
		||||
  {
 | 
			
		||||
    this.list.push(d)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 对排序好的数组进行去重操作
 | 
			
		||||
 * @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
 | 
			
		||||
 * @returns {Array<T>} 返回自身
 | 
			
		||||
 */
 | 
			
		||||
export function arrayRemoveDuplicateBySort<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  if (arr.length < 2)
 | 
			
		||||
    return arr
 | 
			
		||||
  let j = 1
 | 
			
		||||
  for (let i = 1, l = arr.length; i < l; i++)
 | 
			
		||||
    if (!checkFuction(arr[j - 1], arr[i]))
 | 
			
		||||
      arr[j++] = arr[i]
 | 
			
		||||
  arr.length = j
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								tests/dev1/dataHandle/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/dev1/dataHandle/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import * as clipperLib from 'js-angusj-clipper/web/index.js'
 | 
			
		||||
 | 
			
		||||
// nodejs style require
 | 
			
		||||
// 多边形和线条裁剪及偏移库
 | 
			
		||||
export const clipperCpp: { lib?: clipperLib.ClipperLibWrapper } = {}
 | 
			
		||||
export function InitClipperCpp(): Promise<void>
 | 
			
		||||
{
 | 
			
		||||
  if (clipperCpp.lib)
 | 
			
		||||
    return
 | 
			
		||||
  if (!globalThis.document)
 | 
			
		||||
    globalThis.document = {} as any
 | 
			
		||||
  return new Promise((res, rej) =>
 | 
			
		||||
  {
 | 
			
		||||
    clipperLib.loadNativeClipperLibInstanceAsync(
 | 
			
		||||
      // let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
 | 
			
		||||
      // clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
 | 
			
		||||
      clipperLib.NativeClipperLibRequestedFormat.WasmOnly,
 | 
			
		||||
    ).then((c) =>
 | 
			
		||||
    {
 | 
			
		||||
      clipperCpp.lib = c
 | 
			
		||||
      res()
 | 
			
		||||
      console.log('载入成功!')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										369
									
								
								tests/dev1/dataHandle/PolylineHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								tests/dev1/dataHandle/PolylineHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,369 @@
 | 
			
		||||
import type { PolylineProps } from 'cadapi'
 | 
			
		||||
import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi'
 | 
			
		||||
import type { Box3 } from 'three'
 | 
			
		||||
import { Vector2, Vector3 } from 'three'
 | 
			
		||||
import { arrayRemoveDuplicateBySort } from './ArrayExt'
 | 
			
		||||
import type { Curve2d } from './common/base/CAD'
 | 
			
		||||
import { Arc2d, Point2d, copyTextToClipboard } from './common/base/CAD'
 | 
			
		||||
import { CurveWrap } from './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()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								tests/dev1/dataHandle/Simplify2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								tests/dev1/dataHandle/Simplify2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import { Vector2 } from './Vector2'
 | 
			
		||||
 | 
			
		||||
interface P {
 | 
			
		||||
  x: number
 | 
			
		||||
  y: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IOffset {
 | 
			
		||||
  negativeOffset: number
 | 
			
		||||
  positiveOffset: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */
 | 
			
		||||
function GetSqSegDist(p: P, p1: P, p2: P): number {
 | 
			
		||||
  let x = p1.x
 | 
			
		||||
  let y = p1.y
 | 
			
		||||
  let dx = p2.x - x
 | 
			
		||||
  let dy = p2.y - y
 | 
			
		||||
 | 
			
		||||
  if (dx !== 0 || dy !== 0)// 不是0长度线
 | 
			
		||||
  {
 | 
			
		||||
    const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
 | 
			
		||||
    if (t > 1) {
 | 
			
		||||
      x = p2.x
 | 
			
		||||
      y = p2.y
 | 
			
		||||
    }
 | 
			
		||||
    else if (t > 0) {
 | 
			
		||||
      x += dx * t
 | 
			
		||||
      y += dy * t
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  dx = p.x - x
 | 
			
		||||
  dy = p.y - y
 | 
			
		||||
  return dx * dx + dy * dy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CrossVector2(a: P, b: P)
 | 
			
		||||
{
 | 
			
		||||
  return a.x * b.y - a.y * b.x
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ramer-Douglas-Peucker algorithm 用于简化曲线或折线
 | 
			
		||||
function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void {
 | 
			
		||||
  let maxSqDist = 0
 | 
			
		||||
  let index: number
 | 
			
		||||
  const fp = points[first]
 | 
			
		||||
  const lp = points[last]
 | 
			
		||||
 | 
			
		||||
  for (let i = first + 1; i < last; i++) {
 | 
			
		||||
    const p = points[i]
 | 
			
		||||
    const sqDist = GetSqSegDist(p, fp, lp)
 | 
			
		||||
    if (sqDist > maxSqDist) {
 | 
			
		||||
      index = i
 | 
			
		||||
      maxSqDist = sqDist
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (maxSqDist > sqTolerance) {
 | 
			
		||||
    if (index - first > 1)
 | 
			
		||||
      SimplifyDPStep(points, first, index, sqTolerance, simplified, offset)
 | 
			
		||||
    simplified.push(points[index])
 | 
			
		||||
    if (last - index > 1)
 | 
			
		||||
      SimplifyDPStep(points, index, last, sqTolerance, simplified, offset)
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    // 记录偏移
 | 
			
		||||
    const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize()
 | 
			
		||||
    for (let i = first + 1; i < last; i++) {
 | 
			
		||||
      const p = points[i]
 | 
			
		||||
      const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y })
 | 
			
		||||
      offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist)
 | 
			
		||||
      offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ramer-Douglas-Peucker 算法 用于简化曲线或折线
 | 
			
		||||
export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] {
 | 
			
		||||
  const last = points.length - 1
 | 
			
		||||
  const simplified: P[] = [points[0]]
 | 
			
		||||
  const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 }
 | 
			
		||||
  SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset)
 | 
			
		||||
  simplified.push(points[last])
 | 
			
		||||
  return [simplified, offset]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								tests/dev1/dataHandle/StringFormat.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								tests/dev1/dataHandle/StringFormat.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
export class StringFormat
 | 
			
		||||
{
 | 
			
		||||
  /**
 | 
			
		||||
   * 对Date的扩展,将 Date 转化为指定格式的String
 | 
			
		||||
   * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符
 | 
			
		||||
   * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
 | 
			
		||||
   * eg:
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
 | 
			
		||||
   */
 | 
			
		||||
  static Date(date: Date, fmt): string
 | 
			
		||||
  {
 | 
			
		||||
    let o = {
 | 
			
		||||
      'M+': date.getMonth() + 1, // 月份
 | 
			
		||||
      'd+': date.getDate(), // 日
 | 
			
		||||
      'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时
 | 
			
		||||
      'H+': date.getHours(), // 小时
 | 
			
		||||
      'm+': date.getMinutes(), // 分
 | 
			
		||||
      's+': date.getSeconds(), // 秒
 | 
			
		||||
      'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
 | 
			
		||||
      'S': date.getMilliseconds(), // 毫秒
 | 
			
		||||
    }
 | 
			
		||||
    let week = {
 | 
			
		||||
      0: '/u65e5',
 | 
			
		||||
      1: '/u4e00',
 | 
			
		||||
      2: '/u4e8c',
 | 
			
		||||
      3: '/u4e09',
 | 
			
		||||
      4: '/u56db',
 | 
			
		||||
      5: '/u4e94',
 | 
			
		||||
      6: '/u516d',
 | 
			
		||||
    }
 | 
			
		||||
    if (/(y+)/.test(fmt))
 | 
			
		||||
    {
 | 
			
		||||
      fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length))
 | 
			
		||||
    }
 | 
			
		||||
    if (/(E+)/.test(fmt))
 | 
			
		||||
    {
 | 
			
		||||
      fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`])
 | 
			
		||||
    }
 | 
			
		||||
    for (let k in o)
 | 
			
		||||
    {
 | 
			
		||||
      if (new RegExp(`(${k})`).test(fmt))
 | 
			
		||||
      {
 | 
			
		||||
        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return fmt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回数字的固定小数点 123.00  */
 | 
			
		||||
  static number(value: number, bit: number): string
 | 
			
		||||
  {
 | 
			
		||||
    return value?.toFixed(bit).toString()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static toFixed(value: number, bit: number = 3): number
 | 
			
		||||
  {
 | 
			
		||||
    let tt = 10 ** bit
 | 
			
		||||
    return Math.round(value * tt) / tt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static filterIllegalChar(str: string): string
 | 
			
		||||
  {
 | 
			
		||||
    // var pattern = new RegExp("[/:*?'<>|\\]");
 | 
			
		||||
    // var rs = "";
 | 
			
		||||
    // for (var i = 0; i < str.length; i++)
 | 
			
		||||
    // {
 | 
			
		||||
    //     rs = rs + str.substr(i, 1).replace(pattern, '');
 | 
			
		||||
    // }
 | 
			
		||||
    // return rs;
 | 
			
		||||
 | 
			
		||||
    let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\</g, '').replace(/\>/g, '').replace(/\|/g, '').replace(/\'/g, '')
 | 
			
		||||
    return rt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 文本格式化 */
 | 
			
		||||
  static format(str: string, ...args: any[]): string
 | 
			
		||||
  {
 | 
			
		||||
    let data = args
 | 
			
		||||
    let tmpl = str
 | 
			
		||||
    for (const item of tmpl.matchAll(/\{(.+?)\}/g))
 | 
			
		||||
    {
 | 
			
		||||
      let parts = item[1].split(',').map(i => i.trim())
 | 
			
		||||
      let index = Number(parts[0])
 | 
			
		||||
      let arg = data[index]
 | 
			
		||||
 | 
			
		||||
      let val = (arg || '').toString() // 默认
 | 
			
		||||
 | 
			
		||||
      if (arg instanceof Date) // 日期
 | 
			
		||||
      {
 | 
			
		||||
        let fm = 'MM-dd HH;mm'
 | 
			
		||||
        if (parts.length > 1)
 | 
			
		||||
        {
 | 
			
		||||
          fm = parts[1]
 | 
			
		||||
        }
 | 
			
		||||
        val = this.Date(arg, fm)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (parts.length > 1 && parts[1][0] === '#')
 | 
			
		||||
      {
 | 
			
		||||
        // {2,#3} -> 数字 转成3位  001,...023,
 | 
			
		||||
        val = val.padStart(Number(parts[1].substring(1)), '0')
 | 
			
		||||
      }
 | 
			
		||||
      tmpl = tmpl.replace(item[0], val)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tmpl
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 实现右对齐,右边填充 (25,4,'*') => '25**' */
 | 
			
		||||
  static PadEnd(num: number, totalWidth: number, paddingChar: string): string
 | 
			
		||||
  {
 | 
			
		||||
    let str = num.toString()
 | 
			
		||||
    return str.padEnd(totalWidth, paddingChar[0])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 实现右对齐,左边填充 (25,4,'0') => '0025' */
 | 
			
		||||
  static PadStart(num: number, totalWidth: number, paddingChar: string): string
 | 
			
		||||
  {
 | 
			
		||||
    let str = num.toString()
 | 
			
		||||
    return str.padStart(totalWidth, paddingChar[0])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								tests/dev1/dataHandle/base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								tests/dev1/dataHandle/base.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import { ConfigBase } from "./models/config";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 加工处理器上下文
 | 
			
		||||
 */
 | 
			
		||||
export abstract class ProcessorContext<TInput,TOutput,TConfig extends ConfigBase>{
 | 
			
		||||
    /**
 | 
			
		||||
     * 输入数据
 | 
			
		||||
     */
 | 
			
		||||
    public input?:TInput;
 | 
			
		||||
    /**
 | 
			
		||||
     * 合并配置文件与临时输入参
 | 
			
		||||
     */
 | 
			
		||||
    public params?:TConfig;
 | 
			
		||||
    /**
 | 
			
		||||
     * 输出数据
 | 
			
		||||
     */
 | 
			
		||||
    public output?:TOutput;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 处理器基类
 | 
			
		||||
 */
 | 
			
		||||
export abstract class ProcessorBase<TInput,TOutput,TConfig extends ConfigBase> {
 | 
			
		||||
    public abstract get name():string;
 | 
			
		||||
    public abstract get version(): string;
 | 
			
		||||
    public abstract exec(context:ProcessorContext<TInput,TOutput,TConfig>):Promise<void>|void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface resultInfo {
 | 
			
		||||
    code: ResCodeType;
 | 
			
		||||
    data?: any;
 | 
			
		||||
    success?: boolean;
 | 
			
		||||
    message?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ResCodeType{
 | 
			
		||||
    SUCCESS = 1,
 | 
			
		||||
    ERROR = 0,
 | 
			
		||||
    WARNING = -1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								tests/dev1/dataHandle/common/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								tests/dev1/dataHandle/common/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 删除数组中指定的元素,返回数组本身
 | 
			
		||||
 * @param {Array<any>} arr 需要操作的数组
 | 
			
		||||
 * @param {*} el 需要移除的元素
 | 
			
		||||
 */
 | 
			
		||||
export function arrayRemove<T>(arr: Array<T>, el: T): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  let j = 0
 | 
			
		||||
  for (let i = 0, l = arr.length; i < l; i++)
 | 
			
		||||
  {
 | 
			
		||||
    if (arr[i] !== el)
 | 
			
		||||
    {
 | 
			
		||||
      arr[j++] = arr[i]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  arr.length = j
 | 
			
		||||
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function arrayRemoveOnce<T>(arr: Array<T>, el: T): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  let index = arr.indexOf(el)
 | 
			
		||||
  if (index !== -1)
 | 
			
		||||
    arr.splice(index, 1)
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 删除通过函数校验的元素
 | 
			
		||||
 * @param {(e: T) => boolean} checkFuntion 校验函数
 | 
			
		||||
 */
 | 
			
		||||
export function arrayRemoveIf<T>(arr: Array<T>, checkFuntion: (e: T) => boolean): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  let j = 0
 | 
			
		||||
  for (let i = 0, l = arr.length; i < l; i++)
 | 
			
		||||
  {
 | 
			
		||||
    if (!checkFuntion(arr[i]))
 | 
			
		||||
    {
 | 
			
		||||
      arr[j++] = arr[i]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  arr.length = j
 | 
			
		||||
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取数据第一个元素 */
 | 
			
		||||
export function arrayFirst<T>(arr: Array<T>): T
 | 
			
		||||
{
 | 
			
		||||
  return arr[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取数组最后一个元素 */
 | 
			
		||||
export function arrayLast<T>(arr: { [key: number]: T; length: number }): T
 | 
			
		||||
{
 | 
			
		||||
  return arr[arr.length - 1]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据数值从小到大排序数组
 | 
			
		||||
 * @param {Array<T>} arr
 | 
			
		||||
 * @returns {Array<T>} 返回自身
 | 
			
		||||
 */
 | 
			
		||||
export function arraySortByNumber<T>(arr: Array<T>): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  arr.sort(sortNumberCompart)
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 对排序好的数组进行去重操作
 | 
			
		||||
 * @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
 | 
			
		||||
 * @returns {Array<T>} 返回自身
 | 
			
		||||
 */
 | 
			
		||||
export function arrayRemoveDuplicateBySort<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  if (arr.length < 2)
 | 
			
		||||
    return arr
 | 
			
		||||
  let j = 1
 | 
			
		||||
  for (let i = 1, l = arr.length; i < l; i++)
 | 
			
		||||
    if (!checkFuction(arr[j - 1], arr[i]))
 | 
			
		||||
      arr[j++] = arr[i]
 | 
			
		||||
  arr.length = j
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 原地更新数组,注意这个函数并不会比map快 */
 | 
			
		||||
export function arrayMap<T>(arr: Array<T>, mapFunc: (v: T) => T): Array<T>
 | 
			
		||||
{
 | 
			
		||||
  for (let i = 0, count = arr.length; i < count; i++)
 | 
			
		||||
    arr[i] = mapFunc(arr[i])
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 排序数比较 */
 | 
			
		||||
function sortNumberCompart(e1: any, e2: any)
 | 
			
		||||
{
 | 
			
		||||
  return e1 - e2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 判断两个数是否相等 */
 | 
			
		||||
function checkEqual(e1: any, e2: any): boolean
 | 
			
		||||
{
 | 
			
		||||
  return e1 === e2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 改变数组的值顺序
 | 
			
		||||
 * @param arr 需要改变初始值位置的数组
 | 
			
		||||
 * @param index  //将index位置以后的值放到起始位置
 | 
			
		||||
 */
 | 
			
		||||
export function changeArrayStartIndex<T>(arr: T[], index: number): T[]
 | 
			
		||||
{
 | 
			
		||||
  arr.unshift(...arr.splice(index))
 | 
			
		||||
  return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 判断两个数组是否相等 */
 | 
			
		||||
export function equalArray<T>(a: T[], b: T[], checkF = checkEqual)
 | 
			
		||||
{
 | 
			
		||||
  if (a === b)
 | 
			
		||||
    return true
 | 
			
		||||
  if (a.length !== b.length)
 | 
			
		||||
    return false
 | 
			
		||||
  for (let i = 0; i < a.length; ++i)
 | 
			
		||||
    if (!checkF(a[i], b[i]))
 | 
			
		||||
      return false
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 数组克隆 */
 | 
			
		||||
export function arrayClone<T>(arr: T[]): T[]
 | 
			
		||||
{
 | 
			
		||||
  return arr.slice()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 数组2元素合并到数组1末尾 */
 | 
			
		||||
// https://jsperf.com/merge-array-implementations/30
 | 
			
		||||
export function arrayPushArray<T>(arr1: T[], arr2: T[]): T[]
 | 
			
		||||
{
 | 
			
		||||
  let arr1Length = arr1.length
 | 
			
		||||
  let arr2Length = arr2.length
 | 
			
		||||
  arr1.length = arr1Length + arr2Length
 | 
			
		||||
  for (let i = 0; i < arr2Length; i++)
 | 
			
		||||
    arr1[arr1Length + i] = arr2[i]
 | 
			
		||||
 | 
			
		||||
  return arr1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 数组元素求合 */
 | 
			
		||||
export function arraySum(arr: number[])
 | 
			
		||||
{
 | 
			
		||||
  let sum = 0
 | 
			
		||||
  for (let n of arr) sum += n
 | 
			
		||||
  return sum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 条件过滤集合 */
 | 
			
		||||
export function FilterSet<T>(s: Set<T>, fn: (el: T) => boolean): Set<T>
 | 
			
		||||
{
 | 
			
		||||
  let ns = new Set<T>()
 | 
			
		||||
  for (let el of s)
 | 
			
		||||
  {
 | 
			
		||||
    if (fn(el))
 | 
			
		||||
      ns.add(el)
 | 
			
		||||
  }
 | 
			
		||||
  return ns
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 查找数组中最大的元素 */
 | 
			
		||||
export function arrayMax<T>(arr: T[], f: (item: T) => number = a => (a as unknown as number)): [T, number]
 | 
			
		||||
{
 | 
			
		||||
  let max = Number.NEGATIVE_INFINITY
 | 
			
		||||
  let maxIndex = -1
 | 
			
		||||
  for (let i = 0; i < arr.length; i++)
 | 
			
		||||
  {
 | 
			
		||||
    let item = arr[i]
 | 
			
		||||
    let v = f(item)
 | 
			
		||||
    if (v > max)
 | 
			
		||||
    {
 | 
			
		||||
      maxIndex = i
 | 
			
		||||
      max = v
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return [arr[maxIndex], maxIndex]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								tests/dev1/dataHandle/common/BlockBorderPoint.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								tests/dev1/dataHandle/common/BlockBorderPoint.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								tests/dev1/dataHandle/common/BlockSealEdge.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								tests/dev1/dataHandle/common/BlockSealEdge.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								tests/dev1/dataHandle/common/Box2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								tests/dev1/dataHandle/common/Box2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<Point>): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.makeEmpty()
 | 
			
		||||
    for (let p of points)
 | 
			
		||||
    {
 | 
			
		||||
      this.expandByPoint(p)
 | 
			
		||||
    }
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static _setFromCenterAndSize_v1 = new Vector2()
 | 
			
		||||
  setFromCenterAndSize(center: Vector2, size: Vector2): Box2
 | 
			
		||||
  {
 | 
			
		||||
    const v1 = Box2._setFromCenterAndSize_v1
 | 
			
		||||
    const halfSize = v1.copy(size).multiplyScalar(0.5)
 | 
			
		||||
    this.min.copy(center).sub(halfSize)
 | 
			
		||||
    this.max.copy(center).add(halfSize)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clone(): Box2
 | 
			
		||||
  {
 | 
			
		||||
    return new (this.constructor as any)().copy(this)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  copy(box: Box2): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.copy(box.min)
 | 
			
		||||
    this.max.copy(box.max)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  makeEmpty(): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.x = this.min.y = +Number.POSITIVE_INFINITY
 | 
			
		||||
    this.max.x = this.max.y = Number.NEGATIVE_INFINITY
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isEmpty(): boolean
 | 
			
		||||
  {
 | 
			
		||||
    // this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes
 | 
			
		||||
    return (this.max.x < this.min.x) || (this.max.y < this.min.y)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getCenter(result: Vector2 = new Vector2()): Vector2
 | 
			
		||||
  {
 | 
			
		||||
    return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getSize(result: Vector2 = new Vector2()): Vector2
 | 
			
		||||
  {
 | 
			
		||||
    return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  expandByPoint(point: Point): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.min(point)
 | 
			
		||||
    this.max.max(point)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  expandByVector(vector: Vector2): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.sub(vector)
 | 
			
		||||
    this.max.add(vector)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  expandByScalar(scalar: number): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.addScalar(-scalar)
 | 
			
		||||
    this.max.addScalar(scalar)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  containsPoint(point: Vector2): boolean
 | 
			
		||||
  {
 | 
			
		||||
    if (point.x < this.min.x || point.x > this.max.x
 | 
			
		||||
            || point.y < this.min.y || point.y > this.max.y)
 | 
			
		||||
    {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  containsBox(box: Box2): boolean
 | 
			
		||||
  {
 | 
			
		||||
    if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x)
 | 
			
		||||
            && (this.min.y <= box.min.y) && (box.max.y <= this.max.y))
 | 
			
		||||
    {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getParameter(point: Vector2, result: Vector2 = new Vector2()): Vector2
 | 
			
		||||
  {
 | 
			
		||||
    // This can potentially have a divide by zero if the box
 | 
			
		||||
    // has a size dimension of 0.
 | 
			
		||||
    return result.set(
 | 
			
		||||
      (point.x - this.min.x) / (this.max.x - this.min.x),
 | 
			
		||||
      (point.y - this.min.y) / (this.max.y - this.min.y),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  intersectsBox(box: Box2): boolean
 | 
			
		||||
  {
 | 
			
		||||
    // using 6 splitting planes to rule out intersections.
 | 
			
		||||
    if (box.max.x < this.min.x || box.min.x > this.max.x
 | 
			
		||||
            || box.max.y < this.min.y || box.min.y > this.max.y)
 | 
			
		||||
    {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clampPoint(point: Vector2, result: Vector2 = new Vector2()): Vector2
 | 
			
		||||
  {
 | 
			
		||||
    return result.copy(point).clamp(this.min, this.max)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static _distanceToPoint_v1 = new Vector2()
 | 
			
		||||
  distanceToPoint(point: Vector2): number
 | 
			
		||||
  {
 | 
			
		||||
    const v1 = Box2._distanceToPoint_v1
 | 
			
		||||
    const clampedPoint = v1.copy(point).clamp(this.min, this.max)
 | 
			
		||||
    return clampedPoint.sub(point).length()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  intersect(box: Box2): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.max(box.min)
 | 
			
		||||
    this.max.min(box.max)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  union(box: Box2): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.min(box.min)
 | 
			
		||||
    this.max.max(box.max)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  translate(offset: Point): Box2
 | 
			
		||||
  {
 | 
			
		||||
    this.min.add(offset)
 | 
			
		||||
    this.max.add(offset)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  equals(box: Box2): boolean
 | 
			
		||||
  {
 | 
			
		||||
    return box.min.equals(this.min) && box.max.equals(this.max)
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										25
									
								
								tests/dev1/dataHandle/common/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/dev1/dataHandle/common/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import * as clipperLib from 'js-angusj-clipper/web/index.js'
 | 
			
		||||
 | 
			
		||||
// nodejs style require
 | 
			
		||||
 | 
			
		||||
export const clipperCpp: { lib?: clipperLib.ClipperLibWrapper } = {}
 | 
			
		||||
export function InitClipperCpp(): Promise<void>
 | 
			
		||||
{
 | 
			
		||||
  if (clipperCpp.lib)
 | 
			
		||||
    return
 | 
			
		||||
  if (!globalThis.document)
 | 
			
		||||
    globalThis.document = {} as any
 | 
			
		||||
  return new Promise((res, rej) =>
 | 
			
		||||
  {
 | 
			
		||||
    clipperLib.loadNativeClipperLibInstanceAsync(
 | 
			
		||||
      // let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
 | 
			
		||||
      // clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
 | 
			
		||||
      clipperLib.NativeClipperLibRequestedFormat.WasmOnly,
 | 
			
		||||
    ).then((c) =>
 | 
			
		||||
    {
 | 
			
		||||
      clipperCpp.lib = c
 | 
			
		||||
      res()
 | 
			
		||||
      console.log('载入成功!')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								tests/dev1/dataHandle/common/ComparePoint.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								tests/dev1/dataHandle/common/ComparePoint.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
interface Vec2 { x: number; y: number }
 | 
			
		||||
 | 
			
		||||
export type CompareVectorFn = (v1: Vec2, v2: Vec2) => number
 | 
			
		||||
 | 
			
		||||
const comparePointCache: Map<string, CompareVectorFn> = new Map()
 | 
			
		||||
 | 
			
		||||
const ALLKEY = ['x', 'X', 'y', 'Y', 'z', 'Z']
 | 
			
		||||
const KEY = ['x', 'y', 'z']
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 构建返回一个用来排序的函数.根据key创建排序规则.
 | 
			
		||||
 *
 | 
			
		||||
 * 当key = "xyz" 时,点集按 x从小到大,y从小到大 z从小到大
 | 
			
		||||
 *   key = "X"   时,点集按 x从大到小
 | 
			
		||||
 * 以此类推.
 | 
			
		||||
 *
 | 
			
		||||
 * 例子:
 | 
			
		||||
 * let pts:Vector3[] =...;
 | 
			
		||||
 * pts.sort(comparePoint("x")); //x从小到大排序
 | 
			
		||||
 * pts.sort(comparePoint("zX")); //z从小到大 x从大到小
 | 
			
		||||
 *
 | 
			
		||||
 * @export
 | 
			
		||||
 * @param {string} sortKey
 | 
			
		||||
 */
 | 
			
		||||
export function ComparePoint(sortKey: string): CompareVectorFn
 | 
			
		||||
{
 | 
			
		||||
  if (comparePointCache.has(sortKey))
 | 
			
		||||
    return comparePointCache.get(sortKey)
 | 
			
		||||
 | 
			
		||||
  let sortIndex = []
 | 
			
		||||
 | 
			
		||||
  for (let char of sortKey)
 | 
			
		||||
  {
 | 
			
		||||
    let index = ALLKEY.indexOf(char)
 | 
			
		||||
 | 
			
		||||
    let i2 = index / 2
 | 
			
		||||
    let ci = Math.floor(i2)
 | 
			
		||||
    sortIndex.push([KEY[ci], i2 > ci ? 1 : -1])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let compareFunction = (v1: Vec2, v2: Vec2): number =>
 | 
			
		||||
  {
 | 
			
		||||
    if (!v1)
 | 
			
		||||
      return -1
 | 
			
		||||
    if (!v2)
 | 
			
		||||
      return 1
 | 
			
		||||
    for (let s of sortIndex)
 | 
			
		||||
    {
 | 
			
		||||
      let vv1 = v1[s[0]]
 | 
			
		||||
      let vv2 = v2[s[0]]
 | 
			
		||||
      if (vv1 === vv2)
 | 
			
		||||
        continue
 | 
			
		||||
      if (vv2 > vv1)
 | 
			
		||||
        return s[1]
 | 
			
		||||
      else return -s[1]
 | 
			
		||||
    }
 | 
			
		||||
    return 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  comparePointCache.set(sortKey, compareFunction)
 | 
			
		||||
  return compareFunction
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								tests/dev1/dataHandle/common/ConvexHull2D.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/dev1/dataHandle/common/ConvexHull2D.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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])
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								tests/dev1/dataHandle/common/Filer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/dev1/dataHandle/common/Filer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
/**
 | 
			
		||||
 * CAD文件数据
 | 
			
		||||
 */
 | 
			
		||||
export class NestFiler
 | 
			
		||||
{
 | 
			
		||||
  private readIndex: number = 0
 | 
			
		||||
  constructor(public _datas: any[] = []) { }
 | 
			
		||||
 | 
			
		||||
  Clear()
 | 
			
		||||
  {
 | 
			
		||||
    this._datas.length = 0
 | 
			
		||||
    return this.Reset()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Reset()
 | 
			
		||||
  {
 | 
			
		||||
    this.readIndex = 0
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Write(data: any)
 | 
			
		||||
  {
 | 
			
		||||
    this._datas.push(data)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Read(): any
 | 
			
		||||
  {
 | 
			
		||||
    return this._datas[this.readIndex++]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1713
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1713
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockDataPlus.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										365
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockDoFace.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<any>((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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1300
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1300
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockHelper.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										300
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockOverlap.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2395
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2395
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockPlus.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										473
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										473
									
								
								tests/dev1/dataHandle/common/LayoutEngine/BlockSizePlus.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										266
									
								
								tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								tests/dev1/dataHandle/common/LayoutEngine/Curves2Parts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T {
 | 
			
		||||
  v.x += Math.cos(an) * dis
 | 
			
		||||
  v.y += Math.sin(an) * dis
 | 
			
		||||
  return v
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								tests/dev1/dataHandle/common/LayoutEngine/PlaceBase.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										359
									
								
								tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								tests/dev1/dataHandle/common/LayoutEngine/PolylineHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										404
									
								
								tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<any[]> {
 | 
			
		||||
    if (m.originModeling == null)
 | 
			
		||||
      return []
 | 
			
		||||
    let pts = m.originModeling.outline.pts
 | 
			
		||||
    let buls = m.originModeling.outline.buls
 | 
			
		||||
 | 
			
		||||
    if (pts == undefined) {
 | 
			
		||||
      pts = m.originModeling.outline.map(e => e.pts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (buls == undefined) {
 | 
			
		||||
      buls = m.originModeling.outline.map(e => e.buls)
 | 
			
		||||
    }
 | 
			
		||||
    // blocks = [];
 | 
			
		||||
    // 起点
 | 
			
		||||
    let pts_n = []
 | 
			
		||||
    for (let i = 0; i < pts.length; i++) {
 | 
			
		||||
      let p0 = BlockHelper.getPlaceXYInBlock(block, pts[i].x, pts[i].y, false, false)
 | 
			
		||||
      pts_n.push({ x: p0.x, y: p0.y, bul: buls[i] })
 | 
			
		||||
    }
 | 
			
		||||
    // 如果反面,翻转方向,重设坐标
 | 
			
		||||
    if (block.isTurnOver)
 | 
			
		||||
      pts_n = this.reversePoint(pts_n, block.cutWidth)
 | 
			
		||||
 | 
			
		||||
    // pts_n.forEach(t => { t.x += block.placeX; t.y += block.placeY; });
 | 
			
		||||
 | 
			
		||||
    let polyLine
 | 
			
		||||
    polyLine = Points2Polyline(pts_n)
 | 
			
		||||
    let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
 | 
			
		||||
    if (cicle)
 | 
			
		||||
      polyLine = cicle
 | 
			
		||||
    let cureW2 = new CurveWrap(polyLine, this.offset, false)
 | 
			
		||||
    let pts2 = cureW2.GetInsidePoints2(this.offset)
 | 
			
		||||
    if (!pts2)
 | 
			
		||||
      return []
 | 
			
		||||
    let posx = 10000
 | 
			
		||||
    let posy = 10000
 | 
			
		||||
    for (let p of pts2) {
 | 
			
		||||
      if (p.x < posx)
 | 
			
		||||
        posx = p.x
 | 
			
		||||
      if (p.y < posy)
 | 
			
		||||
        posy = p.y
 | 
			
		||||
    }
 | 
			
		||||
    for (let p of pts2) {
 | 
			
		||||
      p.x -= posx
 | 
			
		||||
      p.y -= posy
 | 
			
		||||
    }
 | 
			
		||||
    let binPath = new Path(pts2)
 | 
			
		||||
    if (binPath.Area < 15000)
 | 
			
		||||
      return []
 | 
			
		||||
 | 
			
		||||
    await InitClipperCpp()
 | 
			
		||||
    binPath.Id = undefined
 | 
			
		||||
    PathGeneratorSingle.RegisterId(binPath)
 | 
			
		||||
    // 容器
 | 
			
		||||
    let container = new Container(binPath)
 | 
			
		||||
    let i = 0
 | 
			
		||||
    for (let b of blocks) {
 | 
			
		||||
      let part = this.toPart(b, binPath, this.offset, i)
 | 
			
		||||
      if (part[0].State.Contour == null)
 | 
			
		||||
        continue
 | 
			
		||||
      part[0].Id = i++
 | 
			
		||||
      // let isin = this.blockInBlock(block, b, polyLine);
 | 
			
		||||
      // if (isin == false) continue;
 | 
			
		||||
      // let part = this.toPart(b, binPath, this.offset, i++);
 | 
			
		||||
 | 
			
		||||
      // 设置位置
 | 
			
		||||
      part[0].PlacePosition = { x: (b.placeX - block.placeX - posx) * 1e4, y: (b.placeY - block.placeY - posy) * 1e4 }
 | 
			
		||||
      container.PlacedParts.push(part[0])
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      // let f = new NestFiler();
 | 
			
		||||
      // f.Write(container.PlacedParts.length);
 | 
			
		||||
      // for (let part of container.PlacedParts)
 | 
			
		||||
      //     f.Write(part.State.Contour.BigIntPoints);
 | 
			
		||||
      // container.WriteFile(f);
 | 
			
		||||
      // console.log(JSON.stringify(f._datas));
 | 
			
		||||
      // FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas));
 | 
			
		||||
 | 
			
		||||
      let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
 | 
			
		||||
      for (let space of spaces) {
 | 
			
		||||
        space.Points.forEach((t) => { t.x += posx; t.y += posy })
 | 
			
		||||
      }
 | 
			
		||||
      return spaces
 | 
			
		||||
    }
 | 
			
		||||
    catch (err) {
 | 
			
		||||
      console.log(`板${block.blockNo}造型孔分析余料空间失败.`)
 | 
			
		||||
      throw new Error(`板${block.blockNo}造型孔分析余料空间失败.请保存优化联系售后工程师分析`)
 | 
			
		||||
    }
 | 
			
		||||
    // let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths);
 | 
			
		||||
    // for (let space of spaces)
 | 
			
		||||
    // {
 | 
			
		||||
    //     space.Points.forEach(t => { t.x += posx; t.y += posy; });
 | 
			
		||||
    // }
 | 
			
		||||
    // return spaces;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 转配件 */
 | 
			
		||||
  toPart(block: PlaceBlock, binPath: Path, offset: number, id: number): [Part, Vector2] {
 | 
			
		||||
    let w = 10000
 | 
			
		||||
    let binPath2 = new Path([{ x: -w, y: -w }, { x: w, y: -w }, { x: w, y: w }, { x: -w, y: w }])
 | 
			
		||||
    let part = new Part()
 | 
			
		||||
    part.Id = id
 | 
			
		||||
 | 
			
		||||
    part.UserData = { bno: block.blockNo, area: block.area, isUnRegular: block.isUnRegular, width: block.cutWidth, length: block.cutLength }
 | 
			
		||||
 | 
			
		||||
    let path: Path
 | 
			
		||||
    let bPoint: Vector2
 | 
			
		||||
 | 
			
		||||
    let sizeOff = BlockSizePlus.getOffDis(block) // 外扩偏移
 | 
			
		||||
    let hasSizeOff = (sizeOff.left + sizeOff.right + sizeOff.top + sizeOff.bottom) > (block.placeMaterial.diameter * 2 + block.placeMaterial.cutKnifeGap * 2 + 0.01)
 | 
			
		||||
 | 
			
		||||
    // 矩形且带板外不偏移
 | 
			
		||||
    if (block.isUnRegular == false && hasSizeOff == false) {
 | 
			
		||||
      path = NestCache.CreatePath(block.placeWidth + sizeOff.left + sizeOff.right, block.placeLength + sizeOff.top + sizeOff.bottom, offset)
 | 
			
		||||
      bPoint = path.OrigionMinPoint
 | 
			
		||||
      // 轮廓
 | 
			
		||||
      part.Init2(path, binPath2, [0])
 | 
			
		||||
    }
 | 
			
		||||
    else // 异形
 | 
			
		||||
    {
 | 
			
		||||
      let pts = []
 | 
			
		||||
 | 
			
		||||
      let cured = BlockPlus.getBorder_moving(block)
 | 
			
		||||
      for (let l of cured) {
 | 
			
		||||
        if (l instanceof Arc2d) {
 | 
			
		||||
          pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let polyLine
 | 
			
		||||
      polyLine = Points2Polyline(pts)
 | 
			
		||||
      polyLine.CloseMark = true
 | 
			
		||||
 | 
			
		||||
      let cureW = new CurveWrap(polyLine, offset, true)
 | 
			
		||||
      let pts2 = cureW.GetOutsidePoints()
 | 
			
		||||
      arrayRemoveDuplicateBySort(pts, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
 | 
			
		||||
      path = new Path(pts2)
 | 
			
		||||
 | 
			
		||||
      let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
 | 
			
		||||
      if (!cicle) {
 | 
			
		||||
        let area = path.BoundingBox.area - path.Area
 | 
			
		||||
        if (area < 15000 && pts.length > 6)
 | 
			
		||||
          path = NestCache.CreatePath(block.placeWidth, block.placeLength, offset)
 | 
			
		||||
      }
 | 
			
		||||
      part.Init2(path, binPath2, [0])
 | 
			
		||||
 | 
			
		||||
      // 不能放下,那么尝试不简化路径
 | 
			
		||||
      if (!path.IsRect && !cicle && part.RotatedStates.length == 0) {
 | 
			
		||||
        let pts2 = Polyline2Points(polyLine, true, offset)[1]
 | 
			
		||||
        arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
 | 
			
		||||
        path = new Path(pts2)
 | 
			
		||||
        part.Init2(path, binPath, [0])
 | 
			
		||||
      }
 | 
			
		||||
      // 轮廓
 | 
			
		||||
      bPoint = path.OrigionMinPoint
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (hasSizeOff) {
 | 
			
		||||
      bPoint.x = bPoint.x - sizeOff.left
 | 
			
		||||
      bPoint.y = bPoint.y - sizeOff.bottom
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [part, bPoint]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 余料板空间 */
 | 
			
		||||
  toRemainBlock(path: Path): RemainBlock {
 | 
			
		||||
    let bx = path.OrigionMinPoint.x
 | 
			
		||||
    let by = path.OrigionMinPoint.y
 | 
			
		||||
    let points = path.Points
 | 
			
		||||
    let ptsX = points.map((t) => { return t.x })
 | 
			
		||||
    let ptsY = points.map((t) => { return t.y })
 | 
			
		||||
 | 
			
		||||
    let x0 = ArrayExt.min(ptsX, t => t)
 | 
			
		||||
    let y0 = ArrayExt.min(ptsY, t => t)
 | 
			
		||||
    let x1 = ArrayExt.max(ptsX, t => t)
 | 
			
		||||
    let y1 = ArrayExt.max(ptsY, t => t)
 | 
			
		||||
 | 
			
		||||
    let sp = new RemainBlock(x0, y0, x1 - x0, y1 - y0)
 | 
			
		||||
 | 
			
		||||
    let pts = points.map((t) => { return { x: t.x - x0, y: t.y - y0 } })
 | 
			
		||||
 | 
			
		||||
    sp.placeX = bx + x0
 | 
			
		||||
    sp.placeY = by + y0
 | 
			
		||||
 | 
			
		||||
    sp.setPoints(pts)
 | 
			
		||||
    return sp
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 判断板是否在造型洞里头 */
 | 
			
		||||
  blockInModelOfBlock(block: PlaceBlock, b: PlaceBlock, modlPl: Polyline, posX = 0, posY = 0): boolean {
 | 
			
		||||
    let cured = BlockPlus.getBorder(b)
 | 
			
		||||
    let pts = []
 | 
			
		||||
    for (let l of cured) {
 | 
			
		||||
      if (l instanceof Arc2d) {
 | 
			
		||||
        pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let polyLine = Points2Polyline(pts)
 | 
			
		||||
    let cureW2 = new CurveWrap(polyLine, this.offset, false)
 | 
			
		||||
    let pts2 = cureW2.GetInsidePoints2(this.offset)
 | 
			
		||||
 | 
			
		||||
    // pts2 = pts2.map(t => { return { x: t.x + b.placeX - block.placeX - posX, y: t.y + b.placeY - block.placeY - posY }; });
 | 
			
		||||
 | 
			
		||||
    for (let t of pts2) {
 | 
			
		||||
      let x = t.x + b.placeX - block.placeX - posX
 | 
			
		||||
      let y = t.y + b.placeY - block.placeY - posY
 | 
			
		||||
      if (modlPl.PtInCurve(new Vector3(x, y, 0)) == false)
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 反向点 */
 | 
			
		||||
  private reversePoint(pts, w): any[] {
 | 
			
		||||
    let newPts = []
 | 
			
		||||
    for (let i = pts.length - 1; i >= 0; i--) {
 | 
			
		||||
      let p = pts[i]
 | 
			
		||||
      let x = p.x
 | 
			
		||||
      let y = p.y
 | 
			
		||||
      let j = i == 0 ? pts.length - 1 : i - 1
 | 
			
		||||
 | 
			
		||||
      let bul = pts[j].bul
 | 
			
		||||
      newPts.push({ x, y, bul })
 | 
			
		||||
    }
 | 
			
		||||
    return newPts
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								tests/dev1/dataHandle/common/LayoutEngine/Simplify2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import { Vector2 } from '../Vector2'
 | 
			
		||||
 | 
			
		||||
interface P {
 | 
			
		||||
  x: number
 | 
			
		||||
  y: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IOffset {
 | 
			
		||||
  negativeOffset: number
 | 
			
		||||
  positiveOffset: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */
 | 
			
		||||
function GetSqSegDist(p: P, p1: P, p2: P): number {
 | 
			
		||||
  let x = p1.x
 | 
			
		||||
  let y = p1.y
 | 
			
		||||
  let dx = p2.x - x
 | 
			
		||||
  let dy = p2.y - y
 | 
			
		||||
 | 
			
		||||
  if (dx !== 0 || dy !== 0)// 不是0长度线
 | 
			
		||||
  {
 | 
			
		||||
    const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
 | 
			
		||||
    if (t > 1) {
 | 
			
		||||
      x = p2.x
 | 
			
		||||
      y = p2.y
 | 
			
		||||
    }
 | 
			
		||||
    else if (t > 0) {
 | 
			
		||||
      x += dx * t
 | 
			
		||||
      y += dy * t
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  dx = p.x - x
 | 
			
		||||
  dy = p.y - y
 | 
			
		||||
  return dx * dx + dy * dy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CrossVector2(a: P, b: P)
 | 
			
		||||
{
 | 
			
		||||
  return a.x * b.y - a.y * b.x
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ramer-Douglas-Peucker algorithm
 | 
			
		||||
function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void {
 | 
			
		||||
  let maxSqDist = 0
 | 
			
		||||
  let index: number
 | 
			
		||||
  const fp = points[first]
 | 
			
		||||
  const lp = points[last]
 | 
			
		||||
 | 
			
		||||
  for (let i = first + 1; i < last; i++) {
 | 
			
		||||
    const p = points[i]
 | 
			
		||||
    const sqDist = GetSqSegDist(p, fp, lp)
 | 
			
		||||
    if (sqDist > maxSqDist) {
 | 
			
		||||
      index = i
 | 
			
		||||
      maxSqDist = sqDist
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (maxSqDist > sqTolerance) {
 | 
			
		||||
    if (index - first > 1)
 | 
			
		||||
      SimplifyDPStep(points, first, index, sqTolerance, simplified, offset)
 | 
			
		||||
    simplified.push(points[index])
 | 
			
		||||
    if (last - index > 1)
 | 
			
		||||
      SimplifyDPStep(points, index, last, sqTolerance, simplified, offset)
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    // 记录偏移
 | 
			
		||||
    const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize()
 | 
			
		||||
    for (let i = first + 1; i < last; i++) {
 | 
			
		||||
      const p = points[i]
 | 
			
		||||
      const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y })
 | 
			
		||||
      offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist)
 | 
			
		||||
      offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ramer-Douglas-Peucker 算法
 | 
			
		||||
export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] {
 | 
			
		||||
  const last = points.length - 1
 | 
			
		||||
  const simplified: P[] = [points[0]]
 | 
			
		||||
  const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 }
 | 
			
		||||
  SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset)
 | 
			
		||||
  simplified.push(points[last])
 | 
			
		||||
  return [simplified, offset]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										773
									
								
								tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										773
									
								
								tests/dev1/dataHandle/common/LayoutEngine/SpacePlus.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								tests/dev1/dataHandle/common/LayoutEngine/writeP.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								tests/dev1/dataHandle/common/LayoutEngine/writeP.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
import type { PolylineProps } from 'cadapi'
 | 
			
		||||
import { CADFiler, Polyline } from 'cadapi'
 | 
			
		||||
import { Vector2 } from 'three'
 | 
			
		||||
import { copyTextToClipboard } from '../base/CAD'
 | 
			
		||||
 | 
			
		||||
export class ClipboardTest {
 | 
			
		||||
  public static writePolyline1(pl: Polyline, pts1) {
 | 
			
		||||
    // pl 原图
 | 
			
		||||
    // pts :偏移后的点
 | 
			
		||||
 | 
			
		||||
    const lined: PolylineProps[] = []
 | 
			
		||||
    const count = pts1.length
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const p0 = pts1[i]
 | 
			
		||||
      lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
 | 
			
		||||
    }
 | 
			
		||||
    const pl1 = new Polyline(lined)
 | 
			
		||||
    pl1.CloseMark = true
 | 
			
		||||
 | 
			
		||||
    const f = new CADFiler()
 | 
			
		||||
    f.Clear()
 | 
			
		||||
    f.Write(2)
 | 
			
		||||
    f.WriteObject(pl)
 | 
			
		||||
    f.WriteObject(pl1)
 | 
			
		||||
 | 
			
		||||
    const test = JSON.stringify(f.Data)
 | 
			
		||||
 | 
			
		||||
    // for (let pl of lined)
 | 
			
		||||
    //     f.WriteObject(pl)
 | 
			
		||||
    copyTextToClipboard(test)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static writePolyline2(pl: Polyline, pts1, pts2) {
 | 
			
		||||
    // pl 原图
 | 
			
		||||
    // pts :偏移后的点
 | 
			
		||||
 | 
			
		||||
    const lined: PolylineProps[] = []
 | 
			
		||||
    const count = pts1.length
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const p0 = pts1[i]
 | 
			
		||||
      lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
 | 
			
		||||
    }
 | 
			
		||||
    const pl1 = new Polyline(lined)
 | 
			
		||||
 | 
			
		||||
    const lined2: PolylineProps[] = []
 | 
			
		||||
    const count2 = pts2.length
 | 
			
		||||
    for (let i = 0; i < count2; i++) {
 | 
			
		||||
      const p0 = pts2[i]
 | 
			
		||||
      lined2.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
 | 
			
		||||
    }
 | 
			
		||||
    const pl2 = new Polyline(lined2)
 | 
			
		||||
 | 
			
		||||
    const f = new CADFiler()
 | 
			
		||||
    f.Clear()
 | 
			
		||||
    f.Write(3)
 | 
			
		||||
    f.WriteObject(pl)
 | 
			
		||||
    f.WriteObject(pl1)
 | 
			
		||||
    f.WriteObject(pl2)
 | 
			
		||||
    const test = JSON.stringify(f.Data)
 | 
			
		||||
 | 
			
		||||
    // for (let pl of lined)
 | 
			
		||||
    //     f.WriteObject(pl)
 | 
			
		||||
    copyTextToClipboard(test)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static writeClipboard(pts) {
 | 
			
		||||
    const lined: PolylineProps[] = []
 | 
			
		||||
    const count = pts.length
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const p0 = pts[i]
 | 
			
		||||
      lined.push({ pt: new Vector2(p0.x, p0.y), bul: 0 })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const pls = new Polyline(lined)
 | 
			
		||||
    const f = new CADFiler()
 | 
			
		||||
    f.Clear()
 | 
			
		||||
    f.Write(1)
 | 
			
		||||
    f.WriteObject(pls)
 | 
			
		||||
    const test = JSON.stringify(f.Data)
 | 
			
		||||
 | 
			
		||||
    // for (let pl of lined)
 | 
			
		||||
    //     f.WriteObject(pl)
 | 
			
		||||
    copyTextToClipboard(test)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static writePolyLine(pls) {
 | 
			
		||||
    const f = new CADFiler()
 | 
			
		||||
    f.Clear()
 | 
			
		||||
    f.Write(1)
 | 
			
		||||
    f.WriteObject(pls)
 | 
			
		||||
    const test = JSON.stringify(f.Data)
 | 
			
		||||
    copyTextToClipboard(test)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static write2PolyLine(pls, pls2) {
 | 
			
		||||
    const f = new CADFiler()
 | 
			
		||||
    f.Clear()
 | 
			
		||||
    f.Write(2)
 | 
			
		||||
    f.WriteObject(pls)
 | 
			
		||||
    f.WriteObject(pls2)
 | 
			
		||||
    const test = JSON.stringify(f.Data)
 | 
			
		||||
    copyTextToClipboard(test)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								tests/dev1/dataHandle/common/PlacePosition.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								tests/dev1/dataHandle/common/PlacePosition.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								tests/dev1/dataHandle/common/PlacePostionHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/dev1/dataHandle/common/PlacePostionHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								tests/dev1/dataHandle/common/Random.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/dev1/dataHandle/common/Random.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import { FixIndex } from './Util'
 | 
			
		||||
 | 
			
		||||
export function RandomIndex(count: number, exclude?: number): number
 | 
			
		||||
{
 | 
			
		||||
  let index = Math.floor(Math.random() * count)
 | 
			
		||||
  if (index === count)
 | 
			
		||||
    index = 0
 | 
			
		||||
  if (index === exclude)
 | 
			
		||||
    index = FixIndex(index + 1, count)
 | 
			
		||||
  return index
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								tests/dev1/dataHandle/common/Sleep.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/dev1/dataHandle/common/Sleep.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export async function Sleep(time: number)
 | 
			
		||||
{
 | 
			
		||||
  return new Promise((res) =>
 | 
			
		||||
  {
 | 
			
		||||
    setTimeout(res, time)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								tests/dev1/dataHandle/common/Util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								tests/dev1/dataHandle/common/Util.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/** 判断两个值是否相等 */
 | 
			
		||||
export function equaln(v1: number, v2: number, fuzz = 1e-5)
 | 
			
		||||
{
 | 
			
		||||
  return Math.abs(v1 - v2) <= fuzz
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 修正数组索引号 */
 | 
			
		||||
export function FixIndex(index: number, arr: Array<any> | number)
 | 
			
		||||
{
 | 
			
		||||
  let count = (Array.isArray(arr)) ? arr.length : arr
 | 
			
		||||
  if (index < 0)
 | 
			
		||||
    return count + index
 | 
			
		||||
  else if (index >= count)
 | 
			
		||||
    return index - count
 | 
			
		||||
  else
 | 
			
		||||
    return index
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取数组最大元素的索引号
 | 
			
		||||
 * @param compart t2大于t1返回t2
 | 
			
		||||
 * @returns 索引
 | 
			
		||||
 */
 | 
			
		||||
export function Max<T>(arr: T[], compart: (t1: T, t2: T) => boolean): number
 | 
			
		||||
{
 | 
			
		||||
  let best: T = arr[0]
 | 
			
		||||
  let bestIndex = 0
 | 
			
		||||
  for (let i = 1; i < arr.length; i++)
 | 
			
		||||
  {
 | 
			
		||||
    let t1 = arr[i]
 | 
			
		||||
    if (compart(best, t1))
 | 
			
		||||
    {
 | 
			
		||||
      best = t1
 | 
			
		||||
      bestIndex = i
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return bestIndex
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								tests/dev1/dataHandle/common/Vector2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								tests/dev1/dataHandle/common/Vector2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										375
									
								
								tests/dev1/dataHandle/common/base/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								tests/dev1/dataHandle/common/base/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,375 @@
 | 
			
		||||
export class List<Item> extends Array<Item>
 | 
			
		||||
{
 | 
			
		||||
  /** 返回符合条件的第一个元素 */
 | 
			
		||||
  first(fn: (item: Item) => boolean): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    for (const item of this)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        return item
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的最后一元素 */
 | 
			
		||||
  last(fn: (t: Item) => boolean): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    for (let i = this.length - 1; i >= 0; i--)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(this[i]))
 | 
			
		||||
        return this[i]
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最大值 */
 | 
			
		||||
  max<T>(fn: (item: Item) => T): T
 | 
			
		||||
  {
 | 
			
		||||
    let maxV: T
 | 
			
		||||
    for (const i of this)
 | 
			
		||||
    {
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (maxV == null)
 | 
			
		||||
      {
 | 
			
		||||
        maxV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v > maxV)
 | 
			
		||||
        {
 | 
			
		||||
          maxV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return maxV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最小值 */
 | 
			
		||||
  min<T>(fn: (item: Item) => T): T
 | 
			
		||||
  {
 | 
			
		||||
    let minV: T
 | 
			
		||||
    for (const i of this)
 | 
			
		||||
    {
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (minV == null)
 | 
			
		||||
      {
 | 
			
		||||
        minV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v < minV)
 | 
			
		||||
        {
 | 
			
		||||
          minV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return minV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 累加 */
 | 
			
		||||
  sum(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    let v: number = 0
 | 
			
		||||
    for (const t of this)
 | 
			
		||||
    {
 | 
			
		||||
      v = v + fn(t)
 | 
			
		||||
    }
 | 
			
		||||
    return v
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 平均值 */
 | 
			
		||||
  avg(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let sum = this.sum(fn)
 | 
			
		||||
    return sum / this.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 满足条件的元素数量 */
 | 
			
		||||
  count(fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let c = 0
 | 
			
		||||
    for (const item of this)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        c = c + 1
 | 
			
		||||
    }
 | 
			
		||||
    return c
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FindMax<T>(fn: (item: Item) => T): Item
 | 
			
		||||
  {
 | 
			
		||||
    return this.reduce((a: Item, b: Item): Item => fn(a) > fn(b) ? a : b)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ArrayExt
 | 
			
		||||
{
 | 
			
		||||
  /** 返回满足条件的元素数量 */
 | 
			
		||||
  static count<Item>(list: Item[], fn: (item: Item) => boolean): number
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let c = 0
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item))
 | 
			
		||||
        c = c + 1
 | 
			
		||||
    }
 | 
			
		||||
    return c
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 移除 */
 | 
			
		||||
  static remove<Item>(list: Item[], obj: Item)
 | 
			
		||||
  {
 | 
			
		||||
    let index = list.findIndex(t => t == obj)
 | 
			
		||||
    if (index == -1)
 | 
			
		||||
      return
 | 
			
		||||
    list.splice(index, 1)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的第一个元素 */
 | 
			
		||||
  static first<Item>(list: Item[], fn: (item: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    if (orderFn1 == null)
 | 
			
		||||
    {
 | 
			
		||||
      for (const item of list)
 | 
			
		||||
      {
 | 
			
		||||
        if (fn(item))
 | 
			
		||||
          return item
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let minValue1: number
 | 
			
		||||
    let minValue2: number
 | 
			
		||||
    let minItem: Item
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v1 = orderFn1(item)
 | 
			
		||||
      let v2 = orderFn2 != null ? orderFn2(item) : 0
 | 
			
		||||
      if (minValue1 == null || v1 < minValue1 || (v1 == minValue1 && v2 < minValue2))
 | 
			
		||||
      {
 | 
			
		||||
        minValue1 = v1
 | 
			
		||||
        minValue2 = v2
 | 
			
		||||
        minItem = item
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return minItem
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回符合条件的最后一元素 */
 | 
			
		||||
  static last<Item>(list: Item[], fn: (t: Item) => boolean, orderFn1: (item: Item) => number = null, orderFn2: (item: Item) => number = null): Item
 | 
			
		||||
  {
 | 
			
		||||
    if (list.length == 0)
 | 
			
		||||
      return null
 | 
			
		||||
    if (orderFn1 == null)
 | 
			
		||||
    {
 | 
			
		||||
      for (let i = list.length - 1; i >= 0; i--)
 | 
			
		||||
      {
 | 
			
		||||
        if (fn(list[i]))
 | 
			
		||||
          return list[i]
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    let maxValue1: number
 | 
			
		||||
    let maxValue2: number
 | 
			
		||||
    let maxItem: Item
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (fn(item) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v1 = orderFn1(item)
 | 
			
		||||
      let v2 = orderFn2 ? orderFn2(item) : 0
 | 
			
		||||
      if (maxValue1 == null || v1 > maxValue1 || (v1 == maxValue1 && v2 > maxValue2))
 | 
			
		||||
      {
 | 
			
		||||
        maxValue1 = v1
 | 
			
		||||
        maxValue2 = v2
 | 
			
		||||
        maxItem = item
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return maxItem
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 取最大值 */
 | 
			
		||||
  static max<T1, T2>(list: T1[], fn: (item: T1) => T2, whereF: (item: T1) => boolean = null, defaultV: T2 = null): T2
 | 
			
		||||
  {
 | 
			
		||||
    let maxV: T2
 | 
			
		||||
    for (const i of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (whereF && whereF(i) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (maxV == undefined)
 | 
			
		||||
      {
 | 
			
		||||
        maxV = fn(i)
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v > maxV)
 | 
			
		||||
        {
 | 
			
		||||
          maxV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (maxV != undefined)
 | 
			
		||||
      return maxV
 | 
			
		||||
    return defaultV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 最小值 */
 | 
			
		||||
  static min<Item, T>(list: Item[], fn: (item: Item) => T, whereF: (item: Item) => boolean = null, defaultV: T = null): T
 | 
			
		||||
  {
 | 
			
		||||
    let minV: T
 | 
			
		||||
    for (const i of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (whereF && whereF(i) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      let v = fn(i)
 | 
			
		||||
      if (minV == undefined)
 | 
			
		||||
      {
 | 
			
		||||
        minV = v
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        if (v < minV)
 | 
			
		||||
        {
 | 
			
		||||
          minV = v
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (minV != undefined)
 | 
			
		||||
      return minV
 | 
			
		||||
    return defaultV
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 累加 */
 | 
			
		||||
  static sum<Item>(list: Item[], fn: (item: Item) => number, wn?: (item: Item) => boolean): number
 | 
			
		||||
  {
 | 
			
		||||
    let v: number = 0
 | 
			
		||||
    for (const t of list)
 | 
			
		||||
    {
 | 
			
		||||
      if (wn && wn(t) == false)
 | 
			
		||||
        continue
 | 
			
		||||
      v = v + fn(t)
 | 
			
		||||
    }
 | 
			
		||||
    return v
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 平均值 */
 | 
			
		||||
  static avg<Item>(list: Item[], fn: (item: Item) => number): number
 | 
			
		||||
  {
 | 
			
		||||
    if (this.length == 0)
 | 
			
		||||
      return 0
 | 
			
		||||
    let sum = ArrayExt.sum(list, fn)
 | 
			
		||||
    return sum / this.length
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 排序 */
 | 
			
		||||
  static sortBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null)
 | 
			
		||||
  {
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) - fn(b))
 | 
			
		||||
    else
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 降序 排序 */
 | 
			
		||||
  static sortByDescending<Item>(list: Item[], fn: (item: Item) => number)
 | 
			
		||||
  {
 | 
			
		||||
    list.sort((a: Item, b: Item): number => fn(b) - fn(a))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 排序成新的数组 */
 | 
			
		||||
  static orderBy<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.concat([])
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return newList.sort((a: Item, b: Item): number => fn(a) - fn(b))
 | 
			
		||||
    else
 | 
			
		||||
      return newList.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(a) - fn2(b)) : fn(a) - fn(b))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 降序成新的数组 */
 | 
			
		||||
  static orderByDescending<Item>(list: Item[], fn: (item: Item) => number, fn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.concat([])
 | 
			
		||||
    if (fn2 == null)
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(b) - fn(a))
 | 
			
		||||
    else
 | 
			
		||||
      return list.sort((a: Item, b: Item): number => fn(a) == fn(b) ? (fn2(b) - fn2(a)) : fn(b) - fn(a))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 分组 */
 | 
			
		||||
  static groupBy<Item, gT>(list: Item[], fn: (item: Item) => gT): GroupItem<gT, Item>[]
 | 
			
		||||
  {
 | 
			
		||||
    let groups = new Array<GroupItem<gT, Item>>()
 | 
			
		||||
 | 
			
		||||
    for (const item of list)
 | 
			
		||||
    {
 | 
			
		||||
      let key = fn(item)
 | 
			
		||||
      let group = groups.find(t => t.key == key)
 | 
			
		||||
      if (group == null)
 | 
			
		||||
      {
 | 
			
		||||
        group = new GroupItem(key)
 | 
			
		||||
        groups.push(group)
 | 
			
		||||
      }
 | 
			
		||||
      group.push(item)
 | 
			
		||||
    }
 | 
			
		||||
    return groups
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 选择
 | 
			
		||||
   * let newObjectList = ArrayExt.Select(list,t=>({pA:t.name, pB:t.age + "_"+ t.month}) ) ;
 | 
			
		||||
   */
 | 
			
		||||
  static Select<Item, rT>(list: Item[], fn: (item: Item) => rT): rT[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = new Array<rT>()
 | 
			
		||||
    for (const t of list)
 | 
			
		||||
    {
 | 
			
		||||
      newList.push(fn(t))
 | 
			
		||||
    }
 | 
			
		||||
    return newList
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 过来,并按顺序排序 */
 | 
			
		||||
  static where<Item>(list: Item[], whereFn: (item: Item) => boolean, orderfn1: (item: Item) => number = null, orderfn2: (item: Item) => number = null): Item[]
 | 
			
		||||
  {
 | 
			
		||||
    let newList = list.filter(whereFn)
 | 
			
		||||
    if (orderfn1 == null && orderfn2 == null)
 | 
			
		||||
      return newList
 | 
			
		||||
    return ArrayExt.sortBy(newList, orderfn1, orderfn2)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class GroupItem<gT, Item>
 | 
			
		||||
{
 | 
			
		||||
  key: gT
 | 
			
		||||
  get count() { return this.list.length }
 | 
			
		||||
  list: Item[]
 | 
			
		||||
 | 
			
		||||
  constructor(k: gT)
 | 
			
		||||
  {
 | 
			
		||||
    this.key = k
 | 
			
		||||
    this.list = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  push(d: Item)
 | 
			
		||||
  {
 | 
			
		||||
    this.list.push(d)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1627
									
								
								tests/dev1/dataHandle/common/base/CAD.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1627
									
								
								tests/dev1/dataHandle/common/base/CAD.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										490
									
								
								tests/dev1/dataHandle/common/base/Dxf.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								tests/dev1/dataHandle/common/base/Dxf.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,490 @@
 | 
			
		||||
export class DxfWritor
 | 
			
		||||
{
 | 
			
		||||
  layers: WeakSet<Layer>
 | 
			
		||||
  activeLayer: Layer
 | 
			
		||||
  lineTypes: WeakSet<LineType>
 | 
			
		||||
  offX = 0
 | 
			
		||||
  offY = 0 // 基点
 | 
			
		||||
  constructor()
 | 
			
		||||
  {
 | 
			
		||||
    this.layers = new WeakSet()
 | 
			
		||||
    this.activeLayer = null
 | 
			
		||||
    this.lineTypes = new WeakSet()
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < DrawingType.LINE_TYPES.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      this.addLineType(
 | 
			
		||||
        DrawingType.LINE_TYPES[i].name,
 | 
			
		||||
        DrawingType.LINE_TYPES[i].description,
 | 
			
		||||
        DrawingType.LINE_TYPES[i].elements,
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < DrawingType.LAYERS.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      this.addLayer(
 | 
			
		||||
        DrawingType.LAYERS[i].name,
 | 
			
		||||
        DrawingType.LAYERS[i].colorNumber,
 | 
			
		||||
        DrawingType.LAYERS[i].lineTypeName,
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.setActiveLayer('0')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} name
 | 
			
		||||
   * @param {string} description
 | 
			
		||||
   * @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a
 | 
			
		||||
   */
 | 
			
		||||
  addLineType(name, description, elements)
 | 
			
		||||
  {
 | 
			
		||||
    this.lineTypes[name] = new LineType(name, description, elements)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addLayer(name, colorNumber, lineTypeName)
 | 
			
		||||
  {
 | 
			
		||||
    this.layers[name] = new Layer(name, colorNumber, lineTypeName)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 设置当前图层 白=0, 红=1, 黄=2, 绿=3,CYAN=4,BLUE=5,MAGENTA=6 */
 | 
			
		||||
  setActiveLayer(name)
 | 
			
		||||
  {
 | 
			
		||||
    this.activeLayer = this.layers[name]
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setOffset(x1, y1)
 | 
			
		||||
  {
 | 
			
		||||
    this.offX = x1
 | 
			
		||||
    this.offY = y1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clear()
 | 
			
		||||
  {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawLine(x1, y1, x2, y2)
 | 
			
		||||
  {
 | 
			
		||||
    this.activeLayer.addShape(new Line(x1 + this.offX, y1 + this.offY, x2 + this.offX, y2 + this.offY))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawRect(x1, y1, w, h)
 | 
			
		||||
  {
 | 
			
		||||
    x1 = x1 + this.offX
 | 
			
		||||
    y1 = y1 + this.offY
 | 
			
		||||
    this.activeLayer.addShape(new Line(x1, y1, x1 + w, y1))
 | 
			
		||||
    this.activeLayer.addShape(new Line(x1 + w, y1, x1 + w, y1 + h))
 | 
			
		||||
    this.activeLayer.addShape(new Line(x1 + w, y1 + h, x1, y1 + h))
 | 
			
		||||
    this.activeLayer.addShape(new Line(x1, y1 + h, x1, y1))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - Center x
 | 
			
		||||
   * @param {number} y1 - Center y
 | 
			
		||||
   * @param {number} r - radius
 | 
			
		||||
   * @param {number} startAngle - degree
 | 
			
		||||
   * @param {number} endAngle - degree
 | 
			
		||||
   */
 | 
			
		||||
  drawArc(x1, y1, r, startAngle, endAngle)
 | 
			
		||||
  {
 | 
			
		||||
    this.activeLayer.addShape(new Arc(x1 + this.offX, y1 + this.offY, r, startAngle, endAngle))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - Center x
 | 
			
		||||
   * @param {number} y1 - Center y
 | 
			
		||||
   * @param {number} r - radius
 | 
			
		||||
   */
 | 
			
		||||
  drawCircle(x1, y1, r)
 | 
			
		||||
  {
 | 
			
		||||
    this.activeLayer.addShape(new Circle(x1 + this.offX, y1 + this.offY, r))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - x
 | 
			
		||||
   * @param {number} y1 - y
 | 
			
		||||
   * @param {number} height - Text height
 | 
			
		||||
   * @param {number} rotation - Text rotation
 | 
			
		||||
   * @param {string} value - the string itself
 | 
			
		||||
   */
 | 
			
		||||
  drawText(x1, y1, height, rotation, value)
 | 
			
		||||
  {
 | 
			
		||||
    this.activeLayer.addShape(new Text(x1 + this.offX, y1 + this.offY, height, rotation, value))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ]
 | 
			
		||||
   */
 | 
			
		||||
  drawPolyline(points, isClose = false)
 | 
			
		||||
  {
 | 
			
		||||
    for (const p of points)
 | 
			
		||||
    {
 | 
			
		||||
      p.x += this.offX
 | 
			
		||||
      p.y += this.offY
 | 
			
		||||
    }
 | 
			
		||||
    this.activeLayer.addShape(new Polyline(points, isClose))
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _getDxfLtypeTable()
 | 
			
		||||
  {
 | 
			
		||||
    let s = '0\nTABLE\n' // start table
 | 
			
		||||
    s += '2\nLTYPE\n' // name table as LTYPE table
 | 
			
		||||
 | 
			
		||||
    for (let lineTypeName in this.lineTypes)
 | 
			
		||||
    {
 | 
			
		||||
      s += this.lineTypes[lineTypeName].toDxfString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s += '0\nENDTAB\n' // end table
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _getDxfLayerTable()
 | 
			
		||||
  {
 | 
			
		||||
    let s = '0\nTABLE\n' // start table
 | 
			
		||||
    s += '2\nLAYER\n' // name table as LAYER table
 | 
			
		||||
 | 
			
		||||
    for (let layerName in this.layers)
 | 
			
		||||
    {
 | 
			
		||||
      s += this.layers[layerName].toDxfString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s += '0\nENDTAB\n'
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    let s = ''
 | 
			
		||||
 | 
			
		||||
    // start section
 | 
			
		||||
    s += '0\nSECTION\n'
 | 
			
		||||
    // name section as TABLES section
 | 
			
		||||
    s += '2\nTABLES\n'
 | 
			
		||||
 | 
			
		||||
    s += this._getDxfLtypeTable()
 | 
			
		||||
    s += this._getDxfLayerTable()
 | 
			
		||||
 | 
			
		||||
    // end section
 | 
			
		||||
    s += '0\nENDSEC\n'
 | 
			
		||||
 | 
			
		||||
    // ENTITES section
 | 
			
		||||
    s += '0\nSECTION\n'
 | 
			
		||||
    s += '2\nENTITIES\n'
 | 
			
		||||
 | 
			
		||||
    for (let layerName in this.layers)
 | 
			
		||||
    {
 | 
			
		||||
      let layer = this.layers[layerName]
 | 
			
		||||
      s += layer.shapesToDxf()
 | 
			
		||||
      // let shapes = layer.getShapes();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s += '0\nENDSEC\n'
 | 
			
		||||
 | 
			
		||||
    // close file
 | 
			
		||||
    s += '0\nEOF'
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace DrawingType
 | 
			
		||||
{
 | 
			
		||||
  // AutoCAD Color Index (ACI)
 | 
			
		||||
  // http://sub-atomic.com/~moses/acadcolors.html
 | 
			
		||||
  export const ACI = {
 | 
			
		||||
    LAYER: 0,
 | 
			
		||||
    RED: 1,
 | 
			
		||||
    YELLOW: 2,
 | 
			
		||||
    GREEN: 3,
 | 
			
		||||
    CYAN: 4,
 | 
			
		||||
    BLUE: 5,
 | 
			
		||||
    MAGENTA: 6,
 | 
			
		||||
    WHITE: 7,
 | 
			
		||||
  }
 | 
			
		||||
  export const LINE_TYPES = [
 | 
			
		||||
    { name: 'CONTINUOUS', description: '______', elements: [] },
 | 
			
		||||
    { name: 'DASHED', description: '_ _ _ ', elements: [5.0, -5.0] },
 | 
			
		||||
    { name: 'DOTTED', description: '. . . ', elements: [0.0, -5.0] },
 | 
			
		||||
  ]
 | 
			
		||||
  export const LAYERS = [
 | 
			
		||||
    { name: '0', colorNumber: ACI.WHITE, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '1', colorNumber: ACI.RED, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '2', colorNumber: ACI.YELLOW, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '3', colorNumber: ACI.GREEN, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '4', colorNumber: ACI.CYAN, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '5', colorNumber: ACI.BLUE, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
    { name: '6', colorNumber: ACI.MAGENTA, lineTypeName: 'CONTINUOUS' },
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class LineType
 | 
			
		||||
{
 | 
			
		||||
  name: string
 | 
			
		||||
  description: string
 | 
			
		||||
  elements: any[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} name
 | 
			
		||||
   * @param {string} description
 | 
			
		||||
   * @param {Array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a
 | 
			
		||||
   */
 | 
			
		||||
  constructor(name, description, elements)
 | 
			
		||||
  {
 | 
			
		||||
    this.name = name
 | 
			
		||||
    this.description = description
 | 
			
		||||
    this.elements = elements
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @link https://www.autodesk.com/techpubs/autocad/acadr14/dxf/ltype_al_u05_c.htm
 | 
			
		||||
   */
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    let s = '0\nLTYPE\n'
 | 
			
		||||
    s += '72\n65\n'
 | 
			
		||||
    s += '70\n64\n'
 | 
			
		||||
    s += `2\n${this.name}\n`
 | 
			
		||||
    s += `3\n${this.description}\n`
 | 
			
		||||
    s += `73\n${this.elements.length}\n`
 | 
			
		||||
    s += `40\n${this.getElementsSum()}\n`
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < this.elements.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      s += `49\n${this.elements[i]}\n`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getElementsSum()
 | 
			
		||||
  {
 | 
			
		||||
    let sum = 0
 | 
			
		||||
    for (let i = 0; i < this.elements.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      sum += Math.abs(this.elements[i])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return sum
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Layer
 | 
			
		||||
{
 | 
			
		||||
  name: string
 | 
			
		||||
  colorNumber: string
 | 
			
		||||
  lineTypeName: string
 | 
			
		||||
  shapes: any[]
 | 
			
		||||
  constructor(name, colorNumber, lineTypeName)
 | 
			
		||||
  {
 | 
			
		||||
    this.name = name
 | 
			
		||||
    this.colorNumber = colorNumber
 | 
			
		||||
    this.lineTypeName = lineTypeName
 | 
			
		||||
    this.shapes = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    let s = '0\nLAYER\n'
 | 
			
		||||
    s += '70\n64\n'
 | 
			
		||||
    s += `2\n${this.name}\n`
 | 
			
		||||
    s += `62\n${this.colorNumber}\n`
 | 
			
		||||
    s += `6\n${this.lineTypeName}\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addShape(shape)
 | 
			
		||||
  {
 | 
			
		||||
    this.shapes.push(shape)
 | 
			
		||||
    shape.layer = this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getShapes()
 | 
			
		||||
  {
 | 
			
		||||
    return this.shapes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  shapesToDxf()
 | 
			
		||||
  {
 | 
			
		||||
    let s = ''
 | 
			
		||||
    for (let i = 0; i < this.shapes.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      s += this.shapes[i].toDxfString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Arc
 | 
			
		||||
{
 | 
			
		||||
  x1: number
 | 
			
		||||
  y1: number
 | 
			
		||||
  r: number
 | 
			
		||||
  startAngle: number
 | 
			
		||||
  endAngle: number
 | 
			
		||||
  layer: Layer
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - Center x
 | 
			
		||||
   * @param {number} y1 - Center y
 | 
			
		||||
   * @param {number} r - radius
 | 
			
		||||
   * @param {number} startAngle - degree
 | 
			
		||||
   * @param {number} endAngle - degree
 | 
			
		||||
   */
 | 
			
		||||
  constructor(x1, y1, r, startAngle, endAngle)
 | 
			
		||||
  {
 | 
			
		||||
    this.x1 = x1
 | 
			
		||||
    this.y1 = y1
 | 
			
		||||
    this.r = r
 | 
			
		||||
    this.startAngle = startAngle
 | 
			
		||||
    this.endAngle = endAngle
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm
 | 
			
		||||
    let s = `0\nARC\n`
 | 
			
		||||
    s += `8\n${this.layer.name}\n`
 | 
			
		||||
    s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
 | 
			
		||||
    s += `40\n${this.r}\n50\n${this.startAngle}\n51\n${this.endAngle}\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Circle
 | 
			
		||||
{
 | 
			
		||||
  x1: number
 | 
			
		||||
  y1: number
 | 
			
		||||
  r: number
 | 
			
		||||
  layer: Layer
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - Center x
 | 
			
		||||
   * @param {number} y1 - Center y
 | 
			
		||||
   * @param {number} r - radius
 | 
			
		||||
   */
 | 
			
		||||
  constructor(x1, y1, r)
 | 
			
		||||
  {
 | 
			
		||||
    this.x1 = x1
 | 
			
		||||
    this.y1 = y1
 | 
			
		||||
    this.r = r
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/circle_al_u05_c.htm
 | 
			
		||||
    let s = `0\nCIRCLE\n`
 | 
			
		||||
    s += `8\n${this.layer.name}\n`
 | 
			
		||||
    s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
 | 
			
		||||
    s += `40\n${this.r}\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Line
 | 
			
		||||
{
 | 
			
		||||
  x1: number
 | 
			
		||||
  y1: number
 | 
			
		||||
  x2: number
 | 
			
		||||
  y2: number
 | 
			
		||||
  layer: Layer
 | 
			
		||||
  constructor(x1: number, y1: number, x2: number, y2: number)
 | 
			
		||||
  {
 | 
			
		||||
    this.x1 = x1
 | 
			
		||||
    this.y1 = y1
 | 
			
		||||
    this.x2 = x2
 | 
			
		||||
    this.y2 = y2
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/line_al_u05_c.htm
 | 
			
		||||
    let s = `0\nLINE\n`
 | 
			
		||||
    s += `8\n${this.layer.name}\n`
 | 
			
		||||
    s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
 | 
			
		||||
    s += `11\n${this.x2}\n21\n${this.y2}\n31\n0\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Polyline
 | 
			
		||||
{
 | 
			
		||||
  points: any[]
 | 
			
		||||
  isClose = false
 | 
			
		||||
  layer: Layer
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {Array} points - Array of points like [ [x1, y1,r], [x2, y2,r]... ]
 | 
			
		||||
   */
 | 
			
		||||
  constructor(points, isClose)
 | 
			
		||||
  {
 | 
			
		||||
    this.points = points
 | 
			
		||||
    this.isClose = isClose
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acad2000/dxf/polyline_dxf_06.htm
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acad2000/dxf/vertex_dxf_06.htm
 | 
			
		||||
    let s = `0\nPOLYLINE\n`
 | 
			
		||||
    s += `8\n${this.layer.name}\n`
 | 
			
		||||
    s += `66\n1\n70\n${this.isClose ? 1 : 0}\n`
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < this.points.length; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      s += `0\nVERTEX\n`
 | 
			
		||||
      s += `8\n${this.layer.name}\n`
 | 
			
		||||
      s += `70\n0\n`
 | 
			
		||||
      s += `10\n${this.points[i].x}\n20\n${this.points[i].y}\n42\n${this.points[i].r}\n`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s += `0\nSEQEND\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Text
 | 
			
		||||
{
 | 
			
		||||
  x1: number
 | 
			
		||||
  y1: number
 | 
			
		||||
  height: number
 | 
			
		||||
  rotation: number
 | 
			
		||||
  value: string
 | 
			
		||||
  layer: Layer
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {number} x1 - x
 | 
			
		||||
   * @param {number} y1 - y
 | 
			
		||||
   * @param {number} height - Text height
 | 
			
		||||
   * @param {number} rotation - Text rotation
 | 
			
		||||
   * @param {string} value - the string itself
 | 
			
		||||
   */
 | 
			
		||||
  constructor(x1, y1, height, rotation, value)
 | 
			
		||||
  {
 | 
			
		||||
    this.x1 = x1
 | 
			
		||||
    this.y1 = y1
 | 
			
		||||
    this.height = height
 | 
			
		||||
    this.rotation = rotation
 | 
			
		||||
    this.value = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toDxfString()
 | 
			
		||||
  {
 | 
			
		||||
    // https://www.autodesk.com/techpubs/autocad/acadr14/dxf/text_al_u05_c.htm
 | 
			
		||||
    let s = `0\nTEXT\n`
 | 
			
		||||
    s += `8\n${this.layer.name}\n`
 | 
			
		||||
    s += `1\n${this.value}\n`
 | 
			
		||||
    s += `10\n${this.x1}\n20\n${this.y1}\n30\n0\n`
 | 
			
		||||
    s += `40\n${this.height}\n50\n${this.rotation}\n`
 | 
			
		||||
    return s
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								tests/dev1/dataHandle/common/base/File.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								tests/dev1/dataHandle/common/base/File.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class textFile
 | 
			
		||||
{
 | 
			
		||||
    /**保存单文件 text类型的 */
 | 
			
		||||
    static saveFile(filename: string, content: string)
 | 
			
		||||
    {
 | 
			
		||||
        let blob = new Blob([content], { type: "text/plain;charset=utf-8" });
 | 
			
		||||
        this.saveAs(filename, blob);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**读取文件 选择文件组件,读取文档处理函数 */
 | 
			
		||||
    static readFile(eleFile, fn_doText, fileType = "", fn_msg = null)
 | 
			
		||||
    {
 | 
			
		||||
        let noEleFile = !(eleFile);
 | 
			
		||||
        if (noEleFile)
 | 
			
		||||
        {
 | 
			
		||||
            eleFile = document.createElement('input');
 | 
			
		||||
            eleFile.type = 'file';
 | 
			
		||||
            eleFile.accept = 'text/*';
 | 
			
		||||
            eleFile.hidden = true;
 | 
			
		||||
            document.body.appendChild(eleFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (fileType && fileType != "") eleFile.accept = fileType;
 | 
			
		||||
        let reader = new FileReader();
 | 
			
		||||
        reader.onload = function (event)
 | 
			
		||||
        {
 | 
			
		||||
            let text = event.target["result"];
 | 
			
		||||
            if (fn_doText) fn_doText(text);
 | 
			
		||||
        };
 | 
			
		||||
        // 选择文件
 | 
			
		||||
        eleFile.onchange = function (event)
 | 
			
		||||
        {
 | 
			
		||||
            let file = event.target.files[0];
 | 
			
		||||
            if (file)
 | 
			
		||||
            {
 | 
			
		||||
                reader.readAsText(file);
 | 
			
		||||
            }
 | 
			
		||||
            if (fn_msg != null)
 | 
			
		||||
            {
 | 
			
		||||
                fn_msg();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        eleFile.click();
 | 
			
		||||
 | 
			
		||||
        if (noEleFile)
 | 
			
		||||
        {
 | 
			
		||||
            document.body.removeChild(eleFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getStringFromFile(eleFile, fileType = "", fn_msg = null): string
 | 
			
		||||
    {
 | 
			
		||||
        let noEleFile = !(eleFile);
 | 
			
		||||
        if (noEleFile)
 | 
			
		||||
        {
 | 
			
		||||
            eleFile = document.createElement('input');
 | 
			
		||||
            eleFile.type = 'file';
 | 
			
		||||
            eleFile.accept = 'text/*';
 | 
			
		||||
            eleFile.hidden = true;
 | 
			
		||||
            document.body.appendChild(eleFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (fileType && fileType != "") eleFile.accept = fileType;
 | 
			
		||||
        let reader = new FileReader();
 | 
			
		||||
        reader.onload = function (event)
 | 
			
		||||
        {
 | 
			
		||||
            let text = event.target["result"];
 | 
			
		||||
            return text;
 | 
			
		||||
        };
 | 
			
		||||
        // 选择文件
 | 
			
		||||
        eleFile.onchange = function (event)
 | 
			
		||||
        {
 | 
			
		||||
            let file = event.target.files[0];
 | 
			
		||||
            if (file)
 | 
			
		||||
            {
 | 
			
		||||
                reader.readAsText(file);
 | 
			
		||||
            }
 | 
			
		||||
            if (fn_msg != null)
 | 
			
		||||
            {
 | 
			
		||||
                fn_msg();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        eleFile.click();
 | 
			
		||||
 | 
			
		||||
        if (noEleFile)
 | 
			
		||||
        {
 | 
			
		||||
            document.body.removeChild(eleFile);
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    /**保存文件 */
 | 
			
		||||
    static saveAs(fileName: string, data: Blob)
 | 
			
		||||
    {
 | 
			
		||||
        // 创建隐藏的可下载链接
 | 
			
		||||
        let eleLink = document.createElement('a');
 | 
			
		||||
        eleLink.download = fileName;
 | 
			
		||||
        eleLink.style.display = 'none';
 | 
			
		||||
        eleLink.href = URL.createObjectURL(data);
 | 
			
		||||
        // 触发点击
 | 
			
		||||
        document.body.appendChild(eleLink);
 | 
			
		||||
        eleLink.click();
 | 
			
		||||
        // 然后移除
 | 
			
		||||
        document.body.removeChild(eleLink);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static readFile2()
 | 
			
		||||
    {
 | 
			
		||||
        let rf = document.createElement('input');
 | 
			
		||||
        rf.type = 'file';
 | 
			
		||||
        rf.accept = 'text/*';
 | 
			
		||||
        rf.hidden = true;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        document.body.removeChild(rf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								tests/dev1/dataHandle/common/base/MaskedNumberRange.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								tests/dev1/dataHandle/common/base/MaskedNumberRange.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
/** 文本表示的数字取值范围,比如 : "2,3,5-8" 表示 
 | 
			
		||||
 * () => 大于等于  小于等于  
 | 
			
		||||
 * [] => 大于  小于
 | 
			
		||||
*/
 | 
			
		||||
export class MaskedNumberRange {
 | 
			
		||||
  maskedNumbers: any[]
 | 
			
		||||
  constructor(strRs: string) {
 | 
			
		||||
    let rs = []
 | 
			
		||||
    let strs = strRs.split(',')
 | 
			
		||||
    for (let str of strs) {
 | 
			
		||||
      if (str.trim() == '')
 | 
			
		||||
        continue
 | 
			
		||||
      let n = Number(str)
 | 
			
		||||
 | 
			
		||||
      let s_flag = 'in'
 | 
			
		||||
      let e_flag = 'in'
 | 
			
		||||
      if (!Number.isNaN(n)) {
 | 
			
		||||
        rs.push({ s: n, e: n, s_flag, e_flag })
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        let zys = str.split('-')
 | 
			
		||||
        if (zys.length != 2)
 | 
			
		||||
          continue
 | 
			
		||||
        if (zys[0].trim() == '' || zys[1].trim() == '')
 | 
			
		||||
          continue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let start :any = zys[0]
 | 
			
		||||
        let end :any = zys[1]
 | 
			
		||||
        if (zys[0].trim().includes('(')) {
 | 
			
		||||
          s_flag = 'notIn'
 | 
			
		||||
          start = start.replaceAll('(', '')
 | 
			
		||||
        } else if (zys[0].trim().includes('[')) {
 | 
			
		||||
          s_flag = 'in'
 | 
			
		||||
          start = start.replaceAll('[', '')
 | 
			
		||||
        } 
 | 
			
		||||
        
 | 
			
		||||
        if (start == 'x') {
 | 
			
		||||
          s_flag = 'infinite'
 | 
			
		||||
        }else{
 | 
			
		||||
          start = Number(start)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (zys[1].trim().includes(')')) {
 | 
			
		||||
          e_flag = 'notIn'
 | 
			
		||||
          end = end.replaceAll(')', '')
 | 
			
		||||
        } else if (zys[1].trim().includes(']')) {
 | 
			
		||||
          e_flag = 'in'
 | 
			
		||||
          end = end.replaceAll(']', '')
 | 
			
		||||
        } 
 | 
			
		||||
        
 | 
			
		||||
        if (end == 'x') {
 | 
			
		||||
          e_flag = 'infinite'
 | 
			
		||||
        }else{
 | 
			
		||||
          end = Number(end)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let s = start // Number(zys[0])
 | 
			
		||||
        let e = end //Number(zys[1])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        rs.push({ s: s, e: e, s_flag, e_flag })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.maskedNumbers = rs
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 参数是否在给定范围内 */
 | 
			
		||||
  isInRange(r: number): boolean {
 | 
			
		||||
    let res = false
 | 
			
		||||
 | 
			
		||||
    for (let se of this.maskedNumbers) {
 | 
			
		||||
 | 
			
		||||
      if (se.s_flag == 'infinite') {
 | 
			
		||||
        // 无穷  不判断
 | 
			
		||||
        res = true
 | 
			
		||||
      } else if (se.s_flag == 'notIn') {
 | 
			
		||||
        if (r > se.s) {
 | 
			
		||||
          res = true
 | 
			
		||||
        }
 | 
			
		||||
      } else if (se.s_flag == 'in') {
 | 
			
		||||
        if (r >= se.s) {
 | 
			
		||||
          res = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (res == true) {
 | 
			
		||||
        let res1 = false
 | 
			
		||||
 | 
			
		||||
        if (se.e_flag == 'infinite') {
 | 
			
		||||
          res1 = true
 | 
			
		||||
        } else if (se.e_flag == 'notIn') {
 | 
			
		||||
          if (r < se.e) {
 | 
			
		||||
            res1 = true
 | 
			
		||||
          }
 | 
			
		||||
        } else if (se.e_flag == 'in') {
 | 
			
		||||
          if (r <= se.e) {
 | 
			
		||||
            res1 = true
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        res = res && res1
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if(res == true){
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // if (r > se.s && r < se.e)
 | 
			
		||||
      //   return true
 | 
			
		||||
    }
 | 
			
		||||
    return res
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								tests/dev1/dataHandle/common/base/MathComm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								tests/dev1/dataHandle/common/base/MathComm.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
/** 相等, 相差小于diff */
 | 
			
		||||
export function equal(a: number, b: number, diff = 0.001): boolean
 | 
			
		||||
{
 | 
			
		||||
  return Math.abs(a - b) < diff
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** ab相差dis */
 | 
			
		||||
export function isDiffer(a: number, b: number, dis: number, diff = 0.001): boolean
 | 
			
		||||
{
 | 
			
		||||
  let rdis = Math.abs(a - b)
 | 
			
		||||
  return rdis > dis - diff && rdis < dis + diff
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 两点间距离 */
 | 
			
		||||
export function getDis2(p1, p2): number
 | 
			
		||||
{
 | 
			
		||||
  let x1 = p1.pointX || p1.X || p1.x || 0
 | 
			
		||||
  let y1 = p1.pointY || p1.Y || p1.y || 0
 | 
			
		||||
  let x2 = p2.pointX || p2.X || p2.x || 0
 | 
			
		||||
  let y2 = p2.pointY || p2.Y || p2.y || 0
 | 
			
		||||
  return getDis(x1, y1, x2, y2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 两点间距离 */
 | 
			
		||||
export function getDis(x1: number, y1: number, x2: number, y2: number): number
 | 
			
		||||
{
 | 
			
		||||
  let xt = x1 - x2
 | 
			
		||||
  let yt = y1 - y2
 | 
			
		||||
  return Math.sqrt(xt * xt + yt * yt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 两点间弧度半径 */
 | 
			
		||||
export function getR(x1: number, y1: number, x2: number, y2: number, bul: number): number
 | 
			
		||||
{
 | 
			
		||||
  if (bul == 0)
 | 
			
		||||
    return 0
 | 
			
		||||
  let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2
 | 
			
		||||
  return 0.5 * d * (1 + bul ** 2) / bul
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取两点间的半径 */
 | 
			
		||||
export function getR2(p1, p2): number
 | 
			
		||||
{
 | 
			
		||||
  let bul = p1.Curve || p1.bul || 0
 | 
			
		||||
  if (bul == 0)
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
  let x1 = p1.pointX || p1.X || p1.x
 | 
			
		||||
  let y1 = p1.pointY || p1.Y || p1.y
 | 
			
		||||
  let x2 = p2.pointX || p2.X || p2.x
 | 
			
		||||
  let y2 = p2.pointY || p2.Y || p2.y
 | 
			
		||||
 | 
			
		||||
  return getR(x1, y1, x2, y2, bul)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 截取小数点 */
 | 
			
		||||
export function round(n: number, bit = 3): number
 | 
			
		||||
{
 | 
			
		||||
  if (bit < 1)
 | 
			
		||||
    return n
 | 
			
		||||
  let tt = 10 ** bit
 | 
			
		||||
  return Math.round(n * tt) / tt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获得四点p{x,y}形成的矩形{x,y,w,l};
 | 
			
		||||
 *  不是矩形 返回null
 | 
			
		||||
 */
 | 
			
		||||
export function getRect(p0, p1, p2, p3): any
 | 
			
		||||
{
 | 
			
		||||
  if (equal(p0.x, p1.x) == false && equal(p0.y, p1.y) == false)
 | 
			
		||||
    return null
 | 
			
		||||
 | 
			
		||||
  // 中心点
 | 
			
		||||
  let xc = (p0.x + p1.x + p2.x + p3.x) / 4
 | 
			
		||||
  let yc = (p0.y + p1.y + p2.y + p3.y) / 4
 | 
			
		||||
 | 
			
		||||
  let dis0 = getDis(p0.x, p0.y, xc, yc)
 | 
			
		||||
  let dis1 = getDis(p1.x, p1.y, xc, yc)
 | 
			
		||||
  let dis2 = getDis(p2.x, p2.y, xc, yc)
 | 
			
		||||
  let dis3 = getDis(p3.x, p3.y, xc, yc)
 | 
			
		||||
 | 
			
		||||
  if (equal(dis0, dis1) && equal(dis0, dis2) && equal(dis0, dis3))
 | 
			
		||||
  {
 | 
			
		||||
    let x = Math.min(p0.x, p1.x, p2.x, p3.x)
 | 
			
		||||
    let y = Math.min(p0.y, p1.y, p2.y, p3.y)
 | 
			
		||||
 | 
			
		||||
    let w = Math.max(p0.x, p1.x, p2.x, p3.x) - x
 | 
			
		||||
    let l = Math.max(p0.y, p1.y, p2.y, p3.y) - y
 | 
			
		||||
    if (w < 0.01 || l < 0.01)
 | 
			
		||||
      return null
 | 
			
		||||
    return { x, y, w, l }
 | 
			
		||||
  }
 | 
			
		||||
  return null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取两点的直线的一般式方程AX+BY+C=0  ABC */
 | 
			
		||||
export function getLineABC(x1: number, y1: number, x2: number, y2: number)
 | 
			
		||||
{
 | 
			
		||||
  let A = y2 - y1
 | 
			
		||||
  let B = x1 - x2
 | 
			
		||||
  let C = x2 * y1 - x1 * y2
 | 
			
		||||
  return { A, B, C }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取点 p(x,y) 到两点的线的距离 */
 | 
			
		||||
export function getDis_PointLine(x1: number, y1: number, x2: number, y2: number, x: number, y: number)
 | 
			
		||||
{
 | 
			
		||||
  let abc = getLineABC(x1, y1, x2, y2)
 | 
			
		||||
  let aabb = Math.sqrt(abc.A * abc.A + abc.B * abc.B)
 | 
			
		||||
  if (aabb == 0)
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
  return Math.abs(abc.A * x + abc.B * y + abc.C) / aabb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 删除重复点 */
 | 
			
		||||
export function removeRepeatPoint(pts, fuzz = 0.01)
 | 
			
		||||
{
 | 
			
		||||
  for (let n = pts.length - 1; n >= 0; n--) {
 | 
			
		||||
    let p = n - 1
 | 
			
		||||
    if (p < 0)
 | 
			
		||||
      p = pts.length - 1
 | 
			
		||||
 | 
			
		||||
    if (getDis2(pts[p], pts[n]) < fuzz)
 | 
			
		||||
      pts.splice(n, 1)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										170
									
								
								tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								tests/dev1/dataHandle/common/base/PlaceStyleHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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 }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								tests/dev1/dataHandle/common/base/StringBase64.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								tests/dev1/dataHandle/common/base/StringBase64.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
import gb2312 from './gb2312.json'
 | 
			
		||||
 | 
			
		||||
export class StringBase64 {
 | 
			
		||||
  static ToBase64_gb2312(str: string): string {
 | 
			
		||||
    let bin = this.toGB2312Bytes(str || '')
 | 
			
		||||
    let str64 = this.encode(bin)
 | 
			
		||||
    return str64
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static _table = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 | 
			
		||||
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
 | 
			
		||||
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
 | 
			
		||||
    'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/']
 | 
			
		||||
 | 
			
		||||
  /** 将二进制转换成base64 */
 | 
			
		||||
  static encode(bin: number[]): string {
 | 
			
		||||
    let codes = []
 | 
			
		||||
    let un = 0
 | 
			
		||||
    un = bin.length % 3
 | 
			
		||||
    if (un == 1)
 | 
			
		||||
      bin.push(0, 0)
 | 
			
		||||
    else if (un == 2)
 | 
			
		||||
      bin.push(0)
 | 
			
		||||
    for (let i = 2; i < bin.length; i += 3) {
 | 
			
		||||
      let c = bin[i - 2] << 16
 | 
			
		||||
      c |= bin[i - 1] << 8
 | 
			
		||||
      c |= bin[i]
 | 
			
		||||
      codes.push(this._table[c >> 18 & 0x3F])
 | 
			
		||||
      codes.push(this._table[c >> 12 & 0x3F])
 | 
			
		||||
      codes.push(this._table[c >> 6 & 0x3F])
 | 
			
		||||
      codes.push(this._table[c & 0x3F])
 | 
			
		||||
    }
 | 
			
		||||
    if (un >= 1) {
 | 
			
		||||
      codes[codes.length - 1] = '='
 | 
			
		||||
      bin.pop()
 | 
			
		||||
    }
 | 
			
		||||
    if (un == 1) {
 | 
			
		||||
      codes[codes.length - 2] = '='
 | 
			
		||||
      bin.pop()
 | 
			
		||||
    }
 | 
			
		||||
    return codes.join('')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 将utf8 转成gb2312字符串 */
 | 
			
		||||
  static toGb2312String(str1: string): string {
 | 
			
		||||
    let substr = ''
 | 
			
		||||
    let a = ''
 | 
			
		||||
    let b = ''
 | 
			
		||||
    let c = ''
 | 
			
		||||
    let i = -1
 | 
			
		||||
    i = str1.indexOf('%')
 | 
			
		||||
    if (i == -1) {
 | 
			
		||||
      return str1
 | 
			
		||||
    }
 | 
			
		||||
    while (i != -1) {
 | 
			
		||||
      if (i < 3) {
 | 
			
		||||
        substr = substr + str1.substr(0, i - 1)
 | 
			
		||||
        str1 = str1.substr(i + 1, str1.length - i)
 | 
			
		||||
        a = str1.substr(0, 2)
 | 
			
		||||
        str1 = str1.substr(2, str1.length - 2)
 | 
			
		||||
        if ((Number.parseInt(`0x${a}`) & 0x80) == 0) {
 | 
			
		||||
          substr = substr + String.fromCharCode(Number.parseInt(`0x${a}`))
 | 
			
		||||
        }
 | 
			
		||||
        else if ((Number.parseInt(`0x${a}`) & 0xE0) == 0xC0) { // two byte
 | 
			
		||||
          b = str1.substr(1, 2)
 | 
			
		||||
          str1 = str1.substr(3, str1.length - 3)
 | 
			
		||||
          let widechar = (Number.parseInt(`0x${a}`) & 0x1F) << 6
 | 
			
		||||
          widechar = widechar | (Number.parseInt(`0x${b}`) & 0x3F)
 | 
			
		||||
          substr = substr + String.fromCharCode(widechar)
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          b = str1.substr(1, 2)
 | 
			
		||||
          str1 = str1.substr(3, str1.length - 3)
 | 
			
		||||
          c = str1.substr(1, 2)
 | 
			
		||||
          str1 = str1.substr(3, str1.length - 3)
 | 
			
		||||
          let widechar = (Number.parseInt(`0x${a}`) & 0x0F) << 12
 | 
			
		||||
          widechar = widechar | ((Number.parseInt(`0x${b}`) & 0x3F) << 6)
 | 
			
		||||
          widechar = widechar | (Number.parseInt(`0x${c}`) & 0x3F)
 | 
			
		||||
          substr = substr + String.fromCharCode(widechar)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        substr = substr + str1.substring(0, i)
 | 
			
		||||
        str1 = str1.substring(i)
 | 
			
		||||
      }
 | 
			
		||||
      i = str1.indexOf('%')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return substr + str1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static _unicode2gb
 | 
			
		||||
  static getUnicode2gb() {
 | 
			
		||||
    if (this._unicode2gb == null) {
 | 
			
		||||
      this._unicode2gb = gb2312
 | 
			
		||||
    }
 | 
			
		||||
    return this._unicode2gb
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static toGB2312Bytes(str: string): number[] {
 | 
			
		||||
    let unicode2gb = this.getUnicode2gb()
 | 
			
		||||
    let res = []; let len = str.length
 | 
			
		||||
    for (let i = 0; i < len; i++) {
 | 
			
		||||
      let code = str.charCodeAt(i)
 | 
			
		||||
      if (code <= 0x007F) {
 | 
			
		||||
        res.push(code)
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        let hex = unicode2gb[`0x${code.toString(16).toUpperCase()}`]
 | 
			
		||||
        let gb = Number(hex)
 | 
			
		||||
        if (Number.isNaN(gb))
 | 
			
		||||
          gb = Number('0xA1F5')
 | 
			
		||||
 | 
			
		||||
        let arr = []
 | 
			
		||||
        while (gb > 0) {
 | 
			
		||||
          arr.push(gb & 0xFF)
 | 
			
		||||
          gb >>= 8
 | 
			
		||||
        }
 | 
			
		||||
        while (arr.length > 0) res.push(arr.pop())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return res
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static fromGB2312Bytes(gb2312Bytes: number[]): string {
 | 
			
		||||
    let unicode2gb = this.getUnicode2gb()
 | 
			
		||||
    let res = []
 | 
			
		||||
    // var i = 0
 | 
			
		||||
    for (let i = 0; i < gb2312Bytes.length; i++) {
 | 
			
		||||
      let code = gb2312Bytes[i]
 | 
			
		||||
      if (code < 0xA1 || code > 0xFE || i + 1 == gb2312Bytes.length) {
 | 
			
		||||
        res.push(String.fromCharCode(code))
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      let c2 = gb2312Bytes[i + 1]
 | 
			
		||||
      if (code < 0xA1 || code > 0xFE) {
 | 
			
		||||
        res.push(String.fromCharCode(code))
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      let g = c2 | code << 8
 | 
			
		||||
 | 
			
		||||
      c2 = Number(unicode2gb[`0x${g.toString(16).toUpperCase()}`])
 | 
			
		||||
      if (typeof c2 == 'undefined') {
 | 
			
		||||
        res.push(String.fromCharCode(code))
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      res.push(String.fromCharCode(c2))
 | 
			
		||||
      i++
 | 
			
		||||
    }
 | 
			
		||||
    return res.join('')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								tests/dev1/dataHandle/common/base/StringBuidler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/dev1/dataHandle/common/base/StringBuidler.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
export class StringBuider
 | 
			
		||||
{
 | 
			
		||||
  strings: string[]
 | 
			
		||||
 | 
			
		||||
  constructor()
 | 
			
		||||
  {
 | 
			
		||||
    this.strings = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 插入 */
 | 
			
		||||
  Append(obj: any)
 | 
			
		||||
  {
 | 
			
		||||
    this.strings.push(obj)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 插入文本 */
 | 
			
		||||
  AppendString(str: string) { this.strings.push(str) }
 | 
			
		||||
 | 
			
		||||
  /** 格式化 插入 */
 | 
			
		||||
  AppendFormat(str: string, ...arg: any[]): string
 | 
			
		||||
  {
 | 
			
		||||
    for (let i = 0; i < arg.length; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let parent = `\\{${i}\\}`
 | 
			
		||||
      let reg = new RegExp(parent, 'g')
 | 
			
		||||
      str = str.replace(reg, arg[i])
 | 
			
		||||
    }
 | 
			
		||||
    this.strings.push(str)
 | 
			
		||||
    return str
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 添加一行(回车) */
 | 
			
		||||
  AppendLine(str = '')
 | 
			
		||||
  {
 | 
			
		||||
    if (str != null)
 | 
			
		||||
    {
 | 
			
		||||
      this.AppendString(str)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 插入 */
 | 
			
		||||
  Insert(index: number, ...strs)
 | 
			
		||||
  {
 | 
			
		||||
    if (strs.length == 0)
 | 
			
		||||
      return
 | 
			
		||||
    if (index > this.strings.length)
 | 
			
		||||
      index = this.strings.length
 | 
			
		||||
    if (index < 0)
 | 
			
		||||
      index = 0
 | 
			
		||||
    this.strings.splice(index, 0, ...strs)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 删除片段 */
 | 
			
		||||
  Remove(startIndex: number, length: number): string[]
 | 
			
		||||
  {
 | 
			
		||||
    return this.strings.splice(startIndex, length)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 所有文本 */
 | 
			
		||||
  ToString(): string
 | 
			
		||||
  {
 | 
			
		||||
    return this.strings.join('\r\n')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 清空 */
 | 
			
		||||
  Clear() { this.strings = [] }
 | 
			
		||||
 | 
			
		||||
  /** 容量 */
 | 
			
		||||
  get size() { return this.strings.length }
 | 
			
		||||
  /** 文本数 */
 | 
			
		||||
  get Count() { return this.strings.length }
 | 
			
		||||
 | 
			
		||||
  GetStringLength(): number
 | 
			
		||||
  {
 | 
			
		||||
    let length = 0
 | 
			
		||||
    for (const str of this.strings)
 | 
			
		||||
    {
 | 
			
		||||
      length += str.length
 | 
			
		||||
    }
 | 
			
		||||
    return length
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								tests/dev1/dataHandle/common/base/StringFormat.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								tests/dev1/dataHandle/common/base/StringFormat.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
export class StringFormat
 | 
			
		||||
{
 | 
			
		||||
  /**
 | 
			
		||||
   * 对Date的扩展,将 Date 转化为指定格式的String
 | 
			
		||||
   * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符
 | 
			
		||||
   * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
 | 
			
		||||
   * eg:
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
 | 
			
		||||
   * (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
 | 
			
		||||
   */
 | 
			
		||||
  static Date(date: Date, fmt): string
 | 
			
		||||
  {
 | 
			
		||||
    let o = {
 | 
			
		||||
      'M+': date.getMonth() + 1, // 月份
 | 
			
		||||
      'd+': date.getDate(), // 日
 | 
			
		||||
      'h+': date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时
 | 
			
		||||
      'H+': date.getHours(), // 小时
 | 
			
		||||
      'm+': date.getMinutes(), // 分
 | 
			
		||||
      's+': date.getSeconds(), // 秒
 | 
			
		||||
      'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
 | 
			
		||||
      'S': date.getMilliseconds(), // 毫秒
 | 
			
		||||
    }
 | 
			
		||||
    let week = {
 | 
			
		||||
      0: '/u65e5',
 | 
			
		||||
      1: '/u4e00',
 | 
			
		||||
      2: '/u4e8c',
 | 
			
		||||
      3: '/u4e09',
 | 
			
		||||
      4: '/u56db',
 | 
			
		||||
      5: '/u4e94',
 | 
			
		||||
      6: '/u516d',
 | 
			
		||||
    }
 | 
			
		||||
    if (/(y+)/.test(fmt))
 | 
			
		||||
    {
 | 
			
		||||
      fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length))
 | 
			
		||||
    }
 | 
			
		||||
    if (/(E+)/.test(fmt))
 | 
			
		||||
    {
 | 
			
		||||
      fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '/u661f/u671f' : '/u5468') : '') + week[`${date.getDay()}`])
 | 
			
		||||
    }
 | 
			
		||||
    for (let k in o)
 | 
			
		||||
    {
 | 
			
		||||
      if (new RegExp(`(${k})`).test(fmt))
 | 
			
		||||
      {
 | 
			
		||||
        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return fmt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 返回数字的固定小数点 123.00  */
 | 
			
		||||
  static number(value: number, bit: number): string
 | 
			
		||||
  {
 | 
			
		||||
    return value?.toFixed(bit).toString()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static toFixed(value: number, bit: number = 3): number
 | 
			
		||||
  {
 | 
			
		||||
    let tt = 10 ** bit
 | 
			
		||||
    return Math.round(value * tt) / tt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static filterIllegalChar(str: string): string
 | 
			
		||||
  {
 | 
			
		||||
    // var pattern = new RegExp("[/:*?'<>|\\]");
 | 
			
		||||
    // var rs = "";
 | 
			
		||||
    // for (var i = 0; i < str.length; i++)
 | 
			
		||||
    // {
 | 
			
		||||
    //     rs = rs + str.substr(i, 1).replace(pattern, '');
 | 
			
		||||
    // }
 | 
			
		||||
    // return rs;
 | 
			
		||||
 | 
			
		||||
    let rt = str.replace(/\\/g, '').replace(/\:/g, '').replace(/\*/g, '').replace(/\?/g, '').replace(/\"/g, '').replace(/\</g, '').replace(/\>/g, '').replace(/\|/g, '').replace(/\'/g, '')
 | 
			
		||||
    return rt
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 文本格式化 */
 | 
			
		||||
  static format(str: string, ...args: any[]): string
 | 
			
		||||
  {
 | 
			
		||||
    let data = args
 | 
			
		||||
    let tmpl = str
 | 
			
		||||
    for (const item of tmpl.matchAll(/\{(.+?)\}/g))
 | 
			
		||||
    {
 | 
			
		||||
      let parts = item[1].split(',').map(i => i.trim())
 | 
			
		||||
      let index = Number(parts[0])
 | 
			
		||||
      let arg = data[index]
 | 
			
		||||
 | 
			
		||||
      let val = (arg || '').toString() // 默认
 | 
			
		||||
 | 
			
		||||
      if (arg instanceof Date) // 日期
 | 
			
		||||
      {
 | 
			
		||||
        let fm = 'MM-dd HH;mm'
 | 
			
		||||
        if (parts.length > 1)
 | 
			
		||||
        {
 | 
			
		||||
          fm = parts[1]
 | 
			
		||||
        }
 | 
			
		||||
        val = this.Date(arg, fm)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (parts.length > 1 && parts[1][0] === '#')
 | 
			
		||||
      {
 | 
			
		||||
        // {2,#3} -> 数字 转成3位  001,...023,
 | 
			
		||||
        val = val.padStart(Number(parts[1].substring(1)), '0')
 | 
			
		||||
      }
 | 
			
		||||
      tmpl = tmpl.replace(item[0], val)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tmpl
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 实现右对齐,右边填充 (25,4,'*') => '25**' */
 | 
			
		||||
  static PadEnd(num: number, totalWidth: number, paddingChar: string): string
 | 
			
		||||
  {
 | 
			
		||||
    let str = num.toString()
 | 
			
		||||
    return str.padEnd(totalWidth, paddingChar[0])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 实现右对齐,左边填充 (25,4,'0') => '0025' */
 | 
			
		||||
  static PadStart(num: number, totalWidth: number, paddingChar: string): string
 | 
			
		||||
  {
 | 
			
		||||
    let str = num.toString()
 | 
			
		||||
    return str.padStart(totalWidth, paddingChar[0])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								tests/dev1/dataHandle/common/base/TextFile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								tests/dev1/dataHandle/common/base/TextFile.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
export class textFile
 | 
			
		||||
{
 | 
			
		||||
  /** 保存单文件 text类型的 */
 | 
			
		||||
  static saveFile(filename: string, content: string)
 | 
			
		||||
  {
 | 
			
		||||
    let blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
 | 
			
		||||
    this.saveAs(filename, blob)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 读取文件 选择文件组件,读取文档处理函数 */
 | 
			
		||||
  static readFile(eleFile, fn_doText, fileType = '', fn_msg = null)
 | 
			
		||||
  {
 | 
			
		||||
    let noEleFile = !(eleFile)
 | 
			
		||||
    if (noEleFile)
 | 
			
		||||
    {
 | 
			
		||||
      eleFile = document.createElement('input')
 | 
			
		||||
      eleFile.type = 'file'
 | 
			
		||||
      eleFile.accept = 'text/*'
 | 
			
		||||
      eleFile.hidden = true
 | 
			
		||||
      document.body.appendChild(eleFile)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fileType && fileType != '')
 | 
			
		||||
      eleFile.accept = fileType
 | 
			
		||||
    let reader = new FileReader()
 | 
			
		||||
    reader.onload = function (event)
 | 
			
		||||
    {
 | 
			
		||||
      let text = event.target.result
 | 
			
		||||
      if (fn_doText)
 | 
			
		||||
        fn_doText(text)
 | 
			
		||||
    }
 | 
			
		||||
    // 选择文件
 | 
			
		||||
    eleFile.onchange = function (event)
 | 
			
		||||
    {
 | 
			
		||||
      let file = event.target.files[0]
 | 
			
		||||
      if (file)
 | 
			
		||||
      {
 | 
			
		||||
        reader.readAsText(file)
 | 
			
		||||
      }
 | 
			
		||||
      if (fn_msg != null)
 | 
			
		||||
      {
 | 
			
		||||
        fn_msg()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    eleFile.click()
 | 
			
		||||
 | 
			
		||||
    if (noEleFile)
 | 
			
		||||
    {
 | 
			
		||||
      document.body.removeChild(eleFile)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static getStringFromFile(eleFile, fileType = '', fn_msg = null): string
 | 
			
		||||
  {
 | 
			
		||||
    let noEleFile = !(eleFile)
 | 
			
		||||
    if (noEleFile)
 | 
			
		||||
    {
 | 
			
		||||
      eleFile = document.createElement('input')
 | 
			
		||||
      eleFile.type = 'file'
 | 
			
		||||
      eleFile.accept = 'text/*'
 | 
			
		||||
      eleFile.hidden = true
 | 
			
		||||
      document.body.appendChild(eleFile)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fileType && fileType != '')
 | 
			
		||||
      eleFile.accept = fileType
 | 
			
		||||
    let reader = new FileReader()
 | 
			
		||||
    reader.onload = function (event)
 | 
			
		||||
    {
 | 
			
		||||
      let text = event.target.result
 | 
			
		||||
      return text
 | 
			
		||||
    }
 | 
			
		||||
    // 选择文件
 | 
			
		||||
    eleFile.onchange = function (event)
 | 
			
		||||
    {
 | 
			
		||||
      let file = event.target.files[0]
 | 
			
		||||
      if (file)
 | 
			
		||||
      {
 | 
			
		||||
        reader.readAsText(file)
 | 
			
		||||
      }
 | 
			
		||||
      if (fn_msg != null)
 | 
			
		||||
      {
 | 
			
		||||
        fn_msg()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    eleFile.click()
 | 
			
		||||
 | 
			
		||||
    if (noEleFile)
 | 
			
		||||
    {
 | 
			
		||||
      document.body.removeChild(eleFile)
 | 
			
		||||
    }
 | 
			
		||||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 保存文件 */
 | 
			
		||||
  static saveAs(fileName: string, data: Blob)
 | 
			
		||||
  {
 | 
			
		||||
    // 创建隐藏的可下载链接
 | 
			
		||||
    let eleLink = document.createElement('a')
 | 
			
		||||
    eleLink.download = fileName
 | 
			
		||||
    eleLink.style.display = 'none'
 | 
			
		||||
    eleLink.href = URL.createObjectURL(data)
 | 
			
		||||
    // 触发点击
 | 
			
		||||
    document.body.appendChild(eleLink)
 | 
			
		||||
    eleLink.click()
 | 
			
		||||
    // 然后移除
 | 
			
		||||
    document.body.removeChild(eleLink)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static readFile2()
 | 
			
		||||
  {
 | 
			
		||||
    let rf = document.createElement('input')
 | 
			
		||||
    rf.type = 'file'
 | 
			
		||||
    rf.accept = 'text/*'
 | 
			
		||||
    rf.hidden = true
 | 
			
		||||
 | 
			
		||||
    document.body.removeChild(rf)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										138
									
								
								tests/dev1/dataHandle/common/base/ZipFile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								tests/dev1/dataHandle/common/base/ZipFile.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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 }
 | 
			
		||||
							
								
								
									
										6965
									
								
								tests/dev1/dataHandle/common/base/gb2312.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6965
									
								
								tests/dev1/dataHandle/common/base/gb2312.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										176
									
								
								tests/dev1/dataHandle/common/bmp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/dev1/dataHandle/common/bmp.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
const decoder = new TextDecoder()
 | 
			
		||||
export class BmpDecoder {
 | 
			
		||||
  pos: number
 | 
			
		||||
  buffer: ArrayBuffer
 | 
			
		||||
  is_with_alpha: boolean
 | 
			
		||||
  bottom_up: boolean
 | 
			
		||||
  flag: string
 | 
			
		||||
  constructor(buffer: ArrayBuffer, is_with_alpha: boolean) {
 | 
			
		||||
    this.pos = 0
 | 
			
		||||
    this.buffer = buffer
 | 
			
		||||
    this.is_with_alpha = !!is_with_alpha
 | 
			
		||||
    this.bottom_up = true
 | 
			
		||||
 | 
			
		||||
    this.flag = decoder.decode(this.buffer.slice(0, (this.pos += 2)))
 | 
			
		||||
 | 
			
		||||
    if (this.flag != 'BM')
 | 
			
		||||
      throw new Error('Invalid BMP File')
 | 
			
		||||
    // this.parseHeader();
 | 
			
		||||
    // this.parseRGBA();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parseHeader() {}
 | 
			
		||||
  parseRGBA() {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createBmpFile(
 | 
			
		||||
  imgData: ImageData,
 | 
			
		||||
  paramDic: { [key: string]: string } = {},
 | 
			
		||||
) {
 | 
			
		||||
  let dpi = 300
 | 
			
		||||
  let bitPP = paramDic.bpp ? Number(paramDic.bpp) : 24
 | 
			
		||||
  let printResolution = Math.round(dpi * 39.3701)
 | 
			
		||||
 | 
			
		||||
  if ([1, 24, 32].includes(bitPP) == false) {
 | 
			
		||||
    throw new Error(`不支持的${bitPP}bpp BMP`)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let data = imgData.data
 | 
			
		||||
 | 
			
		||||
  let width = imgData.width
 | 
			
		||||
  let height = imgData.height
 | 
			
		||||
  let extraBytes = width % 4
 | 
			
		||||
  let rgbSize = data.length
 | 
			
		||||
  switch (bitPP) {
 | 
			
		||||
    case 1:
 | 
			
		||||
      {
 | 
			
		||||
        let rowSize = width / 8 + (width % 8 > 0 ? 1 : 0)
 | 
			
		||||
        extraBytes = rowSize % 4
 | 
			
		||||
        rgbSize = height * (rowSize + extraBytes)
 | 
			
		||||
      }
 | 
			
		||||
      break
 | 
			
		||||
    case 24:
 | 
			
		||||
      {
 | 
			
		||||
        let rowSize = 3 * width
 | 
			
		||||
        extraBytes = rowSize % 4
 | 
			
		||||
        rgbSize = height * (rowSize + extraBytes)
 | 
			
		||||
      }
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
  let headerInfoSize = 40
 | 
			
		||||
 | 
			
		||||
  /** ****************header */
 | 
			
		||||
  let flag = 'BM'
 | 
			
		||||
  let reserved = 0
 | 
			
		||||
  let offset = 54
 | 
			
		||||
  let fileSize = rgbSize + offset
 | 
			
		||||
  let planes = 1
 | 
			
		||||
 | 
			
		||||
  let compress = 0
 | 
			
		||||
  let hr = printResolution
 | 
			
		||||
  let vr = printResolution
 | 
			
		||||
  let colors = 0
 | 
			
		||||
  let importantColors = 0
 | 
			
		||||
  let colorPaletteSize = 0
 | 
			
		||||
  if (bitPP == 1) {
 | 
			
		||||
    colorPaletteSize = 2 * 4
 | 
			
		||||
  }
 | 
			
		||||
  let buffer = new ArrayBuffer(offset + colorPaletteSize + rgbSize)
 | 
			
		||||
  let tempBuffer = new DataView(buffer)
 | 
			
		||||
  let pos = 0
 | 
			
		||||
 | 
			
		||||
  // BMP Header
 | 
			
		||||
  // ID
 | 
			
		||||
  tempBuffer.setUint8(pos, flag.charCodeAt(0))
 | 
			
		||||
  pos += 1
 | 
			
		||||
  tempBuffer.setUint8(pos, flag.charCodeAt(1))
 | 
			
		||||
  pos += 1
 | 
			
		||||
  // size
 | 
			
		||||
  tempBuffer.setUint32(pos, fileSize, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  // Application specific
 | 
			
		||||
  tempBuffer.setUint32(pos, reserved, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
  // Offset
 | 
			
		||||
  tempBuffer.setUint32(pos, offset, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  // DIB Header
 | 
			
		||||
  // Number of bytes in the DIB header (from this point)
 | 
			
		||||
  tempBuffer.setUint32(pos, headerInfoSize, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint32(pos, width, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
  tempBuffer.setUint32(pos, height, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint16(pos, planes, true)
 | 
			
		||||
  pos += 2
 | 
			
		||||
  tempBuffer.setUint16(pos, bitPP, true)
 | 
			
		||||
  pos += 2
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint16(pos, compress, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint16(pos, rgbSize, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint16(pos, hr, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
  tempBuffer.setUint16(pos, vr, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  tempBuffer.setUint16(pos, colors, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
  tempBuffer.setUint16(pos, importantColors, true)
 | 
			
		||||
  pos += 4
 | 
			
		||||
 | 
			
		||||
  if (bitPP == 1) {
 | 
			
		||||
    tempBuffer.setUint8(pos++, 0) // b
 | 
			
		||||
    tempBuffer.setUint8(pos++, 0) // g
 | 
			
		||||
    tempBuffer.setUint8(pos++, 0) // r
 | 
			
		||||
    tempBuffer.setUint8(pos++, 0) // a
 | 
			
		||||
 | 
			
		||||
    tempBuffer.setUint8(pos++, 255) // b
 | 
			
		||||
    tempBuffer.setUint8(pos++, 255) // g
 | 
			
		||||
    tempBuffer.setUint8(pos++, 255) // r
 | 
			
		||||
    tempBuffer.setUint8(pos++, 0) // a
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let pixData = ''
 | 
			
		||||
  let writeBinData = () => {
 | 
			
		||||
    tempBuffer.setUint8(pos++, Number.parseInt(pixData.padEnd(8, '0'), 2))
 | 
			
		||||
    pixData = ''
 | 
			
		||||
  }
 | 
			
		||||
  for (let y = height - 1; y >= 0; y--) {
 | 
			
		||||
    let rowIndex = y * width * 4
 | 
			
		||||
    for (let x = 0; x < width; x++) {
 | 
			
		||||
      let p = rowIndex + x * 4
 | 
			
		||||
 | 
			
		||||
      if (bitPP == 1) {
 | 
			
		||||
        if (pixData.length == 8) {
 | 
			
		||||
          writeBinData()
 | 
			
		||||
        }
 | 
			
		||||
        let rgb = [data[p + 0], data[p + 1], data[p + 2]]
 | 
			
		||||
        pixData += rgb.filter((v, i) => v > 170).length == 3 ? '1' : '0' // 170 以下小板缩略图条会断
 | 
			
		||||
      } else {
 | 
			
		||||
        tempBuffer.setUint8(pos++, data[p + 2]) // b
 | 
			
		||||
        tempBuffer.setUint8(pos++, data[p + 1]) // g
 | 
			
		||||
        tempBuffer.setUint8(pos++, data[p + 0]) // r
 | 
			
		||||
        if (bitPP == 32) {
 | 
			
		||||
          tempBuffer.setUint8(pos++, data[p + 3]) // a
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (bitPP == 1 && pixData.length > 0) {
 | 
			
		||||
      writeBinData()
 | 
			
		||||
    }
 | 
			
		||||
    for (let index = 0; index < extraBytes; index++) {
 | 
			
		||||
      tempBuffer.setUint8(pos++, 0)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return tempBuffer.buffer
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										399
									
								
								tests/dev1/dataHandle/common/core/Container.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								tests/dev1/dataHandle/common/core/Container.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,399 @@
 | 
			
		||||
import { ClipType, PolyFillType } from 'js-angusj-clipper/web'
 | 
			
		||||
import type { Paths } from 'js-angusj-clipper/web'
 | 
			
		||||
import type { SubjectInput } from 'js-angusj-clipper/web/clipFunctions'
 | 
			
		||||
import { Box2 } from '../Box2'
 | 
			
		||||
import { clipperCpp } from '../ClipperCpp'
 | 
			
		||||
import type { CompareVectorFn } from '../ComparePoint'
 | 
			
		||||
import { ComparePoint } from '../ComparePoint'
 | 
			
		||||
import { ConvexHull2D } from '../ConvexHull2D'
 | 
			
		||||
import type { NestFiler } from '../Filer'
 | 
			
		||||
import type { Point } from '../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<number>[] = []// 已经无法放置的pathId
 | 
			
		||||
 | 
			
		||||
  private PrePut(part: Part): PartPlacedContainerState
 | 
			
		||||
  {
 | 
			
		||||
    // 无法容纳
 | 
			
		||||
    if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    let cacheKey = `${this.StatusKey},${part.State.Contour.Id}`
 | 
			
		||||
    let cacheP = NestCache.PositionCache[cacheKey]
 | 
			
		||||
    // 读取缓存位置
 | 
			
		||||
    if (cacheP)
 | 
			
		||||
    {
 | 
			
		||||
      NestCache.count1++
 | 
			
		||||
      return this.Calc(part, cacheP)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let binNfp = this.BinPath.GetInsideNFP(part.State.Contour)
 | 
			
		||||
    if (!binNfp || binNfp.length === 0)
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    // 首个
 | 
			
		||||
    if (this.PlacedParts.length === 0)
 | 
			
		||||
    {
 | 
			
		||||
      let p = this.GetFarLeftP(binNfp)
 | 
			
		||||
      NestCache.PositionCache[cacheKey] = p
 | 
			
		||||
      return this.Calc(part, p)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 快速退出
 | 
			
		||||
    let noSet = NestCache.NoPutCache[this.StatusKey]
 | 
			
		||||
    if (noSet)
 | 
			
		||||
      this._NotPuts.push(noSet)
 | 
			
		||||
    for (let set of this._NotPuts)
 | 
			
		||||
    {
 | 
			
		||||
      if (set.has(part.State.Contour.Id))
 | 
			
		||||
      {
 | 
			
		||||
        NestCache.count2++
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let finalNfp = this.GetNFPs(part, binNfp)
 | 
			
		||||
    if (!finalNfp || finalNfp.length === 0)
 | 
			
		||||
    {
 | 
			
		||||
      if (noSet)
 | 
			
		||||
        noSet.add(part.State.Contour.Id)
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        noSet = new Set([part.State.Contour.Id])
 | 
			
		||||
        NestCache.NoPutCache[this.StatusKey] = noSet
 | 
			
		||||
      }
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 选择合适的放置点
 | 
			
		||||
    let minArea: number = Number.POSITIVE_INFINITY
 | 
			
		||||
    let translate: Point
 | 
			
		||||
    let bestBox: Box2
 | 
			
		||||
    let bestHull: Point[]
 | 
			
		||||
 | 
			
		||||
    let tempVec = new Vector2()
 | 
			
		||||
    for (let nfp of finalNfp)
 | 
			
		||||
    {
 | 
			
		||||
      for (let p of nfp)
 | 
			
		||||
      {
 | 
			
		||||
        tempVec.set(p.x * 1e-4, p.y * 1e-4)
 | 
			
		||||
        switch (this._PlaceType)
 | 
			
		||||
        {
 | 
			
		||||
          case PlaceType.Box:
 | 
			
		||||
          {
 | 
			
		||||
            let box2 = part.State.Contour.BoundingBox.clone()
 | 
			
		||||
            box2.translate(tempVec)
 | 
			
		||||
            let rectBox = this.PlacedBox.clone().union(box2)
 | 
			
		||||
            let size = rectBox.getSize()
 | 
			
		||||
            let area = size.x * size.y
 | 
			
		||||
            if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
 | 
			
		||||
            {
 | 
			
		||||
              translate = p
 | 
			
		||||
              minArea = area
 | 
			
		||||
              bestBox = rectBox
 | 
			
		||||
            }
 | 
			
		||||
            break
 | 
			
		||||
          }
 | 
			
		||||
          case PlaceType.Hull:
 | 
			
		||||
          {
 | 
			
		||||
            let pts = TranslatePath(part.State.Contour.Points, tempVec)
 | 
			
		||||
            let nhull = ConvexHull2D([...pts, ...this.PlacedHull])
 | 
			
		||||
            let area = Math.abs(Area(nhull))
 | 
			
		||||
            if (area < minArea || ((equaln(area, minArea, 1)) && this.CompartePoint(p, translate) === -1))
 | 
			
		||||
            {
 | 
			
		||||
              translate = p
 | 
			
		||||
              minArea = area
 | 
			
		||||
              bestHull = nhull
 | 
			
		||||
            }
 | 
			
		||||
            break
 | 
			
		||||
          }
 | 
			
		||||
          case PlaceType.Gravity:
 | 
			
		||||
          {
 | 
			
		||||
            if (!translate || this.CompartePoint(p, translate) === -1)
 | 
			
		||||
              translate = p
 | 
			
		||||
 | 
			
		||||
            break
 | 
			
		||||
          }
 | 
			
		||||
          default:
 | 
			
		||||
            break
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (translate)
 | 
			
		||||
    {
 | 
			
		||||
      NestCache.PositionCache[cacheKey] = translate
 | 
			
		||||
      if (!bestBox)
 | 
			
		||||
        bestBox = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate({ x: translate.x * 1e-4, y: translate.y * 1e-4 }))
 | 
			
		||||
      return { p: translate, area: minArea, box: bestBox, hull: bestHull }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Calc(part: Part, p: Point): PartPlacedContainerState
 | 
			
		||||
  {
 | 
			
		||||
    let d: PartPlacedContainerState = { p }
 | 
			
		||||
 | 
			
		||||
    let m: Point = { x: p.x * 1e-4, y: p.y * 1e-4 }
 | 
			
		||||
 | 
			
		||||
    if (this.PlacedBox)
 | 
			
		||||
      d.box = this.PlacedBox.clone().union(part.State.Contour.BoundingBox.clone().translate(m))
 | 
			
		||||
    else
 | 
			
		||||
      d.box = part.State.Contour.BoundingBox.clone().translate(m)
 | 
			
		||||
 | 
			
		||||
    // 凸包
 | 
			
		||||
    if (this._PlaceType === PlaceType.Hull)
 | 
			
		||||
    {
 | 
			
		||||
      if (!this.PlacedHull)
 | 
			
		||||
      {
 | 
			
		||||
        d.hull = TranslatePath(part.State.Contour.Points, m)
 | 
			
		||||
        d.area = part.State.Contour.Area
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        d.hull = ConvexHull2D([...this.PlacedHull, ...TranslatePath(part.State.Contour.Points, m)])
 | 
			
		||||
        d.area = Area(d.hull)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      d.area = Math.abs(d.area)
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      d.area = d.box.area
 | 
			
		||||
    }
 | 
			
		||||
    return d
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  PutPart(p: Part, greedy = false): boolean
 | 
			
		||||
  {
 | 
			
		||||
    let bestD: PartPlacedContainerState
 | 
			
		||||
    if (greedy)
 | 
			
		||||
    {
 | 
			
		||||
      let bestI: number
 | 
			
		||||
      for (let i = 0; i < p.RotatedStates.length; i++)
 | 
			
		||||
      {
 | 
			
		||||
        p.StateIndex = i
 | 
			
		||||
        let d = this.PrePut(p)
 | 
			
		||||
        if (d && (!bestD || bestD.area > d.area
 | 
			
		||||
                    || (
 | 
			
		||||
                      this._PlaceType === PlaceType.Hull
 | 
			
		||||
                        && equaln(bestD.area, d.area, 0.1)
 | 
			
		||||
                        && d.box.area < bestD.box.area
 | 
			
		||||
                    )
 | 
			
		||||
        ))
 | 
			
		||||
        {
 | 
			
		||||
          bestD = d
 | 
			
		||||
          bestI = i
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (bestD)
 | 
			
		||||
        p.StateIndex = bestI
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      bestD = this.PrePut(p)
 | 
			
		||||
 | 
			
		||||
    if (bestD)
 | 
			
		||||
    {
 | 
			
		||||
      p.PlacePosition = bestD.p
 | 
			
		||||
      this.PlacedBox = bestD.box ?? this.PlacedBox
 | 
			
		||||
      this.PlacedHull = bestD.hull
 | 
			
		||||
      this.AppendPart(p, false)
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected GetNFPs(part: Part, binNfp: Point[][]): Paths
 | 
			
		||||
  {
 | 
			
		||||
    // 合并(零件和所有已经放置零件的NFP)
 | 
			
		||||
    let nfps: SubjectInput[] = []
 | 
			
		||||
    for (let placedPart of this.PlacedParts)
 | 
			
		||||
    {
 | 
			
		||||
      let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour)
 | 
			
		||||
      if (!nfp)
 | 
			
		||||
        return
 | 
			
		||||
      for (let n of nfp)
 | 
			
		||||
      {
 | 
			
		||||
        let nnfp = TranslatePath(n, placedPart.PlacePosition)
 | 
			
		||||
        nfps.push({ data: nnfp, closed: true })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // 合并nfp
 | 
			
		||||
    let combinedNfp = clipperCpp.lib.clipToPaths({
 | 
			
		||||
      subjectInputs: nfps,
 | 
			
		||||
      clipType: ClipType.Union,
 | 
			
		||||
      subjectFillType: PolyFillType.NonZero,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    combinedNfp = clipperCpp.lib.cleanPolygons(combinedNfp, 100)
 | 
			
		||||
 | 
			
		||||
    if (combinedNfp.length === 0)
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    // 减去nfp
 | 
			
		||||
    let finalNfp = clipperCpp.lib.clipToPaths({
 | 
			
		||||
      subjectInputs: [{ data: binNfp, closed: true }],
 | 
			
		||||
      clipInputs: [{ data: combinedNfp }],
 | 
			
		||||
      clipType: ClipType.Difference,
 | 
			
		||||
      subjectFillType: PolyFillType.NonZero,
 | 
			
		||||
    })
 | 
			
		||||
    finalNfp = clipperCpp.lib.cleanPolygons(finalNfp, 100)
 | 
			
		||||
    return finalNfp
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 将Part添加的Placed列表
 | 
			
		||||
   * @param part 零件,已经计算了放置状态
 | 
			
		||||
   * @param [calc] 是否计算当前的容器状态?
 | 
			
		||||
   */
 | 
			
		||||
  private AppendPart(part: Part, calc = true): void
 | 
			
		||||
  {
 | 
			
		||||
    this.StatusKey += `,${part.State.Contour.Id}`
 | 
			
		||||
    this.PlacedParts.push(part)
 | 
			
		||||
    this.PlacedArea += part.State.Contour.Area
 | 
			
		||||
    let m = { x: part.PlacePosition.x * 1e-4, y: part.PlacePosition.y * 1e-4 }
 | 
			
		||||
    if (calc)
 | 
			
		||||
    {
 | 
			
		||||
      // 凸包
 | 
			
		||||
      if (this._PlaceType === PlaceType.Hull)
 | 
			
		||||
      {
 | 
			
		||||
        if (!this.PlacedHull)
 | 
			
		||||
          this.PlacedHull = TranslatePath(part.State.Contour.Points, m)
 | 
			
		||||
        else
 | 
			
		||||
          this.PlacedHull = ConvexHull2D(this.PlacedHull.concat(TranslatePath(part.State.Contour.Points, m)))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (calc || this._PlaceType !== PlaceType.Box)
 | 
			
		||||
    {
 | 
			
		||||
      if (this.PlacedBox)
 | 
			
		||||
        this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(m))
 | 
			
		||||
      else
 | 
			
		||||
        this.PlacedBox = part.State.Contour.BoundingBox.clone().translate(m)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 得到最左边的点
 | 
			
		||||
   */
 | 
			
		||||
  protected GetFarLeftP(nfp: Point[][]): Point
 | 
			
		||||
  {
 | 
			
		||||
    let leftP: Point
 | 
			
		||||
    for (let path of nfp)
 | 
			
		||||
    {
 | 
			
		||||
      for (let p of path)
 | 
			
		||||
      {
 | 
			
		||||
        if (!leftP || this.CompartePoint(p, leftP) === -1)
 | 
			
		||||
          leftP = p
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return leftP
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // #region -------------------------File-------------------------
 | 
			
		||||
  // 对象从文件中读取数据,初始化自身
 | 
			
		||||
  ReadFile(file: NestFiler, parts: Part[])
 | 
			
		||||
  {
 | 
			
		||||
    this.ParentId = file.Read()
 | 
			
		||||
    this.ChildrenIndex = file.Read()
 | 
			
		||||
    this.ParentM = file.Read()
 | 
			
		||||
    let count = file.Read() as number
 | 
			
		||||
    this.PlacedParts = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let index = file.Read() as number
 | 
			
		||||
      let part = parts[index]
 | 
			
		||||
      part.StateIndex = file.Read()
 | 
			
		||||
      part.PlacePosition = file.Read()
 | 
			
		||||
      this.PlacedParts.push(part)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.PlacedBox)
 | 
			
		||||
      this.PlacedBox = new Box2()
 | 
			
		||||
    this.PlacedBox.min.fromArray(file.Read())
 | 
			
		||||
    this.PlacedBox.max.fromArray(file.Read())
 | 
			
		||||
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 对象将自身数据写入到文件.
 | 
			
		||||
  WriteFile(file: NestFiler)
 | 
			
		||||
  {
 | 
			
		||||
    file.Write(this.ParentId)
 | 
			
		||||
    file.Write(this.ChildrenIndex)
 | 
			
		||||
    file.Write(this.ParentM)
 | 
			
		||||
 | 
			
		||||
    let parts: Part[] = []
 | 
			
		||||
    for (let p of this.PlacedParts)
 | 
			
		||||
    {
 | 
			
		||||
      if (p instanceof PartGroup)
 | 
			
		||||
        parts.push(...p.Export())
 | 
			
		||||
      else
 | 
			
		||||
        parts.push(p)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    file.Write(parts.length)
 | 
			
		||||
    for (let p of parts)
 | 
			
		||||
    {
 | 
			
		||||
      file.Write(p.Id)
 | 
			
		||||
      file.Write(p.StateIndex)
 | 
			
		||||
      file.Write(p.PlacePosition)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.PlacedBox)
 | 
			
		||||
      this.PlacedBox = new Box2()
 | 
			
		||||
    file.Write(this.PlacedBox.min.toArray())
 | 
			
		||||
    file.Write(this.PlacedBox.max.toArray())
 | 
			
		||||
  }
 | 
			
		||||
  // #endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								tests/dev1/dataHandle/common/core/GNestConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/dev1/dataHandle/common/core/GNestConfig.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export const GNestConfig = {
 | 
			
		||||
  RotateHole: true, // 在旋转零件的时候旋转网洞
 | 
			
		||||
  UsePartGroup: false, // 如果开启这个特性,将在第一次放置零件时,尝试计算完全对插的板件,并且使用它.(基因注册,模范夫妻)
 | 
			
		||||
  UseOffsetSimplify: true,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										274
									
								
								tests/dev1/dataHandle/common/core/Individual.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								tests/dev1/dataHandle/common/core/Individual.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,274 @@
 | 
			
		||||
import { arrayLast, arrayRemoveOnce } from '../ArrayExt'
 | 
			
		||||
import type { NestFiler } from '../Filer'
 | 
			
		||||
import { RandomIndex } from '../Random'
 | 
			
		||||
import type { PlaceType } from '../../vo/enums/PlaceType'
 | 
			
		||||
import { Container } from './Container'
 | 
			
		||||
import { GNestConfig } from './GNestConfig'
 | 
			
		||||
import { DefaultComparePointKeys } from './NestDatabase'
 | 
			
		||||
import type { Part } from './Part'
 | 
			
		||||
import type { Path } from './Path'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 个体(表示某一次优化的结果,或者还没开始优化的状态)
 | 
			
		||||
 * 个体是由一堆零件组成的,零件可以有不同的状态。
 | 
			
		||||
 *
 | 
			
		||||
 * 个体单独变异
 | 
			
		||||
 *  可以是某个零件的旋转状态发生改变
 | 
			
		||||
 *  可以是零件的顺序发生改变
 | 
			
		||||
 *
 | 
			
		||||
 * 个体交配(感觉暂时不需要)
 | 
			
		||||
 *  个体和其他个体进行基因交换
 | 
			
		||||
 */
 | 
			
		||||
export class Individual {
 | 
			
		||||
  constructor(public Parts?: Part[], public mutationRate = 0.5, private bin?: Path, private OddmentsBins: Path[] = [], private ComparePointKeys: string[] = DefaultComparePointKeys) { }
 | 
			
		||||
 | 
			
		||||
  Fitness: number // (评估健康) 大板个数-1 + 最后一块板的利用率
 | 
			
		||||
  Containers: Container[] // 大板列表(已排
 | 
			
		||||
  HoleContainers: Container[]// 网洞列表
 | 
			
		||||
  OddmentsContainers: Container[]// 余料列表
 | 
			
		||||
  /**
 | 
			
		||||
   * 评估健康程度
 | 
			
		||||
   */
 | 
			
		||||
  Evaluate(bestCount: number, greedy = false, type?: PlaceType) {
 | 
			
		||||
    if (GNestConfig.RotateHole)
 | 
			
		||||
      return this.EvaluateOfUseRotateHole(bestCount, greedy, type)
 | 
			
		||||
 | 
			
		||||
    this.Containers = []
 | 
			
		||||
    this.HoleContainers = []
 | 
			
		||||
    // 初始化余料列表
 | 
			
		||||
    this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
 | 
			
		||||
    this.InitHoleContainers()
 | 
			
		||||
 | 
			
		||||
    // 余料优先
 | 
			
		||||
    let parts = this.Parts.filter(p => !this.OddmentsContainers.some(odd => odd.PutPart(p)))
 | 
			
		||||
 | 
			
		||||
    // 网洞优先
 | 
			
		||||
    parts = parts.filter(p => !this.HoleContainers.some(hole => hole.PutPart(p)))
 | 
			
		||||
 | 
			
		||||
    while (parts.length > 0) {
 | 
			
		||||
      if (this.Containers.length > bestCount) {
 | 
			
		||||
        this.Fitness = Math.ceil(bestCount) + 1
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
 | 
			
		||||
      if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
 | 
			
		||||
      {
 | 
			
		||||
        const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
 | 
			
		||||
 | 
			
		||||
        const PutGroupF = (): boolean => {
 | 
			
		||||
          // 基因注册
 | 
			
		||||
          const nextPartIndex = parts.findIndex(p => p !== maxP)
 | 
			
		||||
          if (nextPartIndex !== -1) {
 | 
			
		||||
            const nextPart = parts[nextPartIndex]
 | 
			
		||||
            const groups = maxP.ParseGroup(nextPart, this.bin)
 | 
			
		||||
            for (const group of groups) {
 | 
			
		||||
              if (container.PutPart(group)) {
 | 
			
		||||
                parts.splice(nextPartIndex, 1)
 | 
			
		||||
                arrayRemoveOnce(parts, maxP)
 | 
			
		||||
                return true
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return false
 | 
			
		||||
        }
 | 
			
		||||
        if (GNestConfig.UsePartGroup && PutGroupF()) { }
 | 
			
		||||
        else if (container.PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
 | 
			
		||||
      }
 | 
			
		||||
      parts = parts.filter(p => !container.PutPart(p, greedy))
 | 
			
		||||
      if (!greedy)// 如果没有贪心,排完后在贪心一下
 | 
			
		||||
        parts = parts.filter(p => !container.PutPart(p, true))
 | 
			
		||||
      this.Containers.push(container)
 | 
			
		||||
    }
 | 
			
		||||
    this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers)?.UseRatio ?? 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 在网洞利用时,保持纹路正确
 | 
			
		||||
   *
 | 
			
		||||
   * @param bestCount
 | 
			
		||||
   * @param greedy
 | 
			
		||||
   * @param type
 | 
			
		||||
   */
 | 
			
		||||
  private EvaluateOfUseRotateHole(bestCount: number, greedy = false, type?: PlaceType) {
 | 
			
		||||
    // 初始化余料列表
 | 
			
		||||
    this.OddmentsContainers = this.OddmentsBins.map(path => new Container(path, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)]))
 | 
			
		||||
 | 
			
		||||
    this.Containers = []
 | 
			
		||||
    this.HoleContainers = []
 | 
			
		||||
    let parts = this.Parts.concat()
 | 
			
		||||
    while (parts.length > 0) {
 | 
			
		||||
      if (this.Containers.length > bestCount)// 提前结束,已经超过最佳用板量
 | 
			
		||||
      {
 | 
			
		||||
        this.Fitness = Math.ceil(bestCount) + 1
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const container = new Container(this.bin, type ?? RandomIndex(3), this.ComparePointKeys[RandomIndex(2)])
 | 
			
		||||
      const holes: Container[] = []
 | 
			
		||||
      const PutPart = (part: Part, greedy: boolean): boolean => {
 | 
			
		||||
        // 先放置在网洞内
 | 
			
		||||
        const isOk
 | 
			
		||||
                    = this.OddmentsContainers.some(oc => oc.PutPart(part, greedy)) // 余料
 | 
			
		||||
                    || holes.some((h) => {
 | 
			
		||||
                      const isOk = h.PutPart(part, greedy)
 | 
			
		||||
                      return isOk
 | 
			
		||||
                    }) // 网洞
 | 
			
		||||
                    || container.PutPart(part, greedy) // 大板
 | 
			
		||||
 | 
			
		||||
        if (isOk)
 | 
			
		||||
          this.AppendHoles(part, holes)
 | 
			
		||||
 | 
			
		||||
        return isOk
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.mutationRate > 0.5) // 大板优先,可以提高收敛速度
 | 
			
		||||
      {
 | 
			
		||||
        const maxP = parts.reduce((preP, curP) => preP.State.Contour.Area > curP.State.Contour.Area ? preP : curP)
 | 
			
		||||
 | 
			
		||||
        const PutGroupF = (): boolean => {
 | 
			
		||||
          // 基因注册
 | 
			
		||||
          const nextPartIndex = parts.findIndex(p => p !== maxP)
 | 
			
		||||
          if (nextPartIndex !== -1) {
 | 
			
		||||
            const nextPart = parts[nextPartIndex]
 | 
			
		||||
            const groups = maxP.ParseGroup(nextPart, this.bin)
 | 
			
		||||
            for (const group of groups) {
 | 
			
		||||
              if (PutPart(group, greedy)) {
 | 
			
		||||
                parts.splice(nextPartIndex, 1)
 | 
			
		||||
                arrayRemoveOnce(parts, maxP)
 | 
			
		||||
                return true
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return false
 | 
			
		||||
        }
 | 
			
		||||
        if (GNestConfig.UsePartGroup && PutGroupF()) { }
 | 
			
		||||
        else if (PutPart(maxP, greedy)) { arrayRemoveOnce(parts, maxP) }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      parts = parts.filter(p => !PutPart(p, greedy))
 | 
			
		||||
      if (!greedy)// 如果没有贪心,排完后在贪心一下
 | 
			
		||||
        parts = parts.filter(p => !PutPart(p, true))
 | 
			
		||||
 | 
			
		||||
      if (container.PlacedParts.length)
 | 
			
		||||
        this.Containers.push(container)
 | 
			
		||||
    }
 | 
			
		||||
    this.Fitness = this.Containers.length - 1 + (arrayLast(this.Containers)?.UseRatio ?? 0)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private AppendHoles(part: Part<any, any>, holes: Container[]) {
 | 
			
		||||
    for (let i = 0; i < part.Holes.length; i++) {
 | 
			
		||||
      const hole = part.Holes[i]
 | 
			
		||||
      const container = new Container(hole.Contour)
 | 
			
		||||
      container.ParentId = part.Id
 | 
			
		||||
      container.ChildrenIndex = i
 | 
			
		||||
      container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
 | 
			
		||||
      this.HoleContainers.push(container)
 | 
			
		||||
      holes.push(container)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private InitHoleContainers() {
 | 
			
		||||
    if (GNestConfig.RotateHole)
 | 
			
		||||
      return
 | 
			
		||||
    for (const part of this.Parts) {
 | 
			
		||||
      for (let i = 0; i < part.Holes.length; i++) {
 | 
			
		||||
        const hole = part.Holes[i]
 | 
			
		||||
        const container = new Container(hole.Contour)
 | 
			
		||||
        container.ParentId = part.Id
 | 
			
		||||
        container.ChildrenIndex = i
 | 
			
		||||
        container.ParentM = GNestConfig.RotateHole ? hole.MinPoint : hole.OrigionMinPoint
 | 
			
		||||
        this.HoleContainers.push(container)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Clone() {
 | 
			
		||||
    const p = new Individual(this.Parts.map(p => p.Clone()), this.mutationRate, this.bin, this.OddmentsBins, this.ComparePointKeys)
 | 
			
		||||
    p.Mutate()
 | 
			
		||||
    return p
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 突变
 | 
			
		||||
   */
 | 
			
		||||
  Mutate() {
 | 
			
		||||
    if (this.mutationRate > 0.5) {
 | 
			
		||||
      for (let i = 0; i < this.Parts.length; i++) {
 | 
			
		||||
        const rand = Math.random()
 | 
			
		||||
        if (rand < this.mutationRate * 0.5) {
 | 
			
		||||
        // 和下一个调换顺序
 | 
			
		||||
          const j = i + 1
 | 
			
		||||
          if (j < this.Parts.length)
 | 
			
		||||
            [this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]]
 | 
			
		||||
        }
 | 
			
		||||
        if (rand < this.mutationRate)
 | 
			
		||||
          this.Parts[i].Mutate()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // 洗牌
 | 
			
		||||
      const rand = Math.random()
 | 
			
		||||
      if (rand < 0.5) {
 | 
			
		||||
        const index = RandomIndex(this.Parts.length - 2)
 | 
			
		||||
        const count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2) || 1
 | 
			
		||||
        const parts = this.Parts.splice(index, count)
 | 
			
		||||
        this.Parts.push(...parts)
 | 
			
		||||
        this.Parts[index].Mutate()
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        for (let i = 0; i < this.Parts.length; i++) {
 | 
			
		||||
          const rand = Math.random()
 | 
			
		||||
          if (rand < this.mutationRate) {
 | 
			
		||||
            this.Parts[i].Mutate()
 | 
			
		||||
            i += 5
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.mutationRate > 0.2)
 | 
			
		||||
      this.mutationRate -= 0.004
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // #region -------------------------File-------------------------
 | 
			
		||||
 | 
			
		||||
  // 对象从文件中读取数据,初始化自身
 | 
			
		||||
  ReadFile(file: NestFiler) {
 | 
			
		||||
    const ver = file.Read()// ver
 | 
			
		||||
 | 
			
		||||
    this.Fitness = file.Read()
 | 
			
		||||
    let count = file.Read() as number
 | 
			
		||||
    this.Containers = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
      this.Containers.push(new Container().ReadFile(file, this.Parts))
 | 
			
		||||
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this.HoleContainers = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
      this.HoleContainers.push(new Container().ReadFile(file, this.Parts))
 | 
			
		||||
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this.OddmentsContainers = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
      this.OddmentsContainers.push(new Container().ReadFile(file, this.Parts))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 对象将自身数据写入到文件.
 | 
			
		||||
  WriteFile(f: NestFiler) {
 | 
			
		||||
    f.Write(1)// ver
 | 
			
		||||
    f.Write(this.Fitness)
 | 
			
		||||
    f.Write(this.Containers.length)
 | 
			
		||||
    for (const c of this.Containers)
 | 
			
		||||
      c.WriteFile(f)
 | 
			
		||||
 | 
			
		||||
    f.Write(this.HoleContainers.length)
 | 
			
		||||
    for (const c of this.HoleContainers)
 | 
			
		||||
      c.WriteFile(f)
 | 
			
		||||
 | 
			
		||||
    f.Write(this.OddmentsContainers.length)
 | 
			
		||||
    for (const c of this.OddmentsContainers)
 | 
			
		||||
      c.WriteFile(f)
 | 
			
		||||
  }
 | 
			
		||||
  // #endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								tests/dev1/dataHandle/common/core/NestCache.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								tests/dev1/dataHandle/common/core/NestCache.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<number> } = {}
 | 
			
		||||
  private static CacheRect = new Map<string, Path>()
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 用于创建原点在0点的矩形路径
 | 
			
		||||
   */
 | 
			
		||||
  static CreatePath(x: number, y: number, knifRadius = 3.5): Path
 | 
			
		||||
  {
 | 
			
		||||
    let minX = -knifRadius
 | 
			
		||||
    let maxX = x + knifRadius
 | 
			
		||||
    let minY = -knifRadius
 | 
			
		||||
    let maxY = y + knifRadius
 | 
			
		||||
    return new Path([
 | 
			
		||||
      { x: minX, y: minY },
 | 
			
		||||
      { x: maxX, y: minY },
 | 
			
		||||
      { x: maxX, y: maxY },
 | 
			
		||||
      { x: minX, y: maxY },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Clear()
 | 
			
		||||
  {
 | 
			
		||||
    this.count1 = 0
 | 
			
		||||
    this.count2 = 0
 | 
			
		||||
    this.CacheRect.clear()
 | 
			
		||||
    this.PositionCache = {}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								tests/dev1/dataHandle/common/core/NestDatabase.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								tests/dev1/dataHandle/common/core/NestDatabase.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
import { NestFiler } from '../Filer'
 | 
			
		||||
import { Part } from './Part'
 | 
			
		||||
import { Path } from './Path'
 | 
			
		||||
import { PathGeneratorSingle } from './PathGenerator'
 | 
			
		||||
 | 
			
		||||
export const DefaultComparePointKeys = ['xy', 'yx']
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 排料数据库,用这个类来序列化需要排料的数据
 | 
			
		||||
 * 用于在Work间传输
 | 
			
		||||
 */
 | 
			
		||||
export class NestDatabase {
 | 
			
		||||
  Bin: Path // 默认的容器
 | 
			
		||||
  OddmentsBins: Path[]// 余料容器列表
 | 
			
		||||
  Paths: Path[] // 所有的Path都在这里
 | 
			
		||||
  Parts: Part[] // 所有的零件
 | 
			
		||||
  ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式
 | 
			
		||||
 | 
			
		||||
  // #region -------------------------File-------------------------
 | 
			
		||||
  // 对象从文件中读取数据,初始化自身
 | 
			
		||||
  ReadFile(file: NestFiler) {
 | 
			
		||||
    const ver = file.Read()
 | 
			
		||||
    let count = file.Read() as number
 | 
			
		||||
    this.Paths = []
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const path = new Path()
 | 
			
		||||
      path.ReadFile(file)
 | 
			
		||||
      this.Paths.push(path)
 | 
			
		||||
    }
 | 
			
		||||
    this.Bin = this.Paths[file.Read()]
 | 
			
		||||
    PathGeneratorSingle.paths = this.Paths
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this.Parts = []
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const part = new Part()
 | 
			
		||||
      part.ReadFile(file)
 | 
			
		||||
      this.Parts.push(part)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this.OddmentsBins = []
 | 
			
		||||
    for (let i = 0; i < count; i++) {
 | 
			
		||||
      const path = new Path()
 | 
			
		||||
      path.ReadFile(file)
 | 
			
		||||
      this.OddmentsBins.push(path)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ver > 1)
 | 
			
		||||
      this.ComparePointKeys = file.Read()
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 对象将自身数据写入到文件.
 | 
			
		||||
  WriteFile(file: NestFiler) {
 | 
			
		||||
    file.Write(2)
 | 
			
		||||
    file.Write(this.Paths.length)
 | 
			
		||||
    for (const path of this.Paths)
 | 
			
		||||
      path.WriteFile(file)
 | 
			
		||||
 | 
			
		||||
    file.Write(this.Bin.Id)
 | 
			
		||||
    file.Write(this.Parts.length)
 | 
			
		||||
    for (const part of this.Parts)
 | 
			
		||||
      part.WriteFile(file)
 | 
			
		||||
 | 
			
		||||
    if (!this.OddmentsBins)
 | 
			
		||||
      this.OddmentsBins = []
 | 
			
		||||
    file.Write(this.OddmentsBins.length)
 | 
			
		||||
    for (const path of this.OddmentsBins)
 | 
			
		||||
      path.WriteFile(file)
 | 
			
		||||
 | 
			
		||||
    file.Write(this.ComparePointKeys)
 | 
			
		||||
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
  // #endregion
 | 
			
		||||
 | 
			
		||||
  get File() {
 | 
			
		||||
    const f = new NestFiler()
 | 
			
		||||
    this.WriteFile(f)
 | 
			
		||||
    return f
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										163
									
								
								tests/dev1/dataHandle/common/core/OptimizeMachine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								tests/dev1/dataHandle/common/core/OptimizeMachine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
import { arrayRemoveIf } from '../ArrayExt'
 | 
			
		||||
import { clipperCpp } from '../ClipperCpp'
 | 
			
		||||
import { Sleep } from '../Sleep'
 | 
			
		||||
import { Individual } from './Individual'
 | 
			
		||||
import { NestCache } from './NestCache'
 | 
			
		||||
import { DefaultComparePointKeys } from './NestDatabase'
 | 
			
		||||
import type { Part } from './Part'
 | 
			
		||||
import type { Path } from './Path'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 优化器
 | 
			
		||||
 * 配置优化器
 | 
			
		||||
 * 放入零件
 | 
			
		||||
 * 按下启动
 | 
			
		||||
 * 按下暂停
 | 
			
		||||
 * 清理机台
 | 
			
		||||
 */
 | 
			
		||||
export class OptimizeMachine {
 | 
			
		||||
  // 配置
 | 
			
		||||
  Config: {
 | 
			
		||||
    PopulationCount: number// 种群个数
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Bin: Path // 默认的容器
 | 
			
		||||
  OddmentsBins: Path[]// 余料容器列表
 | 
			
		||||
  Parts: Part[] // 所有的零件
 | 
			
		||||
  ComparePointKeys: string[] = DefaultComparePointKeys// 用来决定零件靠边模式
 | 
			
		||||
 | 
			
		||||
  // //计算重复的零件 TODO:需要对相同的零件提取出来
 | 
			
		||||
  // PartCount: number[] = [];
 | 
			
		||||
 | 
			
		||||
  private _IsSuspend = false
 | 
			
		||||
 | 
			
		||||
  protected _Individuals: Individual[]// 个体列表
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.Config = { PopulationCount: 50 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 放入零件
 | 
			
		||||
  PutParts(parts: Part[]) {
 | 
			
		||||
    if (globalThis.document)
 | 
			
		||||
      parts = parts.slice()
 | 
			
		||||
    arrayRemoveIf(parts, p => p.RotatedStates.length === 0)
 | 
			
		||||
    this.Parts = parts
 | 
			
		||||
 | 
			
		||||
    // //计算重复的零件(暂时不用)
 | 
			
		||||
    // for (let part of parts)
 | 
			
		||||
    // {
 | 
			
		||||
    //     let count = this.PartCount[part.Id];
 | 
			
		||||
    //     this.PartCount[part.Id] = count === undefined ? 1 : (count + 1);
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  callBack: (i: Individual) => Promise<void>
 | 
			
		||||
 | 
			
		||||
  // 启动
 | 
			
		||||
  async Start() {
 | 
			
		||||
    if (this.Parts.length === 0)
 | 
			
		||||
      return
 | 
			
		||||
    console.log(this.Parts.length)
 | 
			
		||||
    this._IsSuspend = false
 | 
			
		||||
    NestCache.Clear()
 | 
			
		||||
    this.Parts.sort((p1, p2) => p2.State.Contour.Area - p1.State.Contour.Area)
 | 
			
		||||
    this._Individuals = [new Individual(this.Parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys)]
 | 
			
		||||
    for (let i = 1; i < this.Config.PopulationCount; i++) {
 | 
			
		||||
      const parts = this.Parts.map(p => p.Clone())
 | 
			
		||||
      if (i < 3) {
 | 
			
		||||
        for (let i = parts.length - 1; i > 0; i--) {
 | 
			
		||||
          const j = Math.floor(Math.random() * (i + 1));
 | 
			
		||||
          [parts[i], parts[j]] = [parts[j], parts[i]]
 | 
			
		||||
          parts[i].Mutate()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this._Individuals.push(new Individual(parts, 0.8, this.Bin, this.OddmentsBins, this.ComparePointKeys))
 | 
			
		||||
    }
 | 
			
		||||
    // 2.执行
 | 
			
		||||
    await this.Run()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  calcCount = 0
 | 
			
		||||
  best = Number.POSITIVE_INFINITY
 | 
			
		||||
  bestP: Individual
 | 
			
		||||
  bestCount = 0
 | 
			
		||||
  private async Run() {
 | 
			
		||||
    console.time('1')
 | 
			
		||||
    if (this.Parts.length === 0)
 | 
			
		||||
      return
 | 
			
		||||
    // 开始自然选择
 | 
			
		||||
    while (!this._IsSuspend) // 实验停止信号
 | 
			
		||||
    {
 | 
			
		||||
      const goBack = this.calcCount - this.bestCount > 8000
 | 
			
		||||
      // 1.适应环境(放置零件)
 | 
			
		||||
      for (let i = 0; i < this._Individuals.length; i++) {
 | 
			
		||||
        if (globalThis.document || !clipperCpp.lib)
 | 
			
		||||
          await Sleep(0)
 | 
			
		||||
        const p = this._Individuals[i]
 | 
			
		||||
        if (this.calcCount < 1000 || this.calcCount % 1000 === 0)
 | 
			
		||||
          p.Evaluate(this.best, true, i % 3)
 | 
			
		||||
        else
 | 
			
		||||
          p.Evaluate(this.best)
 | 
			
		||||
 | 
			
		||||
        if (!this.bestP || p.Fitness < this.bestP.Fitness) {
 | 
			
		||||
          this.bestP = p
 | 
			
		||||
          this.best = p.Fitness
 | 
			
		||||
          await this.callBack(p)
 | 
			
		||||
        }
 | 
			
		||||
        if (goBack)
 | 
			
		||||
          p.mutationRate = 0.5
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.calcCount += this._Individuals.length
 | 
			
		||||
      if (this.calcCount % 100 === 0) {
 | 
			
		||||
        await Sleep(0)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness)
 | 
			
		||||
      const bestP = this._Individuals[0]
 | 
			
		||||
      // //回调最好的
 | 
			
		||||
      // if (bestP.Fitness < this.best)
 | 
			
		||||
      // {
 | 
			
		||||
      //     this.best = bestP.Fitness;
 | 
			
		||||
      //     this.bestP = bestP;
 | 
			
		||||
      //     // console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2);
 | 
			
		||||
      //     if (this.callBack)
 | 
			
		||||
      //         await this.callBack(bestP);
 | 
			
		||||
      // }
 | 
			
		||||
 | 
			
		||||
      // 自然选择
 | 
			
		||||
      this._Individuals.splice(-10)// 杀死它
 | 
			
		||||
      for (let i = 0; i < 4; i++)
 | 
			
		||||
        this._Individuals.push(bestP.Clone())
 | 
			
		||||
      this._Individuals.push(this.bestP.Clone())
 | 
			
		||||
      for (let i = 0; i < 3; i++)
 | 
			
		||||
        this._Individuals.push(this._Individuals[1].Clone())
 | 
			
		||||
      for (let i = 0; i < 2; i++)
 | 
			
		||||
        this._Individuals.push(this._Individuals[2].Clone())
 | 
			
		||||
      // 全部突变
 | 
			
		||||
      for (const p of this._Individuals) {
 | 
			
		||||
        if (p.Fitness !== undefined)
 | 
			
		||||
          p.Mutate()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 暂停
 | 
			
		||||
  Suspend() {
 | 
			
		||||
    console.timeEnd('1')
 | 
			
		||||
    console.log(this.best, this.calcCount, NestCache.count1)
 | 
			
		||||
    this._IsSuspend = true
 | 
			
		||||
    console.log('暂停')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 继续
 | 
			
		||||
  Continue() {
 | 
			
		||||
    this._IsSuspend = false
 | 
			
		||||
    this.Run()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 清理机台
 | 
			
		||||
  Clear() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										238
									
								
								tests/dev1/dataHandle/common/core/ParseOddments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								tests/dev1/dataHandle/common/core/ParseOddments.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,238 @@
 | 
			
		||||
import { ClipType, EndType, JoinType, PolyFillType } from 'js-angusj-clipper/web'
 | 
			
		||||
import type { ClipInput } from 'js-angusj-clipper/web'
 | 
			
		||||
import { Box2 } from '..//Box2'
 | 
			
		||||
import { clipperCpp } from '../ClipperCpp'
 | 
			
		||||
import type { Container } from './Container'
 | 
			
		||||
import { NestCache } from './NestCache'
 | 
			
		||||
import { Path, PathScale, TranslatePath, TranslatePath_Self } from './Path'
 | 
			
		||||
 | 
			
		||||
const SquarePath = NestCache.CreatePath(60, 60, 0)
 | 
			
		||||
const CanPutPaths = [
 | 
			
		||||
  NestCache.CreatePath(200, 200, 0),
 | 
			
		||||
  NestCache.CreatePath(600, 100, 0),
 | 
			
		||||
  NestCache.CreatePath(100, 600, 0),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 分析排料结果的余料
 | 
			
		||||
 * @param container 排料结果的容器
 | 
			
		||||
 * @param binPath 容器的bin
 | 
			
		||||
 * @param [knifeRadius] 刀半径(以便我们再次偏移)
 | 
			
		||||
 * @param squarePath 使用一个正方形路径来简化余料轮廓
 | 
			
		||||
 * @param canPutPaths 使用可以放置的路径列表来测试余料是否可用,如果可用,则保留
 | 
			
		||||
 * @returns Path[] 轮廓的位置存储在OrigionMinPoint中
 | 
			
		||||
 */
 | 
			
		||||
export function ParseOddments(container: Container, binPath: Path, knifeRadius: number = 3.5, squarePath: Path = SquarePath, canPutPaths: Path[] = CanPutPaths): Path[]
 | 
			
		||||
{
 | 
			
		||||
  // 构建轮廓数据
 | 
			
		||||
  let partPaths: ClipInput[] = container.PlacedParts.map((part) =>
 | 
			
		||||
  {
 | 
			
		||||
    // 直接在这里偏移,而不缓存,应该没有性能问题
 | 
			
		||||
    let newPts = clipperCpp.lib.offsetToPaths({
 | 
			
		||||
      delta: knifeRadius * 1e4,
 | 
			
		||||
      offsetInputs: [{ data: part.State.Contour.BigIntPoints, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
 | 
			
		||||
    })[0]
 | 
			
		||||
 | 
			
		||||
    let path = TranslatePath(newPts, { x: part.PlacePosition.x - 5e3, y: part.PlacePosition.y - 5e3 })// 因为移动了0.5,0.5,所以这里也要移动0.5
 | 
			
		||||
    return { data: path }
 | 
			
		||||
  })
 | 
			
		||||
  // console.log('构建轮廓数据', partPaths)
 | 
			
		||||
  // 所有的余料(使用布尔差集)
 | 
			
		||||
  let oddmentsPolygon = clipperCpp.lib.clipToPolyTree({
 | 
			
		||||
    subjectInputs: [{ data: binPath.BigIntPoints, closed: true }],
 | 
			
		||||
    clipInputs: partPaths,
 | 
			
		||||
    clipType: ClipType.Difference,
 | 
			
		||||
    subjectFillType: PolyFillType.NonZero,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // 现在我们用树状结构,应该不会自交了?(文档写了,返回的结果不可能重叠或者自交)
 | 
			
		||||
  // 简化结果,避免自交
 | 
			
		||||
  // oddmentsPolygon = clipperCpp.lib.simplifyPolygons(oddmentsPolygon);
 | 
			
		||||
 | 
			
		||||
  function CreatePolygon(minx: number, miny: number, maxx: number, maxy: number)
 | 
			
		||||
  {
 | 
			
		||||
    return [
 | 
			
		||||
      { x: minx, y: miny },
 | 
			
		||||
      { x: maxx, y: miny },
 | 
			
		||||
      { x: maxx, y: maxy },
 | 
			
		||||
      { x: minx, y: maxy },
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let clipedPaths: Path[] = []// 已经减去网洞投影的余料轮廓列表
 | 
			
		||||
 | 
			
		||||
  // 由于手动排版可能造成余料网洞,我们将网洞的盒子投影,然后裁剪余料,避免余料有网洞
 | 
			
		||||
  for (let node of oddmentsPolygon.childs)
 | 
			
		||||
  {
 | 
			
		||||
    let nodePolygon = node.contour
 | 
			
		||||
    // 减去网洞
 | 
			
		||||
    if (node.childs.length)
 | 
			
		||||
    {
 | 
			
		||||
      let box = new Box2().setFromPoints(nodePolygon)
 | 
			
		||||
 | 
			
		||||
      let childBoxPolygon = node.childs.map((cnode) =>
 | 
			
		||||
      {
 | 
			
		||||
        let cbox = new Box2().setFromPoints(cnode.contour)
 | 
			
		||||
        let type = 0// 0左1右2上3下
 | 
			
		||||
        let minDist = Number.POSITIVE_INFINITY
 | 
			
		||||
 | 
			
		||||
        let letftDist = cbox.min.x - box.min.x
 | 
			
		||||
        let rightDist = box.max.x - cbox.max.x
 | 
			
		||||
        let topDist = box.max.y - cbox.max.y
 | 
			
		||||
        let downDist = cbox.min.y - box.min.y
 | 
			
		||||
 | 
			
		||||
        if (rightDist < letftDist)
 | 
			
		||||
        {
 | 
			
		||||
          type = 1
 | 
			
		||||
          minDist = rightDist
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (topDist < minDist)
 | 
			
		||||
        {
 | 
			
		||||
          type = 2
 | 
			
		||||
          minDist = topDist
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (downDist < minDist)
 | 
			
		||||
          type = 3
 | 
			
		||||
 | 
			
		||||
        if (type === 0)
 | 
			
		||||
          return CreatePolygon(box.min.x, cbox.min.y, cbox.max.x, cbox.max.y)
 | 
			
		||||
        if (type === 1)
 | 
			
		||||
          return CreatePolygon(cbox.min.x, cbox.min.y, box.max.x, cbox.max.y)
 | 
			
		||||
        if (type === 2)
 | 
			
		||||
          return CreatePolygon(cbox.min.x, cbox.min.y, cbox.max.x, box.max.y)
 | 
			
		||||
        if (type === 3)
 | 
			
		||||
          return CreatePolygon(cbox.min.x, box.min.y, cbox.max.x, cbox.max.y)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      let splits = clipperCpp.lib.clipToPaths({
 | 
			
		||||
        subjectInputs: [{ data: nodePolygon, closed: true }],
 | 
			
		||||
        clipInputs: childBoxPolygon.map((polygon) => { return { data: polygon } }),
 | 
			
		||||
        clipType: ClipType.Difference,
 | 
			
		||||
        subjectFillType: PolyFillType.NonZero,
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      for (let p of splits)
 | 
			
		||||
        clipedPaths.push(new Path(PathScale(p, 1e-4)))
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      clipedPaths.push(new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } })))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let OddmentsPaths: Path[] = []
 | 
			
		||||
  for (let polygonPath of clipedPaths)
 | 
			
		||||
  {
 | 
			
		||||
    // 先获取内部的nfp
 | 
			
		||||
    let insideNFPS = polygonPath.GetInsideNFP(squarePath)
 | 
			
		||||
 | 
			
		||||
    if (!insideNFPS)
 | 
			
		||||
      continue
 | 
			
		||||
 | 
			
		||||
    let beferPolygons: ClipInput[] = []
 | 
			
		||||
 | 
			
		||||
    for (let nfp of insideNFPS)
 | 
			
		||||
    {
 | 
			
		||||
      let nfpPath = new Path(PathScale(nfp, 1e-4))
 | 
			
		||||
      // 通过内部nfp还原实际轮廓
 | 
			
		||||
      let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, squarePath.BigIntPoints, true)
 | 
			
		||||
      sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons)
 | 
			
		||||
 | 
			
		||||
      for (let poly of sumPolygons)
 | 
			
		||||
      {
 | 
			
		||||
        if (clipperCpp.lib.area(poly) < 0)
 | 
			
		||||
          continue// 移除内部的,无意义的
 | 
			
		||||
 | 
			
		||||
        let tempPath = new Path(poly.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))// 这里new一个新的,下面就复用这个
 | 
			
		||||
        if (canPutPaths.some(p => tempPath.GetInsideNFP(p)?.length))// 能塞的下指定的轮廓才会被留下
 | 
			
		||||
        {
 | 
			
		||||
          if (beferPolygons.length)
 | 
			
		||||
          {
 | 
			
		||||
            // 移动到实际位置
 | 
			
		||||
            TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4)
 | 
			
		||||
 | 
			
		||||
            // 在这里裁剪之前的余料轮廓
 | 
			
		||||
            let tree = clipperCpp.lib.clipToPolyTree({
 | 
			
		||||
              subjectInputs: [{ data: poly, closed: true }],
 | 
			
		||||
              clipInputs: beferPolygons,
 | 
			
		||||
              clipType: ClipType.Difference,
 | 
			
		||||
              subjectFillType: PolyFillType.NonZero,
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            for (let node of tree.childs)
 | 
			
		||||
            {
 | 
			
		||||
              if (node.childs.length)
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
              tempPath = new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))
 | 
			
		||||
 | 
			
		||||
              // 继续简化
 | 
			
		||||
              tempPath = SimplifyPathOfSqPath(tempPath, squarePath)
 | 
			
		||||
 | 
			
		||||
              if (!tempPath)
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
              OddmentsPaths.push(tempPath)
 | 
			
		||||
 | 
			
		||||
              // 偏移2把刀
 | 
			
		||||
              let offsetedPolygon = clipperCpp.lib.offsetToPaths({
 | 
			
		||||
                delta: knifeRadius * 2e4,
 | 
			
		||||
                offsetInputs: [{ data: node.contour, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
 | 
			
		||||
              })[0]
 | 
			
		||||
              beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          else
 | 
			
		||||
          {
 | 
			
		||||
            // 设置轮廓的位置
 | 
			
		||||
            tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x
 | 
			
		||||
            tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y
 | 
			
		||||
            OddmentsPaths.push(tempPath)
 | 
			
		||||
 | 
			
		||||
            // 将余料轮廓加入到裁剪轮廓中,用于裁剪后续的余料
 | 
			
		||||
            if (insideNFPS.length)
 | 
			
		||||
            {
 | 
			
		||||
              // 移动到实际位置
 | 
			
		||||
              TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4)
 | 
			
		||||
              // 偏移2把刀
 | 
			
		||||
              let offsetedPolygon = clipperCpp.lib.offsetToPaths({
 | 
			
		||||
                delta: knifeRadius * 2e4,
 | 
			
		||||
                offsetInputs: [{ data: poly, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
 | 
			
		||||
              })[0]
 | 
			
		||||
              beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // console.log('ParseOddments end', OddmentsPaths)
 | 
			
		||||
  return OddmentsPaths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 使用矩形轮廓来简化余料轮廓(通常一进一出)
 | 
			
		||||
function SimplifyPathOfSqPath(polygonPath: Path, sqPath: Path): Path | undefined
 | 
			
		||||
{
 | 
			
		||||
  // 先获取内部的nfp
 | 
			
		||||
  let insideNFPS = polygonPath.GetInsideNFP(sqPath)
 | 
			
		||||
  if (insideNFPS.length > 0)// 目前一般只有1个,不知道会不会有多个
 | 
			
		||||
  {
 | 
			
		||||
    let nfp = insideNFPS[0]
 | 
			
		||||
    let nfpPath = new Path(PathScale(nfp, 1e-4))
 | 
			
		||||
    // 通过内部nfp还原实际轮廓
 | 
			
		||||
    let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, sqPath.BigIntPoints, true)
 | 
			
		||||
    sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons)
 | 
			
		||||
 | 
			
		||||
    for (let poly of sumPolygons)// 通常是一个内部的+一个外部的
 | 
			
		||||
    {
 | 
			
		||||
      if (clipperCpp.lib.area(poly) < 0)
 | 
			
		||||
        continue// 移除内部的,无意义的
 | 
			
		||||
 | 
			
		||||
      let tempPath = new Path(PathScale(poly, 1e-4))
 | 
			
		||||
 | 
			
		||||
      tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x
 | 
			
		||||
      tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y
 | 
			
		||||
      return tempPath
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										394
									
								
								tests/dev1/dataHandle/common/core/Part.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								tests/dev1/dataHandle/common/core/Part.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<T = any, Matrix = any>
 | 
			
		||||
{
 | 
			
		||||
  Id: number// 用于确定Part的唯一性,并且有助于在其他Work中还原
 | 
			
		||||
  private _Holes: PartState[] = []
 | 
			
		||||
  _RotateHoles: PartState[][] = []
 | 
			
		||||
  // 零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态
 | 
			
		||||
  StateIndex = 0 // 旋转状态
 | 
			
		||||
  RotatedStates: PartState[] = []// 可能的旋转状态列表
 | 
			
		||||
  PlacePosition: Point // 放置位置(相对于容器的位置)
 | 
			
		||||
 | 
			
		||||
  HolePosition: Point//
 | 
			
		||||
 | 
			
		||||
  // #region 临时数据(不会被序列化到优化线程)
 | 
			
		||||
  UserData: T// 只用于还原零件的显示状态(或者关联到实际数据)
 | 
			
		||||
  Parent: Part// 如果这个零件放置在了网洞中,这个Parent表示这个网洞所属的零件,我们可以得到这个零件的放置信息,并且可以从Container中的ParentM得到网洞相对于零件的位置
 | 
			
		||||
  PlaceCS: Matrix// 放置矩阵 Matrix4
 | 
			
		||||
  PlaceIndex: number// 放置的大板索引
 | 
			
		||||
  // #endregion
 | 
			
		||||
 | 
			
		||||
  GroupMap: { [key: number]: Part[] } = {}
 | 
			
		||||
  get State(): PartState // 零件当前的状态
 | 
			
		||||
  {
 | 
			
		||||
    return this.RotatedStates[this.StateIndex]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get Holes(): PartState[]
 | 
			
		||||
  {
 | 
			
		||||
    if (GNestConfig.RotateHole)
 | 
			
		||||
      return this._RotateHoles[this.StateIndex] || EmptyArray
 | 
			
		||||
    return this._Holes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 初始化零件的各个状态,按360度旋转个数
 | 
			
		||||
  Init(path: Path, bin: Path, rotateCount = 4): this
 | 
			
		||||
  {
 | 
			
		||||
    let rotations: number[] = []
 | 
			
		||||
    let a = 2 * Math.PI / rotateCount
 | 
			
		||||
    for (let i = 0; i < rotateCount; i++)
 | 
			
		||||
    {
 | 
			
		||||
      rotations.push(a * i)
 | 
			
		||||
    }
 | 
			
		||||
    this.Init2(path, bin, rotations)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 初始化零件的各个状态,按旋转角度表
 | 
			
		||||
  Init2(path: Path, bin: Path, rotations: number[] = []): this
 | 
			
		||||
  {
 | 
			
		||||
    let pathP = path.OrigionMinPoint
 | 
			
		||||
    let path_0 = PathGeneratorSingle.Allocate(path)
 | 
			
		||||
    let pathSet = new Set<Path>()
 | 
			
		||||
 | 
			
		||||
    // 初始化零件的状态集合
 | 
			
		||||
    for (let pa of rotations)
 | 
			
		||||
    {
 | 
			
		||||
      let partState = new PartState()
 | 
			
		||||
      partState.Rotation = pa
 | 
			
		||||
      if (pa === 0)
 | 
			
		||||
      {
 | 
			
		||||
        partState.Contour = path_0
 | 
			
		||||
        partState.OrigionMinPoint = pathP
 | 
			
		||||
        partState.MinPoint = path.OrigionMinPoint
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        let path_r = new Path(path.Points, pa)
 | 
			
		||||
        partState.Contour = PathGeneratorSingle.Allocate(path_r)
 | 
			
		||||
        partState.Contour.Area = path_0.Area
 | 
			
		||||
        // 避免重复的Path进入State
 | 
			
		||||
        if (pathSet.has(partState.Contour))
 | 
			
		||||
          continue
 | 
			
		||||
        let p0 = path_r.OrigionMinPoint
 | 
			
		||||
        let c = Math.cos(-pa)
 | 
			
		||||
        let s = Math.sin(-pa)
 | 
			
		||||
        let x1 = p0.x * c - p0.y * s
 | 
			
		||||
        let y1 = p0.x * s + p0.y * c
 | 
			
		||||
        partState.OrigionMinPoint = new Vector2(pathP.x + x1, pathP.y + y1)
 | 
			
		||||
 | 
			
		||||
        // 计算正确的最小点
 | 
			
		||||
        let tempPath = new Path(path.OrigionPoints, pa)
 | 
			
		||||
        partState.MinPoint = tempPath.OrigionMinPoint
 | 
			
		||||
      }
 | 
			
		||||
      // 记录已有Path
 | 
			
		||||
      pathSet.add(partState.Contour)
 | 
			
		||||
      // 必须能放置
 | 
			
		||||
      if (bin.GetInsideNFP(partState.Contour))
 | 
			
		||||
      {
 | 
			
		||||
        this.RotatedStates.push(partState)
 | 
			
		||||
        PathGeneratorSingle.RegisterId(partState.Contour)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 为了复用NFP,不管第0个Path是否可用,都注册它.
 | 
			
		||||
    if (this.RotatedStates.length > 4)
 | 
			
		||||
      PathGeneratorSingle.RegisterId(path_0)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ParseGroup(partOther: Part, bin: Path): Part[]
 | 
			
		||||
  {
 | 
			
		||||
    let arr = this.GroupMap[partOther.Id]
 | 
			
		||||
    if (arr)
 | 
			
		||||
      return arr
 | 
			
		||||
 | 
			
		||||
    arr = []
 | 
			
		||||
    if (this.Holes.length || partOther.Holes.length)
 | 
			
		||||
      return arr
 | 
			
		||||
    this.GroupMap[partOther.Id] = arr
 | 
			
		||||
    if (this.State.Contour.IsRect || partOther.State.Contour.IsRect)
 | 
			
		||||
      return arr
 | 
			
		||||
    if (this.State.Contour.Area > this.State.Contour.BoundingBox.area * 0.9)
 | 
			
		||||
      return arr
 | 
			
		||||
    if (partOther.State.Contour.Area > partOther.State.Contour.BoundingBox.area * 0.9)
 | 
			
		||||
      return arr
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < this.RotatedStates.length; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let s1 = this.RotatedStates[i]
 | 
			
		||||
      for (let j = 1; j < partOther.RotatedStates.length; j++)
 | 
			
		||||
      {
 | 
			
		||||
        let s2 = partOther.RotatedStates[j]
 | 
			
		||||
        let nfps = s1.Contour.GetOutsideNFP(s2.Contour)
 | 
			
		||||
        for (let nfp of nfps)
 | 
			
		||||
        {
 | 
			
		||||
          for (let k = 0; k < nfp.length * 2; k++)
 | 
			
		||||
          {
 | 
			
		||||
            let p: Point
 | 
			
		||||
            if (k % 2 === 0)
 | 
			
		||||
            {
 | 
			
		||||
              p = { ...nfp[k / 2] }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
              let p1 = nfp[FixIndex(k / 2 - 0.5, nfp)]
 | 
			
		||||
              let p2 = nfp[FixIndex(k / 2 + 0.5, nfp)]
 | 
			
		||||
              p = { x: p1.x + p2.x, y: p1.y + p2.y }
 | 
			
		||||
              p.x *= 0.5
 | 
			
		||||
              p.y *= 0.5
 | 
			
		||||
            }
 | 
			
		||||
            p.x *= 1e-4
 | 
			
		||||
            p.y *= 1e-4
 | 
			
		||||
 | 
			
		||||
            let newBox = s2.Contour.BoundingBox.clone().translate(p)
 | 
			
		||||
            newBox.union(s1.Contour.BoundingBox)
 | 
			
		||||
 | 
			
		||||
            if (newBox.area < (s1.Contour.Area + s2.Contour.Area) * 1.3)
 | 
			
		||||
            {
 | 
			
		||||
              let partGroup = new PartGroup(this, partOther, i, j, p, newBox, bin)
 | 
			
		||||
              if (partGroup.RotatedStates.length > 0
 | 
			
		||||
                                && !arr.some(p => p.State.Contour === partGroup.State.Contour)// 类似的
 | 
			
		||||
              )
 | 
			
		||||
              {
 | 
			
		||||
                arr.push(partGroup)
 | 
			
		||||
                return arr
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return arr
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 添加网洞
 | 
			
		||||
  AppendHole(path: Path)
 | 
			
		||||
  {
 | 
			
		||||
    let hole = new PartState()
 | 
			
		||||
    hole.Contour = PathGeneratorSingle.Allocate(path)
 | 
			
		||||
    PathGeneratorSingle.RegisterId(hole.Contour)
 | 
			
		||||
    hole.OrigionMinPoint = path.OrigionMinPoint
 | 
			
		||||
    hole.Rotation = 0
 | 
			
		||||
    this._Holes.push(hole)
 | 
			
		||||
 | 
			
		||||
    if (GNestConfig.RotateHole)
 | 
			
		||||
      for (let i = 0; i < this.RotatedStates.length; i++)
 | 
			
		||||
      {
 | 
			
		||||
        let r = this.RotatedStates[i].Rotation
 | 
			
		||||
        let arr = this._RotateHoles[i]
 | 
			
		||||
        if (!arr)
 | 
			
		||||
        {
 | 
			
		||||
          arr = []
 | 
			
		||||
          this._RotateHoles[i] = arr
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (r === 0)
 | 
			
		||||
        {
 | 
			
		||||
          hole.MinPoint = path.OrigionMinPoint
 | 
			
		||||
          arr.push(hole)
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
          let newPath = new Path(path.Points, r)
 | 
			
		||||
          let newHole = new PartState()
 | 
			
		||||
          newHole.Rotation = r
 | 
			
		||||
          newHole.Contour = PathGeneratorSingle.Allocate(newPath)
 | 
			
		||||
          PathGeneratorSingle.RegisterId(newHole.Contour)
 | 
			
		||||
          newHole.OrigionMinPoint = newPath.OrigionMinPoint
 | 
			
		||||
 | 
			
		||||
          // 计算正确的最小点
 | 
			
		||||
          let tempPath = new Path(path.OrigionPoints, r)
 | 
			
		||||
          newHole.MinPoint = tempPath.OrigionMinPoint
 | 
			
		||||
 | 
			
		||||
          arr.push(newHole)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO:因为现在实现的是左右翻转,所以会出现角度匹配不完全的问题(缺失上下翻转)
 | 
			
		||||
  Mirror(doubleFace: boolean)
 | 
			
		||||
  {
 | 
			
		||||
    let states = this.RotatedStates
 | 
			
		||||
    let holes = this._Holes
 | 
			
		||||
    let roholes = this._RotateHoles
 | 
			
		||||
    if (!doubleFace)
 | 
			
		||||
    {
 | 
			
		||||
      this.RotatedStates = []
 | 
			
		||||
      this._Holes = []
 | 
			
		||||
      this._RotateHoles = []
 | 
			
		||||
    }
 | 
			
		||||
    let count = states.length
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let s = states[i]
 | 
			
		||||
      let ns = s.Mirror()
 | 
			
		||||
      if (ns && !this.RotatedStates.some(state => state.Contour === s.Contour))
 | 
			
		||||
      {
 | 
			
		||||
        this.RotatedStates.push(ns)
 | 
			
		||||
 | 
			
		||||
        if (this._Holes.length > 0)
 | 
			
		||||
          this._Holes.push(holes[i].Mirror())
 | 
			
		||||
 | 
			
		||||
        if (this._RotateHoles.length > 0)
 | 
			
		||||
          this._RotateHoles.push(roholes[i].map(s => s.Mirror()))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 浅克隆
 | 
			
		||||
  Clone()
 | 
			
		||||
  {
 | 
			
		||||
    let part = new Part()
 | 
			
		||||
    part.Id = this.Id
 | 
			
		||||
    part.UserData = this.UserData
 | 
			
		||||
    part.RotatedStates = this.RotatedStates
 | 
			
		||||
    part.StateIndex = this.StateIndex
 | 
			
		||||
    part._Holes = this._Holes
 | 
			
		||||
    part._RotateHoles = this._RotateHoles
 | 
			
		||||
    return part
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 旋转起来,改变自身旋转状态(变异)
 | 
			
		||||
  Mutate(): this
 | 
			
		||||
  {
 | 
			
		||||
    this.StateIndex = RandomIndex(this.RotatedStates.length, this.StateIndex)
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // #region -------------------------File-------------------------
 | 
			
		||||
  ReadFile(file: NestFiler)
 | 
			
		||||
  {
 | 
			
		||||
    this.Id = file.Read()
 | 
			
		||||
    let count = file.Read() as number
 | 
			
		||||
    this.RotatedStates = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let state = new PartState()
 | 
			
		||||
      state.ReadFile(file)
 | 
			
		||||
      this.RotatedStates.push(state)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 无旋转网洞
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this._Holes = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let state = new PartState()
 | 
			
		||||
      state.ReadFile(file)
 | 
			
		||||
      this._Holes.push(state)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 旋转网洞
 | 
			
		||||
    count = file.Read()
 | 
			
		||||
    this._RotateHoles = []
 | 
			
		||||
    for (let i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
      let count2 = file.Read() as number
 | 
			
		||||
 | 
			
		||||
      let holes: PartState[] = []
 | 
			
		||||
      for (let j = 0; j < count2; j++)
 | 
			
		||||
      {
 | 
			
		||||
        let state = new PartState()
 | 
			
		||||
        state.ReadFile(file)
 | 
			
		||||
        holes.push(state)
 | 
			
		||||
      }
 | 
			
		||||
      this._RotateHoles.push(holes)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WriteFile(file: NestFiler)
 | 
			
		||||
  {
 | 
			
		||||
    file.Write(this.Id)
 | 
			
		||||
    file.Write(this.RotatedStates.length)
 | 
			
		||||
    for (let state of this.RotatedStates)
 | 
			
		||||
      state.WriteFile(file)
 | 
			
		||||
 | 
			
		||||
    // 非旋转网洞
 | 
			
		||||
    file.Write(this._Holes.length)
 | 
			
		||||
    for (let hole of this._Holes)
 | 
			
		||||
      hole.WriteFile(file)
 | 
			
		||||
 | 
			
		||||
    // 写入旋转网洞
 | 
			
		||||
    file.Write(this._RotateHoles.length)
 | 
			
		||||
    for (let holes of this._RotateHoles)
 | 
			
		||||
    {
 | 
			
		||||
      file.Write(holes.length)
 | 
			
		||||
      for (let hole of holes)
 | 
			
		||||
      {
 | 
			
		||||
        hole.WriteFile(file)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // #endregion
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 零件组合
 | 
			
		||||
export class PartGroup extends Part
 | 
			
		||||
{
 | 
			
		||||
  constructor(public part1: Part,
 | 
			
		||||
    public part2: Part,
 | 
			
		||||
    public index1: number,
 | 
			
		||||
    public index2: number,
 | 
			
		||||
    public p: Point,
 | 
			
		||||
    public box: Box2,
 | 
			
		||||
    bin: Path,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    super()
 | 
			
		||||
    let size = box.getSize(new Vector2())
 | 
			
		||||
    this.Init2(NestCache.CreatePath(size.x, size.y, 0), bin, [0])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Export(): Part[]
 | 
			
		||||
  {
 | 
			
		||||
    this.part1.StateIndex = this.index1
 | 
			
		||||
    this.part2.StateIndex = this.index2
 | 
			
		||||
 | 
			
		||||
    this.part1.PlacePosition = {
 | 
			
		||||
      x: this.PlacePosition.x - this.box.min.x * 1e4,
 | 
			
		||||
      y: this.PlacePosition.y - this.box.min.y * 1e4,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.part2.PlacePosition = {
 | 
			
		||||
      x: this.PlacePosition.x - this.box.min.x * 1e4 + this.p.x * 1e4,
 | 
			
		||||
      y: this.PlacePosition.y - this.box.min.y * 1e4 + this.p.y * 1e4,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [this.part1, this.part2]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								tests/dev1/dataHandle/common/core/PartState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/dev1/dataHandle/common/core/PartState.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										381
									
								
								tests/dev1/dataHandle/common/core/Path.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								tests/dev1/dataHandle/common/core/Path.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,381 @@
 | 
			
		||||
import { Box2 } from '../Box2.js'
 | 
			
		||||
import { clipperCpp } from '../ClipperCpp.js'
 | 
			
		||||
import type { NestFiler } from '../Filer.js'
 | 
			
		||||
import type { Point } from '../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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								tests/dev1/dataHandle/common/core/PathGenerator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								tests/dev1/dataHandle/common/core/PathGenerator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
import { FixIndex, equaln } from '../Util'
 | 
			
		||||
import type { Path } from './Path'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 轮廓路径构造器
 | 
			
		||||
 * 传递一组简化后的点表过来,如果已经有同样的点表时,返回已经生产的Path,避免重复产生Path。
 | 
			
		||||
 * 使用相同的PATH有复用路径缓存。
 | 
			
		||||
 *
 | 
			
		||||
 * 每次进行优化时,必须清理构造器,保证Path生成是对本次优化唯一。
 | 
			
		||||
 */
 | 
			
		||||
class PathGenerator
 | 
			
		||||
{
 | 
			
		||||
  paths: Path[] = []
 | 
			
		||||
  pathAreaMap: { [key: string]: Path[] } = {}
 | 
			
		||||
 | 
			
		||||
  // 缓存命中次数
 | 
			
		||||
  cacheCount = 0
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 如果存在同样的轮廓,则返回已经构造的轮廓,
 | 
			
		||||
   * 如果没有,则返回自身,并且注册它。
 | 
			
		||||
   * 如果id没有被注册,那么证明它无法放置在bin中
 | 
			
		||||
   */
 | 
			
		||||
  Allocate(path: Path): Path
 | 
			
		||||
  {
 | 
			
		||||
    let area = path.Area.toFixed(0)
 | 
			
		||||
    let paths = this.pathAreaMap[area]
 | 
			
		||||
    if (paths)
 | 
			
		||||
    {
 | 
			
		||||
      for (let ps of paths)
 | 
			
		||||
      {
 | 
			
		||||
        if (EqualPath(ps, path))
 | 
			
		||||
        {
 | 
			
		||||
          this.cacheCount++
 | 
			
		||||
          return ps
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      paths.push(path)
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      this.pathAreaMap[area] = [path]
 | 
			
		||||
    return path
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  RegisterId(path: Path)
 | 
			
		||||
  {
 | 
			
		||||
    if (path.Id === undefined)
 | 
			
		||||
    {
 | 
			
		||||
      path.Id = this.paths.length
 | 
			
		||||
      this.paths.push(path)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Clear()
 | 
			
		||||
  {
 | 
			
		||||
    this.paths = []
 | 
			
		||||
    this.pathAreaMap = {}
 | 
			
		||||
    this.cacheCount = 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 两路径相等,点表个数相等且每个点都相似
 | 
			
		||||
 */
 | 
			
		||||
function EqualPath(path1: Path, path2: Path): boolean
 | 
			
		||||
{
 | 
			
		||||
  if (path1.Points.length !== path2.Points.length)
 | 
			
		||||
    return false
 | 
			
		||||
 | 
			
		||||
  let p0 = path1.Points[0]
 | 
			
		||||
  let p2Index = path2.Points.findIndex((p) =>
 | 
			
		||||
  {
 | 
			
		||||
    return equaln(p.x, p0.x, 1e-3) && equaln(p.y, p0.y, 1e-3)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < path1.Points.length; i++)
 | 
			
		||||
  {
 | 
			
		||||
    let p1 = path1.Points[i]
 | 
			
		||||
    let p2 = path2.Points[FixIndex(p2Index + i, path2.Points)]
 | 
			
		||||
 | 
			
		||||
    if (!equaln(p1.x, p2.x, 1e-4) || !equaln(p1.y, p2.y, 1e-4))
 | 
			
		||||
      return false
 | 
			
		||||
  }
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const PathGeneratorSingle = new PathGenerator()
 | 
			
		||||
							
								
								
									
										11
									
								
								tests/dev1/dataHandle/common/core/PlaceType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/dev1/dataHandle/common/core/PlaceType.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
 | 
			
		||||
/**排牌类型:Hull=0凸包模式 (凸包面积) Box=1盒子模式 (长x宽)  Gravity=2重力模式(重力) */
 | 
			
		||||
export enum PlaceType
 | 
			
		||||
{
 | 
			
		||||
    /**凸包模式 (凸包面积) */
 | 
			
		||||
    Hull = 0,
 | 
			
		||||
    /**盒子模式 (长乘以宽) */
 | 
			
		||||
    Box = 1,
 | 
			
		||||
    /**重力模式(重力) */
 | 
			
		||||
    Gravity = 2
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										387
									
								
								tests/dev1/dataHandle/common/cutorder/CutOrder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								tests/dev1/dataHandle/common/cutorder/CutOrder.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										440
									
								
								tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										440
									
								
								tests/dev1/dataHandle/common/cutorder/CutPointHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								tests/dev1/dataHandle/common/decorators/json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/dev1/dataHandle/common/decorators/json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import 'reflect-metadata'
 | 
			
		||||
 | 
			
		||||
export const jsonOptionMataDataKey = Symbol('JsonOptions')
 | 
			
		||||
 | 
			
		||||
export enum JsonOptionType
 | 
			
		||||
  {
 | 
			
		||||
  Ignore = 0,
 | 
			
		||||
  Property = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class JsonOption
 | 
			
		||||
{
 | 
			
		||||
  constructor(type: JsonOptionType, data: any = null)
 | 
			
		||||
  {
 | 
			
		||||
    this.type = type
 | 
			
		||||
    this.data = data
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type: JsonOptionType
 | 
			
		||||
  data: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function jsonProperty(key: string | number)
 | 
			
		||||
{
 | 
			
		||||
  return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Property, { key }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function jsonIgnore()
 | 
			
		||||
{
 | 
			
		||||
  return Reflect.metadata(jsonOptionMataDataKey, new JsonOption(JsonOptionType.Ignore))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								tests/dev1/dataHandle/common/decorators/model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tests/dev1/dataHandle/common/decorators/model.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import 'reflect-metadata'
 | 
			
		||||
 | 
			
		||||
const displayMetadataKey = Symbol('display')
 | 
			
		||||
 | 
			
		||||
export function display(
 | 
			
		||||
  name: string,
 | 
			
		||||
  group: string = '',
 | 
			
		||||
  type: string = 'color',
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
  return Reflect.metadata(displayMetadataKey, {
 | 
			
		||||
    name,
 | 
			
		||||
    group,
 | 
			
		||||
    type,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getDisplayInfo(target: any)
 | 
			
		||||
{
 | 
			
		||||
  let keys = Object.getOwnPropertyNames(target)
 | 
			
		||||
  let list = []
 | 
			
		||||
  keys.forEach((key) =>
 | 
			
		||||
  {
 | 
			
		||||
    let info = <{ name: string; group: string; type: number }>(
 | 
			
		||||
            Reflect.getMetadata(displayMetadataKey, target, key)
 | 
			
		||||
        )
 | 
			
		||||
    if (info)
 | 
			
		||||
    {
 | 
			
		||||
      info
 | 
			
		||||
      list.push({
 | 
			
		||||
        prop: key,
 | 
			
		||||
        name: info.name,
 | 
			
		||||
        group: info.group,
 | 
			
		||||
        type: info.type,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return list
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								tests/dev1/dataHandle/common/drawing/base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								tests/dev1/dataHandle/common/drawing/base.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
export abstract class Drawing
 | 
			
		||||
{
 | 
			
		||||
  width: number
 | 
			
		||||
  height: number
 | 
			
		||||
 | 
			
		||||
  scale: number = 1
 | 
			
		||||
  abstract drawPath(path: DrawPathContext, option: DrawLineOption)
 | 
			
		||||
 | 
			
		||||
  abstract drawLine(
 | 
			
		||||
    x0: number,
 | 
			
		||||
    y0: number,
 | 
			
		||||
    x1: number,
 | 
			
		||||
    y1: number,
 | 
			
		||||
    option: DrawLineOption,
 | 
			
		||||
  )
 | 
			
		||||
  abstract drawRect(
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawRectOption,
 | 
			
		||||
  )
 | 
			
		||||
  abstract drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption)
 | 
			
		||||
  abstract drawText(text: string, x: number, y: number, option: DrawTextOption)
 | 
			
		||||
  abstract drawForeignObjectText(
 | 
			
		||||
    text: string,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    option: DrawTextOption,
 | 
			
		||||
  )
 | 
			
		||||
  abstract drawImage(
 | 
			
		||||
    source: CanvasImageSource,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawImageOption,
 | 
			
		||||
    border: number,
 | 
			
		||||
  ): Promise<any>
 | 
			
		||||
 | 
			
		||||
  abstract clearAll()
 | 
			
		||||
 | 
			
		||||
  abstract toHTML(): string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BaseDrawOption
 | 
			
		||||
{
 | 
			
		||||
  lineWidth: number = 1
 | 
			
		||||
  strokeStyle: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IDrawPath
 | 
			
		||||
{
 | 
			
		||||
  method: string
 | 
			
		||||
  args: number[]
 | 
			
		||||
}
 | 
			
		||||
export class DrawPathContext
 | 
			
		||||
{
 | 
			
		||||
  data: Array<IDrawPath> = []
 | 
			
		||||
 | 
			
		||||
  moveTo(x: number, y: number)
 | 
			
		||||
  {
 | 
			
		||||
    this.data.push({ method: 'M', args: [x, y] })
 | 
			
		||||
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  lineTo(x: number, y: number)
 | 
			
		||||
  {
 | 
			
		||||
    this.data.push({ method: 'L', args: [x, y] })
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DrawLineOption extends BaseDrawOption { }
 | 
			
		||||
export class DrawRectOption extends BaseDrawOption
 | 
			
		||||
{
 | 
			
		||||
  fillStyle: string
 | 
			
		||||
}
 | 
			
		||||
export class DrawImageOption extends BaseDrawOption { }
 | 
			
		||||
export class DrawLinePolygonOption extends BaseDrawOption
 | 
			
		||||
{
 | 
			
		||||
  isFill: boolean = true
 | 
			
		||||
  fillStyle: string = 'rgb(0,0,0)'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type DrawTextBaseLnie = 'alphabetic' | 'bottom' | 'hanging' | 'ideographic' | 'middle' | 'top'
 | 
			
		||||
export type DrawTextAlign = 'start' | 'center' | 'right'
 | 
			
		||||
export class DrawTextOption
 | 
			
		||||
{
 | 
			
		||||
  maxWidth?: number
 | 
			
		||||
  textAlign: DrawTextAlign = 'center'
 | 
			
		||||
  textBaseline: DrawTextBaseLnie = 'middle'
 | 
			
		||||
  fillStyle: string = 'rgb(0,0,0)'
 | 
			
		||||
  fontSize: string = '12'
 | 
			
		||||
  fontWeight: string = '400'
 | 
			
		||||
  fontFamily: string = '宋体'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										197
									
								
								tests/dev1/dataHandle/common/drawing/canvasDrawing.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								tests/dev1/dataHandle/common/drawing/canvasDrawing.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,197 @@
 | 
			
		||||
import { createBmpFile } from '../bmp.js'
 | 
			
		||||
import { getFileExt } from '../file.js'
 | 
			
		||||
import type {
 | 
			
		||||
  DrawImageOption,
 | 
			
		||||
  DrawLineOption,
 | 
			
		||||
  DrawLinePolygonOption,
 | 
			
		||||
  DrawPathContext,
 | 
			
		||||
  DrawRectOption,
 | 
			
		||||
  DrawTextOption } from './base.js'
 | 
			
		||||
import
 | 
			
		||||
{
 | 
			
		||||
  Drawing,
 | 
			
		||||
} from './base.js'
 | 
			
		||||
 | 
			
		||||
export class CanvasDrawing extends Drawing {
 | 
			
		||||
  canvas: HTMLCanvasElement
 | 
			
		||||
  ctx: CanvasRenderingContext2D
 | 
			
		||||
  // isCaching: boolean;
 | 
			
		||||
  outputType: string = 'png'
 | 
			
		||||
  orgWidth: number
 | 
			
		||||
  orgHeight: number
 | 
			
		||||
  toHTML(): string {
 | 
			
		||||
    let img = new Image(this.canvas.width, this.canvas.height)
 | 
			
		||||
    img.src = this.canvas.toDataURL(`image/${this.outputType}`)
 | 
			
		||||
    return img.outerHTML
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAll() {
 | 
			
		||||
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  constructor(
 | 
			
		||||
    canvas: HTMLCanvasElement,
 | 
			
		||||
    // isCaching: boolean = false,
 | 
			
		||||
  ) {
 | 
			
		||||
    super()
 | 
			
		||||
    this.canvas = canvas
 | 
			
		||||
    this.width = this.orgWidth = canvas.width = canvas.width
 | 
			
		||||
    this.height = this.orgHeight = canvas.height = canvas.height
 | 
			
		||||
    // this.isCaching = isCaching;
 | 
			
		||||
    this.ctx = this.canvas.getContext('2d')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawPath(path: DrawPathContext, option: DrawLineOption) {
 | 
			
		||||
    this.ctx.beginPath()
 | 
			
		||||
    for (const item of path.data) {
 | 
			
		||||
      switch (item.method) {
 | 
			
		||||
        case 'M':
 | 
			
		||||
          this.ctx.moveTo(item.args[0], item.args[1])
 | 
			
		||||
          break
 | 
			
		||||
        case 'L':
 | 
			
		||||
          this.ctx.lineTo(item.args[0], item.args[1])
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.ctx.lineWidth = option.lineWidth
 | 
			
		||||
    this.ctx.strokeStyle = option.strokeStyle
 | 
			
		||||
    this.ctx.stroke()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawLine(
 | 
			
		||||
    x0: number,
 | 
			
		||||
    y0: number,
 | 
			
		||||
    x1: number,
 | 
			
		||||
    y1: number,
 | 
			
		||||
    option: DrawLineOption,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.ctx.lineWidth = option.lineWidth
 | 
			
		||||
    this.ctx.strokeStyle = option.strokeStyle
 | 
			
		||||
    this.ctx.beginPath()
 | 
			
		||||
    this.ctx.moveTo(x0, y0)
 | 
			
		||||
    this.ctx.lineTo(x1, y1)
 | 
			
		||||
    this.ctx.stroke()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawRect(
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawRectOption,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.ctx.lineWidth = option.lineWidth
 | 
			
		||||
    this.ctx.strokeStyle = option.strokeStyle
 | 
			
		||||
    this.ctx.fillStyle = option.fillStyle
 | 
			
		||||
    if (option.fillStyle) {
 | 
			
		||||
      this.ctx.fillRect(x, y, width, height)
 | 
			
		||||
    } else {
 | 
			
		||||
      this.ctx.strokeRect(x, y, width, height)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawText(text: string, x: number, y: number, option: DrawTextOption) {
 | 
			
		||||
    this.ctx.textAlign = option.textAlign as CanvasTextAlign
 | 
			
		||||
    this.ctx.textBaseline = option.textBaseline as CanvasTextBaseline
 | 
			
		||||
    this.ctx.fillStyle = option.fillStyle
 | 
			
		||||
    this.ctx.font
 | 
			
		||||
      = `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}`
 | 
			
		||||
    this.ctx.fillText(text, x, y, option.maxWidth)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawForeignObjectText(
 | 
			
		||||
    text: string,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    option: DrawTextOption,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  imageCache: Map<string, HTMLImageElement> = new Map()
 | 
			
		||||
  drawImage(
 | 
			
		||||
    source: CanvasImageSource,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawImageOption,
 | 
			
		||||
    border: number,
 | 
			
		||||
  ) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      this.ctx.drawImage(source, x + border, y + border, width, height)
 | 
			
		||||
      resolve(null)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption) {
 | 
			
		||||
    this.ctx.beginPath()
 | 
			
		||||
    this.ctx.moveTo(points[0].x, points[0].y)
 | 
			
		||||
    for (let i = 1; i < points.length; i++) {
 | 
			
		||||
      this.ctx.lineTo(points[i].x, points[i].y)
 | 
			
		||||
    }
 | 
			
		||||
    this.ctx.closePath()
 | 
			
		||||
 | 
			
		||||
    this.ctx.stroke()
 | 
			
		||||
    if (option.isFill)
 | 
			
		||||
    {
 | 
			
		||||
      this.ctx.fillStyle = option.fillStyle
 | 
			
		||||
      this.ctx.fill()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setScale(value: number) {
 | 
			
		||||
    this.scale = value
 | 
			
		||||
    this.canvas.width = this.width = this.orgWidth * value
 | 
			
		||||
    this.canvas.height = this.height = this.orgHeight * value
 | 
			
		||||
    this.ctx.scale(value, value)
 | 
			
		||||
  }
 | 
			
		||||
  // resize(width: number, height: number)
 | 
			
		||||
  // {
 | 
			
		||||
  //     let oldWidth = this.width;
 | 
			
		||||
  //     let oldHeight = this.height;
 | 
			
		||||
  //     if (oldWidth != width || oldHeight != height)
 | 
			
		||||
  //     {
 | 
			
		||||
  //         let newCanvas = document.createElement('canvas');
 | 
			
		||||
  //         this.width = newCanvas.width = width;
 | 
			
		||||
  //         this.height = newCanvas.height = height;
 | 
			
		||||
  //         let newCtx = newCanvas.getContext('2d');
 | 
			
		||||
  //         newCtx.drawImage(this.canvas, 0, 0, oldWidth, oldHeight, 0, 0, width, height);
 | 
			
		||||
  //         this.canvas = newCanvas;
 | 
			
		||||
  //         this.ctx = newCtx;
 | 
			
		||||
  //     }
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  exportFile(nameOrType: string, param: string): Promise<Blob | Uint8Array> {
 | 
			
		||||
    let mimeType = 'image/png'
 | 
			
		||||
    let ext = getFileExt(nameOrType)
 | 
			
		||||
    if (ext === null) {
 | 
			
		||||
      ext = nameOrType
 | 
			
		||||
    }
 | 
			
		||||
    let paramDic: { [key: string]: string } = {}
 | 
			
		||||
    if (param != null) {
 | 
			
		||||
      for (const item of param.split('&')) {
 | 
			
		||||
        let kv = item.split('=')
 | 
			
		||||
        paramDic[kv[0]] = kv[1]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    switch (ext) {
 | 
			
		||||
      case 'bmp':
 | 
			
		||||
        let data = createBmpFile(
 | 
			
		||||
          this.ctx.getImageData(0, 0, this.width, this.height),
 | 
			
		||||
          paramDic,
 | 
			
		||||
        )
 | 
			
		||||
        return Promise.resolve(new Uint8Array(data))
 | 
			
		||||
      case 'jpg':
 | 
			
		||||
      case 'jpeg':
 | 
			
		||||
        mimeType = 'image/jpeg'
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
    return new Promise<Blob>((resolve) => {
 | 
			
		||||
      this.canvas.toBlob((data: Blob) => {
 | 
			
		||||
        resolve(data)
 | 
			
		||||
      }, mimeType)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								tests/dev1/dataHandle/common/drawing/canvasUtil.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tests/dev1/dataHandle/common/drawing/canvasUtil.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import type { DrawTextOption } from './base'
 | 
			
		||||
 | 
			
		||||
// const canvas = new OffscreenCanvas(0, 0);
 | 
			
		||||
const canvas = document.createElement('canvas')
 | 
			
		||||
const canvasCtx = canvas.getContext('2d')
 | 
			
		||||
 | 
			
		||||
export function blobToDataURL(data: Blob): Promise<string>
 | 
			
		||||
{
 | 
			
		||||
  return new Promise((resolve) =>
 | 
			
		||||
  {
 | 
			
		||||
    let reader = new FileReader()
 | 
			
		||||
    reader.onload = (response) =>
 | 
			
		||||
    {
 | 
			
		||||
      resolve(response.target.result as string)
 | 
			
		||||
    }
 | 
			
		||||
    reader.readAsDataURL(data)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getStringWidth(
 | 
			
		||||
  text: string,
 | 
			
		||||
  width: number,
 | 
			
		||||
  height: number,
 | 
			
		||||
  option: DrawTextOption,
 | 
			
		||||
): number
 | 
			
		||||
{
 | 
			
		||||
  canvasCtx.clearRect(
 | 
			
		||||
    0,
 | 
			
		||||
    0,
 | 
			
		||||
    width,
 | 
			
		||||
    height,
 | 
			
		||||
  )
 | 
			
		||||
  canvasCtx.textAlign = option.textAlign as CanvasTextAlign
 | 
			
		||||
  canvasCtx.textBaseline = option.textBaseline as CanvasTextBaseline
 | 
			
		||||
  canvasCtx.fillStyle = option.fillStyle
 | 
			
		||||
  canvasCtx.font
 | 
			
		||||
        = `${option.fontWeight} ${option.fontSize}px ${option.fontFamily}`
 | 
			
		||||
  return canvasCtx.measureText(text).width
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								tests/dev1/dataHandle/common/drawing/imageCode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/dev1/dataHandle/common/drawing/imageCode.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import jsbarcode from 'jsbarcode'
 | 
			
		||||
import type { QRCodeRenderersOptions } from 'qrcode'
 | 
			
		||||
import { toCanvas } from 'qrcode'
 | 
			
		||||
 | 
			
		||||
// const canvas = new OffscreenCanvas(0, 0);
 | 
			
		||||
const canvas = document.createElement('canvas')
 | 
			
		||||
const canvasCtx = canvas.getContext('2d')
 | 
			
		||||
 | 
			
		||||
export async function genBarcode(text: string, options: jsbarcode.Options)
 | 
			
		||||
{
 | 
			
		||||
  canvas.width = options.width
 | 
			
		||||
  canvas.height = options.height
 | 
			
		||||
  canvasCtx.clearRect(0, 0, options.width, options.height)
 | 
			
		||||
  try
 | 
			
		||||
  {
 | 
			
		||||
    jsbarcode(canvas, text, options)
 | 
			
		||||
    // return await canvas.transferToImageBitmap();
 | 
			
		||||
    return await createImageBitmap(canvas)
 | 
			
		||||
  } catch
 | 
			
		||||
  {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export async function genQrcode(text: string, options: QRCodeRenderersOptions)
 | 
			
		||||
{
 | 
			
		||||
  canvas.width = options.width
 | 
			
		||||
  canvas.height = options.width
 | 
			
		||||
  canvasCtx.clearRect(0, 0, options.width, options.width)
 | 
			
		||||
  await toCanvas(canvas, text, options)
 | 
			
		||||
 | 
			
		||||
  // return await canvas.transferToImageBitmap();
 | 
			
		||||
  return await createImageBitmap(canvas)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								tests/dev1/dataHandle/common/drawing/imageUtil.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/dev1/dataHandle/common/drawing/imageUtil.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
export function UrlToBitmap(src: string)
 | 
			
		||||
{
 | 
			
		||||
  return new Promise<HTMLImageElement>((resolve) =>
 | 
			
		||||
  {
 | 
			
		||||
    let img = new Image()
 | 
			
		||||
    img.src = src
 | 
			
		||||
    img.onload = () =>
 | 
			
		||||
    {
 | 
			
		||||
      resolve(img)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								tests/dev1/dataHandle/common/drawing/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tests/dev1/dataHandle/common/drawing/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export * from './base'
 | 
			
		||||
export * from './canvasDrawing'
 | 
			
		||||
export * from './svgDrawing'
 | 
			
		||||
							
								
								
									
										251
									
								
								tests/dev1/dataHandle/common/drawing/svgDrawing.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								tests/dev1/dataHandle/common/drawing/svgDrawing.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,251 @@
 | 
			
		||||
import type {
 | 
			
		||||
  DrawImageOption, DrawLineOption, DrawLinePolygonOption, DrawPathContext, DrawRectOption,
 | 
			
		||||
  DrawTextOption,
 | 
			
		||||
} from './base.js'
 | 
			
		||||
import
 | 
			
		||||
{ Drawing,
 | 
			
		||||
} from './base.js'
 | 
			
		||||
 | 
			
		||||
const canvas = document.createElement('canvas')
 | 
			
		||||
const canvasCtx = canvas.getContext('2d')
 | 
			
		||||
export class SvgDrawing extends Drawing
 | 
			
		||||
{
 | 
			
		||||
  svg: SVGElement
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  constructor(
 | 
			
		||||
    width: number | string,
 | 
			
		||||
    height: number | string,
 | 
			
		||||
        scale: number = 0.8,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    super()
 | 
			
		||||
    this.scale = scale
 | 
			
		||||
    this.width = Number(width)
 | 
			
		||||
    this.height = Number(height)
 | 
			
		||||
    this.svg = this.createElement('svg')
 | 
			
		||||
    // this.svg.setAttribute('width', this.width.toString());//(Number(width) * this.scale)
 | 
			
		||||
    // this.svg.setAttribute('height', this.height.toString());//(Number(height) * this.scale)
 | 
			
		||||
    // this.svg.style.transform = 'scale(' + this.scale + ')';
 | 
			
		||||
    // this.svg.style.transformOrigin = 'left top';
 | 
			
		||||
    this.svg.setAttribute('viewBox', `0 0 ${width} ${height}`)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private createElement(name)
 | 
			
		||||
  {
 | 
			
		||||
    return document.createElementNS('http://www.w3.org/2000/svg', name)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toHTML(): string
 | 
			
		||||
  {
 | 
			
		||||
    return this.svg.outerHTML
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAll()
 | 
			
		||||
  {
 | 
			
		||||
    this.svg.innerHTML = ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawPath(path: DrawPathContext, option: DrawLineOption)
 | 
			
		||||
  {
 | 
			
		||||
    let el = this.createElement('path')
 | 
			
		||||
 | 
			
		||||
    let pathStr = ''
 | 
			
		||||
    for (const item of path.data)
 | 
			
		||||
    {
 | 
			
		||||
      switch (item.method)
 | 
			
		||||
      {
 | 
			
		||||
        case 'M':
 | 
			
		||||
          pathStr += `M ${item.args.join(' ')} `
 | 
			
		||||
          break
 | 
			
		||||
        case 'L':
 | 
			
		||||
          pathStr += `L ${item.args.join(' ')} `
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    el.setAttribute('d', pathStr)
 | 
			
		||||
    el.setAttribute('stroke-width', option.lineWidth.toString())
 | 
			
		||||
    el.setAttribute('stroke', option.strokeStyle)
 | 
			
		||||
    this.svg.appendChild(el)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawLine(
 | 
			
		||||
    x0: number,
 | 
			
		||||
    y0: number,
 | 
			
		||||
    x1: number,
 | 
			
		||||
    y1: number,
 | 
			
		||||
    option: DrawLineOption,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    let el = this.createElement('line')
 | 
			
		||||
    el.setAttribute('x1', x0.toString()) // (Number(x0) * this.scale)
 | 
			
		||||
    el.setAttribute('y1', y0.toString()) // (Number(y0) * this.scale)
 | 
			
		||||
    el.setAttribute('x2', x1.toString()) // (Number(x1) * this.scale)
 | 
			
		||||
    el.setAttribute('y2', y1.toString()) // (Number(y1) * this.scale)
 | 
			
		||||
    el.setAttribute('stroke', option.strokeStyle)
 | 
			
		||||
    el.setAttribute('fill', 'rgba(0,0,0,0)')
 | 
			
		||||
    el.setAttribute('stroke-width', option.lineWidth.toString())
 | 
			
		||||
    this.svg.appendChild(el)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawRect(
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawRectOption,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    let el = this.createElement('rect')
 | 
			
		||||
    el.setAttribute('x', x.toString()) // ((x + option.baseLine / 2) * this.scale)
 | 
			
		||||
    el.setAttribute('y', y.toString()) // ((y + option.baseLine) * this.scale)
 | 
			
		||||
    el.setAttribute('width', width.toString()) // (Number(width) * this.scale)
 | 
			
		||||
    el.setAttribute('height', height.toString()) // (Number(height) * this.scale)
 | 
			
		||||
    if (option.fillStyle)
 | 
			
		||||
    {
 | 
			
		||||
      el.setAttribute('fill', option.fillStyle)
 | 
			
		||||
      el.setAttribute('stroke', option.strokeStyle)
 | 
			
		||||
    } else
 | 
			
		||||
    {
 | 
			
		||||
      el.setAttribute('fill', 'rgba(1,1,1,0)')
 | 
			
		||||
      el.setAttribute('stroke', option.strokeStyle)
 | 
			
		||||
      el.setAttribute(
 | 
			
		||||
        'stroke-width',
 | 
			
		||||
        (Math.floor(option.lineWidth) + 0.5).toString(),
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    this.svg.appendChild(el)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawText(text: string, x: number, y: number, option: DrawTextOption)
 | 
			
		||||
  {
 | 
			
		||||
    let el = this.createElement('text')
 | 
			
		||||
    el.setAttribute('x', x.toString()) // (Number(x) * this.scale)  option['CncDict'] ? (x - Number(option.fontSize) / 6).toString() :
 | 
			
		||||
    el.setAttribute('y', y.toString()) // (Number(y) * this.scale)
 | 
			
		||||
    el.style.dominantBaseline = 'text-before-edge'
 | 
			
		||||
    if (option.resetFont)
 | 
			
		||||
    {
 | 
			
		||||
      el.style.dominantBaseline = 'middle'
 | 
			
		||||
      el.setAttribute('text-anchor', 'middle')
 | 
			
		||||
    }
 | 
			
		||||
    el.setAttribute('fill', option.fillStyle)
 | 
			
		||||
    el.setAttribute('font-size', `${option.fontSize}px`)
 | 
			
		||||
    el.setAttribute('font-weight', option.fontWeight)
 | 
			
		||||
    el.setAttribute('font-family', option.fontFamily)
 | 
			
		||||
    if (option.overflowText)
 | 
			
		||||
    {
 | 
			
		||||
      el.setAttribute('textLength', option.maxWidth.toString())
 | 
			
		||||
      el.setAttribute('lengthAdjust', 'spacingAndGlyphs')
 | 
			
		||||
    }
 | 
			
		||||
    el.setAttribute('alignment-baseline', option.textBaseline)
 | 
			
		||||
    switch (option.textAlign)
 | 
			
		||||
    {
 | 
			
		||||
      case 'start':
 | 
			
		||||
        el.setAttribute('text-anchor', 'left')
 | 
			
		||||
        break
 | 
			
		||||
      case 'center':
 | 
			
		||||
        el.setAttribute('text-anchor', 'middle')
 | 
			
		||||
        break
 | 
			
		||||
      case 'right':
 | 
			
		||||
        el.setAttribute('text-anchor', 'end')
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof text === 'string')
 | 
			
		||||
    {
 | 
			
		||||
      el.innerHTML = text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')
 | 
			
		||||
    } else
 | 
			
		||||
    {
 | 
			
		||||
      el.innerHTML = text
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.svg.appendChild(el)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawForeignObjectText(
 | 
			
		||||
    text: string,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    option: DrawTextOption,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    //   let el = this.createElement('foreignObject');
 | 
			
		||||
    //   el.setAttribute('x',x.toString());
 | 
			
		||||
    //   el.setAttribute('y',y.toString());
 | 
			
		||||
    //   el.setAttribute('width',option.maxWidth.toString());
 | 
			
		||||
    //   el.setAttribute('height',option['maxHeight'].toString());
 | 
			
		||||
    //   let span = this.createElement('span');
 | 
			
		||||
    //   span.style.dominantBaseline = 'text-before-edge';
 | 
			
		||||
    //   if(option['resetFont']){
 | 
			
		||||
    //     span.style.dominantBaseline = 'middle';
 | 
			
		||||
    //     span.style.textAnchor = 'middle';
 | 
			
		||||
    //   }
 | 
			
		||||
    //   span.style.fontSize = option.fontSize + 'px';
 | 
			
		||||
    //   span.style.fontWeight = option.fontWeight;
 | 
			
		||||
    //   span.style.fontFamily = option.fontFamily;
 | 
			
		||||
    //   span.style.display = 'inline-block';
 | 
			
		||||
    //   span.style.width = option.maxWidth + 'px';
 | 
			
		||||
    //   span.style.height = option['maxHeight'] + 'px';
 | 
			
		||||
    //   span.style.overflow = 'hidden';
 | 
			
		||||
    //   span.innerHTML = text;
 | 
			
		||||
    //   el.appendChild(span);
 | 
			
		||||
    //   this.svg.appendChild(el);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async drawImage(
 | 
			
		||||
    source: CanvasImageSource,
 | 
			
		||||
    x: number,
 | 
			
		||||
    y: number,
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    option: DrawImageOption,
 | 
			
		||||
  )
 | 
			
		||||
  {
 | 
			
		||||
    let src = ''
 | 
			
		||||
    if (source instanceof ImageBitmap)
 | 
			
		||||
    {
 | 
			
		||||
      canvas.width = source.width
 | 
			
		||||
      canvas.height = source.height
 | 
			
		||||
      canvasCtx.clearRect(0, 0, source.width, source.height)
 | 
			
		||||
      canvasCtx.drawImage(source, 0, 0, source.width, source.height)
 | 
			
		||||
      src = canvas.toDataURL()
 | 
			
		||||
    } else if (source instanceof HTMLImageElement)
 | 
			
		||||
    {
 | 
			
		||||
      src = source.src
 | 
			
		||||
    }
 | 
			
		||||
    return new Promise((resolve, reject) =>
 | 
			
		||||
    {
 | 
			
		||||
      let el = this.createElement('image')
 | 
			
		||||
      el.setAttribute('x', x.toString()) // (Number(x) * this.scale)
 | 
			
		||||
      el.setAttribute('y', y.toString()) // (Number(y) * this.scale)
 | 
			
		||||
      el.setAttribute('width', width.toString()) // (Number(width) * this.scale)
 | 
			
		||||
      el.setAttribute('height', height.toString()) // (Number(height) * this.scale)
 | 
			
		||||
      el.setAttribute('xlink:href', src)
 | 
			
		||||
      el.setAttribute('preserveAspectRatio', 'none meet')
 | 
			
		||||
      this.svg.appendChild(el)
 | 
			
		||||
      resolve(null)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drawLinePolygon(points: Array<any>, option: DrawLinePolygonOption)
 | 
			
		||||
  {
 | 
			
		||||
    let el = this.createElement('polygon')
 | 
			
		||||
    let pointsStr = ''
 | 
			
		||||
    for (let i = 0; i < points.length; i++)
 | 
			
		||||
    {
 | 
			
		||||
      if (i == points.length - 1)
 | 
			
		||||
      {
 | 
			
		||||
        pointsStr += `${points[i].x} ${points[i].y}`
 | 
			
		||||
      } else
 | 
			
		||||
      {
 | 
			
		||||
        pointsStr += `${points[i].x} ${points[i].y},`
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    el.setAttribute('points', pointsStr)
 | 
			
		||||
    el.setAttribute('fill', option.isFill ? option.fillStyle : 'none')
 | 
			
		||||
    el.setAttribute('stroke', option.strokeStyle)
 | 
			
		||||
    el.setAttribute('stroke-width', option.lineWidth.toString())// 添加多段线的线宽--xyh
 | 
			
		||||
    this.svg.appendChild(el)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								tests/dev1/dataHandle/common/file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/dev1/dataHandle/common/file.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
export async function readText(file: Blob, encoding?: string): Promise<string>
 | 
			
		||||
{
 | 
			
		||||
  return new Promise((resolve) =>
 | 
			
		||||
  {
 | 
			
		||||
    let fileReader = new FileReader()
 | 
			
		||||
    fileReader.onload = function (e)
 | 
			
		||||
    {
 | 
			
		||||
      resolve(<string> this.result)
 | 
			
		||||
    }
 | 
			
		||||
    fileReader.readAsText(file, encoding)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getFileExt(name: string)
 | 
			
		||||
{
 | 
			
		||||
  let index = name.lastIndexOf('.')
 | 
			
		||||
  if (index != -1)
 | 
			
		||||
  {
 | 
			
		||||
    return name.substring(index + 1, name.length)
 | 
			
		||||
  }
 | 
			
		||||
  return null
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								tests/dev1/dataHandle/common/log.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/dev1/dataHandle/common/log.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
export class Logger
 | 
			
		||||
{
 | 
			
		||||
  name: string
 | 
			
		||||
  static interceptors: LogInterceptor[] = []
 | 
			
		||||
  constructor(name: string)
 | 
			
		||||
  {
 | 
			
		||||
    this.name = name
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static AddInterceptor(interceptor: LogInterceptor)
 | 
			
		||||
  {
 | 
			
		||||
    Logger.interceptors.push(interceptor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  error(error: Error, payload: any = null)
 | 
			
		||||
  {
 | 
			
		||||
    console.error(error, payload)
 | 
			
		||||
    Logger.interceptors.forEach((i) =>
 | 
			
		||||
    {
 | 
			
		||||
      i.call(this, error, payload)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  info(message: string, payload: any = null)
 | 
			
		||||
  {
 | 
			
		||||
    console.info(message, payload)
 | 
			
		||||
    Logger.interceptors.forEach((i) =>
 | 
			
		||||
    {
 | 
			
		||||
      i.call(this, message, payload)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export type LogInterceptor = (this: Logger, info: string | Error, payload: any) => void
 | 
			
		||||
							
								
								
									
										151
									
								
								tests/dev1/dataHandle/common/zip.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								tests/dev1/dataHandle/common/zip.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
// import JsZip from 'jszip';
 | 
			
		||||
import type { DeflateOptions } from 'fflate'
 | 
			
		||||
import { Zip, ZipDeflate, strToU8 } from 'fflate'
 | 
			
		||||
import { getFileExt } from './file'
 | 
			
		||||
 | 
			
		||||
export class FileInfo
 | 
			
		||||
{
 | 
			
		||||
  name: string
 | 
			
		||||
  content: string | Blob | Uint8Array
 | 
			
		||||
  encode: string = '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<void>;
 | 
			
		||||
  protected files: FileInfo[] = []
 | 
			
		||||
 | 
			
		||||
  addFile(file: FileInfo)
 | 
			
		||||
  {
 | 
			
		||||
    this.files.push(file)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract saveAsync(): Promise<Blob>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export class JsZipProvider extends ZipProvider
 | 
			
		||||
// {
 | 
			
		||||
 | 
			
		||||
//     private ctx = new JsZip();
 | 
			
		||||
 | 
			
		||||
//     // async loadAsync(file: File): Promise<void>
 | 
			
		||||
//     // {
 | 
			
		||||
//     //     await this.ctx.loadAsync(file);
 | 
			
		||||
//     // }
 | 
			
		||||
 | 
			
		||||
//     async saveAsync(): Promise<Blob>
 | 
			
		||||
//     {
 | 
			
		||||
//         for (const file of this.files)
 | 
			
		||||
//         {
 | 
			
		||||
//             this.ctx.file(file.name, file.content, {
 | 
			
		||||
//                 createFolders: true,
 | 
			
		||||
//                 base64: file.isBase64,
 | 
			
		||||
//                 binary: file.binary,
 | 
			
		||||
//                 compression: "DEFLATE",
 | 
			
		||||
//             });
 | 
			
		||||
//         }
 | 
			
		||||
//         return await this.ctx.generateAsync({ type: 'blob' });
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export class FflateZipProvider extends ZipProvider
 | 
			
		||||
{
 | 
			
		||||
  private ctx = new Zip()
 | 
			
		||||
 | 
			
		||||
  private task: Promise<Blob>
 | 
			
		||||
 | 
			
		||||
  constructor()
 | 
			
		||||
  {
 | 
			
		||||
    super()
 | 
			
		||||
    this.task = new Promise<Blob>((resolve, reject) =>
 | 
			
		||||
    {
 | 
			
		||||
      let result = []
 | 
			
		||||
      this.ctx.ondata = (err, data, final) =>
 | 
			
		||||
      {
 | 
			
		||||
        if (!err)
 | 
			
		||||
        {
 | 
			
		||||
          result.push(data)
 | 
			
		||||
          if (final)
 | 
			
		||||
          {
 | 
			
		||||
            resolve(new Blob(result, { type: 'application/zip' }))
 | 
			
		||||
          }
 | 
			
		||||
        } else
 | 
			
		||||
        {
 | 
			
		||||
          reject()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async saveAsync(): Promise<Blob>
 | 
			
		||||
  {
 | 
			
		||||
    for (const file of this.files)
 | 
			
		||||
    {
 | 
			
		||||
      let data: Uint8Array
 | 
			
		||||
      if (file.content instanceof Blob)
 | 
			
		||||
      {
 | 
			
		||||
        // console.log(file.name);
 | 
			
		||||
        let buffer = await file.content.arrayBuffer()
 | 
			
		||||
        // console.log('buffer', buffer);
 | 
			
		||||
        data = new Uint8Array(buffer)
 | 
			
		||||
      } else if (file.content instanceof Uint8Array)
 | 
			
		||||
      {
 | 
			
		||||
        data = file.content
 | 
			
		||||
      }
 | 
			
		||||
      else if (file.isBase64)
 | 
			
		||||
      {
 | 
			
		||||
        data = new Uint8Array(atob(file.content).split('').map((c) =>
 | 
			
		||||
        {
 | 
			
		||||
          return c.charCodeAt(0)
 | 
			
		||||
        }))
 | 
			
		||||
      } else
 | 
			
		||||
      {
 | 
			
		||||
        data = strToU8(file.content)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let zipInput = new ZipDeflate(file.name, this.getOptionByExt(file.name))
 | 
			
		||||
      this.ctx.add(zipInput)
 | 
			
		||||
      zipInput.push(data, true)
 | 
			
		||||
    }
 | 
			
		||||
    this.ctx.end()
 | 
			
		||||
    return this.task
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getOptionByExt(fileName: string)
 | 
			
		||||
  {
 | 
			
		||||
    let option: DeflateOptions = {
 | 
			
		||||
      level: 6,
 | 
			
		||||
      // mem: 12
 | 
			
		||||
    }
 | 
			
		||||
    let ext = getFileExt(fileName)
 | 
			
		||||
    if (ext !== null)
 | 
			
		||||
    {
 | 
			
		||||
      switch (ext)
 | 
			
		||||
      {
 | 
			
		||||
        case 'bmp':
 | 
			
		||||
          option.level = 1
 | 
			
		||||
          option.mem = 0
 | 
			
		||||
          break
 | 
			
		||||
        case 'jpg':
 | 
			
		||||
        case 'jpeg':
 | 
			
		||||
          option.level = 1
 | 
			
		||||
          break
 | 
			
		||||
        case 'png':
 | 
			
		||||
          option.level = 0
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return option
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DefaultZipProvider = () => new FflateZipProvider()
 | 
			
		||||
							
								
								
									
										4814
									
								
								tests/dev1/dataHandle/confClass.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4814
									
								
								tests/dev1/dataHandle/confClass.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										28
									
								
								tests/dev1/dataHandle/models/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								tests/dev1/dataHandle/models/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 配置基类,下划线开头的变量不会被序列化
 | 
			
		||||
 */
 | 
			
		||||
export class ConfigBase {
 | 
			
		||||
    name: string = '';
 | 
			
		||||
    version:string = '1.0.0';
 | 
			
		||||
    [key: string]: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 加载反序列化数据
 | 
			
		||||
     * @param data 
 | 
			
		||||
     */
 | 
			
		||||
    load(data:Record<string,unknown>){
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								tests/dev1/dataHandle/models/file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tests/dev1/dataHandle/models/file.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
export interface FileOptions {
 | 
			
		||||
    encode?: string;
 | 
			
		||||
    addBOM?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export interface FileInfo extends FileOptions {
 | 
			
		||||
    name: string,
 | 
			
		||||
    content: string | Blob | Uint8Array,
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -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)
 | 
			
		||||
							
								
								
									
										1605
									
								
								tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1605
									
								
								tests/dev1/dataHandle/optimizeLayout/RectOptimizeWorker/bang.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										400
									
								
								tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								tests/dev1/dataHandle/optimizeLayout/optimizeLayout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<any, Function, any> {
 | 
			
		||||
    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<any, Function, any>): Promise<void> {
 | 
			
		||||
        // 输入数据
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								tests/dev1/dataHandle/optimizeLayout/优化排版.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/dev1/dataHandle/optimizeLayout/优化排版.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										253
									
								
								tests/dev1/dataHandle/tools/tool.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								tests/dev1/dataHandle/tools/tool.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,253 @@
 | 
			
		||||
import { ProcessorBase, ProcessorContext } from "../base";
 | 
			
		||||
import { ConfigBase } from "../models/config";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/** 刀具工具类 
 | 
			
		||||
 * 通过上下文的形式使用
 | 
			
		||||
*/
 | 
			
		||||
export class ToolsHelper extends ProcessorBase<any, any, any> {
 | 
			
		||||
    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<Knife[], any, any>) {
 | 
			
		||||
 | 
			
		||||
        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<Knife>
 | 
			
		||||
 | 
			
		||||
/** 刀功能: 
 | 
			
		||||
 ** 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								tests/dev1/dataHandle/tools/刀库.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/dev1/dataHandle/tools/刀库.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										1513
									
								
								tests/dev1/index3.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1513
									
								
								tests/dev1/index3.vue
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user