init
This commit is contained in:
		
							
								
								
									
										31
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								.eslintrc.js
									
									
									
									
									
								
							@@ -1,31 +0,0 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  env: {
 | 
			
		||||
    node: true
 | 
			
		||||
  },
 | 
			
		||||
  extends: [
 | 
			
		||||
    "plugin:vue/essential",
 | 
			
		||||
    "eslint:recommended",
 | 
			
		||||
    "@vue/typescript/recommended",
 | 
			
		||||
    "@vue/prettier",
 | 
			
		||||
    "@vue/prettier/@typescript-eslint"
 | 
			
		||||
  ],
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    ecmaVersion: 2020
 | 
			
		||||
  },
 | 
			
		||||
  rules: {
 | 
			
		||||
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
 | 
			
		||||
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
 | 
			
		||||
  },
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: [
 | 
			
		||||
        "**/__tests__/*.{j,t}s?(x)",
 | 
			
		||||
        "**/tests/unit/**/*.spec.{j,t}s?(x)"
 | 
			
		||||
      ],
 | 
			
		||||
      env: {
 | 
			
		||||
        jest: true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							@@ -1,29 +1,3 @@
 | 
			
		||||
# cut-demo
 | 
			
		||||
 | 
			
		||||
## Project setup
 | 
			
		||||
```
 | 
			
		||||
npm install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Compiles and hot-reloads for development
 | 
			
		||||
```
 | 
			
		||||
npm run serve
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Compiles and minifies for production
 | 
			
		||||
```
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Run your unit tests
 | 
			
		||||
```
 | 
			
		||||
npm run test:unit
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Lints and fixes files
 | 
			
		||||
```
 | 
			
		||||
npm run lint
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Customize configuration
 | 
			
		||||
See [Configuration Reference](https://cli.vuejs.org/config/).
 | 
			
		||||
### 依赖
 | 
			
		||||
- js-angusj-clipper
 | 
			
		||||
- monotone-convex-hull-2d
 | 
			
		||||
							
								
								
									
										1067
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1067
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
								
							@@ -10,27 +10,26 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "core-js": "^3.6.4",
 | 
			
		||||
    "element-ui": "^2.13.1",
 | 
			
		||||
    "iconv-lite": "^0.5.1",
 | 
			
		||||
    "js-angusj-clipper": "^1.0.4",
 | 
			
		||||
    "jszip": "^3.4.0",
 | 
			
		||||
    "monotone-convex-hull-2d": "^1.0.1",
 | 
			
		||||
    "vue": "^2.6.11"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/jest": "^24.0.19",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^2.26.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^2.26.0",
 | 
			
		||||
    "@types/node": "^13.13.4",
 | 
			
		||||
    "@vue/cli-plugin-babel": "~4.3.0",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "~4.3.0",
 | 
			
		||||
    "@vue/cli-plugin-typescript": "~4.3.0",
 | 
			
		||||
    "@vue/cli-plugin-unit-jest": "~4.3.0",
 | 
			
		||||
    "@vue/cli-service": "~4.3.0",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^6.0.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^5.0.2",
 | 
			
		||||
    "@vue/test-utils": "1.0.0-beta.31",
 | 
			
		||||
    "eslint": "^6.7.2",
 | 
			
		||||
    "eslint-plugin-prettier": "^3.1.1",
 | 
			
		||||
    "eslint-plugin-vue": "^6.2.2",
 | 
			
		||||
    "prettier": "^1.19.1",
 | 
			
		||||
    "sass": "^1.26.3",
 | 
			
		||||
    "sass-loader": "^8.0.2",
 | 
			
		||||
    "typescript": "~3.8.3",
 | 
			
		||||
    "vue-template-compiler": "^2.6.11"
 | 
			
		||||
    "vue-template-compiler": "^2.6.11",
 | 
			
		||||
    "worker-loader": "^2.0.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								src/App.vue
									
									
									
									
									
								
							@@ -1,29 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="app">
 | 
			
		||||
    <img alt="Vue logo" src="./assets/logo.png" />
 | 
			
		||||
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
 | 
			
		||||
    <select-file accept=".cfdat" button-type="primary" @change="loadCFData"
 | 
			
		||||
      >导入CFDATA</select-file
 | 
			
		||||
    >
 | 
			
		||||
    <el-button @click="stopThread">停止优化</el-button>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import HelloWorld from "./components/HelloWorld.vue";
 | 
			
		||||
import SelectFile from "@/components/SelectFile.vue";
 | 
			
		||||
import * as fileHelper from "@/Business/FileHelper";
 | 
			
		||||
 | 
			
		||||
// import JSZip from "jszip";
 | 
			
		||||
// import iconv from "iconv-lite";
 | 
			
		||||
import { BlockConvert } from "./Business/BlockConvert";
 | 
			
		||||
import { LayoutEngine } from "./Business/LayoutEngine";
 | 
			
		||||
 | 
			
		||||
let engine: { stopThread: () => void };
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: "App",
 | 
			
		||||
  components: {
 | 
			
		||||
    HelloWorld
 | 
			
		||||
  name: "app",
 | 
			
		||||
  components: { SelectFile },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activeGroupName: "config",
 | 
			
		||||
      activeName: "0",
 | 
			
		||||
      planOrder: null,
 | 
			
		||||
      importCncText: "",
 | 
			
		||||
      files: new Array<string>()
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    document.title = "开料调试工具 - " + document.title;
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    /**读取文件 */
 | 
			
		||||
    async loadCFData(files: FileList) {
 | 
			
		||||
      const json = await fileHelper.readText(files[0]);
 | 
			
		||||
 | 
			
		||||
      this.newLayout(json);
 | 
			
		||||
    },
 | 
			
		||||
    newLayout(planJson: string) {
 | 
			
		||||
      const plan = JSON.parse(planJson);
 | 
			
		||||
      console.log(plan);
 | 
			
		||||
      const engine = new LayoutEngine();
 | 
			
		||||
      const blockConvert = new BlockConvert();
 | 
			
		||||
      const parts = [];
 | 
			
		||||
      for (const block of plan["BlockList"]) {
 | 
			
		||||
        parts.push(blockConvert.createPart(block, engine.board));
 | 
			
		||||
      }
 | 
			
		||||
      console.log(parts);
 | 
			
		||||
 | 
			
		||||
      engine.init(parts);
 | 
			
		||||
      // engine.run();
 | 
			
		||||
      engine.startThread();
 | 
			
		||||
    },
 | 
			
		||||
    stopThread() {
 | 
			
		||||
      engine.stopThread();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#app {
 | 
			
		||||
  font-family: Avenir, Helvetica, Arial, sans-serif;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  color: #2c3e50;
 | 
			
		||||
  margin-top: 60px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/Business/BlockConvert.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Business/BlockConvert.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import { Path } from '../Nest/Core/Path';
 | 
			
		||||
import { Part } from '../Nest/Core/Part';
 | 
			
		||||
export class BlockConvert{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        // super();
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    createPart(data:any,board:Path){
 | 
			
		||||
        let id = data['BlockID'];
 | 
			
		||||
        let width = data['Width'];
 | 
			
		||||
        let height = data['Length'];
 | 
			
		||||
        let part = new Part();
 | 
			
		||||
        part.Id = id;
 | 
			
		||||
        part.Init(new Path([{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]),board);
 | 
			
		||||
        part.UserData = data;
 | 
			
		||||
        return part;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/Business/BlockView.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Business/BlockView.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
export class BlockView{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    constructor(canvas:HTMLCanvasElement) {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    drawBlock(){
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/Business/FileHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Business/FileHelper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
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)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/Business/LayoutConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/Business/LayoutConfig.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export class LayoutConfig{
 | 
			
		||||
    loadLegacyConfig(){
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								src/Business/LayoutEngine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/Business/LayoutEngine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
import { Part } from '../Nest/Core/Part';
 | 
			
		||||
import { Path } from '../Nest/Core/Path';
 | 
			
		||||
import { DefaultBin } from '../Nest/Core/DefaultBin';
 | 
			
		||||
import { PathGeneratorSingle } from '../Nest/Core/PathGenerator';
 | 
			
		||||
import { NestCache } from '../Nest/Core/NestCache';
 | 
			
		||||
import { NestDatabase } from '../Nest/Core/NestDatabase';
 | 
			
		||||
import { NestFiler } from '../Nest/Common/Filer';
 | 
			
		||||
import { OptimizeMachine } from '../Nest/Core/OptimizeMachine';
 | 
			
		||||
import { Individual } from '../Nest/Core/Individual';
 | 
			
		||||
import Worker from "../Nest/Core/OptimizeWorker.worker";
 | 
			
		||||
import { InitClipperCpp } from '../Nest/Common/ClipperCpp';
 | 
			
		||||
 | 
			
		||||
export class LayoutEngine {
 | 
			
		||||
    parts: Part[] = [];
 | 
			
		||||
    board: Path;
 | 
			
		||||
    threads: Worker[] = [];
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
 | 
			
		||||
        //清理缓存,这个缓存是全局的
 | 
			
		||||
        PathGeneratorSingle.Clear();
 | 
			
		||||
        NestCache.Clear();
 | 
			
		||||
        DefaultBin.InsideNFPCache = {};
 | 
			
		||||
 | 
			
		||||
        //注册bin
 | 
			
		||||
        let binPath = DefaultBin;
 | 
			
		||||
        // binPath.Id = undefined; //清除这个缓存
 | 
			
		||||
        PathGeneratorSingle.RegisterId(binPath);
 | 
			
		||||
 | 
			
		||||
        this.board = binPath;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    init(parts: Part[]) {
 | 
			
		||||
        this.parts = parts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async run() {
 | 
			
		||||
        await InitClipperCpp();
 | 
			
		||||
        let m = new OptimizeMachine;//优化器
 | 
			
		||||
        m.Bin = this.board; //指定Bin容器
 | 
			
		||||
        m.PutParts(this.parts);//把零件放上去
 | 
			
		||||
        let count = 0;
 | 
			
		||||
        m.callBack = async (i: Individual) => {
 | 
			
		||||
            console.log('优化结果', i);
 | 
			
		||||
            count++;
 | 
			
		||||
            if(count>10){
 | 
			
		||||
                m.Suspend();//关闭优化器
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
        }//当有新结果时,回调
 | 
			
		||||
 | 
			
		||||
        await m.Start();//启动这个
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async startThread() {
 | 
			
		||||
 | 
			
		||||
        let db = new NestDatabase();
 | 
			
		||||
        db.Paths = PathGeneratorSingle.paths;
 | 
			
		||||
        db.Parts = this.parts;
 | 
			
		||||
        db.Bin = DefaultBin
 | 
			
		||||
 | 
			
		||||
        let f = new NestFiler();
 | 
			
		||||
        db.WriteFile(f);//写入到文件中
 | 
			
		||||
        let best = Infinity;
 | 
			
		||||
 | 
			
		||||
        let t = new Worker;
 | 
			
		||||
        this.threads = [t];
 | 
			
		||||
 | 
			
		||||
        t.onmessage = (e) => {
 | 
			
		||||
            let f = new NestFiler(e.data);
 | 
			
		||||
            let inv = new Individual(db.Parts);
 | 
			
		||||
            inv.ReadFile(f);
 | 
			
		||||
            if (best <= inv.Fitness)
 | 
			
		||||
                return;
 | 
			
		||||
            best = inv.Fitness;
 | 
			
		||||
            let text = `优化率:${inv.Fitness}`;
 | 
			
		||||
            console.log(text);
 | 
			
		||||
        };
 | 
			
		||||
        t.postMessage(f._datas);//Post给Worker
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async stopThread() {
 | 
			
		||||
        this.threads[0].terminate()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										185
									
								
								src/Common/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/Common/ArrayExt.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 删除数组中指定的元素,返回数组本身
 | 
			
		||||
 * @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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 对排序好的数组进行去重操作
 | 
			
		||||
 * @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数
 | 
			
		||||
 * @returns {Array<T>} 返回新数组
 | 
			
		||||
 */
 | 
			
		||||
export function arrayRemoveDuplicateBySort2<T>(arr: Array<T>, checkFuction: (e1: T, e2: T) => boolean = checkEqual): Array<T>
 | 
			
		||||
{
 | 
			
		||||
    if (arr.length < 2) return arr;
 | 
			
		||||
 | 
			
		||||
    let pre = arr[0],
 | 
			
		||||
        newArr = [pre],
 | 
			
		||||
        now: T;
 | 
			
		||||
 | 
			
		||||
    for (let i = 1, len = arr.length; i < len; i++)
 | 
			
		||||
    {
 | 
			
		||||
        now = arr[i];
 | 
			
		||||
        if (!checkFuction(pre, now))
 | 
			
		||||
        {
 | 
			
		||||
            newArr.push(now);
 | 
			
		||||
            pre = now;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (pre !== now) newArr.push(now);
 | 
			
		||||
 | 
			
		||||
    return newArr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//原地更新数组,注意这个函数并不会比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 (var 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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/Common/Sleep.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Common/Sleep.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export async function Sleep(time: number)
 | 
			
		||||
{
 | 
			
		||||
    return new Promise(res =>
 | 
			
		||||
    {
 | 
			
		||||
        setTimeout(res, time);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										160
									
								
								src/Nest/Common/Box2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/Nest/Common/Box2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
import { Vector2 } from "./Vector2";
 | 
			
		||||
import { Point } from "./Point";
 | 
			
		||||
 | 
			
		||||
export class Box2
 | 
			
		||||
{
 | 
			
		||||
    min: Vector2;
 | 
			
		||||
    max: Vector2;
 | 
			
		||||
    constructor(min = new Vector2(+ Infinity, + Infinity), max = new Vector2(- Infinity, - 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: Vector2[]): Box2
 | 
			
		||||
    {
 | 
			
		||||
        this.makeEmpty();
 | 
			
		||||
        for (let i = 0, il = points.length; i < il; i++)
 | 
			
		||||
        {
 | 
			
		||||
            this.expandByPoint(points[i]);
 | 
			
		||||
        }
 | 
			
		||||
        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 = + Infinity;
 | 
			
		||||
        this.max.x = this.max.y = - 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: Vector2): 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);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										21
									
								
								src/Nest/Common/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Nest/Common/ClipperCpp.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import * as clipperLib from "js-angusj-clipper/web"; // nodejs style require
 | 
			
		||||
 | 
			
		||||
export let 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
 | 
			
		||||
        ).then(c =>
 | 
			
		||||
        {
 | 
			
		||||
            clipperCpp.lib = c;
 | 
			
		||||
            res();
 | 
			
		||||
            console.log("载入成功!");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/Nest/Common/ConvexHull2D.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Nest/Common/ConvexHull2D.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import { Point } from "./Point";
 | 
			
		||||
import convexHull from 'monotone-convex-hull-2d';
 | 
			
		||||
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]);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/Nest/Common/Filer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Nest/Common/Filer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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++];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/Nest/Common/Point.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Nest/Common/Point.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
 | 
			
		||||
export interface Point
 | 
			
		||||
{
 | 
			
		||||
    x: number;
 | 
			
		||||
    y: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/Nest/Common/Random.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Nest/Common/Random.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/Nest/Common/Shuffle.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Nest/Common/Shuffle.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
export function ShuffleArray<T = any>(array: T[])
 | 
			
		||||
{
 | 
			
		||||
    for (let i = array.length - 1; i > 0; i--)
 | 
			
		||||
    {
 | 
			
		||||
        const j = Math.floor(Math.random() * (i + 1));
 | 
			
		||||
        [array[i], array[j]] = [array[j], array[i]];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/Nest/Common/Util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/Nest/Common/Util.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
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 = (arr instanceof Array) ? 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										287
									
								
								src/Nest/Common/Vector2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								src/Nest/Common/Vector2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
import { Point } from "./Point";
 | 
			
		||||
 | 
			
		||||
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 (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: Vector2): Vector2
 | 
			
		||||
    {
 | 
			
		||||
        this.x = Math.min(this.x, v.x);
 | 
			
		||||
        this.y = Math.min(this.y, v.y);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    max(v: Vector2): 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, 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), 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										373
									
								
								src/Nest/Core/Container.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								src/Nest/Core/Container.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,373 @@
 | 
			
		||||
import { ClipType, PolyFillType, Paths } from "js-angusj-clipper/web";
 | 
			
		||||
import { SubjectInput } from "js-angusj-clipper/web/clipFunctions";
 | 
			
		||||
import { Box2 } from "../Common/Box2";
 | 
			
		||||
import { clipperCpp } from "../Common/ClipperCpp";
 | 
			
		||||
import { ConvexHull2D } from "../Common/ConvexHull2D";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { NestCache } from "./NestCache";
 | 
			
		||||
import { Part } from "./Part";
 | 
			
		||||
import { Area, Path, TranslatePath } from "./Path";
 | 
			
		||||
import { PlaceType } from "./PlaceType";
 | 
			
		||||
import { Point } from "../Common/Point";
 | 
			
		||||
import { equaln } from "../Common/Util";
 | 
			
		||||
import { Vector2 } from "../Common/Vector2";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 当零件尝试放置后容器的状态,用于尝试放置,并且尝试贪心
 | 
			
		||||
 */
 | 
			
		||||
interface PartPlacedContainerState
 | 
			
		||||
{
 | 
			
		||||
    p: Point;
 | 
			
		||||
    area?: number;
 | 
			
		||||
    hull?: Point[];
 | 
			
		||||
    box?: Box2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 排料零件的容器,用来放置零件
 | 
			
		||||
 * 它是一块大板
 | 
			
		||||
 * 也可以是一个网洞
 | 
			
		||||
 * 也可以是余料
 | 
			
		||||
 */
 | 
			
		||||
export class Container
 | 
			
		||||
{
 | 
			
		||||
    ParentId: number = -1;//父亲id,-1表示默认的bin,-2开始表示余料,大于等于0表示板件
 | 
			
		||||
    ChildrenIndex: number = 0;//网洞的索引位置
 | 
			
		||||
    ParentM: Point;
 | 
			
		||||
 | 
			
		||||
    Placed: Part[] = [];
 | 
			
		||||
    PlaceType: PlaceType = PlaceType.Box;
 | 
			
		||||
 | 
			
		||||
    //放置状态
 | 
			
		||||
    PlacedArea = 0;
 | 
			
		||||
    PlacedBox: Box2;
 | 
			
		||||
    PlacedHull: Point[];
 | 
			
		||||
 | 
			
		||||
    StatusKey: string;
 | 
			
		||||
    constructor(protected BinPath?: Path)
 | 
			
		||||
    {
 | 
			
		||||
        if (BinPath)
 | 
			
		||||
            this.StatusKey = this.BinPath.Id.toString() + "," + this.PlaceType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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.Placed.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 = 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))
 | 
			
		||||
                                    && (p.x < translate.x || (p.x === translate.x && p.y < translate.y))))
 | 
			
		||||
                            // && (p.y < translate.y || (p.y === translate.y && p.x < translate.x))))
 | 
			
		||||
                            // && (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y)))
 | 
			
		||||
                            {
 | 
			
		||||
                                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))
 | 
			
		||||
                                    && (p.x < translate.x || (p.x === translate.x && p.y < translate.y))))
 | 
			
		||||
                            // && (p.y < translate.y || (p.y === translate.y && p.x < translate.x))))
 | 
			
		||||
                            // && (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y)))
 | 
			
		||||
                            {
 | 
			
		||||
                                translate = p;
 | 
			
		||||
                                minArea = area;
 | 
			
		||||
                                bestHull = nhull;
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    case PlaceType.Gravity:
 | 
			
		||||
                        {
 | 
			
		||||
                            if (!translate || p.x < translate.x || (p.x === translate.x && p.y < translate.y))
 | 
			
		||||
                                translate = p;
 | 
			
		||||
                        }
 | 
			
		||||
                    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.Placed)
 | 
			
		||||
        {
 | 
			
		||||
            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=true] 是否计算当前的容器状态?
 | 
			
		||||
     */
 | 
			
		||||
    private AppendPart(part: Part, calc = true): void
 | 
			
		||||
    {
 | 
			
		||||
        this.StatusKey += "," + part.State.Contour.Id;
 | 
			
		||||
        this.Placed.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, ...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 = { x: Infinity, y: Infinity };
 | 
			
		||||
        for (let path of nfp)
 | 
			
		||||
        {
 | 
			
		||||
            for (let p of path)
 | 
			
		||||
            {
 | 
			
		||||
                if (p.x < leftP.x)
 | 
			
		||||
                    leftP = p;
 | 
			
		||||
                else if (p.x === leftP.x && p.y < leftP.y)
 | 
			
		||||
                    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.Placed = [];
 | 
			
		||||
        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.Placed.push(part);
 | 
			
		||||
        }
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    //对象将自身数据写入到文件.
 | 
			
		||||
    WriteFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        file.Write(this.ParentId);
 | 
			
		||||
        file.Write(this.ChildrenIndex);
 | 
			
		||||
        file.Write(this.ParentM);
 | 
			
		||||
        file.Write(this.Placed.length);
 | 
			
		||||
        for (let p of this.Placed)
 | 
			
		||||
        {
 | 
			
		||||
            file.Write(p.Id);
 | 
			
		||||
            file.Write(p.StateIndex);
 | 
			
		||||
            file.Write(p.PlacePosition);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //#endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								src/Nest/Core/DefaultBin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/Nest/Core/DefaultBin.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
let width = 1221;
 | 
			
		||||
let height = 2441;
 | 
			
		||||
export let DefaultBin = new Path([{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]);
 | 
			
		||||
							
								
								
									
										190
									
								
								src/Nest/Core/Individual.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/Nest/Core/Individual.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
import { arrayLast } from "../../Common/ArrayExt";
 | 
			
		||||
import { Container } from "./Container";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { Part } from "./Part";
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { PlaceType } from "./PlaceType";
 | 
			
		||||
import { RandomIndex } from "../Common/Random";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 个体(表示某一次优化的结果,或者还没开始优化的状态)
 | 
			
		||||
 * 个体是由一堆零件组成的,零件可以有不同的状态。
 | 
			
		||||
 *
 | 
			
		||||
 * 个体单独变异
 | 
			
		||||
 *  可以是某个零件的旋转状态发生改变
 | 
			
		||||
 *  可以是零件的顺序发生改变
 | 
			
		||||
 *
 | 
			
		||||
 * 个体交配(感觉暂时不需要)
 | 
			
		||||
 *  个体和其他个体进行基因交换
 | 
			
		||||
 */
 | 
			
		||||
export class Individual
 | 
			
		||||
{
 | 
			
		||||
    //强壮程度 越低越好
 | 
			
		||||
    Fitness: number;
 | 
			
		||||
    constructor(public Parts?: Part[], public mutationRate = 0.5) { }
 | 
			
		||||
 | 
			
		||||
    Clone()
 | 
			
		||||
    {
 | 
			
		||||
        let p = new Individual(this.Parts.map(p => p.Clone(), this.mutationRate));
 | 
			
		||||
        p.Mutate();
 | 
			
		||||
        return p;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 突变
 | 
			
		||||
    */
 | 
			
		||||
    Mutate()
 | 
			
		||||
    {
 | 
			
		||||
        if (this.mutationRate > 0.5)
 | 
			
		||||
            for (let i = 0; i < this.Parts.length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                let rand = Math.random();
 | 
			
		||||
                if (rand < this.mutationRate * 0.5)
 | 
			
		||||
                {
 | 
			
		||||
                    //和下一个调换顺序
 | 
			
		||||
                    let 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
 | 
			
		||||
        {
 | 
			
		||||
            //洗牌
 | 
			
		||||
            let rand = Math.random();
 | 
			
		||||
            if (rand < 0.5)
 | 
			
		||||
            {
 | 
			
		||||
                let index = RandomIndex(this.Parts.length - 2);
 | 
			
		||||
                let count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2) || 1;
 | 
			
		||||
                let parts = this.Parts.splice(index, count);
 | 
			
		||||
                this.Parts.push(...parts);
 | 
			
		||||
                this.Parts[index].Mutate();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                for (let i = 0; i < this.Parts.length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    let rand = Math.random();
 | 
			
		||||
                    if (rand < this.mutationRate)
 | 
			
		||||
                    {
 | 
			
		||||
                        this.Parts[i].Mutate();
 | 
			
		||||
                        i += 5;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.mutationRate > 0.2)
 | 
			
		||||
            this.mutationRate -= 0.004;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Containers: Container[];
 | 
			
		||||
    HoleContainers: Container[];
 | 
			
		||||
    /**
 | 
			
		||||
     * 评估健康程度
 | 
			
		||||
     */
 | 
			
		||||
    Evaluate(bin: Path, bestCount: number, greedy = false, type?: PlaceType)
 | 
			
		||||
    {
 | 
			
		||||
        let parts = this.Parts;
 | 
			
		||||
        this.Containers = [];
 | 
			
		||||
        this.HoleContainers = [];
 | 
			
		||||
        for (let part of parts)
 | 
			
		||||
        {
 | 
			
		||||
            for (let i = 0; i < part.Holes.length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                let hole = part.Holes[i];
 | 
			
		||||
                let container = new Container(hole.Contour);
 | 
			
		||||
                container.ParentId = part.Id;
 | 
			
		||||
                container.ChildrenIndex = i;
 | 
			
		||||
                container.ParentM = hole.OrigionMinPoint;
 | 
			
		||||
                this.HoleContainers.push(container);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //网洞优先
 | 
			
		||||
        parts = parts.filter(p =>
 | 
			
		||||
        {
 | 
			
		||||
            for (let hole of this.HoleContainers)
 | 
			
		||||
            {
 | 
			
		||||
                if (hole.PutPart(p))
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        while (parts.length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (this.Containers.length > bestCount)
 | 
			
		||||
            {
 | 
			
		||||
                this.Fitness = Math.ceil(bestCount) + 1;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            let container = new Container(bin);
 | 
			
		||||
            container.ParentId = -1;
 | 
			
		||||
            if (greedy)
 | 
			
		||||
                container.PlaceType = type;
 | 
			
		||||
            else
 | 
			
		||||
                container.PlaceType = RandomIndex(2);
 | 
			
		||||
            if (this.mutationRate > 0.5)
 | 
			
		||||
            {
 | 
			
		||||
                let area = 0;
 | 
			
		||||
                let maxP: Part;
 | 
			
		||||
                for (let i = 0; i < parts.length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    let p = parts[i];
 | 
			
		||||
                    if (p.State.Contour.Area > area)
 | 
			
		||||
                    {
 | 
			
		||||
                        maxP = parts[i];
 | 
			
		||||
                        area = p.State.Contour.Area;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (container.PutPart(maxP, greedy))
 | 
			
		||||
                    parts = parts.filter(p => p !== maxP);
 | 
			
		||||
            }
 | 
			
		||||
            parts = parts.filter(p =>
 | 
			
		||||
            {
 | 
			
		||||
                return container.PutPart(p, greedy) !== true;
 | 
			
		||||
            });
 | 
			
		||||
            if (!greedy)
 | 
			
		||||
            {
 | 
			
		||||
                parts = parts.filter(p =>
 | 
			
		||||
                {
 | 
			
		||||
                    return container.PutPart(p, true) !== true;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            this.Containers.push(container);
 | 
			
		||||
        }
 | 
			
		||||
        this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers).UseRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //#region -------------------------File-------------------------
 | 
			
		||||
 | 
			
		||||
    //对象从文件中读取数据,初始化自身
 | 
			
		||||
    ReadFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //对象将自身数据写入到文件.
 | 
			
		||||
    WriteFile(f: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        f.Write(this.Fitness);
 | 
			
		||||
        f.Write(this.Containers.length);
 | 
			
		||||
        for (let c of this.Containers)
 | 
			
		||||
            c.WriteFile(f);
 | 
			
		||||
 | 
			
		||||
        f.Write(this.HoleContainers.length);
 | 
			
		||||
        for (let c of this.HoleContainers)
 | 
			
		||||
            c.WriteFile(f);
 | 
			
		||||
    }
 | 
			
		||||
    //#endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Nest/Core/NestCache.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Nest/Core/NestCache.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { Point } from "../Common/Point";
 | 
			
		||||
 | 
			
		||||
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 = {};
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/Nest/Core/NestDatabase.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/Nest/Core/NestDatabase.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import { Part } from "./Part";
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { PathGeneratorSingle } from "./PathGenerator";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 排料数据库,用这个类来序列化需要排料的数据
 | 
			
		||||
 * 用于在Work间传输
 | 
			
		||||
 */
 | 
			
		||||
export class NestDatabase
 | 
			
		||||
{
 | 
			
		||||
    Paths: Path[]; //所有的Path都在这里
 | 
			
		||||
    Bin: Path;     //默认的容器
 | 
			
		||||
    Parts: Part[]; //所有的零件
 | 
			
		||||
 | 
			
		||||
    //#region -------------------------File-------------------------
 | 
			
		||||
    //对象从文件中读取数据,初始化自身
 | 
			
		||||
    ReadFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        file.Read();
 | 
			
		||||
        let count = file.Read() as number;
 | 
			
		||||
        this.Paths = [];
 | 
			
		||||
        for (let i = 0; i < count; i++)
 | 
			
		||||
        {
 | 
			
		||||
            let 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++)
 | 
			
		||||
        {
 | 
			
		||||
            let part = new Part();
 | 
			
		||||
            part.ReadFile(file);
 | 
			
		||||
            this.Parts.push(part);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //对象将自身数据写入到文件.
 | 
			
		||||
    WriteFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        file.Write(1);
 | 
			
		||||
        file.Write(this.Paths.length);
 | 
			
		||||
        for (let path of this.Paths)
 | 
			
		||||
            path.WriteFile(file);
 | 
			
		||||
 | 
			
		||||
        file.Write(this.Bin.Id);
 | 
			
		||||
        file.Write(this.Parts.length);
 | 
			
		||||
        for (let part of this.Parts)
 | 
			
		||||
            part.WriteFile(file);
 | 
			
		||||
    }
 | 
			
		||||
    //#endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										160
									
								
								src/Nest/Core/OptimizeMachine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/Nest/Core/OptimizeMachine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
import { arrayRemoveIf } from "../../Common/ArrayExt";
 | 
			
		||||
import { Sleep } from "../../Common/Sleep";
 | 
			
		||||
import { clipperCpp } from "../Common/ClipperCpp";
 | 
			
		||||
import { Individual } from "./Individual";
 | 
			
		||||
import { NestCache } from "./NestCache";
 | 
			
		||||
import { Part } from "./Part";
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 优化器
 | 
			
		||||
 * 配置优化器
 | 
			
		||||
 * 放入零件
 | 
			
		||||
 * 按下启动
 | 
			
		||||
 * 按下暂停
 | 
			
		||||
 * 清理机台
 | 
			
		||||
 */
 | 
			
		||||
export class OptimizeMachine
 | 
			
		||||
{
 | 
			
		||||
    //配置
 | 
			
		||||
    Config: {
 | 
			
		||||
        PopulationCount: number;//种群个数
 | 
			
		||||
    };
 | 
			
		||||
    //容器板
 | 
			
		||||
    Bin: Path;
 | 
			
		||||
    //机台上的零件
 | 
			
		||||
    Parts: Part[];
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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)];
 | 
			
		||||
        for (let i = 1; i < this.Config.PopulationCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
            let 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));
 | 
			
		||||
        }
 | 
			
		||||
        //2.执行
 | 
			
		||||
        await this.Run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    calcCount = 0;
 | 
			
		||||
    best = Infinity;
 | 
			
		||||
    bestP: Individual;
 | 
			
		||||
    bestCount = 0;
 | 
			
		||||
    private async Run()
 | 
			
		||||
    {
 | 
			
		||||
        console.time("1");
 | 
			
		||||
        if (this.Parts.length === 0) return;
 | 
			
		||||
        //开始自然选择
 | 
			
		||||
        while (!this._IsSuspend) //实验停止信号
 | 
			
		||||
        {
 | 
			
		||||
            let goBack = this.calcCount - this.bestCount > 8000;
 | 
			
		||||
            //1.适应环境(放置零件)
 | 
			
		||||
            for (let i = 0; i < this._Individuals.length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (globalThis.document || !clipperCpp.lib)
 | 
			
		||||
                    await Sleep(0);
 | 
			
		||||
                let p = this._Individuals[i];
 | 
			
		||||
                if (this.calcCount < 1000 || this.calcCount % 1000 === 0)
 | 
			
		||||
                    p.Evaluate(this.Bin, this.best, true, i % 2);
 | 
			
		||||
                else
 | 
			
		||||
                    p.Evaluate(this.Bin, 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)
 | 
			
		||||
            {
 | 
			
		||||
                console.timeLog("1", this.bestP.Fitness, this.calcCount, NestCache.count1, NestCache.count2);
 | 
			
		||||
                await Sleep(0);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness);
 | 
			
		||||
            let 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 (let 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()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/Nest/Core/OptimizeWorker.worker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/Nest/Core/OptimizeWorker.worker.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
const ctx: Worker = self as any;
 | 
			
		||||
import { InitClipperCpp } from "../Common/ClipperCpp";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { NestDatabase } from "./NestDatabase";
 | 
			
		||||
import { OptimizeMachine } from "./OptimizeMachine";
 | 
			
		||||
 | 
			
		||||
ctx.addEventListener("message", async (event) =>
 | 
			
		||||
{
 | 
			
		||||
    await InitClipperCpp();
 | 
			
		||||
    let f = new NestFiler(event.data);
 | 
			
		||||
    let db = new NestDatabase;
 | 
			
		||||
    db.ReadFile(f);
 | 
			
		||||
 | 
			
		||||
    let m = new OptimizeMachine;
 | 
			
		||||
    m.Bin = db.Bin;
 | 
			
		||||
    m.PutParts(db.Parts);
 | 
			
		||||
 | 
			
		||||
    m.callBack = async (inv) =>
 | 
			
		||||
    {
 | 
			
		||||
        let f = new NestFiler();
 | 
			
		||||
        inv.WriteFile(f);
 | 
			
		||||
        ctx.postMessage(f._datas);
 | 
			
		||||
    };
 | 
			
		||||
    await m.Start();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default {} as typeof Worker & (new () => Worker);
 | 
			
		||||
							
								
								
									
										178
									
								
								src/Nest/Core/Part.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/Nest/Core/Part.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { PartState } from "./PartState";
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { PathGeneratorSingle } from "./PathGenerator";
 | 
			
		||||
import { Point } from "../Common/Point";
 | 
			
		||||
import { RandomIndex } from "../Common/Random";
 | 
			
		||||
import { Vector2 } from "../Common/Vector2";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 零件类
 | 
			
		||||
 *  零件类可以绑定数据,也存在位置和旋转状态的信息
 | 
			
		||||
 *
 | 
			
		||||
 * 初始化零件:
 | 
			
		||||
 *  传入零件的轮廓,刀半径,包围容器(或者为空?)
 | 
			
		||||
 *  初始化用于放置的轮廓。将轮廓首点移动到0点,记录移动的点P。
 | 
			
		||||
 *
 | 
			
		||||
 * 零件放置位置:
 | 
			
		||||
 *  表示零件轮廓首点的位置。
 | 
			
		||||
 *
 | 
			
		||||
 * 零件的旋转:
 | 
			
		||||
 *  表示零件轮廓按照首点(0)旋转。
 | 
			
		||||
 *
 | 
			
		||||
 * 还原零件的放置状态:
 | 
			
		||||
 *  同样将零件移动到0点
 | 
			
		||||
 *  同样将零件旋转
 | 
			
		||||
 *  同样将零件移动到指定的位置
 | 
			
		||||
 *  零件可能处于容器中,变换到容器坐标系
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
export class Part<T = any, Matrix = any>
 | 
			
		||||
{
 | 
			
		||||
    Id: number;//用于确定Part的唯一性,并且有助于在其他Work中还原
 | 
			
		||||
 | 
			
		||||
    //下面数据只用于还原零件的显示状态,在优化中无作用
 | 
			
		||||
    UserData: T; //应该也是可序列化的实体
 | 
			
		||||
    PlaceCS: Matrix;//放置矩阵 Matrix4
 | 
			
		||||
    Parent: Part;
 | 
			
		||||
 | 
			
		||||
    Holes: PartState[] = [];
 | 
			
		||||
    //零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态
 | 
			
		||||
    RotatedStates: PartState[] = [];
 | 
			
		||||
    PlacePosition: Point; //放置位置(相对于容器的位置)
 | 
			
		||||
    StateIndex = 0;       //放置状态
 | 
			
		||||
 | 
			
		||||
    get State(): PartState  //零件当前的状态
 | 
			
		||||
    {
 | 
			
		||||
        return this.RotatedStates[this.StateIndex];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //初始化零件的各个状态,按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;
 | 
			
		||||
            }
 | 
			
		||||
            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);
 | 
			
		||||
                if (partState.Contour.Origion === undefined)
 | 
			
		||||
                {
 | 
			
		||||
                    partState.Contour.Origion = path_0;
 | 
			
		||||
                    partState.Contour.OrigionMinPoint = p0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //记录已有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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //浅克隆
 | 
			
		||||
    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;
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
    //#endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/Nest/Core/PartState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/Nest/Core/PartState.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { Point } from "../Common/Point";
 | 
			
		||||
import { PathGeneratorSingle } from "./PathGenerator";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于存放零件旋转后的状态
 | 
			
		||||
 * 记录了用于放置时的轮廓。该轮廓总是首点等于0,便于放置时的计算。
 | 
			
		||||
 */
 | 
			
		||||
export class PartState
 | 
			
		||||
{
 | 
			
		||||
    Rotation: number;
 | 
			
		||||
    OrigionMinPoint: Point;
 | 
			
		||||
    Contour: Path;//轮廓
 | 
			
		||||
 | 
			
		||||
    //#region -------------------------File-------------------------
 | 
			
		||||
    ReadFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        this.Rotation = file.Read();
 | 
			
		||||
        this.OrigionMinPoint = file.Read();
 | 
			
		||||
 | 
			
		||||
        let index = file.Read() as number;
 | 
			
		||||
        this.Contour = PathGeneratorSingle.paths[index];
 | 
			
		||||
 | 
			
		||||
        if (!this.Contour)
 | 
			
		||||
            console.log(index);
 | 
			
		||||
    }
 | 
			
		||||
    WriteFile(file: NestFiler)
 | 
			
		||||
    {
 | 
			
		||||
        file.Write(this.Rotation);
 | 
			
		||||
        file.Write(this.OrigionMinPoint);
 | 
			
		||||
        file.Write(this.Contour.Id);
 | 
			
		||||
    }
 | 
			
		||||
    //#endregion
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										345
									
								
								src/Nest/Core/Path.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								src/Nest/Core/Path.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,345 @@
 | 
			
		||||
import { Box2 } from "../Common/Box2";
 | 
			
		||||
import { clipperCpp } from "../Common/ClipperCpp";
 | 
			
		||||
import { NestFiler } from "../Common/Filer";
 | 
			
		||||
import { Point } from "../Common/Point";
 | 
			
		||||
import { equaln } from "../Common/Util";
 | 
			
		||||
import { Vector2 } from "../Common/Vector2";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 轮廓路径类
 | 
			
		||||
 * 可以求NFP,和保存NFPCahce
 | 
			
		||||
 * 因为NFP结果是按照最低点移动的,所以将点旋转后,按照盒子将点移动到0点.
 | 
			
		||||
 */
 | 
			
		||||
export class Path
 | 
			
		||||
{
 | 
			
		||||
    Id: number;
 | 
			
		||||
    Points: Point[];
 | 
			
		||||
    OutsideNFPCache: { [key: number]: Point[][]; } = {};
 | 
			
		||||
    InsideNFPCache: { [key: number]: Point[][]; } = {};
 | 
			
		||||
 | 
			
		||||
    constructor(origionPoints?: Point[], rotation: number = 0)
 | 
			
		||||
    {
 | 
			
		||||
        if (origionPoints)
 | 
			
		||||
            this.Init(origionPoints, rotation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Origion: Path;
 | 
			
		||||
    //点表在旋转后的原始最小点.使用这个点将轮廓移动到0点
 | 
			
		||||
    OrigionMinPoint: Vector2;
 | 
			
		||||
    Rotation: number;
 | 
			
		||||
 | 
			
		||||
    Size: Vector2;//序列化
 | 
			
		||||
    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 = 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];
 | 
			
		||||
 | 
			
		||||
            if (ax === bx) ax += 1;
 | 
			
		||||
            if (ay === by) ay += 1;
 | 
			
		||||
 | 
			
		||||
            if (bx > ax || by > ay)
 | 
			
		||||
                return;
 | 
			
		||||
            nfps = [[
 | 
			
		||||
                { x: 0, y: 0 },
 | 
			
		||||
                { x: ax - bx, y: 0 },
 | 
			
		||||
                { x: ax - bx, y: ay - by },
 | 
			
		||||
                { x: 0, y: ay - by }
 | 
			
		||||
            ]];
 | 
			
		||||
        }
 | 
			
		||||
        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 PathScale(pts: Point[], scale: number): Point[]
 | 
			
		||||
{
 | 
			
		||||
    for (let p of pts)
 | 
			
		||||
    {
 | 
			
		||||
        p.x *= scale;
 | 
			
		||||
        p.y *= scale;
 | 
			
		||||
    }
 | 
			
		||||
    return pts;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								src/Nest/Core/PathGenerator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/Nest/Core/PathGenerator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import { Path } from "./Path";
 | 
			
		||||
import { equaln, FixIndex } from "../Common/Util";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 轮廓路径构造器
 | 
			
		||||
 * 传递一组简化后的点表过来,如果已经有同样的点表时,返回已经生产的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.length = 0;
 | 
			
		||||
        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 let PathGeneratorSingle = new PathGenerator;
 | 
			
		||||
							
								
								
									
										7
									
								
								src/Nest/Core/PlaceType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Nest/Core/PlaceType.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
 | 
			
		||||
export enum PlaceType
 | 
			
		||||
{
 | 
			
		||||
    Hull = 0,//凸包模式 (凸包面积)
 | 
			
		||||
    Box = 1, //盒子模式 (长乘以宽)
 | 
			
		||||
    Gravity = 2,//重力模式(重力)
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 6.7 KiB  | 
@@ -1,132 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="hello">
 | 
			
		||||
    <h1>{{ msg }}</h1>
 | 
			
		||||
    <p>
 | 
			
		||||
      For a guide and recipes on how to configure / customize this project,<br />
 | 
			
		||||
      check out the
 | 
			
		||||
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener"
 | 
			
		||||
        >vue-cli documentation</a
 | 
			
		||||
      >.
 | 
			
		||||
    </p>
 | 
			
		||||
    <h3>Installed CLI Plugins</h3>
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >babel</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >typescript</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >eslint</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >unit-jest</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <h3>Essential Links</h3>
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://forum.vuejs.org" target="_blank" rel="noopener"
 | 
			
		||||
          >Forum</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://chat.vuejs.org" target="_blank" rel="noopener"
 | 
			
		||||
          >Community Chat</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
 | 
			
		||||
          >Twitter</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <h3>Ecosystem</h3>
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://router.vuejs.org" target="_blank" rel="noopener"
 | 
			
		||||
          >vue-router</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/vue-devtools#vue-devtools"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >vue-devtools</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
 | 
			
		||||
          >vue-loader</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://github.com/vuejs/awesome-vue"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
          rel="noopener"
 | 
			
		||||
          >awesome-vue</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: "HelloWorld",
 | 
			
		||||
  props: {
 | 
			
		||||
    msg: String
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
h3 {
 | 
			
		||||
  margin: 40px 0 0;
 | 
			
		||||
}
 | 
			
		||||
ul {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
li {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin: 0 10px;
 | 
			
		||||
}
 | 
			
		||||
a {
 | 
			
		||||
  color: #42b983;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										36
									
								
								src/components/SelectFile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/SelectFile.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <span>
 | 
			
		||||
    <el-button :type="buttonType" @click="$refs.inputFile.click()">
 | 
			
		||||
      <slot>
 | 
			
		||||
        选择文件
 | 
			
		||||
      </slot>
 | 
			
		||||
    </el-button>
 | 
			
		||||
    <input
 | 
			
		||||
      ref="inputFile"
 | 
			
		||||
      v-show="false"
 | 
			
		||||
      type="file"
 | 
			
		||||
      id="input"
 | 
			
		||||
      :accept="accept"
 | 
			
		||||
      @change="
 | 
			
		||||
        e => {
 | 
			
		||||
          $emit('change', e.target.files);
 | 
			
		||||
        }
 | 
			
		||||
      "
 | 
			
		||||
    />
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  props: {
 | 
			
		||||
    accept: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: ""
 | 
			
		||||
    },
 | 
			
		||||
    buttonType: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: ""
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										46
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								src/main.ts
									
									
									
									
									
								
							@@ -1,8 +1,44 @@
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
// import dayjs from 'dayjs';
 | 
			
		||||
import App from './App.vue';
 | 
			
		||||
import ElementUI from 'element-ui';
 | 
			
		||||
import 'element-ui/lib/theme-chalk/index.css';
 | 
			
		||||
import 'element-ui/lib/theme-chalk/display.css';
 | 
			
		||||
// import 'element-ui/lib/theme-default/index.css'
 | 
			
		||||
// import './icons'; // icon
 | 
			
		||||
 | 
			
		||||
// import '@/styles/index.scss'; // global css
 | 
			
		||||
// import '@/styles/custom.scss'; // custom css
 | 
			
		||||
 | 
			
		||||
// import VueRouter from 'vue-router';
 | 
			
		||||
// import store from './store';
 | 
			
		||||
// import Vuex from 'vuex';
 | 
			
		||||
 | 
			
		||||
// import router from './routes';
 | 
			
		||||
 | 
			
		||||
// import './permission'; // permission control
 | 
			
		||||
// import 'dayjs/locale/zh-cn'; // load on demand
 | 
			
		||||
// import GlobalConfig from "./config";
 | 
			
		||||
// GlobalConfig.event.saveAllConfig = ()=>{};
 | 
			
		||||
 | 
			
		||||
// console.log(GlobalConfig.event)
 | 
			
		||||
 | 
			
		||||
// dayjs.locale('zh-cn');
 | 
			
		||||
 | 
			
		||||
// import('element-ui').then(ElementUI=>{
 | 
			
		||||
  Vue.use(ElementUI, { size: 'small' });
 | 
			
		||||
  // Vue.use(VueRouter);
 | 
			
		||||
  // Vue.use(Vuex);
 | 
			
		||||
 | 
			
		||||
Vue.config.productionTip = false;
 | 
			
		||||
 | 
			
		||||
  new Vue({
 | 
			
		||||
  render: h => h(App)
 | 
			
		||||
}).$mount("#app");
 | 
			
		||||
    // el: '#app',
 | 
			
		||||
    // template: '<App/>',
 | 
			
		||||
  //   router,
 | 
			
		||||
  //   store,
 | 
			
		||||
    // components: { App }
 | 
			
		||||
    render: (h: any) => h(App),
 | 
			
		||||
  }).$mount('#app');
 | 
			
		||||
// })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
import { shallowMount } from "@vue/test-utils";
 | 
			
		||||
import HelloWorld from "@/components/HelloWorld.vue";
 | 
			
		||||
 | 
			
		||||
describe("HelloWorld.vue", () => {
 | 
			
		||||
  it("renders props.msg when passed", () => {
 | 
			
		||||
    const msg = "new message";
 | 
			
		||||
    const wrapper = shallowMount(HelloWorld, {
 | 
			
		||||
      propsData: { msg }
 | 
			
		||||
    });
 | 
			
		||||
    expect(wrapper.text()).toMatch(msg);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "esnext",
 | 
			
		||||
    "module": "esnext",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    // "strict": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "importHelpers": true,
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
@@ -10,21 +10,11 @@
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "sourceMap": true,
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "types": [
 | 
			
		||||
      "webpack-env",
 | 
			
		||||
      "jest"
 | 
			
		||||
    ],
 | 
			
		||||
    "types": ["node","webpack-env", "jest"],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": [
 | 
			
		||||
        "src/*"
 | 
			
		||||
      ]
 | 
			
		||||
      "@/*": ["src/*"]
 | 
			
		||||
    },
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "esnext",
 | 
			
		||||
      "dom",
 | 
			
		||||
      "dom.iterable",
 | 
			
		||||
      "scripthost"
 | 
			
		||||
    ]
 | 
			
		||||
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src/**/*.ts",
 | 
			
		||||
@@ -33,7 +23,5 @@
 | 
			
		||||
    "tests/**/*.ts",
 | 
			
		||||
    "tests/**/*.tsx"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "node_modules"
 | 
			
		||||
  ]
 | 
			
		||||
  "exclude": ["node_modules"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								vue.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								vue.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
// vue.config.js
 | 
			
		||||
const path = require("path");
 | 
			
		||||
function resolve(dir) {
 | 
			
		||||
  return path.join(__dirname, dir);
 | 
			
		||||
}
 | 
			
		||||
module.exports = {
 | 
			
		||||
  // publicPath: '',
 | 
			
		||||
  runtimeCompiler: true,
 | 
			
		||||
  productionSourceMap: false,
 | 
			
		||||
  configureWebpack: {
 | 
			
		||||
    devtool: "source-map",
 | 
			
		||||
    output: {
 | 
			
		||||
      globalObject: "this",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  chainWebpack(config) {
 | 
			
		||||
    let  tsRule = config.module
 | 
			
		||||
    .rule("ts").uses;
 | 
			
		||||
    config.module
 | 
			
		||||
      .rule("worker")
 | 
			
		||||
      .test(/\.worker\.ts$/i)
 | 
			
		||||
      .use("worker-loader")
 | 
			
		||||
      .loader("worker-loader").end()
 | 
			
		||||
      .use("ts-loader")
 | 
			
		||||
      .loader("ts-loader").options({
 | 
			
		||||
        transpileOnly: true,
 | 
			
		||||
        happyPackMode: false
 | 
			
		||||
      }).end();
 | 
			
		||||
    config.module
 | 
			
		||||
      .rule("fonts")
 | 
			
		||||
      .use("url-loader")
 | 
			
		||||
      .loader("url-loader")
 | 
			
		||||
      .options({
 | 
			
		||||
        limit: 4096,
 | 
			
		||||
        fallback: {
 | 
			
		||||
          loader: "file-loader",
 | 
			
		||||
          options: {
 | 
			
		||||
            name: "fonts/[name].[contenthash].[ext]",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      .end();
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user