import { Vector3, FrontSide, LineDashedMaterial, Vector2, DoubleSide, MeshBasicMaterial, LineBasicMaterial, ShaderMaterial, Color, Matrix4, Box3, Object3D, Shape as Shape$1, EllipseCurve, ExtrudeGeometry, Mesh, BufferGeometry, ShapeGeometry, BufferAttribute, Line as Line$1, Plane as Plane$1, Line3, MathUtils, CatmullRomCurve3, Geometry, Face3, Path, LineSegments, CylinderBufferGeometry, Float32BufferAttribute, ShapeUtils, BoxBufferGeometry, InstancedInterleavedBuffer, InterleavedBufferAttribute, BoxGeometry } from 'three'; import { Line2 } from 'three/examples/jsm/lines/Line2'; import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'; import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'; function iaop() { }; function observable() { }; function toJS() { }; import Flatbush from 'flatbush'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } /** * 删除数组中指定的元素,返回数组本身 * @param {Array} arr 需要操作的数组 * @param {*} el 需要移除的元素 */ function arrayRemoveOnce(arr, el) { let index = arr.indexOf(el); if (index !== -1) arr.splice(index, 1); return arr; } /** * 删除通过函数校验的元素 * @param {(e: T) => boolean} checkFuntion 校验函数 */ function arrayRemoveIf(arr, checkFuntion) { 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; } function arrayLast(arr) { return arr[arr.length - 1]; } /** * 根据数值从小到大排序数组 * @param {Array} arr * @returns {Array} 返回自身 */ function arraySortByNumber(arr) { arr.sort(sortNumberCompart); return arr; } /** * 对排序好的数组进行去重操作 * @param {(e1, e2) => boolean} [checkFuction] 校验对象相等函数 * @returns {Array} 返回自身 */ function arrayRemoveDuplicateBySort(arr, checkFuction = checkEqual) { 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; } function sortNumberCompart(e1, e2) { return e1 - e2; } function checkEqual(e1, e2) { return e1 === e2; } /** * 改变数组的值顺序 * @param arr 需要改变初始值位置的数组 * @param index //将index位置以后的值放到起始位置 */ function changeArrayStartIndex(arr, index) { arr.unshift(...arr.splice(index)); return arr; } function equalArray(a, b, 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; } function arrayClone(arr) { return arr.slice(); } //https://jsperf.com/merge-array-implementations/30 function arrayPushArray(arr1, arr2) { 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; } function arraySum(arr) { let sum = 0; for (let n of arr) sum += n; return sum; } function GetGoodShaderSimple(color = new Vector3, side = FrontSide) { return { uniforms: { "SurfaceColor": { value: color } }, vertexShader: "", fragmentShader: "", side, polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 }; } const ColorPalette = [ [0, 0, 0, 0], //[255, 255, 255, 255],//----- 0 - ByBlock - White [255, 0, 0, 255], // [255, 0, 0, 255], //----- 1 - Red [255, 255, 0, 255], [0, 255, 0, 255], [0, 255, 255, 255], [0, 0, 255, 255], [255, 0, 255, 255], // [255, 0, 0, 255], //----- 7 - More red Red // [255, 0, 0, 255], //----- 8 - More red Red // [255, 0, 0, 255], //----- 9 - More red Red [255, 255, 255, 255], [128, 128, 128, 255], [192, 192, 192, 255], [255, 0, 0, 255], [255, 127, 127, 255], [165, 0, 0, 255], [165, 82, 82, 255], [127, 0, 0, 255], [127, 63, 63, 255], [76, 0, 0, 255], [76, 38, 38, 255], [38, 0, 0, 255], [38, 19, 19, 255], [255, 63, 0, 255], [255, 159, 127, 255], [165, 41, 0, 255], [165, 103, 82, 255], [127, 31, 0, 255], [127, 79, 63, 255], [76, 19, 0, 255], [76, 47, 38, 255], [38, 9, 0, 255], [38, 23, 19, 255], [255, 127, 0, 255], [255, 191, 127, 255], [165, 82, 0, 255], [165, 124, 82, 255], [127, 63, 0, 255], [127, 95, 63, 255], [76, 38, 0, 255], [76, 57, 38, 255], [38, 19, 0, 255], [38, 28, 19, 255], [255, 191, 0, 255], [255, 223, 127, 255], [165, 124, 0, 255], [165, 145, 82, 255], [127, 95, 0, 255], [127, 111, 63, 255], [76, 57, 0, 255], [76, 66, 38, 255], [38, 28, 0, 255], [38, 33, 19, 255], [255, 255, 0, 255], [255, 255, 127, 255], [165, 165, 0, 255], [165, 165, 82, 255], [127, 127, 0, 255], [127, 127, 63, 255], [76, 76, 0, 255], [76, 76, 38, 255], [38, 38, 0, 255], [38, 38, 19, 255], [191, 255, 0, 255], [223, 255, 127, 255], [124, 165, 0, 255], [145, 165, 82, 255], [95, 127, 0, 255], [111, 127, 63, 255], [57, 76, 0, 255], [66, 76, 38, 255], [28, 38, 0, 255], [33, 38, 19, 255], [127, 255, 0, 255], [191, 255, 127, 255], [82, 165, 0, 255], [124, 165, 82, 255], [63, 127, 0, 255], [95, 127, 63, 255], [38, 76, 0, 255], [57, 76, 38, 255], [19, 38, 0, 255], [28, 38, 19, 255], [63, 255, 0, 255], [159, 255, 127, 255], [41, 165, 0, 255], [103, 165, 82, 255], [31, 127, 0, 255], [79, 127, 63, 255], [19, 76, 0, 255], [47, 76, 38, 255], [9, 38, 0, 255], [23, 38, 19, 255], [0, 255, 0, 255], [127, 255, 127, 255], [0, 165, 0, 255], [82, 165, 82, 255], [0, 127, 0, 255], [63, 127, 63, 255], [0, 76, 0, 255], [38, 76, 38, 255], [0, 38, 0, 255], [19, 38, 19, 255], [0, 255, 63, 255], [127, 255, 159, 255], [0, 165, 41, 255], [82, 165, 103, 255], [0, 127, 31, 255], [63, 127, 79, 255], [0, 76, 19, 255], [38, 76, 47, 255], [0, 38, 9, 255], [19, 38, 23, 255], [0, 255, 127, 255], [127, 255, 191, 255], [0, 165, 82, 255], [82, 165, 124, 255], [0, 127, 63, 255], [63, 127, 95, 255], [0, 76, 38, 255], [38, 76, 57, 255], [0, 38, 19, 255], [19, 38, 28, 255], [0, 255, 191, 255], [127, 255, 223, 255], [0, 165, 124, 255], [82, 165, 145, 255], [0, 127, 95, 255], [63, 127, 111, 255], [0, 76, 57, 255], [38, 76, 66, 255], [0, 38, 28, 255], [19, 38, 33, 255], [0, 255, 255, 255], [127, 255, 255, 255], [0, 165, 165, 255], [82, 165, 165, 255], [0, 127, 127, 255], [63, 127, 127, 255], [0, 76, 76, 255], [38, 76, 76, 255], [0, 38, 38, 255], [19, 38, 38, 255], [0, 191, 255, 255], [127, 223, 255, 255], [0, 124, 165, 255], [82, 145, 165, 255], [0, 95, 127, 255], [63, 111, 127, 255], [0, 57, 76, 255], [38, 66, 76, 255], [0, 28, 38, 255], [19, 33, 38, 255], [0, 127, 255, 255], [127, 191, 255, 255], [0, 82, 165, 255], [82, 124, 165, 255], [0, 63, 127, 255], [63, 95, 127, 255], [0, 38, 76, 255], [38, 57, 76, 255], [0, 19, 38, 255], [19, 28, 38, 255], [0, 63, 255, 255], [127, 159, 255, 255], [0, 41, 165, 255], [82, 103, 165, 255], [0, 31, 127, 255], [63, 79, 127, 255], [0, 19, 76, 255], [38, 47, 76, 255], [0, 9, 38, 255], [19, 23, 38, 255], [0, 0, 255, 255], [127, 127, 255, 255], [0, 0, 165, 255], [82, 82, 165, 255], [0, 0, 127, 255], [63, 63, 127, 255], [0, 0, 76, 255], [38, 38, 76, 255], [0, 0, 38, 255], [19, 19, 38, 255], [63, 0, 255, 255], [159, 127, 255, 255], [41, 0, 165, 255], [103, 82, 165, 255], [31, 0, 127, 255], [79, 63, 127, 255], [19, 0, 76, 255], [47, 38, 76, 255], [9, 0, 38, 255], [23, 19, 38, 255], [127, 0, 255, 255], [191, 127, 255, 255], [82, 0, 165, 255], [124, 82, 165, 255], [63, 0, 127, 255], [95, 63, 127, 255], [38, 0, 76, 255], [57, 38, 76, 255], [19, 0, 38, 255], [28, 19, 38, 255], [191, 0, 255, 255], [223, 127, 255, 255], [124, 0, 165, 255], [145, 82, 165, 255], [95, 0, 127, 255], [111, 63, 127, 255], [57, 0, 76, 255], [66, 38, 76, 255], [28, 0, 38, 255], [33, 19, 38, 255], [255, 0, 255, 255], [255, 127, 255, 255], [165, 0, 165, 255], [165, 82, 165, 255], [127, 0, 127, 255], [127, 63, 127, 255], [76, 0, 76, 255], [76, 38, 76, 255], [38, 0, 38, 255], [38, 19, 38, 255], [255, 0, 191, 255], [255, 127, 223, 255], [165, 0, 124, 255], [165, 82, 145, 255], [127, 0, 95, 255], [127, 63, 111, 255], [76, 0, 57, 255], [76, 38, 66, 255], [38, 0, 28, 255], [38, 19, 33, 255], [255, 0, 127, 255], [255, 127, 191, 255], [165, 0, 82, 255], [165, 82, 124, 255], [127, 0, 63, 255], [127, 63, 95, 255], [76, 0, 38, 255], [76, 38, 57, 255], [38, 0, 19, 255], [38, 19, 28, 255], [255, 0, 63, 255], [255, 127, 159, 255], [165, 0, 41, 255], [165, 82, 103, 255], [127, 0, 31, 255], [127, 63, 79, 255], [76, 0, 19, 255], [76, 38, 47, 255], [38, 0, 9, 255], [38, 19, 23, 255], [84, 84, 84, 255], [118, 118, 118, 255], [152, 152, 152, 255], [186, 186, 186, 255], [220, 220, 220, 255], [255, 255, 255, 255], [0, 0, 0, 0] //----- ByLayer - White ]; const LINE_WIDTH = 2; //颜色材质,对于二维图像来说可能有用,应该不对三维对象使用该材质 class ColorMaterial { constructor() { } static GetLineMaterial(color) { if (this._LineMaterialMap.has(color)) return this._LineMaterialMap.get(color); let mat = new LineBasicMaterial({ color: this.GetColor(color), side: DoubleSide }); this._LineMaterialMap.set(color, mat); return mat; } static GetBasicMaterial(color) { if (this._BasicMaterialMap.has(color)) return this._BasicMaterialMap.get(color); let mtl = new MeshBasicMaterial({ color: this.GetColor(color) }); this._BasicMaterialMap.set(color, mtl); return mtl; } static GetBasicMaterialDoubleSide(color) { if (this._BasicDoubleSideMaterialMap.has(color)) return this._BasicDoubleSideMaterialMap.get(color); let mtl = new MeshBasicMaterial({ color: this.GetColor(color), side: DoubleSide }); this._BasicDoubleSideMaterialMap.set(color, mtl); return mtl; } static GetConceptualMaterial(color, side = FrontSide) { let key = `${color}${side}`; if (this._ConceptualMaterial.has(key)) return this._ConceptualMaterial.get(key); let shaderParams = GetGoodShaderSimple(new Vector3().fromArray(this.GetColor(color).toArray()), side); let mtl = new ShaderMaterial(shaderParams); this._ConceptualMaterial.set(key, mtl); return mtl; } static GetPrintConceptualMaterial() { if (!this._printConceptualMaterial) { this._printConceptualMaterial = new ShaderMaterial({ uniforms: { "SurfaceColor": { value: [1.0, 1.0, 1.0] } }, vertexShader: "", fragmentShader: "", polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: LINE_WIDTH }); } return this._printConceptualMaterial; } static GetBasicMaterialTransparent(color, opacity) { let key = `${color},${opacity}`; let mat = this._BasicTransparentMaterialMap.get(key); if (mat) return mat; mat = new MeshBasicMaterial({ transparent: true, opacity: opacity, side: DoubleSide }); this._BasicTransparentMaterialMap.set(key, mat); return mat; } static GetBasicMaterialTransparent2(color, opacity) { let key = `${color},${opacity}`; let mat = this._BasicTransparentMaterialMap2.get(key); if (mat) return mat; mat = new MeshBasicMaterial({ transparent: true, opacity: opacity }); this._BasicTransparentMaterialMap2.set(key, mat); return mat; } static GetColor(color) { let rgb = ColorPalette[color]; if (rgb) return new Color(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); //避免无法获得到颜色而产生的错误 return new Color(); } } ColorMaterial._LineMaterialMap = new Map(); ColorMaterial._BasicMaterialMap = new Map(); ColorMaterial._BasicDoubleSideMaterialMap = new Map(); ColorMaterial._ConceptualMaterial = new Map(); ColorMaterial._BasicTransparentMaterialMap = new Map(); ColorMaterial._BasicTransparentMaterialMap2 = new Map(); //橡皮筋材质: 黄色 点划线 ColorMaterial.RubberBandMaterial = new LineDashedMaterial({ color: 0xF0B41E, dashSize: 20, gapSize: 8, }); //极轴材质: 绿色 点划线 ColorMaterial.SnapAxisMaterial = new LineDashedMaterial({ color: 0x008B00, dashSize: 5, gapSize: 5 }); ColorMaterial.PrintLineMatrial = new LineMaterial({ color: 0x000000, linewidth: LINE_WIDTH, dashed: false, resolution: new Vector2(1000, 1000), side: DoubleSide, }); ColorMaterial.GrayTransparentMeshMaterial = new MeshBasicMaterial({ color: 0xcccccc, transparent: true, opacity: 0.3, }); ColorMaterial.TransparentMeshMaterial = new MeshBasicMaterial({ transparent: true, opacity: 0, }); ColorMaterial.TransparentLineMaterial = new MeshBasicMaterial({ transparent: true, opacity: 0, }); /** * 坐标系运算. */ class CoordinateSystem { constructor(postion, xAxis, yAxis, zAxis) { this.Postion = postion || new Vector3(0, 0, 0); this.XAxis = xAxis || new Vector3(1, 0, 0); this.YAxis = yAxis || new Vector3(0, 1, 0); this.ZAxis = zAxis || new Vector3(0, 0, 1); } applyMatrix4(mat4) { this.Postion.applyMatrix4(mat4); let roMat = mat4.clone().setPosition(new Vector3()); this.XAxis.applyMatrix4(roMat); this.YAxis.applyMatrix4(roMat); this.ZAxis.applyMatrix4(roMat); return this; } getMatrix4() { let m = new Matrix4(); m.makeBasis(this.XAxis, this.YAxis, this.ZAxis); m.setPosition(this.Postion); return m; } CopyForm(mat4) { this.Postion.setFromMatrixPosition(mat4); mat4.extractBasis(this.XAxis, this.YAxis, this.ZAxis); return this; } extractBasis(xAxisA, yAxisA, zAxisA) { xAxisA.copy(this.XAxis); yAxisA.copy(this.YAxis); zAxisA.copy(this.ZAxis); } copy(cs) { this.Postion.copy(cs.Postion); this.XAxis.copy(cs.XAxis); this.YAxis.copy(cs.YAxis); this.ZAxis.copy(cs.ZAxis); return this; } clone() { let r = new CoordinateSystem(); r.Postion = this.Postion.clone(); r.XAxis = this.XAxis.clone(); r.YAxis = this.YAxis.clone(); r.ZAxis = this.ZAxis.clone(); return r; } } let HostApplicationServices = { ShowHistoryLog: true }; /** * 销毁Object对象的Geometry,并不会销毁材质(新版本销毁材质,好像问题不大?) */ function DisposeThreeObj(obj) { for (let o of obj.children) { let oany = o; //文字的geometry缓存保留下来 if (oany.geometry && oany.geometry.name !== "Text") oany.geometry.dispose(); if (oany.material) if (Array.isArray(oany.material)) { for (let m of oany.material) m.dispose(); } else { oany.material.dispose(); } DisposeThreeObj(o); // 下面这个代码可能导致Object3d无法复用,删除它应该问题不大 // o.parent = null; // o.dispatchEvent({ type: "removed" }); } // 下面这个代码可能导致Object3d无法复用,删除它应该问题不大 // obj.children.length = 0; return obj; } function Object3DRemoveAll(obj) { for (let o of obj.children) { o.parent = null; o.dispatchEvent({ type: "removed" }); } obj.children.length = 0; return obj; } var Status; (function (Status) { Status[Status["False"] = 0] = "False"; Status[Status["True"] = 1] = "True"; Status[Status["Canel"] = -1] = "Canel"; Status[Status["ConverToCircle"] = 101] = "ConverToCircle"; Status[Status["DuplicateRecordName"] = 102] = "DuplicateRecordName"; })(Status || (Status = {})); var UpdateDraw; (function (UpdateDraw) { UpdateDraw[UpdateDraw["None"] = 0] = "None"; UpdateDraw[UpdateDraw["Matrix"] = 1] = "Matrix"; UpdateDraw[UpdateDraw["Geometry"] = 2] = "Geometry"; UpdateDraw[UpdateDraw["Material"] = 4] = "Material"; UpdateDraw[UpdateDraw["All"] = 63] = "All"; })(UpdateDraw || (UpdateDraw = {})); /** * WblockClne时,遇到重复记录的操作方式 */ var DuplicateRecordCloning; (function (DuplicateRecordCloning) { DuplicateRecordCloning[DuplicateRecordCloning["Ignore"] = 1] = "Ignore"; DuplicateRecordCloning[DuplicateRecordCloning["Replace"] = 2] = "Replace"; DuplicateRecordCloning[DuplicateRecordCloning["Rename"] = 3] = "Rename"; })(DuplicateRecordCloning || (DuplicateRecordCloning = {})); var StoreageKeys; (function (StoreageKeys) { StoreageKeys["IsLogin"] = "isLogin"; StoreageKeys["PlatSession"] = "platSession"; StoreageKeys["PlatToken"] = "platToken"; StoreageKeys["UserName"] = "userName"; StoreageKeys["UserPhone"] = "userPhone"; StoreageKeys["RenderType"] = "renderType"; StoreageKeys["ExactDrill"] = "openExactDrill"; StoreageKeys["ConfigName"] = "configName_"; StoreageKeys["IsNewErp"] = "isNewErp"; StoreageKeys["RoomName"] = "roomName"; StoreageKeys["LastOpenFileId"] = "lastfid"; StoreageKeys["Uid"] = "uid"; StoreageKeys["Goods"] = "Goods_"; StoreageKeys["DrillTemp"] = "drilltemp_"; StoreageKeys["DrillReactor"] = "drillRreactor"; StoreageKeys["kjlConfig"] = "kjl"; })(StoreageKeys || (StoreageKeys = {})); var AAType; (function (AAType) { AAType[AAType["FXAA"] = 0] = "FXAA"; AAType[AAType["SMAA"] = 1] = "SMAA"; })(AAType || (AAType = {})); var ViewDirType; (function (ViewDirType) { ViewDirType[ViewDirType["FS"] = 0] = "FS"; ViewDirType[ViewDirType["YAS"] = 1] = "YAS"; ViewDirType[ViewDirType["ZS"] = 2] = "ZS"; ViewDirType[ViewDirType["YS"] = 3] = "YS"; ViewDirType[ViewDirType["QS"] = 4] = "QS"; ViewDirType[ViewDirType["HS"] = 5] = "HS"; ViewDirType[ViewDirType["XN"] = 6] = "XN"; })(ViewDirType || (ViewDirType = {})); var FractionDigitsType; (function (FractionDigitsType) { FractionDigitsType[FractionDigitsType["zero"] = 0] = "zero"; FractionDigitsType[FractionDigitsType["one"] = 1] = "one"; FractionDigitsType[FractionDigitsType["two"] = 2] = "two"; FractionDigitsType[FractionDigitsType["three"] = 3] = "three"; FractionDigitsType[FractionDigitsType["four"] = 4] = "four"; FractionDigitsType[FractionDigitsType["five"] = 5] = "five"; })(FractionDigitsType || (FractionDigitsType = {})); /** * 场景的渲染类型. */ var RenderType; (function (RenderType) { /** * 线框模式 */ RenderType[RenderType["Wireframe"] = 1] = "Wireframe"; /** * 概念 */ RenderType[RenderType["Conceptual"] = 2] = "Conceptual"; /** * 物理着色PBR */ RenderType[RenderType["Physical"] = 3] = "Physical"; RenderType[RenderType["Jig"] = 4] = "Jig"; RenderType[RenderType["Print"] = 5] = "Print"; /**物理带线框 */ RenderType[RenderType["Physical2"] = 6] = "Physical2"; RenderType[RenderType["Edge"] = 7] = "Edge"; RenderType[RenderType["PlaceFace"] = 8] = "PlaceFace"; /******************************************** 在视口时的渲染模式 */ /** * 线框模式 */ RenderType[RenderType["WireframePrint"] = 101] = "WireframePrint"; /** * 概念 */ RenderType[RenderType["ConceptualPrint"] = 102] = "ConceptualPrint"; /** * 物理着色PBR */ RenderType[RenderType["PhysicalPrint"] = 103] = "PhysicalPrint"; RenderType[RenderType["JigPrint"] = 104] = "JigPrint"; RenderType[RenderType["PrintPrint"] = 105] = "PrintPrint"; /**物理带线框 */ RenderType[RenderType["Physical2Print"] = 106] = "Physical2Print"; })(RenderType || (RenderType = {})); class UserConfig { constructor() { this._version = 16; this._renderType = RenderType.Wireframe; this.maxSize = { isShow: false, height: 2440, width: 1220, }; this._drillConfigs = new Map(); this.openDrillingReactor = true; this.openReDrilling = false; //插入图纸的时候,响应排钻反应器 this.openAutoCuttingReactor = true; this.userConfigName = {}; this._modeling2HoleRad = 20; //圆造型小于等于该值拆成孔数据 this.isAdmin = false; this.isMaster = false; this.rights = []; this.kjlConfig = { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "1", }; this.SystemConfig = { maxHightightCount: 15000, snapSize: 15, aaType: AAType.FXAA, background: "#000000", layoutBackground: "#cccccc", }; this.viewDirType = ViewDirType.XN; this.fractionDigitsType = FractionDigitsType.two; this.useCtrlRotate = true; this.cursorSize = { D2: 1000, D3: 100, SquareSize: 10, }; this.smalliconmode = false; //小图标模式 this.autoSaveConfig = { enable: true, time: 1, }; this.showLines = false; this.keepConfig = false; this.autoClearHistory = true; this.chaidanOption = { changXiuBian: 6, duanXiuBian: 6, useDefaultRad: false, radius: 2.5, modeling2HoleRad: 20, isCheckInterfere: false, noModeingData: "", statTk: false, statSt: false, //统计双头排钻 }; this.viewSize = { minViewHeight: 1e-3, maxViewHeight: 3e6, zoomSpeed: 0.6 }; this.autoLines = false; this.dimTextHeight = 60; this.performanceConfig = { fakersweep: false, disablerefcut: false, }; this.lineWidth = 2; //打印线框 this.forceFilterPxl = true; this.checkSealing = false; this.sealingColorMap = []; this.textStyleOption = { appointTextHight: false, noNeedAngle: false, textHight: 30, }; this.configName = "default"; this.configsNames = []; this.Init(); } Init() { var _a; let type = Number((_a = globalThis.localStorage) === null || _a === void 0 ? void 0 : _a.getItem(StoreageKeys.RenderType)); if (type && type !== RenderType.Edge) this._renderType = type; } set RenderType(t) { if (t !== this._renderType) { this._renderType = t; this.checkSealing = t === RenderType.Edge; this.SetRenderTypeEvent(); localStorage.setItem(StoreageKeys.RenderType, t.toString()); } } get RenderType() { return this._renderType; } SetRenderTypeEvent() { } get DrillConfigs() { return this._drillConfigs || new Map(); } set DrillConfigs(config) { observable(this._drillConfigs).replace(config); this.SetDrillConfigsEvent(); } SetDrillConfigsEvent() { } InitOption() { this.openDrillingReactor = true; this.openReDrilling = false; this.openAutoCuttingReactor = true; Object.assign(this.maxSize, { height: 2440, width: 1220 }); Object.assign(this.kjlConfig, { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "1" }); Object.assign(this.chaidanOption, { changXiuBian: 6, duanXiuBian: 6, useDefaultRad: false, radius: 2.5, modeling2HoleRad: 20, noModeingData: "", }); Object.assign(this.textStyleOption, { appointTextHight: false, needAngle: false, textHight: 60, }); this.dimTextHeight = 60; this.fractionDigitsType = FractionDigitsType.two; Object.assign(this.viewSize, { minViewHeight: 1e-3, maxViewHeight: 3e6, zoomSpeed: 0.6, }); this.smalliconmode = false; } SaveConfig() { return { option: { version: this._version, openDrillingReactor: this.openDrillingReactor, openReDrilling: this.openReDrilling, openAutoCuttingReactor: this.openAutoCuttingReactor, maxSize: toJS(this.maxSize), kjlConfig: toJS(this.kjlConfig), systemConfig: toJS(this.SystemConfig), viewDirType: this.viewDirType, useCtrlRotate: this.useCtrlRotate, cursorSize: toJS(this.cursorSize), autoSaveConfig: toJS(this.autoSaveConfig), showLines: this.showLines, keepConfig: this.keepConfig, autoClearHistory: this.autoClearHistory, chaidanOption: toJS(this.chaidanOption), autoLines: this.autoLines, dimTextHeight: this.dimTextHeight, lineWidth: this.lineWidth, forceFilterPxl: this.forceFilterPxl, checkSealing: this.checkSealing, sealingColorMap: this.sealingColorMap, fractionDigitsType: this.fractionDigitsType, textStyleOption: toJS(this.textStyleOption), viewSize: toJS(this.viewSize), smalliconmode: this.smalliconmode, } }; } UpdateOption(config) { this.openDrillingReactor = config.option.openDrillingReactor; this.openReDrilling = config.option.openReDrilling; this.openAutoCuttingReactor = config.option.openAutoCuttingReactor; Object.assign(this.maxSize, config.option.maxSize); Object.assign(this.kjlConfig, config.option.kjlConfig); this._modeling2HoleRad = config.option.modeling2HoleRad; if (config.option.version > 1) { Object.assign(this.SystemConfig, config.option.systemConfig); } if (config.option.version > 2) { this.viewDirType = config.option.viewDirType; this.useCtrlRotate = config.option.useCtrlRotate; } if (config.option.version > 3) { Object.assign(this.cursorSize, config.option.cursorSize); } if (config.option.version > 4) { Object.assign(this.autoSaveConfig, config.option.autoSaveConfig); } if (config.option.version > 5) { this.showLines = config.option.showLines; } if (config.option.version > 6) { this.keepConfig = config.option.keepConfig; } if (config.option.version > 7) { this.autoClearHistory = config.option.autoClearHistory; } if (config.option.version > 8) { config.option.chaidanOption.modeling2HoleRad = Number(config.option.chaidanOption.modeling2HoleRad); Object.assign(this.chaidanOption, config.option.chaidanOption); this.autoLines = config.option.autoLines; } else this.chaidanOption.modeling2HoleRad = this._modeling2HoleRad; if (config.option.version > 9) this.dimTextHeight = config.option.dimTextHeight; if (config.option.version > 10) this.lineWidth = config.option.lineWidth; if (config.option.version > 11) this.forceFilterPxl = config.option.forceFilterPxl; if (config.option.version > 12) { this.checkSealing = config.option.checkSealing; this.sealingColorMap = config.option.sealingColorMap; } if (config.option.version > 13) { this.fractionDigitsType = config.option.fractionDigitsType; Object.assign(this.textStyleOption, config.option.textStyleOption); } if (config.option.version > 14) { this.smalliconmode = config.option.smalliconmode; } if (config.option.version > 15) { Object.assign(this.viewSize, config.option.viewSize); } } } __decorate([ observable ], UserConfig.prototype, "maxSize", void 0); __decorate([ observable ], UserConfig.prototype, "_drillConfigs", void 0); __decorate([ observable ], UserConfig.prototype, "openDrillingReactor", void 0); __decorate([ observable ], UserConfig.prototype, "openReDrilling", void 0); __decorate([ observable ], UserConfig.prototype, "openAutoCuttingReactor", void 0); __decorate([ observable ], UserConfig.prototype, "isAdmin", void 0); __decorate([ observable ], UserConfig.prototype, "kjlConfig", void 0); __decorate([ observable ], UserConfig.prototype, "SystemConfig", void 0); __decorate([ observable ], UserConfig.prototype, "viewDirType", void 0); __decorate([ observable ], UserConfig.prototype, "fractionDigitsType", void 0); __decorate([ observable ], UserConfig.prototype, "useCtrlRotate", void 0); __decorate([ observable ], UserConfig.prototype, "cursorSize", void 0); __decorate([ observable ], UserConfig.prototype, "smalliconmode", void 0); __decorate([ observable ], UserConfig.prototype, "autoSaveConfig", void 0); __decorate([ observable ], UserConfig.prototype, "showLines", void 0); __decorate([ observable ], UserConfig.prototype, "keepConfig", void 0); __decorate([ observable ], UserConfig.prototype, "autoClearHistory", void 0); __decorate([ observable ], UserConfig.prototype, "chaidanOption", void 0); __decorate([ observable ], UserConfig.prototype, "viewSize", void 0); __decorate([ observable ], UserConfig.prototype, "autoLines", void 0); __decorate([ observable ], UserConfig.prototype, "dimTextHeight", void 0); __decorate([ observable ], UserConfig.prototype, "performanceConfig", void 0); __decorate([ observable ], UserConfig.prototype, "forceFilterPxl", void 0); __decorate([ observable ], UserConfig.prototype, "textStyleOption", void 0); const userConfig = new UserConfig(); /** * 盒子的切割类型 */ var SplitType; (function (SplitType) { SplitType[SplitType["X"] = 0] = "X"; SplitType[SplitType["Y"] = 1] = "Y"; SplitType[SplitType["Z"] = 2] = "Z"; })(SplitType || (SplitType = {})); /** * 扩展Box3,添加切割方法,体积等 */ class Box3Ext extends Box3 { get Volume() { let size = this.getSize(new Vector3()); return size.x * size.y * size.z; } //每个轴的大小必须大于最小的size isSolid(minSize = 1) { return this.getSize(new Vector3()).toArray().every(x => x > minSize); } substract(b, spaceType) { let interBox = this.clone().intersect(b); if (interBox.isEmpty() || !interBox.isSolid()) return [this]; let p1 = interBox.min.clone().setComponent(spaceType, this.min.getComponent(spaceType)); let p2 = interBox.max.clone().setComponent(spaceType, interBox.min.getComponent(spaceType)); let p3 = interBox.min.clone().setComponent(spaceType, interBox.max.getComponent(spaceType)); let p4 = interBox.max.clone().setComponent(spaceType, this.max.getComponent(spaceType)); return [ new Box3Ext(p1, p2), new Box3Ext(p3, p4) ].filter(b => b.isSolid()); } clampSpace(b2, splitType) { let interBox = this.clone(); interBox.min.max(b2.min); interBox.max.min(b2.max); interBox.min.setComponent(splitType, Math.min(this.max.getComponent(splitType), b2.max.getComponent(splitType))); interBox.max.setComponent(splitType, Math.max(this.min.getComponent(splitType), b2.min.getComponent(splitType))); return interBox; } intersectsBox(box, fuzz = 1e-8) { return IntersectsBox(this, box, fuzz); } } function IntersectsBox(box1, box2, fuzz = 1e-6) { return box2.max.x < box1.min.x - fuzz || box2.min.x > box1.max.x + fuzz || box2.max.y < box1.min.y - fuzz || box2.min.y > box1.max.y + fuzz || box2.max.z < box1.min.z - fuzz || box2.min.z > box1.max.z + fuzz ? false : true; } /**盒子二维面是否相交 */ function IntersectBox2(box1, box2, fuzz = 1e-3) { return box2.max.x < box1.min.x - fuzz || box2.min.x > box1.max.x + fuzz || box2.max.y < box1.min.y - fuzz || box2.min.y > box1.max.y + fuzz ? false : true; } const ISPROXYKEY = "_isProxy"; /** * 自动对CADObject的属性添加属性记录器,自动调用 `WriteAllObjectRecord` * 如果属性是数组,那么自动添加`Proxy`. * 可以使用`ISPROXYKEY`覆盖这个函数的代理行为(使用CADObject.CreateProxyArray快速覆盖) * * @param target * @param property * @param [descriptor] */ function AutoRecord(target, property, descriptor) { let privateKey = '__' + property; Object.defineProperty(target, property, { set: function (value) { if (value instanceof Array) { if (!this[privateKey]) { if (value[ISPROXYKEY]) this[privateKey] = value; else this[privateKey] = new Proxy(value, { set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) this.WriteAllObjectRecord(); return Reflect.set(target, key, value, receiver); }, get: (target, key, receiver) => { if (key === ISPROXYKEY) return true; //实体先被删除后在触发length = xxx if (key === "splice" || key === "pop" || key === "shift") this.WriteAllObjectRecord(); return Reflect.get(target, key, receiver); } }); } else { let arr = this[privateKey]; arr.length = 0; arr.push(...value); } } else { let oldv = this[privateKey]; if (oldv !== value) { this.WriteAllObjectRecord(); this[privateKey] = value; } } }, get: function () { return this[privateKey]; }, enumerable: true, configurable: true }); } /** * CAD对象工厂,通过注册 和暴露的创建方法,动态创建对象 */ class CADFactory { constructor() { this.objectNameMap = new Map(); } static RegisterObject(C) { this.factory.objectNameMap.set(C.name, C); } static RegisterObjectAlias(C, name) { this.factory.objectNameMap.set(name, C); } static CreateObject(name) { let C = this.factory.objectNameMap.get(name); if (C) return new C(); } } CADFactory.factory = new CADFactory(); //可以通过添加装饰器 在类前面(@Factory),自动注册工厂的序列化 function Factory(target) { CADFactory.RegisterObject(target); } var RelevancyType; (function (RelevancyType) { RelevancyType[RelevancyType["General"] = 0] = "General"; RelevancyType[RelevancyType["Soft"] = 1] = "Soft"; RelevancyType[RelevancyType["Hard"] = 2] = "Hard"; })(RelevancyType || (RelevancyType = {})); /* CADObject对象拥有Id属性,用来记录引用关系. 通过id可以得到对应的关联实体,或者记录实体的关联关系. ObjectId必须使用 Database分配(db里面会存id的列表,以便同时更新id指向实体) */ class ObjectId { constructor(index = 0, obj) { this.index = index; this.obj = obj; this._RelevancyType = RelevancyType.General; } get IsErase() { return !this.obj || this.obj.IsErase; } set Object(obj) { this.obj = obj; } get Object() { return this.obj; } get Index() { return this.index; } set Index(index) { this.index = index; } } /** * CAD文件数据 */ class CADFiler { constructor(_datas = []) { this._datas = _datas; this.readIndex = 0; } Destroy() { delete this._datas; delete this.readIndex; } get Data() { return this._datas; } set Data(data) { this._datas = data; this.Reset(); } Clear() { this._datas.length = 0; return this.Reset(); } Reset() { this.readIndex = 0; return this; } WriteString(str) { this._datas.push(str); return this; } ReadString() { return this._datas[this.readIndex++]; } WriteObject(obj) { if (!obj) { this.Write(""); return; } this.WriteString(obj.constructor.name); obj.WriteFile(this); return this; } ReadObject(obj) { let className = this.ReadString(); if (className) { if (obj === undefined) { obj = CADFactory.CreateObject(className); if (this.database !== undefined && obj instanceof CADObject) obj.SetDefaultDb(this.database); } obj.ReadFile(this); return obj; } } CloneObjects(objects, clonedObjects = []) { for (let o of objects) this.WriteObject(o); let count = objects.length; for (let i = 0; i < count; i++) { let obj = this.ReadObject(); if (obj instanceof Entity) obj.CloneDrawObject(objects[i]); clonedObjects.push(obj); } return clonedObjects; } Write(data) { if (data instanceof ObjectId) this._datas.push(data.Index); else this._datas.push(data); return this; } Read() { return this._datas[this.readIndex++]; } ReadArray(count) { let arr = this._datas.slice(this.readIndex, this.readIndex + count); this.readIndex += count; return arr; } //------------------------ID序列化------------------------ /* Id关联分为三种情况: 1.普通关联:关联对象未被拷贝时,关联到空对象. 2.软关联 :关联对象未被拷贝时,关联到原先的对象. 3.硬关联 :对象被拷贝时,被关联的对象必须也被拷贝. */ //-------1.普通关联 WriteObjectId(id) { if (id) this.Write(id.Index); else this.Write(0); return this; } ReadObjectId() { let index = this.Read(); if (this.database) return this.database.GetObjectId(index, true); } //-------2.软关联 WriteSoftObjectId(id) { return this.WriteObjectId(id); } ReadSoftObjectId() { return this.ReadObjectId(); } //-------3.硬关联 WriteHardObjectId(id) { return this.WriteObjectId(id); } ReadHardObjectId() { return this.ReadObjectId(); } //序列化 ToString() { return JSON.stringify(this._datas); } FromString(str) { this._datas = JSON.parse(str); } } /** * 保存对象创建或者修改时的所有数据记录 */ let AllObjectData = class AllObjectData { constructor(obj) { this.file = new CADFiler(); if (obj) obj.WriteFile(this.file); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); let data = file.Read(); this.file.Data = data; return this; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); file.Write(this.file.Data); return this; } }; AllObjectData = __decorate([ Factory ], AllObjectData); let EraseEntityData = class EraseEntityData { constructor(isErase = true) { this.isErase = isErase; } ReadFile(file) { this.isErase = file.Read(); return this; } WriteFile(file) { file.Write(this.isErase); return this; } }; EraseEntityData = __decorate([ Factory ], EraseEntityData); class CADObject { constructor() { //-------------------------DB End------------------------- // -------------------------isErase------------------------- this._isErase = false; } set Owner(owner) { this._Owner = owner; } get Owner() { return this._Owner; } Destroy() { //在效果图同步反应器中,需要知道被删除的实体的id,所以不删除这个属性 // this.objectId = undefined; this._db = undefined; } //对象被彻底遗弃 GoodBye() { this.Destroy(); this.Erase(true); } /** * 当实体异步更新绘制实体完成后触发这个函数. * Application通过注入的方式得知这个事件,刷新视图显示. */ AsyncUpdated() { } get Db() { return this._db; } //对象在加入数据库时,必须指定一个源数据库,否则无法读取引用id. SetDefaultDb(db) { if (!this._db) this._db = db; else console.warn("重复设置默认Database!"); return this; } //private 私有的方法,暴露给Db的添加对象,方法使用. //只用对象加入到db中,我们才初始化ObjectId. //从db池中分配id给自身使用. 除非你创建对象往db里面加,否则不要调用该方法 SetOwnerDatabase(db) { if (!this._db) { this._db = db; this.objectId = db.AllocateId(); this.objectId.Object = this; } else console.warn("重复设置源Database!"); return this; } /** * WblockClone 的时候,id是db分配的,此刻我们只需要设置它的db */ SetDatabase(db) { this._db = db; } get IsErase() { return this._isErase; } Erase(isErase = true) { if (isErase === this._isErase) return; let undoData = this.UndoRecord(); if (undoData) undoData.CreateEraseHistory(this, isErase); this._isErase = isErase; } get Id() { return this.objectId; } // -------------------------id End------------------------- // -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); //write Id; let id = file.ReadObjectId(); if (!this.objectId && id) //避免CopyFrom时错误的修改自身Id { this.objectId = id; id.Object = this; } this._isErase = file.Read(); if (ver > 1) this._Owner = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); file.WriteObjectId(this.objectId); file.Write(this._isErase); file.WriteObjectId(this._Owner); } //局部撤销 ApplyPartialUndo(undoData) { if (undoData instanceof AllObjectData) { undoData.file.database = this._db; undoData.file.Reset(); this.ReadFile(undoData.file); } else if (undoData instanceof EraseEntityData) { this.Erase(undoData.isErase); } } //撤销所保存的位置 UndoRecord() { if (this._db && this.objectId) return this._db.hm.UndoData; } //写入所有的对象数据 以便还原对象 WriteAllObjectRecord() { let undoData = this.UndoRecord(); if (undoData) { undoData.WriteObjectSnapshoot(this); return true; } return false; } //复制出一个实体,如果存在关联,则指向原关联实体 Clone() { let newObject = CADFactory.CreateObject(this.constructor.name); //备份 let bakId = this.objectId; this.objectId = undefined; let file = new CADFiler(); file.database = this._db; this.WriteFile(file); file.Reset(); newObject.ReadFile(file); newObject.objectId = undefined; newObject._db = undefined; this.objectId = bakId; return newObject; } DeepClone(ownerObject, cloneObejct, idMaping = undefined, isPrimary = true) { return this; } //从一个实体拷贝数据,实体类型必须相同. CopyFrom(obj) { let idBak = this.objectId; let ownerBak = this._Owner; this.WriteAllObjectRecord(); let f = new CADFiler(); obj.WriteFile(f); this.ReadFile(f); this.objectId = idBak; this._Owner = ownerBak; } //-------------------------File End------------------------- //Utils /** * 配合 `@AutoRecord` 使用 * 使用这个方法来覆盖AutoRecord的监听行为. * 这个行为只能用来监听实体添加和实体修改. * 实体删除行为暂时无法监听 * @param setCallback 设置新的实体到数组时的回调函数 */ CreateProxyArray(setCallback) { return new Proxy([], { set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) { this.WriteAllObjectRecord(); setCallback(value); } return Reflect.set(target, key, value, receiver); }, get: (target, key, receiver) => { if (key === ISPROXYKEY) return true; //实体先被删除后在触发length = xxx if (key === "splice" || key === "pop" || key === "shift") { this.WriteAllObjectRecord(); setCallback(undefined); } return Reflect.get(target, key, receiver); } }); } } __decorate([ iaop ], CADObject.prototype, "AsyncUpdated", null); /** * 历史记录,用于撤销和重做的数据. */ let HistorycRecord = class HistorycRecord extends CADObject { //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); this.undoData = file.ReadObject(); this.redoData = file.ReadObject(); this.userData = file.ReadObject(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); file.WriteObject(this.undoData); file.WriteObject(this.redoData); file.WriteObject(this.userData); } }; HistorycRecord = __decorate([ Factory ], HistorycRecord); var Entity_1; /** * Entity 是所有图元的基类,绘制的实体都集成该类. */ let Entity = Entity_1 = class Entity extends CADObject { constructor() { super(...arguments); this.IsEmbedEntity = false; /** * 该实体的只有一个渲染类型,任何渲染类型都一个样 */ this.OnlyRenderType = false; this.HasEdgeRenderType = false; this.HasPlaceFaceRenderType = false; this._CacheDrawObject = new Map(); this._Color = 7; //自身坐标系 this._Matrix = new Matrix4(); //模块空间的标系 this._SpaceOCS = new Matrix4(); this._Visible = true; //加工组 this.ProcessingGroupList = []; /** * 当AutoUpdate为false时,记录需要更新的标志. * 以便延迟更新时找到相应的更新标志. */ this.NeedUpdateFlag = UpdateDraw.None; this.AutoUpdate = true; //实体绘制更新版本号 this.__UpdateVersion__ = 0; //#endregion } get SpaceOCS() { return this._SpaceOCS.clone(); } get SpaceOCSInv() { return new Matrix4().getInverse(this._SpaceOCS); } set SpaceOCS(m) { this.WriteAllObjectRecord(); this._SpaceOCS.copy(m); } set Material(materialId) { if (materialId === this.materialId) return; this.WriteAllObjectRecord(); this.materialId = materialId; for (let [type, obj] of this._CacheDrawObject) this.UpdateDrawObjectMaterial(type, obj); } get Material() { return this.materialId; } set ColorIndex(color) { if (color !== this._Color) { let undoRec = this.UndoRecord(); if (undoRec) { let hisRec = new HistorycRecord(); hisRec.redoData = new EntityColorHistoryRecord(color); hisRec.undoData = new EntityColorHistoryRecord(this._Color); undoRec.WriteObjectHistoryPath(this, hisRec); } this._Color = color; this.Update(UpdateDraw.Material); } } get ColorIndex() { return this._Color; } /** * 炸开实体 */ Explode() { return []; } /** * 返回对象的包围框. */ get BoundingBox() { for (let [, obj] of this._CacheDrawObject) return GetBox(obj); return GetBox(this.GetDrawObjectFromRenderType()); } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { let mtxBak = this._Matrix; this._Matrix = IdentityMtx4; let box = this.BoundingBox; this._Matrix = mtxBak; return new Box3Ext().copy(box); } GetBoundingBoxInMtx(mtx) { return this.BoundingBoxInOCS.applyMatrix4(this.OCS.premultiply(mtx)); } get BoundingBoxInSpaceCS() { return this.GetBoundingBoxInMtx(this.SpaceOCSInv); } get OCS() { return this._Matrix.clone(); } get OCSNoClone() { return this._Matrix; } //直接设置实体的矩阵,谨慎使用该函数,没有更新实体. set OCS(mat4) { this._Matrix.copy(mat4); } get Normal() { return new Vector3().setFromMatrixColumn(this._Matrix, 2); } get Position() { return new Vector3().setFromMatrixPosition(this._Matrix); } set Position(pt) { let moveX = pt.x - this._Matrix.elements[12]; let moveY = pt.y - this._Matrix.elements[13]; let moveZ = pt.z - this._Matrix.elements[14]; if (moveX === 0 && moveY === 0 && moveZ === 0) return; this.WriteAllObjectRecord(); this._Matrix.setPosition(pt); this._SpaceOCS.elements[12] += moveX; this._SpaceOCS.elements[13] += moveY; this._SpaceOCS.elements[14] += moveZ; this.Update(UpdateDraw.Matrix); } //Z轴归0 Z0() { if (this._Matrix.elements[14] === 0) return this; this.WriteAllObjectRecord(); this._Matrix.elements[14] = 0; this.Update(UpdateDraw.Matrix); return this; } //坐标系二维化 MatrixPlanarizere() { let z = this._Matrix.elements[10]; if (equaln(Math.abs(z), 1, 1e-4)) { this.WriteAllObjectRecord(); MatrixPlanarizere(this._Matrix, false); } return this; } get OCSInv() { return new Matrix4().getInverse(this._Matrix); } /** * 与指定实体是否共面. */ IsCoplaneTo(e) { return matrixIsCoplane(this._Matrix, e.OCS, 1e-4); } /** * 测试两个实体的包围盒是否相交. */ BoundingBoxIntersectWith(en) { let box = this.BoundingBox; let box2 = en.BoundingBox; return box && box2 && box.intersectsBox(box2); } //#region Draw ClearDraw() { if (this._drawObject) { DisposeThreeObj(this._drawObject); this._drawObject = undefined; } for (let [, obj] of this._CacheDrawObject) DisposeThreeObj(obj); this._CacheDrawObject.clear(); return this; } ClearDrawOfJig() { let jig = this._CacheDrawObject.get(RenderType.Jig); if (jig) this._CacheDrawObject.delete(RenderType.Jig); for (let [type, obj] of this._CacheDrawObject) DisposeThreeObj(obj); this._CacheDrawObject.clear(); if (jig) this._CacheDrawObject.set(RenderType.Jig, jig); } get IsOnlyRender() { return this.OnlyRenderType; } get DrawObject() { if (this._drawObject) return this._drawObject; this._drawObject = new Object3D(); if (!this.IsEmbedEntity) this._drawObject.userData.Entity = this; if (this.IsVisible) { this._CurRenderType = userConfig.RenderType; let obj = this.GetDrawObjectFromRenderType(userConfig.RenderType); if (obj) this._drawObject.add(obj); } else this._drawObject.visible = false; return this._drawObject; } get JigObject() { let obj = this.GetDrawObjectFromRenderType(RenderType.Jig); if (obj && !this.IsEmbedEntity) obj.userData.Entity = this; return obj; } DestroyJigObject() { let obj = this._CacheDrawObject.get(RenderType.Jig); if (obj) { this._CacheDrawObject.delete(RenderType.Jig); DisposeThreeObj(obj); if (obj.parent) obj.parent.remove(obj); } } UpdateRenderType(type) { if (this._CurRenderType !== type || this.DrawObject.children.length === 0) { this._CurRenderType = type; if ((this.OnlyRenderType && this.DrawObject.children.length > 0) || !this._Visible) return; Object3DRemoveAll(this.DrawObject); let obj = this.GetDrawObjectFromRenderType(type); if (obj) this.DrawObject.add(obj); } } GetDrawObjectFromRenderType(renderType = RenderType.Wireframe) { if (this.OnlyRenderType) { if (renderType === RenderType.Jig) return; if (renderType < 100) renderType = RenderType.Wireframe; else renderType = RenderType.WireframePrint; } if (renderType === RenderType.Edge && !this.HasEdgeRenderType) renderType = RenderType.Conceptual; if (renderType === RenderType.PlaceFace && !this.HasPlaceFaceRenderType) renderType = RenderType.Wireframe; if (this._CacheDrawObject.has(renderType)) { return this._CacheDrawObject.get(renderType); } else { let drawObj = this.InitDrawObject(renderType); if (drawObj === undefined) { if (renderType > 100) //如果实体没有实现打印类型,那么就使用原先的实体的渲染类型 return this.GetDrawObjectFromRenderType(renderType - 100); return; } //矩阵直接使用指针,因为已经关闭自动更新,所以矩阵不会被Object3D修改. drawObj.matrixAutoUpdate = false; drawObj.matrix = this._Matrix; drawObj.updateMatrixWorld(true); drawObj.traverse(UpdateBoundingSphere); if (!this.IsEmbedEntity) drawObj.userData.Entity = this; this._CacheDrawObject.set(renderType, drawObj); return drawObj; } } /** * 初始化绘制的threejs实体,子类型重载该函数初始化绘制实体. */ InitDrawObject(renderType = RenderType.Wireframe) { return undefined; } /** * 当实体数据改变时,绘制的实体必须做出改变.供框架调用 */ Update(mode = UpdateDraw.All) { this.__UpdateVersion__++; this.NeedUpdateFlag |= mode; if (this.AutoUpdate) this.DeferUpdate(); } //三维实体总是一起生成线框实体和网格实体,这个通知更新,然后统一更新就好了 //避免重复更新 UpdateDrawGeometry() { } DeferUpdate() { let mode = this.NeedUpdateFlag; if (mode === 0) return; if (mode & UpdateDraw.Geometry && this._CacheDrawObject.size > 0) this.UpdateDrawGeometry(); this.UpdateVisible(); let isJigIng = this._CacheDrawObject.has(RenderType.Jig); for (let [type, obj] of this._CacheDrawObject) { if (isJigIng && type !== RenderType.Jig) continue; if (mode & UpdateDraw.Geometry) { if (obj.userData.IsClone) { let parent = obj.parent; DisposeThreeObj(obj); this._CacheDrawObject.delete(type); let newObj = this.GetDrawObjectFromRenderType(type); if (parent) { parent.remove(obj); parent.add(newObj); } obj = newObj; } else this.UpdateDrawObject(type, obj); } if (mode & UpdateDraw.Material) this.UpdateDrawObjectMaterial(type, obj); if (mode & UpdateDraw.Matrix || mode & UpdateDraw.Geometry) { obj.updateMatrixWorld(true); // if (this.Id)//如果这个是Jig实体,那么我们更新这个盒子球似乎也没有意义 (虽然这在某些情况能改进性能,但是在绘制圆弧的时候,因为没有更新圆弧的盒子,导致绘制出来的圆弧无法被选中) obj.traverse(UpdateBoundingSphere); } } this.NeedUpdateFlag = UpdateDraw.None; } /** * 当实体需要更新时,需要重载该方法,实现实体更新 */ UpdateDrawObject(type, en) { } /** * 当实体需要被更新时,更新实体材质 */ UpdateDrawObjectMaterial(type, obj, material) { } get MeshMaterial() { if (this.materialId && this.materialId.Object) return this.materialId.Object.Material; return HostApplicationServices.DefaultMeshMaterial; } /** * 更新实体Jig状态时的材质 */ UpdateJigMaterial(color = 8) { } RestoreJigMaterial() { for (let [type, en] of this._CacheDrawObject) this.UpdateDrawObjectMaterial(type, en); } get Visible() { return this._Visible; } set Visible(v) { if (v !== this._Visible) { this.WriteAllObjectRecord(); this._Visible = v; this.UpdateVisible(); } } get IsVisible() { return !this._isErase && this._Visible; } UpdateVisible() { if (this._drawObject) { this._drawObject.visible = this.IsVisible; if (this.IsVisible) this.UpdateRenderType(userConfig.RenderType); } } //#endregion GoodBye() { super.GoodBye(); if (this._drawObject && this._drawObject.parent) this._drawObject.parent.remove(this._drawObject); this.ClearDraw(); } Erase(isErase = true) { if (isErase === this._isErase) return; this.__UpdateVersion__++; super.Erase(isErase); this.UpdateVisible(); this.EraseEvent(isErase); } EraseEvent(isErase) { } /** * 使用统一的方法设置对象的矩阵. * 需要对缩放矩形进行重载.避免对象矩阵不是单位矩阵 */ ApplyMatrix(m) { this.WriteAllObjectRecord(); if (equaln(m.getMaxScaleOnAxis(), 1)) { let xA = new Vector3(); let yA = new Vector3(); let zA = new Vector3(); m.extractBasis(xA, yA, zA); this._Matrix.multiplyMatrices(m, this._Matrix); this._SpaceOCS.multiplyMatrices(m, this._SpaceOCS); if (!equalv3(xA.clone().cross(yA).normalize(), zA)) this.ApplyMirrorMatrix(m); else this.Update(UpdateDraw.Matrix); } else { this.ApplyScaleMatrix(m); } return this; } ApplyScaleMatrix(m) { return this; } ApplyMirrorMatrix(m) { return this; } GetGripPoints() { return []; } MoveGripPoints(indexList, vec) { } /** * * @param snapMode 捕捉模式(单一) * @param pickPoint const * @param lastPoint const * @param viewXform const 最近点捕捉需要这个变量 * @returns object snap points */ GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { return []; } GetStretchPoints() { return []; } /** * 拉伸夹点,用于Stretch命令 * * @param {Array} indexList 拉伸点索引列表. * @param {Vector3} vec 移动向量 * @memberof Entity */ MoveStretchPoints(indexList, vec) { } IntersectWith(curve, intType) { return; } //#region -------------------------File------------------------- Clone() { let ent = super.Clone(); ent._CurRenderType = this._CurRenderType; ent.Template = undefined; ent.CloneDrawObject(this); return ent; } CloneDrawObject(from) { for (let [type, obj] of from._CacheDrawObject) { let oldUserDaata = obj.userData; obj.traverse(o => o.userData = {}); let newObj = obj.clone(); obj.userData = oldUserDaata; obj.userData.IsClone = true; newObj.matrix = this._Matrix; newObj.userData = { Entity: this }; newObj.userData.IsClone = true; this._CacheDrawObject.set(type, newObj); } this.NeedUpdateFlag = UpdateDraw.None; } get ReadFileIng() { return this.__ReadFileIng__ || Entity_1.__ReadFileIng__; } /** * 从文件读取,序列化自身,如果需要,重载_ReadFile */ ReadFile(file) { this.__ReadFileIng__ = true; this._ReadFile(file); this.Update(); this.__ReadFileIng__ = false; } //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this._Color = file.Read(); this.materialId = file.ReadHardObjectId(); this._Matrix.fromArray(file.Read()); if (ver === 2) this.Owner = file.ReadObjectId(); if (ver > 3) this.Template = file.ReadObjectId(); if (ver > 4) this.GroupId = file.ReadHardObjectId(); if (ver > 5) this._Visible = file.Read(); if (ver > 6) this._SpaceOCS.fromArray(file.Read()); if (ver > 7) { let count = file.Read(); this.ProcessingGroupList.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadHardObjectId(); if (id) this.ProcessingGroupList.push(id); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(8); super.WriteFile(file); file.Write(this._Color); file.WriteHardObjectId(this.materialId); file.Write(this._Matrix.toArray()); file.WriteObjectId(this.Template); file.WriteHardObjectId(this.GroupId); file.Write(this._Visible); file.Write(this._SpaceOCS.toArray()); file.Write(this.ProcessingGroupList.length); for (let id of this.ProcessingGroupList) file.WriteHardObjectId(id); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); if (undoData instanceof EntityColorHistoryRecord) this.ColorIndex = undoData.color; } CopyFrom(obj) { let templateIdBak = this.Template; super.CopyFrom(obj); this.Update(); this.Template = templateIdBak; } }; __decorate([ AutoRecord ], Entity.prototype, "GroupId", void 0); __decorate([ AutoRecord ], Entity.prototype, "Template", void 0); __decorate([ AutoRecord ], Entity.prototype, "ProcessingGroupList", void 0); __decorate([ iaop ], Entity.prototype, "Update", null); __decorate([ iaop ], Entity.prototype, "EraseEvent", null); Entity = Entity_1 = __decorate([ Factory ], Entity); let EntityColorHistoryRecord = class EntityColorHistoryRecord { constructor(color) { this.color = color; } ReadFile(file) { this.color = file.Read(); return this; } WriteFile(file) { file.Write(this.color); return this; } }; EntityColorHistoryRecord = __decorate([ Factory ], EntityColorHistoryRecord); let OPERATORS = new Set(["+", "-", "*", "/"]); /** * eval2("+10", { L: 100 }, "L") * @param expr * @param [params] * @param [defaultParam] 当输入 +10 这样的表达式时,设置默认的操作变量 * @returns 计算结果 */ function eval2(expr, params, defaultParam) { let code = ""; if (params) for (let name in params) code += `let ${name} = ${params[name]};`; if (defaultParam) { expr = expr.trimLeft(); if (expr[0] && OPERATORS.has(expr[0])) expr = defaultParam + expr; } code += expr; let result = eval(code); if (typeof result === "function") return result(); return Number(result); //防止bigint乱入 } function safeEval(expr, params, defaultParam) { try { return eval2(expr, params); } catch (error) { return NaN; } } const Reg_Expr = /\{[^\}]+\}/g; /**解析大括号内的 */ function ParseExpr(expr, params) { let strs = expr.match(Reg_Expr); if (!strs) return expr; for (let str of strs) expr = expr.replace(str, FixedNotZero(safeEval(str.slice(1, -1), params), 2)); return expr; } /**扣除封边是否相连和连接共用精度 */ const LINK_FUZZ = 1e-3; function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function FixIndex$1(index, arr) { let count = (arr instanceof Array) ? arr.length : arr; if (index < 0) return count + index; else if (index >= count) return index - count; else return index; } //使用定点表示法来格式化一个数,小数点后面不尾随0. 如 FixedNotZero(1.1 , 3) => 1.1 function FixedNotZero(v, fractionDigits = 0) { if (typeof v === "string") v = parseFloat(v); if (isNaN(v)) return ""; if (Math.abs(v) < Math.pow(0.1, fractionDigits) * 0.49) return "0"; if (!fractionDigits) return v.toFixed(0); else return v.toFixed(fractionDigits).replace(/[.]?0+$/, ""); } /** * To fixed * @param v * @param [fractionDigits] * @returns */ function ToFixed(v, fractionDigits = 5) { if (equaln(v, 0, Math.pow(0.1, fractionDigits))) return "0"; return v.toFixed(fractionDigits); } const IdentityMtx4 = new Matrix4(); const ZeroVec = new Vector3(); const XAxis = new Vector3(1, 0, 0); new Vector3(-1, 0, 0); const YAxis = new Vector3(0, 1, 0); new Vector3(0, -1, 0); const ZAxis = new Vector3(0, 0, 1); new Vector3(0, 0, -1); function AsVector2(p) { return new Vector2(p.x, p.y); } function AsVector3(p) { return new Vector3(p.x, p.y, p.z); } /** * 判断一维线段a和b是否存在交集 */ function isIntersect(amin, amax, bmin, bmax, eps = 0) { return Math.max(amin, bmin) < Math.min(amax, bmax) + eps; } function isIntersect2(a1, a2, b1, b2, eps = 0) { if (a1 > a2) [a1, a2] = [a2, a1]; if (b1 > b2) [b1, b2] = [b2, b1]; return Math.max(a1, b1) < Math.min(a2, b2) + eps; } /** * 旋转一个点,旋转中心在原点 * @param {Vector3} p 点 * @param {number} a 角度. * @returns {Vector3} 返回pt不拷贝. */ function rotatePoint(p, a) { let s = Math.sin(a); let c = Math.cos(a); let x = p.x * c - p.y * s; let y = p.x * s + p.y * c; p.x = x; p.y = y; return p; } function equaln(v1, v2, fuzz = 1e-5) { return Math.abs(v1 - v2) <= fuzz; } function equalv3(v1, v2, fuzz = 1e-8) { return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz) && equaln(v1.z, v2.z, fuzz); } function equalv2(v1, v2, fuzz = 1e-8) { return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz); } /** * 按照极坐标的方式移动一个点 * * @export * @template * @param {T} v 向量(2d,3d) * @param {number} an 角度 * @param {number} dis 距离 * @returns {T} */ function polar(v, an, dis) { v.x += Math.cos(an) * dis; v.y += Math.sin(an) * dis; return v; } function angle(v) { let angle = Math.atan2(v.y, v.x); if (equaln(angle, 0, 1e-8)) return 0; if (angle < 0) angle += Math.PI * 2; return angle; } /** * 求两个向量的夹角,顺时针为负,逆时针为正 * * @param {Vector3} v1 * @param {Vector3} v2 * @param {Vector3} [ref] 参考向量,如果为世界坐标系则为0,0,1 * @returns */ function angleTo(v1, v2, ref = ZAxis) { if (v1.equals(ZeroVec) || v2.equals(ZeroVec)) return 0; v1 = v1.clone(); v2 = v2.clone(); if (ref !== ZAxis && !ref.equals(ZAxis)) { ref = ref.clone(); //任意轴坐标系. 使用相机的构造矩阵. ref.multiplyScalar(-1); let up = getLoocAtUpVec(ref); let refOcs = new Matrix4(); refOcs.lookAt(ZeroVec, ref, up); let refOcsInv = new Matrix4().getInverse(refOcs); v1.applyMatrix4(refOcsInv); v2.applyMatrix4(refOcsInv); v1.z = 0; v2.z = 0; } if (v1.equals(ZeroVec) || v2.equals(ZeroVec)) //修复,这里有可能被更改为0 return 0; //平行的向量返回0向量,不需要归一化 let cv = new Vector3().crossVectors(v1, v2); if (equalv3(cv, ZeroVec)) return 0; cv.normalize(); return equaln(cv.z, 0) ? v1.angleTo(v2) : v1.angleTo(v2) * cv.z; } function getLoocAtUpVec(dir) { if (dir.equals(ZeroVec)) { throw ("zero vector"); } let norm = dir.clone().normalize(); if (norm.equals(ZAxis)) { return new Vector3(0, 1, 0); } else if (norm.equals(ZAxis.clone().negate())) { return new Vector3(0, -1, 0); } else { let xv = new Vector3(); xv.crossVectors(ZAxis, norm); let up = new Vector3(); up.crossVectors(norm, xv); return up; } } /** * 判断2个向量是不是平行,尽量传入单位向量,才能保证计算精度 */ function isParallelTo(v1, v2, fuzz = 1e-8) { return v1.clone().cross(v2).lengthSq() < fuzz; } /** * 垂直向量 */ function isPerpendicularityTo(v1, v2, fuzz = 1e-8) { return equaln(v1.dot(v2), 0, fuzz); } let tempBox = new Box3(); /** * 获得Three对象的包围盒. * @param obj * @param [updateMatrix] 是否应该更新对象矩阵 * @returns box */ function GetBox(obj, updateMatrix = false) { let box = new Box3(); if (updateMatrix) obj.updateMatrixWorld(false); if (!obj.visible) return box; obj.traverseVisible(o => { //@ts-ignore let geo = o.geometry; if (geo) { if (!geo.boundingBox) geo.computeBoundingBox(); tempBox.copy(geo.boundingBox).applyMatrix4(o.matrixWorld); box.union(tempBox); } }); return box; } function MoveMatrix(v) { return new Matrix4().setPosition(v); } /** * 将角度调整为0-2pi之间 */ function clampRad(an) { an = an % (Math.PI * 2); if (an < 0) an += Math.PI * 2; return an; } function updateGeometry(l, geometry) { let geo = l.geometry; geo.dispose(); l.geometry = geometry; geometry.computeBoundingSphere(); } function UpdateBoundingSphere(obj) { //@ts-ignore let geo = obj.geometry; if (geo) { geo.computeBoundingSphere(); geo.computeBoundingBox(); //样条线更新位置和更新点位置后,无法被选中 } } const comparePointCache = new Map(); /** * 构建返回一个用来排序的函数.根据key创建排序规则. * * 当key = "xyz" 时,点集按 x从小到大,y从小到大 z从小到大 * key = "X" 时,点集按 x从大到小 * 以此类推. * * 例子: * let pts:Vector3[] =...; * pts.sort(comparePoint("x")); //x从小到大排序 * pts.sort(comparePoint("zX")); //z从小到大 x从大到小 * * @export * @param {string} sortKey * @returns {compareVectorFn} */ function comparePoint(sortKey) { if (comparePointCache.has(sortKey)) return comparePointCache.get(sortKey); let sortIndex = []; const keys = ['x', 'X', 'y', 'Y', 'z', 'Z']; for (let char of sortKey) { let index = keys.indexOf(char); let i2 = index / 2; let ci = Math.floor(i2); sortIndex.push([ci, i2 > ci ? 1 : -1]); } let compareFunction = (v1, v2) => { if (!v1) return -1; if (!v2) return 1; for (let s of sortIndex) { let vv1 = v1.getComponent(s[0]); let vv2 = v2.getComponent(s[0]); if (equaln(vv1, vv2)) continue; if (vv2 > vv1) return s[1]; else return -s[1]; } return 0; }; comparePointCache.set(sortKey, compareFunction); return compareFunction; } function SelectNearP(pts, refPt) { if (pts.length > 1) { let dist1 = refPt.distanceToSquared(pts[0]); let dist2 = refPt.distanceToSquared(pts[1]); return dist1 <= dist2 ? pts[0] : pts[1]; } return pts[0]; } /**n是否在AB之间,fuzz 若为负的,允许相等 */ function IsBetweenA2B(n, A, B, fuzz = -1e-8) { return n > A + fuzz && n < B - fuzz; } /** 矩阵是世界坐标系 */ function MatrixIsIdentityCS(mtx) { return mtx.elements[0] === 1 && mtx.elements[5] === 1 && mtx.elements[10] === 1 && mtx.elements[12] === 0 && mtx.elements[13] === 0 && mtx.elements[14] === 0; } /** * 设置矩阵的某列的向量 * @param {Matrix4} mat 矩阵 * @param {number} col 列索引,0x 1y 2z 3org * @param {Vector3} v 向量或点 */ function matrixSetVector(mat, col, v) { let index = col * 4; mat.elements[index] = v.x; mat.elements[index + 1] = v.y; mat.elements[index + 2] = v.z; } /** * 返回矩阵,该坐标系将坐标系与原点的坐标系映射为坐标系, * 并将坐标系与X轴坐标系, * Y轴坐标轴以及Z轴坐标系统之间的坐标系统坐标系统的原点坐标系和原点坐标系统坐标轴的坐标系分别设置为XAxis,YAxis和ZAxis * @returns {Matrix4} 返回新的矩阵 */ function matrixAlignCoordSys(matrixFrom, matrixTo) { return new Matrix4().getInverse(matrixTo).multiply(matrixFrom); } /** * 判断2个矩形共面 * @param {Matrix4} matrixFrom * @param {Matrix4} matrixTo * @returns {boolean} 2个矩阵共面 */ function matrixIsCoplane(matrixFrom, matrixTo, fuzz = 1e-5) { let nor1 = new Vector3().setFromMatrixColumn(matrixFrom, 2); let nor2 = new Vector3().setFromMatrixColumn(matrixTo, 2); //法线共面 if (!isParallelTo(nor1, nor2)) return false; //高共面 let pt = new Vector3().setFromMatrixPosition(matrixTo); //变换到自身对象坐标系. pt.applyMatrix4(new Matrix4().getInverse(matrixFrom)); return equaln(pt.z, 0, fuzz); } /** * 修正镜像后矩阵 */ function reviseMirrorMatrix(mtx, index = 1) { let cs = new CoordinateSystem().applyMatrix4(mtx); if (index === 0) cs.XAxis.negate(); else if (index === 1) cs.YAxis.negate(); else cs.ZAxis.negate(); mtx.copy(cs.getMatrix4()); return mtx; } let cacheVec; function Vector2ApplyMatrix4(mtx, vec) { if (!cacheVec) cacheVec = new Vector3(); cacheVec.x = vec.x; cacheVec.y = vec.y; cacheVec.z = 0; cacheVec.applyMatrix4(mtx); vec.x = cacheVec.x; vec.y = cacheVec.y; } function GetMirrorMat(v) { let mirrorMat = new Matrix4(); let xAxis = new Vector3(1 - 2 * v.x ** 2, -2 * v.x * v.y, -2 * v.x * v.z); let yAxis = new Vector3(-2 * v.x * v.y, 1 - 2 * v.y ** 2, -2 * v.y * v.z); let zAxis = new Vector3(-2 * v.x * v.z, -2 * v.y * v.z, 1 - 2 * v.z ** 2); mirrorMat.makeBasis(xAxis, yAxis, zAxis); return mirrorMat; } /** * 把变换矩阵展平成2d矩阵,避免出现三维坐标. */ function MatrixPlanarizere(mtx, z0 = true) { mtx.elements[2] = 0; mtx.elements[6] = 0; mtx.elements[8] = 0; mtx.elements[9] = 0; mtx.elements[10] = Math.sign(mtx.elements[10]); if (z0) mtx.elements[14] = 0; return mtx; } const tempMatrix1 = new Matrix4; const ZMirrorMatrix = GetMirrorMat(new Vector3(0, 0, 1)); /** * OSMODE */ var ObjectSnapMode; (function (ObjectSnapMode) { ObjectSnapMode[ObjectSnapMode["None"] = 0] = "None"; ObjectSnapMode[ObjectSnapMode["End"] = 1] = "End"; ObjectSnapMode[ObjectSnapMode["Mid"] = 2] = "Mid"; ObjectSnapMode[ObjectSnapMode["Cen"] = 4] = "Cen"; ObjectSnapMode[ObjectSnapMode["Node"] = 8] = "Node"; ObjectSnapMode[ObjectSnapMode["Qua"] = 16] = "Qua"; ObjectSnapMode[ObjectSnapMode["Int"] = 32] = "Int"; ObjectSnapMode[ObjectSnapMode["Ins"] = 64] = "Ins"; ObjectSnapMode[ObjectSnapMode["Per"] = 128] = "Per"; ObjectSnapMode[ObjectSnapMode["Tan"] = 256] = "Tan"; ObjectSnapMode[ObjectSnapMode["Nea"] = 512] = "Nea"; ObjectSnapMode[ObjectSnapMode["NotEntitySnap"] = 1024] = "NotEntitySnap"; ObjectSnapMode[ObjectSnapMode["App"] = 2048] = "App"; ObjectSnapMode[ObjectSnapMode["Ext"] = 4096] = "Ext"; ObjectSnapMode[ObjectSnapMode["Par"] = 8192] = "Par"; ObjectSnapMode[ObjectSnapMode["Axis"] = 16384] = "Axis"; ObjectSnapMode[ObjectSnapMode["All"] = 31743] = "All"; })(ObjectSnapMode || (ObjectSnapMode = {})); let tempArc; class Shape2 extends Shape$1 { getPoints(divisions = 12, optimizeArc = true) { let points = [], last; for (let i = 0, curves = this.curves; i < curves.length; i++) { let curve = curves[i]; let resolution = divisions; //@ts-ignore if (curve && curve.isEllipseCurve) { if (optimizeArc) { if (!tempArc) tempArc = new Arc; tempArc.IsClockWise = curve.aClockwise; tempArc.StartAngle = curve.aStartAngle; tempArc.EndAngle = curve.aEndAngle; tempArc.Radius = Math.abs(curve.xRadius); //根据圆弧的角度,来确定绘制个数 let count = Math.max(2, Math.abs(Math.ceil((tempArc.AllAngle) / Math.PI)) * divisions); resolution = clamp(Math.ceil(tempArc.Length / 20), count, 60); } else resolution = divisions * 2; } else { //@ts-ignore resolution = (curve && (curve.isLineCurve || curve.isLineCurve3)) ? 1 //@ts-ignore : (curve && curve.isSplineCurve) ? divisions * curve.points.length : divisions; } let pts = curve.getPoints(resolution); for (let j = 0; j < pts.length; j++) { let point = pts[j]; if (last && equalv2(last, point, 1e-4)) continue; // ensures no consecutive points are duplicates points.push(point); last = point; if (j === pts.length - 1) point["_mask_"] = true; } } if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) { points.push(points[0]); } return points; } absellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) { let curve = new EllipseCurve(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation); /* if (this.curves.length > 0) { // if a previous curve is present, attempt to join let firstPoint = curve.getPoint(0); if (!equalv2(firstPoint, this.currentPoint)) { this.lineTo(firstPoint.x, firstPoint.y); } } */ this.curves.push(curve); let lastPoint = curve.getPoint(1); this.currentPoint.copy(lastPoint); return this; } } class Matrix2 { constructor() { //column-major this.el = [1, 0, 0, 1]; //ix iy jx jy [a c b d] } set(ix, iy, jx, jy) { this.el[0] = ix; this.el[1] = iy; this.el[2] = jx; this.el[3] = jy; } applyVector(vec) { let x = vec.x; let y = vec.y; let e = this.el; vec.x = e[0] * x + e[2] * y; vec.y = e[1] * x + e[3] * y; return this; } fromMatrix4(mtx4) { this.set(mtx4.elements[0], mtx4.elements[1], mtx4.elements[3], mtx4.elements[4]); } setRotate(theta) { let c = Math.cos(theta); let s = Math.sin(theta); this.set(c, s, -s, c); return this; } //自我求逆矩阵,返回自身 invert() { //ref:https://www.mathsisfun.com/algebra/matrix-inverse.html let [a, c, b, d] = this.el; let det = 1 / (a * d - b * c); this.set(d * det, -c * det, -b * det, a * det); return this; } } let r = new Matrix2(); function RotateUVs(geo) { r.set(0, -1, 1, 0); for (let uvs of geo.faceVertexUvs) { for (let uv of uvs) { for (let v of uv) r.applyVector(v); } } geo.uvsNeedUpdate = true; } var CreateBoardUtil; (function (CreateBoardUtil) { //解析二维圆弧 class Arc2d { constructor(p1, p2, bul) { p1 = p1.clone(); p2 = p2.clone(); //a (* 2 (atan b)) let a = Math.atan(bul) * 2; //r (/ (distance p1 p2) 2 (sin a)) let r = p1.distanceTo(p2) / 2 / Math.sin(a); //c (polar p1 (+ (- (/ pi 2) a) (angle p1 p2)) r) let c = polar(p1.clone(), Math.PI / 2 - a + angle(p2.clone().sub(p1)), r); this._Radius = Math.abs(r); this._StartAn = angle(p1.sub(c)); this._EndAn = angle(p2.sub(c)); this._Center = c; } } CreateBoardUtil.Arc2d = Arc2d; //创建轮廓 通过点表和凸度 function CreatePath(pts, buls) { let shape = new Shape2(); if (pts.length === 0) return shape; let firstPt = pts[0]; shape.moveTo(firstPt.x, firstPt.y); for (let i = 0; i < pts.length - 1; i++) { let prePt = pts[i]; let nextPt = pts[i + 1]; if (equaln(buls[i], 0, 1e-8) || equalv2(prePt, nextPt, 1e-2)) { shape.lineTo(nextPt.x, nextPt.y); } else { //参考 //http://www.dorodnic.com/blog/tag/three-js/ 绘制一个齿轮 //https://www.kirupa.com/html5/drawing_circles_canvas.htm //html5 let arc2 = new Arc2d(prePt, nextPt, buls[i]); let cen = arc2._Center; shape.absarc(cen.x, cen.y, arc2._Radius, arc2._StartAn, arc2._EndAn, buls[i] < 0); } } return shape; } CreateBoardUtil.CreatePath = CreatePath; //创建板件 暂时这么写 function createBoard(boardData) { var pts = new Array(); var buls = new Array(); var boardPts = boardData["Pts"]; var boardBuls = boardData["Buls"]; let boardHeight = boardData["H"]; var boardMat = new Matrix4(); var matInv = new Matrix4(); //InitBoardMat { var xD = AsVector3(boardData["XVec"]); var yD = AsVector3(boardData["YVec"]); var ZD = AsVector3(boardData["ZVec"]); var pBase = AsVector3(boardData["BasePoint"]).multiplyScalar(0.001); boardMat.makeBasis(xD, yD, ZD); boardMat.setPosition(pBase); matInv.getInverse(boardMat); } for (let i = 0; i < boardPts.length; i++) { var pt = AsVector3(boardPts[i]).multiplyScalar(0.001); pt.applyMatrix4(matInv); pts.push(new Vector2(pt.x, pt.y)); buls.push(boardBuls[i]); } var sp = CreatePath(pts, buls); var extrudeSettings = { steps: 1, bevelEnabled: false, amount: boardHeight * 0.001 }; var ext = new ExtrudeGeometry(sp, extrudeSettings); ext.translate(0, 0, -boardHeight * 0.001); ext.applyMatrix4(boardMat); if (boardData["BoardName"] === "地脚线") { RotateUVs(ext); } var mesh = new Mesh(ext); return mesh; } CreateBoardUtil.createBoard = createBoard; })(CreateBoardUtil || (CreateBoardUtil = {})); const BufferGeometry2GeometryCacheMap = new WeakMap(); globalThis.fuck = BufferGeometry2GeometryCacheMap; var BufferGeometryUtils; (function (BufferGeometryUtils) { function CreateFromPts(pts) { return new BufferGeometry().setFromPoints(pts); } BufferGeometryUtils.CreateFromPts = CreateFromPts; /** * 更新BufferGeometry的顶点 * @param geo * @param pts * @param ignoreBoxError 忽略更新点后盒子错误的问题 * @returns 当成功时返回true,更新失败时返回false */ function UpdatePts(geo, pts, ignoreBoxError = false) { let bf = geo.getAttribute("position"); if (bf === undefined) geo.setFromPoints(pts); else if (bf.count === pts.length || (ignoreBoxError && bf.count > pts.length)) //现在我们只有等于的时候才更新,因为如果不是这样,那么计算盒子的时候会出错(因为盒子内部的代码用的是所有的顶点) { bf.copyVector3sArray(pts); bf.needsUpdate = true; geo.drawRange.count = pts.length; } else return false; BufferGeometry2GeometryCacheMap.delete(geo); return true; } BufferGeometryUtils.UpdatePts = UpdatePts; let arrowGeometry; function ArrowGeometry() { if (arrowGeometry) return arrowGeometry; else { let arrowShape = new Shape$1(); arrowShape.lineTo(-0.5, -1.8); arrowShape.lineTo(0.5, -1.8); arrowGeometry = new ShapeGeometry(arrowShape); arrowGeometry.computeBoundingBox(); return arrowGeometry; } } BufferGeometryUtils.ArrowGeometry = ArrowGeometry; function MergeBufferGeometries(geometries, useGroups = false) { if (geometries.length === 0) return new BufferGeometry(); let isIndexed = geometries[0].index !== null; let attributesUsed = new Set(Object.keys(geometries[0].attributes)); let morphAttributesUsed = new Set(Object.keys(geometries[0].morphAttributes)); let attributes = {}; let morphAttributes = {}; let morphTargetsRelative = geometries[0].morphTargetsRelative; let mergedGeometry = new BufferGeometry(); let offset = 0; for (let i = 0; i < geometries.length; ++i) { let geometry = geometries[i]; // ensure that all geometries are indexed, or none if (isIndexed !== (geometry.index !== null)) return null; // gather attributes, exit early if they're different for (let name in geometry.attributes) { if (!attributesUsed.has(name)) continue; if (attributes[name] === undefined) attributes[name] = []; attributes[name].push(geometry.attributes[name]); } // gather morph attributes, exit early if they're different if (morphTargetsRelative !== geometry.morphTargetsRelative) return null; for (let name in geometry.morphAttributes) { if (!morphAttributesUsed.has(name)) continue; if (morphAttributes[name] === undefined) morphAttributes[name] = []; morphAttributes[name].push(geometry.morphAttributes[name]); } // gather .userData mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || []; mergedGeometry.userData.mergedUserData.push(geometry.userData); if (useGroups) { let count; if (isIndexed) { count = geometry.index.count; } else if (geometry.attributes.position !== undefined) { count = geometry.attributes.position.count; } else { return null; } mergedGeometry.addGroup(offset, count, i); offset += count; } } // merge indices if (isIndexed) { let indexOffset = 0; let mergedIndex = []; for (let i = 0; i < geometries.length; ++i) { let index = geometries[i].index; for (let j = 0; j < index.count; ++j) { mergedIndex.push(index.getX(j) + indexOffset); } indexOffset += geometries[i].attributes.position.count; } mergedGeometry.setIndex(mergedIndex); } // merge attributes for (let name in attributes) { let mergedAttribute = MergeBufferAttributes(attributes[name]); if (!mergedAttribute) return null; mergedGeometry.setAttribute(name, mergedAttribute); } // merge morph attributes for (let name in morphAttributes) { let numMorphTargets = morphAttributes[name][0].length; if (numMorphTargets === 0) break; mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}; mergedGeometry.morphAttributes[name] = []; for (let i = 0; i < numMorphTargets; ++i) { let morphAttributesToMerge = []; for (let j = 0; j < morphAttributes[name].length; ++j) { morphAttributesToMerge.push(morphAttributes[name][j][i]); } let mergedMorphAttribute = MergeBufferAttributes(morphAttributesToMerge); if (!mergedMorphAttribute) return null; mergedGeometry.morphAttributes[name].push(mergedMorphAttribute); } } return mergedGeometry; } BufferGeometryUtils.MergeBufferGeometries = MergeBufferGeometries; function MergeBufferAttributes(attributes) { let TypedArray; let itemSize; let normalized; let arrayLength = 0; for (let i = 0; i < attributes.length; ++i) { let attribute = attributes[i]; if (TypedArray === undefined) TypedArray = attribute.array.constructor; if (TypedArray !== attribute.array.constructor) return null; if (itemSize === undefined) itemSize = attribute.itemSize; if (itemSize !== attribute.itemSize) return null; if (normalized === undefined) normalized = attribute.normalized; if (normalized !== attribute.normalized) return null; arrayLength += attribute.array.length; } let array = new TypedArray(arrayLength); let offset = 0; for (let i = 0; i < attributes.length; ++i) { array.set(attributes[i].array, offset); offset += attributes[i].array.length; } return new BufferAttribute(array, itemSize, normalized); } BufferGeometryUtils.MergeBufferAttributes = MergeBufferAttributes; })(BufferGeometryUtils || (BufferGeometryUtils = {})); var ExtendType; (function (ExtendType) { /** * 前后都不延伸 */ ExtendType[ExtendType["None"] = 0] = "None"; /** * 只允许延伸前面 */ ExtendType[ExtendType["Front"] = 1] = "Front"; /** * 只允许延伸后面 */ ExtendType[ExtendType["Back"] = 2] = "Back"; /** * 前后延伸 */ ExtendType[ExtendType["Both"] = 3] = "Both"; })(ExtendType || (ExtendType = {})); /** * 曲线的基类,子类请实现以下方法. */ let Curve = class Curve extends Entity { constructor() { super(); //------------------绘制相关------------------ //重载 this.OnlyRenderType = true; } get Is2D() { return equaln(this._Matrix.elements[14], 0); } get StartPoint() { return; } set StartPoint(v) { return; } get StartParam() { return; } get EndPoint() { return; } set EndPoint(v) { return; } /** 曲线中点 */ get Midpoint() { return this.GetPointAtParam(this.MidParam); } get MidParam() { if (this.EndParam === 1) return 0.5; else return this.GetParamAtDist(this.Length * 0.5); } get EndParam() { return; } get Area() { return 0; } /** *获得曲线的面积,逆时针为正,顺时针为负. */ get Area2() { return 0; } get Length() { return 0; } get IsClose() { return false; } /** 曲线为顺时针 */ get IsClockWise() { return this.Area2 < 0; } GetPointAtParam(param) { return; } GetPointAtDistance(distance) { return; } GetDistAtParam(param) { return; } GetDistAtPoint(pt) { return; } GetParamAtPoint(pt) { return; } GetParamAtPoint2(pt) { return this.GetParamAtPoint(pt); } GetParamAtDist(d) { return; } /** * 返回曲线在指定位置的一阶导数(在wcs内) * @param {(number | Vector3)} param */ GetFistDeriv(param) { return; } GetFistDerivAngle(param) { let d = this.GetFistDeriv(param); return Math.atan2(d.y, d.x); } /** * 返回切割曲线后的结果.总是从起点开始切割,并且按顺序返回曲线. * @param {(number[] | number)} param */ GetSplitCurves(param) { return; } //未完善 GetCurveAtParamRange(startParam, EndParam) { return; } GetSplitCurvesByPts(pt) { let pts = Array.isArray(pt) ? pt : [pt]; let pars = pts.map(p => this.GetParamAtPoint(p)); return this.GetSplitCurves(pars); } SplitParamSort(param) { if (Array.isArray(param)) { param = param.filter(p => this.ParamOnCurve(p)); if (param.length === 0) return []; param.push(0, this.EndParam); arraySortByNumber(param); arrayRemoveDuplicateBySort(param, (e1, e2) => equaln(e1, e2, 1e-7)); return param; } else if (this.ParamOnCurve(param)) return [0, param, this.EndParam]; else return []; } Extend(newParam) { } /** * 连接曲线到本曲线,如果成功返回true * @param {Curve} cu 需要连接的曲线 * @returns {boolean} 连接成功 * @memberof Curve */ Join(cu, allowGap = false, tolerance = 1e-4) { return Status.False; } //翻转曲线.首尾调换. Reverse() { return this; } //点在曲线上 PtOnCurve(pt, fuzz = 1e-6) { return equalv3(this.StartPoint, pt, fuzz) || equalv3(this.EndPoint, pt, fuzz) || this.ParamOnCurve(this.GetParamAtPoint(pt)); } //点在曲线中,不在起点或者终点. PtOnCurve2(pt) { return !(equalv3(this.StartPoint, pt, 1e-6) || equalv3(this.EndPoint, pt, 1e-6)) && this.ParamOnCurve(this.GetParamAtPoint(pt), 0); } //点在曲线上,已经确定点在曲线的延伸线上 PtOnCurve3(p, fuzz = 1e-6) { return this.PtOnCurve(p, fuzz); } //参数在曲线上 容差,1e-6 ParamOnCurve(param, fuzz = 1e-6) { return !isNaN(param) && param >= -fuzz && param <= this.EndParam + fuzz; } GetOffsetCurves(offsetDist) { return; } GetClosestPointTo(pt, extend) { return; } /** * 曲线相交点 */ IntersectWith(curve, intType, tolerance = 1e-6) { return this.IntersectWith2(curve, intType, tolerance).map(r => r.pt); } /** * 曲线相交点和点的参数 */ IntersectWith2(curve, intType, tolerance = 1e-6) { return []; } /** * 拽托点个数 */ GetDragPointCount(drag) { return 0; } //样条线重载了这个,得到了更高的绘制精度 GetDrawCount() { return 30; } /** * 重载: 初始化绘制实体. * @param {RenderType} [renderType=RenderType.Wireframe] */ InitDrawObject(renderType = RenderType.Wireframe) { let pts = this.Shape.getPoints(this.GetDrawCount()); if (renderType === RenderType.WireframePrint) { let array = []; for (let p of pts) array.push(p.x, p.y, 0); let geometry = new LineGeometry().setPositions(array); return new Line2(geometry, ColorMaterial.PrintLineMatrial); } let geo = new BufferGeometry().setFromPoints(pts); return new Line$1(geo, ColorMaterial.GetLineMaterial(this._Color)); } /** * 重载:更新绘制的实体 * @param {RenderType} type * @param {Object3D} obj */ UpdateDrawObject(type, obj) { let pts = this.Shape.getPoints(this.GetDrawCount()); let plObj = obj; let geo = plObj.geometry; if (geo instanceof LineGeometry) { let array = []; for (let p of pts) array.push(p.x, p.y, 0); geo.setPositions(array); } else { //@ts-ignore for (let p of pts) p.z = 0; if (!BufferGeometryUtils.UpdatePts(geo, pts)) updateGeometry(plObj, new BufferGeometry().setFromPoints(pts)); } } /** * 重载:更新实体材质 */ UpdateDrawObjectMaterial(type, obj, material) { if (type === RenderType.WireframePrint); else { let m = obj; m.material = material || ColorMaterial.GetLineMaterial(this._Color); } } UpdateJigMaterial(color = 8) { for (let [type, obj] of this._CacheDrawObject) { this.UpdateDrawObjectMaterial(type, obj, ColorMaterial.GetLineMaterial(color)); } } }; Curve = __decorate([ Factory ], Curve); class PlaneExt extends Plane$1 { constructor(normal = new Vector3(0, 0, 1), constant) { super(normal); if (typeof constant === "number") this.constant = constant; else if (constant) this.constant = -this.normal.dot(constant); } intersectLine(line, optionalTarget = new Vector3(), extendLine = false) { let v1 = new Vector3(); let direction = line.delta(v1); let denominator = this.normal.dot(direction); if (denominator === 0) { // line is coplanar, return origin if (this.distanceToPoint(line.start) === 0) { return optionalTarget.copy(line.start); } // Unsure if this is the correct method to handle this case. return undefined; } let t = -(line.start.dot(this.normal) + this.constant) / denominator; //If you not extendLine,check intersect point in Line if (!extendLine && (t < -1e-6 || t > 1)) { return undefined; } return optionalTarget.copy(direction).multiplyScalar(t).add(line.start); } intersectRay(ray, optionalTarget, extendLine) { // 从射线初始位置 let line = new Line3(ray.origin.clone(), ray.origin.clone().add(ray.direction)); return this.intersectLine(line, optionalTarget, extendLine); } } var Line_1; let Line = Line_1 = class Line extends Curve { constructor(_StartPoint = new Vector3, _EndPoint = new Vector3) { super(); this._StartPoint = _StartPoint; this._EndPoint = _EndPoint; } get Is2D() { return super.Is2D && equaln(this._StartPoint.z, 0) && equaln(this._EndPoint.z, 0); } get Shape() { return new Shape$1([AsVector2(this._StartPoint), AsVector2(this._EndPoint)]); } Z0() { this.WriteAllObjectRecord(); let ocsInv = this.OCSInv; let sp = this.StartPoint.setZ(0).applyMatrix4(ocsInv); let ep = this.EndPoint.setZ(0).applyMatrix4(ocsInv); this._StartPoint.copy(sp); this._EndPoint.copy(ep); this.Update(); return this; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); this.StartPoint = this.StartPoint.applyMatrix4(m); this.EndPoint = this.EndPoint.applyMatrix4(m); return this; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let sp = this.StartPoint; let ep = this.EndPoint; reviseMirrorMatrix(this._Matrix); this.StartPoint = sp; this.EndPoint = ep; return this; } InitDrawObject(renderType = RenderType.Wireframe) { let pts = [this._StartPoint, this._EndPoint]; if (renderType === RenderType.WireframePrint) { let array = []; for (let p of pts) array.push(p.x, p.y, p.z); let geometry = new LineGeometry().setPositions(array); return new Line2(geometry, ColorMaterial.PrintLineMatrial); } let geo = new BufferGeometry().setFromPoints(pts); return new Line$1(geo, ColorMaterial.GetLineMaterial(this._Color)); } /** * 重载:更新绘制的实体 * @param {RenderType} type * @param {Object3D} obj */ UpdateDrawObject(type, obj) { let pts = [this._StartPoint, this._EndPoint]; let plObj = obj; let geo = plObj.geometry; if (geo instanceof LineGeometry) { let array = []; for (let p of pts) array.push(p.x, p.y, p.z); geo.setPositions(array); } else { if (!BufferGeometryUtils.UpdatePts(geo, pts)) updateGeometry(plObj, new BufferGeometry().setFromPoints(pts)); } } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return [this.StartPoint, this.EndPoint]; case ObjectSnapMode.Mid: return [this.GetPointAtParam(0.5)]; case ObjectSnapMode.Nea: { let derv = this.GetFistDeriv(0).normalize(); let viewNormal = new Vector3().fromArray(viewXform.elements, 2 * 3); //平行不捕捉 if (isParallelTo(viewNormal, derv)) return []; let fNormal = new Vector3().crossVectors(viewNormal, derv); fNormal.crossVectors(derv, fNormal); let plane = new PlaneExt(fNormal, this.StartPoint); let plocal = plane.intersectLine(new Line3(pickPoint, pickPoint.clone().add(viewNormal)), new Vector3(), true); let pclosest = this.GetClosestPointTo(plocal, false); return [pclosest]; } case ObjectSnapMode.Ext: return [this.GetClosestPointTo(pickPoint, true)]; case ObjectSnapMode.Per: if (lastPoint) { let { closestPt, param } = this.GetClosestAtPoint(lastPoint, true); if (this.ParamOnCurve(param)) return [closestPt]; } } return []; } GetGripPoints() { return [this.StartPoint, this.GetPointAtParam(0.5), this.EndPoint]; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); for (let index of indexList) { if (index === 0) this.StartPoint = this.StartPoint.add(vec); else if (index === 2) this.EndPoint = this.EndPoint.add(vec); else { let m = MoveMatrix(vec); this.ApplyMatrix(m); } } } GetStretchPoints() { return [this.StartPoint, this.EndPoint]; } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); for (let index of indexList) { if (index === 0) this.StartPoint = this.StartPoint.add(vec); else this.EndPoint = this.EndPoint.add(vec); } } GetFistDeriv(param) { return this.EndPoint.sub(this.StartPoint); } IntersectWith2(curve, intType, tolerance = 1e-4) { if (curve instanceof Line_1) { return IntersectLineAndLine(this, curve, intType, tolerance); } if (curve instanceof Arc) { return IntersectLineAndArc(this, curve, intType, tolerance); } if (curve instanceof Circle) { return IntersectLineAndCircle(this, curve, intType, tolerance); } if (curve instanceof Polyline) { return SwapParam(IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType), tolerance)); } if (curve instanceof Ellipse) return IntersectEllipseAndLine(this, curve, intType, tolerance); //其他的尚未实现. return []; } //Param GetPointAtParam(param) { return this.StartPoint.add(this.GetFistDeriv(0).multiplyScalar(param)); } GetParamAtPoint(pt) { let { closestPt, param } = this.GetClosestAtPoint(pt, true); if (!equalv3(closestPt, pt, 1e-5)) return NaN; return param; } GetParamAtDist(d) { return d / this.Length; } GetPointAtDistance(distance) { return this.GetPointAtParam(this.GetParamAtDist(distance)); } GetDistAtParam(param) { return this.Length * param; } GetDistAtPoint(pt) { return this.GetDistAtParam(this.GetParamAtPoint(pt)); } GetSplitCurves(param) { let params = this.SplitParamSort(param); let pts = params.map(param => this.GetPointAtParam(param)); let ret = new Array(); if (pts.length >= 2) { for (let i = 0; i < pts.length - 1; i++) { let newLine = this.Clone(); newLine.ColorIndex = this.ColorIndex; newLine.StartPoint = pts[i]; newLine.EndPoint = pts[i + 1]; ret.push(newLine); } } return ret; } GetParamAtPoint2(pt) { let { param } = this.GetClosestAtPoint(pt, true); return param; } //点在曲线上,已经确定点在曲线的延伸线上 PtOnCurve3(p, fuzz = 1e-6) { let { param } = this.GetClosestAtPoint(p, true); return this.ParamOnCurve(param, fuzz); } GetClosestAtPoint(pt, extend) { let sp = this.StartPoint; let ep = this.EndPoint; if (equalv3(pt, sp, 1e-8)) return { closestPt: sp, param: 0 }; else if (equalv3(pt, ep, 1e-8)) return { closestPt: ep, param: 1 }; let direction = this.GetFistDeriv(0); let length = direction.length(); if (length === 0) { let param = NaN; if (equalv3(pt, this.StartPoint, 1e-6)) param = 0; return { closestPt: sp, param: param }; } direction.divideScalar(length); let diff = pt.clone().sub(sp); let param = direction.dot(diff); let closestPt; if (extend) closestPt = sp.add(direction.multiplyScalar(param)); else if (param < 0) { closestPt = sp; param = 0; } else if (param > length) { closestPt = this.EndPoint; param = length; } else closestPt = sp.add(direction.multiplyScalar(param)); return { closestPt: closestPt, param: param / length }; } GetClosestPointTo(pt, extend) { return this.GetClosestAtPoint(pt, extend).closestPt; } Extend(newParam) { this.WriteAllObjectRecord(); if (newParam < this.StartParam) { this.StartPoint = this.GetPointAtParam(newParam); } else if (newParam > this.EndParam) { this.EndPoint = this.GetPointAtParam(newParam); } } Join(cu, allowGap = false, tolerance = 1e-5) { if (cu instanceof Line_1) { //平行 if (!isParallelTo(this.GetFistDeriv(0).normalize(), cu.GetFistDeriv(0).normalize())) return Status.False; let sp = cu.StartPoint; let { closestPt: cp1, param: param1 } = this.GetClosestAtPoint(sp, true); if (!equalv3(sp, cp1, tolerance)) //点在曲线上,允许较低的精度 return Status.False; let ep = cu.EndPoint; let { closestPt: cp2, param: param2 } = this.GetClosestAtPoint(ep, true); if (!equalv3(ep, cp2, tolerance)) return Status.False; if (param1 > param2) { [param1, param2] = [param2, param1]; [sp, ep] = [ep, sp]; } if (allowGap || Math.max(0, param1) < Math.min(1, param2) + tolerance / this.Length) //这里的容差是值容差,但是我们用它来判断参数,所以进行转换 { if (param1 < 0) this.StartPoint = sp; if (param2 > 1) this.EndPoint = ep; return Status.True; } } return Status.False; } Reverse() { this.WriteAllObjectRecord(); [this._StartPoint, this._EndPoint] = [this._EndPoint, this._StartPoint]; return this; } GetOffsetCurves(offsetDist) { let derv = this.GetFistDeriv(0).normalize().multiplyScalar(offsetDist); derv.applyMatrix4(new Matrix4().makeRotationAxis(this.Normal, -Math.PI / 2)); let newLine = this.Clone(); newLine.StartPoint = this.StartPoint.add(derv); newLine.EndPoint = this.EndPoint.add(derv); return [newLine]; } get BoundingBox() { return new Box3().setFromPoints([this.StartPoint, this.EndPoint]); } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { return new Box3Ext().setFromPoints([this._StartPoint, this._EndPoint]); } get StartParam() { return 0; } get EndParam() { return 1; } //属性 get Length() { return this._StartPoint.distanceTo(this._EndPoint); } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); file.Read(); //1 this._StartPoint.fromArray(file.Read()); this._EndPoint.fromArray(file.Read()); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(1); //ver file.Write(this._StartPoint.toArray()); file.Write(this._EndPoint.toArray()); } //#endregion-----------------------------File End----------------------------- //#region 属性 set StartPoint(p) { this.WriteAllObjectRecord(); this._StartPoint.copy(p).applyMatrix4(this.OCSInv); this.Update(); } get StartPoint() { return this._StartPoint.clone().applyMatrix4(this.OCSNoClone); } get EndPoint() { return this._EndPoint.clone().applyMatrix4(this.OCSNoClone); } set EndPoint(p) { this.WriteAllObjectRecord(); this._EndPoint.copy(p).applyMatrix4(this.OCSInv); this.Update(); } }; Line = Line_1 = __decorate([ Factory ], Line); var Ellipse_1; let Ellipse = Ellipse_1 = class Ellipse extends Curve { constructor(center, radX = 1e-3, radY = 1e-3, angle = 0) { super(); this._startAngle = 0; this._endAngle = Math.PI * 2; center && this._Matrix.setPosition(center); this._radX = radX; this._radY = radY; this._rotate = angle; } get StartParam() { return 0; } get EndParam() { return 1; } get StartPoint() { return this.GetPointAtParam(0); } get EndPoint() { return this.GetPointAtParam(1); } get Shape() { let sp = new Shape$1(); sp.ellipse(0, 0, this._radX, this._radY, this._startAngle, this._endAngle, false, this._rotate); return sp; } get IsClose() { return equaln(this.TotalAngle, Math.PI * 2); } get Center() { return new Vector3().setFromMatrixPosition(this._Matrix); } set Center(v) { this.WriteAllObjectRecord(); this._Matrix.setPosition(v); this.Update(); } get RadX() { return this._radX; } set RadX(v) { this.WriteAllObjectRecord(); this._radX = v; this.Update(); } get RadY() { return this._radY; } set RadY(v) { this.WriteAllObjectRecord(); this._radY = v; this.Update(); } get Rotation() { return this._rotate; } set Rotation(v) { this.WriteAllObjectRecord(); this._rotate = v; this.Update(); } get StartAngle() { return this._startAngle; } get EndAngle() { return this._startAngle; } set StartAngle(v) { this.WriteAllObjectRecord(); this._startAngle = v; this.Update(); } set EndAngle(v) { this.WriteAllObjectRecord(); this._endAngle = v; this.Update(); } get Length() { let a = this._radX; let b = this._radY; return Math.PI * Math.abs(3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b))) * this.TotalAngle / Math.PI * 0.5; } get Area() { let area = Math.PI * this._radX * this._radY; let an = this._endAngle - this._startAngle; if (an < 0) an = Math.PI * 2 + an; area *= an / Math.PI * 0.5; let area2 = Math.abs(getDeterminantFor2V(AsVector2(this.StartPoint.sub(this.Center)), AsVector2(this.EndPoint.sub(this.Center)))) / 2; if (an < Math.PI) area -= area2; else area += area2; return area; } get TotalAngle() { let totolAngle = this._endAngle - this._startAngle; if (totolAngle < 0) totolAngle = Math.PI * 2 + totolAngle; return totolAngle; } PtInCurve(pt) { let p = rotatePoint(pt.clone().sub(this.Center), -this.Rotation); return p.x ** 2 / this.RadX ** 2 + p.y ** 2 / this.RadY ** 2 < 1; } PtOnCurve(pt) { if (this.PtOnEllipse(pt)) { let a = this.GetCircleAngleAtPoint(pt); return a <= this.TotalAngle + 1e-6; } return false; } PtOnEllipse(pt) { let p = rotatePoint(pt.clone().applyMatrix4(this.OCSInv), -this.Rotation); return equaln(p.x ** 2 / this.RadX ** 2 + p.y ** 2 / this.RadY ** 2, 1, 1e-3); } GetPointAtParam(param) { let an = this.TotalAngle * param + this._startAngle; if (an > Math.PI) an -= 2 * Math.PI; let a = this.RadX; let b = this.RadY; let pt = new Vector3(a * Math.cos(an), b * Math.sin(an), 0); pt.applyMatrix4(new Matrix4().makeRotationZ(this._rotate)); return pt.applyMatrix4(this.OCS); } GetParamAtPoint(pt) { if (!this.PtOnEllipse(pt)) { return NaN; } let an = this.GetCircleAngleAtPoint(pt); let par = an / this.TotalAngle; if (this.IsClose || par < 1 + 1e-6) return par; else { let diffPar = Math.PI * 2 / this.TotalAngle - 1; if (par - 1 < diffPar / 2) return par; else return par - 1 - diffPar; } } GetPointAtDistance(distance) { let param = distance / this.Length; return this.GetPointAtParam(param); } GetDistAtParam(param) { return this.Length * param; } GetDistAtPoint(pt) { let param = this.GetParamAtPoint(pt); return this.GetDistAtParam(param); } GetParamAtDist(d) { return d / this.Length; } GetAngleAtParam(par) { let pt = this.GetPointAtParam(par).applyMatrix4(this.OCSInv).applyMatrix4(new Matrix4().makeRotationZ(-this.Rotation)); return angle(pt) + this._startAngle; } GetCircleAngleAtPoint(pt) { pt = pt.clone().applyMatrix4(this.OCSInv); let an = angle(pt) - this._rotate; if (an < 0) an = Math.PI * 2 - an; if (an > Math.PI * 2) an -= Math.PI * 2; let dist = pt.length(); let k = dist * Math.cos(an) / this._radX; if (Math.abs(k) > 1) k = Math.floor(Math.abs(k)) * Math.sign(k); if (Math.abs(an) <= Math.PI) an = Math.acos(k); else an = Math.PI * 2 - Math.acos(k); an -= this._startAngle; if (an < 0) an = Math.PI * 2 + an; return an; } GetFistDeriv(pt) { if (typeof pt === "number") pt = this.GetPointAtParam(pt); else pt = pt.clone(); let refPts = this.GetGripPoints(); let p = pt.clone().applyMatrix4(this.OCSInv).applyMatrix4(new Matrix4().makeRotationZ(-this._rotate)); let vec = new Vector3(); if (equalv3(pt, refPts[0])) vec.set(0, 1, 0); else if (equalv3(pt, refPts[1])) vec.set(0, -1, 0); else if (p.y > 0) { let k = -(this._radY ** 2 * p.x) / (this._radX ** 2 * p.y); vec.set(-1, -k, 0); } else { let k = -(this._radY ** 2 * p.x) / (this._radX ** 2 * p.y); vec.set(1, k, 0); } vec.applyMatrix4(new Matrix4().makeRotationZ(this._rotate)); return vec.applyMatrix4(new Matrix4().extractRotation(this.OCS)); } GetClosestPointTo(p, extend) { //参考:https://wet-robots.ghost.io/simple-method-for-distance-to-ellipse/ let ro = new Matrix4().makeRotationZ(this._rotate); let roInv = new Matrix4().getInverse(ro); let pt = p.clone().applyMatrix4(this.OCSInv).setZ(0).applyMatrix4(roInv); let px = pt.x; let py = pt.y; let t = angle(pt); let a = this._radX; let b = this._radY; let x, y; for (let i = 0; i < 3; i++) { x = a * Math.cos(t); y = b * Math.sin(t); let ex = (a ** 2 - b ** 2) * Math.cos(t) ** 3 / a; let ey = (b * b - a * a) * Math.sin(t) ** 3 / b; let rx = x - ex; let ry = y - ey; let qx = px - ex; let qy = py - ey; let r = Math.sqrt(ry ** 2 + rx ** 2); let q = Math.sqrt(qy ** 2 + qx ** 2); let dc = r * Math.asin((rx * qy - ry * qx) / (r * q)); let dt = dc / Math.sqrt(a * a + b * b - x * x - y * y); t += dt; } let retPt = new Vector3(x, y).applyMatrix4(ro).applyMatrix4(this.OCS); if (this.IsClose || extend) { return retPt; } else if (this.PtOnCurve(retPt)) { return retPt; } else { let d1 = p.distanceToSquared(this.StartPoint); let d2 = p.distanceToSquared(this.EndPoint); return d1 < d2 ? this.StartPoint : this.EndPoint; } } GetOffsetCurves(offsetDist) { if ((offsetDist + Math.min(this._radX, this._radY)) > 0) { let el = this.Clone(); el.RadX = this._radX + offsetDist; el.RadY = this._radY + offsetDist; return [el]; } return []; } GetSplitCurves(param) { let params; if (param instanceof Array) { params = param.filter(p => this.ParamOnCurve(p)); params.sort((a1, a2) => a2 - a1); //从大到小 } else params = [param]; //补上最后一个到第一个的弧 if (this.IsClose) params.unshift(arrayLast(params)); else { params.unshift(1); params.push(0); } arrayRemoveDuplicateBySort(params); let anglelist = params.map(param => this.TotalAngle * param + this._startAngle); let elllist = []; for (let i = 0; i < anglelist.length - 1; i++) { let sa = anglelist[i]; let ea = anglelist[i + 1]; let el = this.Clone(); if (!equaln(sa, ea, 1e-6)) { el.StartAngle = ea; el.EndAngle = equaln(sa, 0) ? Math.PI * 2 : sa; elllist.push(el); } } return elllist; } Join(el) { if (this.IsClose || el.IsClose || !this.IsCoplaneTo(el) || !equalv3(el.Center, this.Center)) return Status.False; let status = Status.False; if (equaln(this._endAngle, this._startAngle)) { this.EndAngle = this._endAngle; status = Status.True; } else if (equaln(this._startAngle, el._endAngle)) { this.StartAngle = el._startAngle; status = Status.True; } if (status === Status.True && !this.IsClose && equaln(this._startAngle, this._endAngle)) { this.StartAngle = 0; this.EndAngle = Math.PI * 2; } return status; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: { let pts = this.GetGripPoints(); return pts; } case ObjectSnapMode.Cen: return [this.Center]; case ObjectSnapMode.Nea: { return getArcOrCirNearPts(this, pickPoint, viewXform); } case ObjectSnapMode.Per: if (lastPoint) { if (equaln(lastPoint.distanceToSquared(this.Center), 0, 1e-10)) return []; return [this.GetClosestPointTo(lastPoint, false)]; } case ObjectSnapMode.Tan: { //TODO:过某点获取椭圆全部切点 if (lastPoint) { return getTanPtsOnEllipse(); } } default: return []; } } IntersectWith2(curve, intType) { //TODO:优化椭圆和椭圆,椭圆和圆相交 if (curve instanceof Line) { return SwapParam(IntersectEllipseAndLine(curve, this, reverseIntersectOption(intType))); } else if (curve instanceof Circle || curve instanceof Arc) { return IntersectEllipseAndCircleOrArc(this, curve, intType); } else if (curve instanceof Polyline) { return SwapParam(IntersectPolylineAndCurve(curve, this, intType)); } else if (curve instanceof Ellipse_1) { return IntersectEllipse(this, curve); } else return []; } GetStretchPoints() { return this.GetGripPoints(); } GetGripPoints() { let tmpMat4 = new Matrix4().makeRotationZ(this.Rotation); let pts = [ new Vector3(this._radX, 0), new Vector3(-this._radX, 0), new Vector3(0, this._radY), new Vector3(0, -this._radY) ].map(p => p.applyMatrix4(tmpMat4).applyMatrix4(this.OCS)); if (!equaln(0, this._startAngle)) pts.push(this.StartPoint); if (!equaln(0, this._endAngle)) pts.push(this.EndPoint); pts.push(this.Center); return pts; } MoveStretchPoints(indexList, vec) { this.ApplyMatrix(MoveMatrix(vec)); } MoveGripPoints(indexList, vec) { let pts = this.GetStretchPoints(); if (indexList.length > 0) { let p = pts[indexList[0]].clone(); p.add(vec); if (indexList[0] <= 1) this.RadX = p.distanceTo(this.Center); else if (indexList[0] <= 3) this.RadY = p.distanceTo(this.Center); else { let p1 = pts[indexList[0]]; //TODO:跟cad不一致待优化 if (equalv3(p1, this.StartPoint)) { let v1 = p1.clone().sub(this.Center); let v2 = p.clone().sub(this.Center); let an = angleTo(v1, v2); this.StartAngle = this.StartAngle + an; } else if (equalv3(p1, this.EndPoint)) { let v1 = p1.clone().sub(this.Center); let v2 = p.clone().sub(this.Center); let an = angleTo(v2, v1); this.EndAngle = this.EndAngle + an; } else this.Center = p; } } } Convert2Polyline(count = 0) { const MIN_LEN = 80; const par = this.TotalAngle / Math.PI * 0.5; if (!count) { count = Math.floor(this.Length / par / MIN_LEN); count = MathUtils.clamp(count, 15, 80); } count = Math.floor(count * par); if ((count & 1) === 0) count++; let pts = this.Shape.getPoints(count); if (this.IsClose) pts.pop(); let pl = Pts2Polyline(pts, this.IsClose); pl.ColorIndex = this.ColorIndex; pl.ApplyMatrix(this.OCS); if (this.IsClose) pl.CloseMark = true; return pl; } _ReadFile(file) { super._ReadFile(file); file.Read(); this._radX = file.Read(); this._radY = file.Read(); this._rotate = file.Read(); this._startAngle = file.Read(); this._endAngle = file.Read(); this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(1); file.Write(this.RadX); file.Write(this.RadY); file.Write(this.Rotation); file.Write(this._startAngle); file.Write(this._endAngle); } }; Ellipse = Ellipse_1 = __decorate([ Factory ], Ellipse); function SplineConver2Polyline(spl, tolerance = 0.1) { let cu = spl.Shape; let cacheParam = new Map(); let cacheTange = new Map(); const GetPointAtParam = (param) => { let p = cacheParam.get(param); if (p) return p; p = cu.getPoint(param); cacheParam.set(param, p); return p; }; const GetTangentAtParam = (param) => { let t = cacheTange.get(param); if (t) return t; if (equaln(param, 1)) { if (spl.CloseMark) t = cu.getPoint(1e-4).sub(GetPointAtParam(param)).normalize(); else t = GetPointAtParam(param).clone().sub(cu.getPoint(1 - 1e-4)).normalize(); } else t = cu.getPoint(param + 1e-4).sub(GetPointAtParam(param)).normalize(); cacheTange.set(param, t); return t; }; let count = spl.EndParam; let stepx = 1 / count; let curves = []; for (let i = 0; i < 1;) { let step = 0.25 * stepx; //0.5的时候也可以有不错的收敛,但是在0.25的时候会有比较贴合的效果 while (true) { let param1 = i; let param2 = Math.min(1, i + step); let x = param2 - param1; let midp1 = GetPointAtParam(param1 + x * 0.25); let midp2 = GetPointAtParam(param1 + x * 0.75); let p1 = GetPointAtParam(param1); let p2 = GetPointAtParam(param2); let t1 = GetTangentAtParam(param1); let t2 = GetTangentAtParam(param2); let [c1, c2] = ComputeBiarc(p1, p2, t1, t2); // TestDraw(new Point(midp1)); // TestDraw(new Point(midp2)); // TestDraw(new Point(c1.GetPointAtParam(0.5))); // TestDraw(new Point(c2.GetPointAtParam(0.5))); if (c1.GetClosestPointTo(midp1, false).distanceTo(midp1) < tolerance && c2.GetClosestPointTo(midp2, false).distanceTo(midp2) < tolerance) { curves.push(c1, c2); break; } else step = step * 0.5; } i += step; } let polyline = Polyline.Combine(curves, 1e-3); polyline.ApplyMatrix(spl.OCSNoClone); polyline.ColorIndex = spl.ColorIndex; return polyline; } //types function Vec2(x, y) { this.x = x; this.y = y; } let c_Epsilon = 0.0001; // math functions function Sqr(val) { return val * val; } function IsEqualEps(lhs, rhs) { return Math.abs(lhs - rhs) <= c_Epsilon; } function Vec2_Add(lhs, rhs) { return new Vec2(lhs.x + rhs.x, lhs.y + rhs.y); } function Vec2_Sub(lhs, rhs) { return new Vec2(lhs.x - rhs.x, lhs.y - rhs.y); } function Vec2_Scale(lhs, scale) { return new Vec2(lhs.x * scale, lhs.y * scale); } function Vec2_AddScaled(lhs, rhs, scale) { return new Vec2(lhs.x + rhs.x * scale, lhs.y + rhs.y * scale); } function Vec2_Dot(lhs, rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; } function Vec2_MagSqr(val) { return val.x * val.x + val.y * val.y; } function CreateArcFromEdge(p1, t1, p2, fromP1) { let chord = Vec2_Sub(p2, p1); let n1 = new Vec2(-t1.y, t1.x); let chordDotN1 = Vec2_Dot(chord, n1); if (IsEqualEps(chordDotN1, 0)) return new Line(AsVector3(p1), AsVector3(p2)); else { let radius = Vec2_MagSqr(chord) / (2 * chordDotN1); let center = Vec2_AddScaled(p1, n1, radius); let p1Offset = Vec2_Sub(p1, center); let p2Offset = Vec2_Sub(p2, center); let p1Ang1 = Math.atan2(p1Offset.y, p1Offset.x); let p2Ang1 = Math.atan2(p2Offset.y, p2Offset.x); if (p1Offset.x * t1.y - p1Offset.y * t1.x > 0) return new Arc(AsVector3(center), Math.abs(radius), p1Ang1, p2Ang1, !fromP1); else return new Arc(AsVector3(center), Math.abs(radius), p1Ang1, p2Ang1, fromP1); } } /** * 计算双圆弧插值的圆弧 * @param p1 起点 * @param p2 终点 * @param t1 起点切线 * @param t2 终点切线 * @returns 两个圆弧(或者其中一个是直线) */ function ComputeBiarc(p1, p2, t1, t2) { let v = Vec2_Sub(p2, p1); let vMagSqr = Vec2_MagSqr(v); let vDotT1 = Vec2_Dot(v, t1); { let t = Vec2_Add(t1, t2); let tMagSqr = Vec2_MagSqr(t); let equalTangents = IsEqualEps(tMagSqr, 4.0); let perpT1 = IsEqualEps(vDotT1, 0.0); if (equalTangents && perpT1) //2个半圆 { let angle = Math.atan2(v.y, v.x); let center1 = Vec2_AddScaled(p1, v, 0.25); let center2 = Vec2_AddScaled(p1, v, 0.75); let radius = Math.sqrt(vMagSqr) * 0.25; let cross = v.x * t1.y - v.y * t1.x; return [ new Arc(AsVector3(center1), radius, angle, angle + Math.PI, cross < 0), new Arc(AsVector3(center2), radius, angle, angle + Math.PI, cross > 0) ]; } else { let vDotT = Vec2_Dot(v, t); let d1; if (equalTangents) d1 = vMagSqr / (4 * vDotT1); else { let denominator = 2 - 2 * Vec2_Dot(t1, t2); let discriminant = Sqr(vDotT) + denominator * vMagSqr; d1 = (Math.sqrt(discriminant) - vDotT) / denominator; } let joint = Vec2_Scale(Vec2_Sub(t1, t2), d1); joint = Vec2_Add(joint, p1); joint = Vec2_Add(joint, p2); joint = Vec2_Scale(joint, 0.5); return [ CreateArcFromEdge(p1, t1, joint, true), CreateArcFromEdge(p2, t2, joint, false) ]; } } } var Spline_1; const DrawSplitCount = 120; let Spline = Spline_1 = class Spline extends Curve { constructor(_PointList = []) { super(); this._PointList = _PointList; this._ClosedMark = false; } get Shape() { return new CatmullRomCurve3(this.Points, this._ClosedMark); } get Length() { //TODO:这个的性能挺低的(因为还需要重新获取一遍点表(如果我们有绘制对象,应该用绘制对象的点表来计算长度)) return this.Shape.getLength(); } get Points() { return this._PointList; } set Points(pts) { if (pts.length < 2) return; this.WriteAllObjectRecord(); let ocsInv = this.OCSInv; this._PointList = pts.map(p => p.clone().applyMatrix4(ocsInv)); if (pts.length > 2 && equalv3(this._PointList[0], arrayLast(this._PointList), 1e-3)) { this._PointList.pop(); this._ClosedMark = true; } this.Update(); } //闭合标志 get CloseMark() { return this._ClosedMark; } //曲线是否闭合 get IsClose() { return this.CloseMark || (equalv3(this.StartPoint, this.EndPoint, 1e-4)) && this.EndParam > 1; } set CloseMark(v) { if (this._ClosedMark === v) return; this.WriteAllObjectRecord(); this._ClosedMark = v; this.Update(); } get StartPoint() { return this._PointList[0]; } get EndPoint() { return arrayLast(this._PointList); } get StartParam() { return 0; } get EndParam() { return this._ClosedMark ? this._PointList.length : this._PointList.length - 1; } GetClosestPointTo(pt, extend) { return this.Convert2Polyline().GetClosestPointTo(pt, extend); } GetOffsetCurves(offsetDist) { if (offsetDist === 0) return []; let pld = this._PointList.map(p => { return { pt: AsVector2(p), bul: 0 }; }); let pl = new Polyline(pld); let pls = pl.GetOffsetCurves(offsetDist); return pls.map(pl => { let pts = pl.LineData.map(p => AsVector3(p.pt)); let spl = new Spline_1(pts); spl.OCS = this._Matrix; spl._ClosedMark = this._ClosedMark; return spl; }); } GetGripPoints() { return this._PointList.map(p => p.clone().applyMatrix4(this.OCSNoClone)); } GetStretchPoints() { return this.GetGripPoints(); } MoveGripPoints(indexList, vec) { vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)).setZ(0); this.WriteAllObjectRecord(); for (let index of indexList) this._PointList[index].add(vec); this.Update(); } MoveStretchPoints(indexList, vec) { vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)).setZ(0); this.WriteAllObjectRecord(); for (let index of indexList) this._PointList[index].add(vec); this.Update(); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Cen: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: } return []; } GetDrawCount() { return this.EndParam * DrawSplitCount; } Convert2Polyline() { return SplineConver2Polyline(this); } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 let count = file.Read(); this._PointList.length = 0; for (let i = 0; i < count; i++) this._PointList.push(new Vector3().fromArray(file.Read())); if (ver > 1) this._ClosedMark = file.Read(); } WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.Write(this._PointList.length); this._PointList.forEach(p => file.Write(p.toArray())); file.Write(this._ClosedMark); } }; Spline = Spline_1 = __decorate([ Factory ], Spline); /** * 简化优化版本的曲线求交, 优化版本可以参考(算法导论33.2 确定任意一对线段是否相交 p599) */ class CurveIntersection { /** * @param {Curve[]} cus 请注意数组的顺序会被更改,如果你在意数组的顺序,请拷贝数组后传进来 * @memberof CurveIntersection */ constructor(cus, parseIntersectionParam = false, intType = IntersectOption.OnBothOperands, fuzz = 1e-6) { this.fuzz = fuzz; //用来缓存的曲线包围盒 this.boxMap = new Map(); /** * 交点数据集,key 为曲线 value 为和它相交的(曲线和交点的Map) */ this.intersect = new Map(); this.intersect2 = new Map(); this.GenBox(cus); //按x排序 this.SortCurve(cus); let count = cus.length; for (let i = 0; i < count; i++) { let c1 = cus[i]; let c1d = this.GetIntersect(c1); let c1b = this.boxMap.get(c1); for (let j = i + 1; j < count; j++) { let c2 = cus[j]; //过滤掉不需要计算的曲线 let c2b = this.boxMap.get(c2); if (c2b.min.x - c1b.max.x > fuzz) break; if (c2b.min.y - c1b.max.y > fuzz) continue; let ints = this.IntersectWith2(c1, c2, intType); if (ints.length > 0) { let pts = ints.map(i => i.pt); c1d.set(c2, pts); this.GetIntersect(c2).set(c1, pts); if (parseIntersectionParam) { this.AppendIntersectionParams(c1, ints.map(i => i.thisParam)); this.AppendIntersectionParams(c2, ints.map(i => i.argParam)); } } } } } IntersectWith2(c1, c2, intType) { return c1.IntersectWith2(c2, intType); } AppendIntersectionParams(curve, params) { let arr = this.intersect2.get(curve); if (!arr) { arr = []; this.intersect2.set(curve, arr); } arr.push(...params); } GenBox(cus) { for (let c of cus) this.boxMap.set(c, c.BoundingBox); } SortCurve(cus) { cus.sort((c1, c2) => { return this.boxMap.get(c1).min.x - this.boxMap.get(c2).min.x; }); } GetIntersect(cu) { if (this.intersect.has(cu)) return this.intersect.get(cu); let m = new Map(); this.intersect.set(cu, m); return m; } } class CurveIntersection2 extends CurveIntersection { /** * Curve2Polyline使用这个时,为了避免多余的交点导致曲线切割过度,过滤掉无关的点 */ IntersectWith2(c1, c2, intType) { let pts = c1.IntersectWith2(c2, intType); return pts.filter(p => { let inC1 = c1.ParamOnCurve(p.thisParam) || c1.StartPoint.distanceTo(p.pt) < this.fuzz || c1.EndPoint.distanceTo(p.pt) < this.fuzz; if (!inC1) return false; let inC2 = c2.ParamOnCurve(p.argParam) || c2.StartPoint.distanceTo(p.pt) < this.fuzz || c2.EndPoint.distanceTo(p.pt) < this.fuzz; return inC2; }); } } /** * 曲线连接图 * 所有的顶点和边的关系 */ class CurveMap { constructor(numdimensions = 4, _RemoveSortLine = false, multiplier = 10 ** numdimensions) { this.numdimensions = numdimensions; this._RemoveSortLine = _RemoveSortLine; this.multiplier = multiplier; /* 节点图. 每个节点对应下一个路口的路线表. 路口表使用逆时针排序,起始角度使用正x轴. */ this._VerticeMap = new Map(); this._Vertices = []; this._LookupTable = {}; } /** * 得到节点图的所有站点列表 */ get Stands() { return this._Vertices; } /** * @param curve * @param [isArc=curve instanceof Arc] * @param [removeDuplicate=false] * @returns 加入成功? */ AddCurveToMap(curve, isArc = curve instanceof Arc, removeDuplicate = false, parseAngle = false) { let sp = curve.StartPoint; let ep = curve.EndPoint; let startS = this.GetOnlyVertice(sp); let endS = this.GetOnlyVertice(ep); //在面域分析中,路线指向同一个顶点已经没有意义了 if (this._RemoveSortLine && startS === endS) return false; if (removeDuplicate) //删除重复 { let index = startS.routes.findIndex(r => { if (r.to === endS && r.curve.constructor.name === curve.constructor.name) { if (isArc) return equalv3(curve.GetPointAtParam(0.5), r.curve.GetPointAtParam(0.5)); return true; } }); if (index !== -1) return false; } let length = curve.Length; curve.TempData = 0; let routeS2E = { curve, isReverse: false, length, from: startS, to: endS, s: sp, e: ep }; let routeE2S = { curve, isReverse: true, length, from: endS, to: startS, e: sp, s: ep }; if (!isArc && parseAngle) { let an = angle(endS.position.clone().sub(startS.position)); routeS2E.an = an; routeE2S.an = clampRad(an + Math.PI); } startS.routes.push(routeS2E); endS.routes.push(routeE2S); return true; } /** * 获得唯一的顶点 */ GetOnlyVertice(p) { let gp = this.GenerateP(p); if (this._VerticeMap.has(gp)) return this._VerticeMap.get(gp); let vertice = { position: gp, routes: [] }; this._VerticeMap.set(p, vertice); this._Vertices.push(vertice); return vertice; } /** * 生成一个唯一的向量. */ GenerateP(p) { let key = ""; let els = p.toArray(); for (let n of els) { let valueQuantized = Math.round(n * this.multiplier); key += valueQuantized + '/'; } if (key in this._LookupTable) return this._LookupTable[key]; let hashparts = els.map((el) => { let q0 = Math.floor(el * this.multiplier); let q1 = q0 + 1; return ['' + q0 + '/', '' + q1 + '/']; }); let numelements = els.length; let numhashes = 1 << numelements; for (let hashmask = 0; hashmask < numhashes; ++hashmask) { let hashmaskShifted = hashmask; key = ''; hashparts.forEach(function (hashpart) { key += hashpart[hashmaskShifted & 1]; hashmaskShifted >>= 1; }); this._LookupTable[key] = p; } return p; } } /** 面域分析,基于最小循环图重新实现的版本,拓展了实现求最大轮廓。 当最大轮廓=最小轮廓时,只绘制最大轮廓(独立轮廓无分裂)。 算法只实现去重模式,业务场景应该没有非去重模式。 如果需要非去重模式,那么应该获取到多个CurveMap,然后对多个CurveMap进行面域分析,得出多个重叠的面域。 */ class RegionParse { /** * @param cuList 请不要传递圆和椭圆. * @param [numDimensions=3] 精度:小数点后个数 * @param [removeDuplicate=true] 删除重复(现在必须是true,请不要修改它) */ constructor(cuList, numDimensions = 3, removeDuplicate = true) { this.numDimensions = numDimensions; this.removeDuplicate = removeDuplicate; //区域列表 通常是外轮廓 this.RegionsOutline = []; //区域列表 通常是内轮廓 this.RegionsInternal = []; //碎线 曲线进入到这里会被炸开. this.ExpLineMap = new Map(); //需要搜索的站 let vertices = this.GenerateVerticeMap(cuList); //移除细丝 while (true) { let v = vertices.find(v => v.routes.length < 2); if (v) this.RemoveFilamentAt(v, vertices); else break; } let lowerVertice; while (vertices.length > 0) { lowerVertice = (lowerVertice === null || lowerVertice === void 0 ? void 0 : lowerVertice.routes.length) > 1 ? lowerVertice : this.FindLowerLeftStand(vertices); let minWalk = ClosedWalkFrom(lowerVertice, this._CurveCount, WalkType.Min); let maxWalk = ClosedWalkFrom(lowerVertice, this._CurveCount, WalkType.Max); this.RemoveEdge(minWalk[0]); this.RemoveFilamentAt(minWalk[0].from, vertices); this.RemoveFilamentAt(minWalk[0].to, vertices); minWalk = ReduceWalk(minWalk); maxWalk = ReduceWalk(maxWalk); if (maxWalk.length > 1) { this.RegionsOutline.push(maxWalk); if (minWalk.length === maxWalk.length && minWalk.every((w1, index) => w1 === maxWalk[index])) //大小重叠 { //直接remove,不用计算引用个数 for (let w of minWalk) { this.RemoveEdge(w); this.RemoveFilamentAt(w.from, vertices); this.RemoveFilamentAt(w.to, vertices); } continue; //继续循环 } else for (let w of maxWalk) w.curve.TempData = 1; } if (minWalk.length > 1) // && minWalk.every(w => (w.curve.TempData) < 2) 没有重复线应该不会被用2次 { this.RegionsInternal.push(minWalk); for (let w of minWalk) { w.curve.TempData++; if (w.curve.TempData === 2) { this.RemoveEdge(w); this.RemoveFilamentAt(w.from, vertices); this.RemoveFilamentAt(w.to, vertices); } } } } } RemoveFilamentAt(v, vertices) { let current = v; while (current && current.routes.length < 2) { vertices = arrayRemoveOnce(vertices, current); let r = current.routes[0]; if (r) { this.RemoveEdge(r); current = r.to; } else current = undefined; } } RemoveEdge(r) { let index = r.from.routes.findIndex(rr => rr.curve === r.curve); if (index !== -1) r.from.routes.splice(index, 1); index = r.to.routes.findIndex(rr => rr.curve === r.curve); if (index !== -1) r.to.routes.splice(index, 1); } /** * 找到最下方并且最左边的站 yx */ FindLowerLeftStand(vertices) { return vertices.reduce((m, v) => { let dy = v.position.y - m.position.y; if (dy < 0) return v; if (dy > 0) return m; return v.position.x - m.position.x < 0 ? v : m; }); } /** * 构造路线图. 每个节点对应下一个路口的路线表. 路口表使用逆时针排序,起始角度使用正x轴. * @returns 所有的顶点 */ GenerateVerticeMap(curveList) { let curveMap = new CurveMap(this.numDimensions, true); //将多段线炸开 let plcus = []; arrayRemoveIf(curveList, c => { if (c instanceof Polyline) { let cus = c.Explode(); //如果为圆弧,提前打断 let arcs = []; arrayRemoveIf(cus, c => { if (c.Length < 1e-5) return true; if (c instanceof Arc) { let arcBrs = this.BreakArc(c); for (let arc of arcBrs) arcs.push(arc); } return false; }); //加入到计算 cus.push(...arcs); this.ExpLineMap.set(c, cus); plcus.push(...cus); return true; } return false; }); curveList.push(...plcus); this._CurveCount = curveList.length; for (let cu of curveList) { //由于圆弧可能导致最低点计算错误的问题. if (cu instanceof Arc) { let arcs = this.BreakArc(cu); if (arcs.length > 1) { arcs.forEach(a => curveMap.AddCurveToMap(a, true, this.removeDuplicate, true)); this.ExpLineMap.set(cu, arcs); continue; } else curveMap.AddCurveToMap(cu, true, this.removeDuplicate, true); } else curveMap.AddCurveToMap(cu, false, this.removeDuplicate, true); } //排序,根据角度逆时针排序. for (let v of curveMap._Vertices) { let minLength = Infinity; for (let r of v.routes) if (r.length < minLength) minLength = r.length; for (let r of v.routes) CalcRouteAngle(r, minLength * 0.2); v.routes.sort((r1, r2) => r1.an - r2.an); } return curveMap.Stands; } BreakArc(arc) { let underPt = arc.Center.add(new Vector3(0, -arc.Radius)); let param = arc.GetParamAtPoint(underPt); if (param > 0.01 && param < 0.99) return arc.GetSplitCurves(param); else return [arc]; } /** * 曲线是否已经被算法使用 */ GetCueveUsed(cu) { if (this.ExpLineMap.has(cu)) { let use = this.ExpLineMap.get(cu).some(c => c.TempData > 0); if (!use) this.ExpLineMap.delete(cu); return use; } else return cu.TempData > 0; } } function CalcRouteAngle(r, length) { if (r.an !== undefined) return; let cu = r.curve; let p = r.isReverse ? cu.GetPointAtParam(cu.GetParamAtDist(r.length - length)) : cu.GetPointAtParam(cu.GetParamAtDist(length)); r.an = angle(p.sub(r.from.position)); } var WalkType; (function (WalkType) { WalkType[WalkType["Min"] = 1] = "Min"; WalkType[WalkType["Max"] = -1] = "Max"; })(WalkType || (WalkType = {})); function ClosedWalkFrom(startVertice, maxRoute, type = WalkType.Min) { let walk = []; let curVertice = startVertice; let preRoute; // console.log("start", type, startVertice.position.toArray()); do { let route = GetNextRoute(curVertice, preRoute, type); if (type === WalkType.Max && route.curve.TempData > 0) return []; // console.log(route.to.position.toArray()); walk.push(route); [curVertice, preRoute] = [route.to, route]; if (walk.length > maxRoute * 2) throw "超过计算次数限制"; } while (curVertice !== startVertice); return walk; } /** * 删除中途回路 */ function ReduceWalk(w) { if (w.length === 0) return w; //未构成回路,直接回家 if (w[0].curve === arrayLast(w).curve) return []; for (let i = 0; i < w.length; i++) { let r1 = w[i]; for (let j = w.length; j--;) { if (i === j) break; let r2 = w[j]; if (r1.to === r2.to) { if (j > i) w.splice(i + 1, j - i); break; } } } return w; } function GetNextRoute(v, prev, type = WalkType.Min) { if (!prev) return arrayLast(v.routes); //顺时针 cw \|/ 从左往右 //逆时针 ccw 往左 let index = v.routes.findIndex(r => r.curve === prev.curve); let newIndex = FixIndex$1(index + 1 * type, v.routes); return v.routes[newIndex]; } /** * 某些时候我们不能创建轮廓,此时我们使用类似c2r的方法来构建一个外部轮廓. */ function CreateContours(curves, fuzz = 1e-4) { let contours = []; let extendsMinDistSq = fuzz * fuzz; //炸开多段线(防止自交多段线) let newCurves = []; for (let cu of curves) { if (cu instanceof Circle) contours.push(Contour.CreateContour(cu.Clone())); //避免将原始曲线传递给板,导致撤销这个圆失败 else if (cu instanceof Polyline) arrayPushArray(newCurves, cu.Explode()); else if (cu instanceof Spline) { let pl = cu.Convert2Polyline(); if (pl.IsClose) contours.push(Contour.CreateContour(pl, false)); else newCurves.push(pl); } else if (cu instanceof Ellipse) Contour.CreateContour(cu.Convert2Polyline(), false); else newCurves.push(cu); } let intersect = new CurveIntersection2(newCurves, false, IntersectOption.ExtendBoth, fuzz); let curves2 = []; //延伸+打断 for (let [cu, pmap] of intersect.intersect) { let sp = cu.StartPoint; let ep = cu.EndPoint; let epExtend; let epDist = Infinity; let spExtend; let spDist = Infinity; let isClose = cu.IsClose; let ipts = []; for (let [, pts] of pmap) { arrayPushArray(ipts, pts); if (!isClose) for (let p of pts) { let d = p.distanceToSquared(ep); if (d < epDist) { epDist = d; epExtend = p; } d = p.distanceToSquared(sp); if (d < spDist) { spDist = d; spExtend = p; } } } if (!isClose) { //延伸 if (epDist > 0 && epDist < extendsMinDistSq) { let param = cu.GetParamAtPoint(epExtend); if (param > cu.EndParam) cu.Extend(param); } if (spDist > 0 && spDist < extendsMinDistSq) { let param = cu.GetParamAtPoint(spExtend); if (param < 0) cu.Extend(param); } } //打断 let curves; if (ipts.length > 0) curves = cu.GetSplitCurvesByPts(ipts); else curves = [cu]; let tempCus = []; for (let c of curves) { if (c instanceof Polyline) arrayPushArray(tempCus, c.Explode()); else tempCus.push(c); } arrayPushArray(curves2, tempCus); } let parse = new RegionParse(curves2); for (let rs of parse.RegionsOutline) { let curves = rs.map(r => r.curve); let contour = Contour.CreateContour(curves, false); if (contour) contours.push(contour); } return contours; } function CreateContour2(curves, fuzz = 1e-4) { return CreateContours(curves, fuzz)[0]; } var BoolOpeartionType; (function (BoolOpeartionType) { BoolOpeartionType[BoolOpeartionType["Intersection"] = 0] = "Intersection"; BoolOpeartionType[BoolOpeartionType["Union"] = 1] = "Union"; BoolOpeartionType[BoolOpeartionType["Subtract"] = 2] = "Subtract"; })(BoolOpeartionType || (BoolOpeartionType = {})); const fuzz = 1e-3; let fuzzV3 = new Vector3(fuzz, fuzz, fuzz); //判断曲线是否在源封闭曲线内 function isTargetCurInOrOnSourceCur(sourceCur, targetCur) { if (!sourceCur.BoundingBox.expandByVector(fuzzV3).containsBox(targetCur.BoundingBox)) return false; let cus = []; if (targetCur instanceof Polyline) cus = targetCur.Explode(); else cus = [targetCur]; return cus.every(c => { let pts = getIntPtContextPts(sourceCur, c); if (pts.length <= 1) pts.push(c.StartPoint, c.EndPoint); return IsPtsAllInOrOnReg(sourceCur, pts); }); } //获取交点处上下距0.01par的点 function getIntPtContextPts(sourceCur, cu, pts = []) { let interPts = cu.IntersectWith(sourceCur, IntersectOption.OnBothOperands); if (interPts.length > 0) { let pars = interPts.map(pt => cu.GetParamAtPoint(pt)); for (let par of pars) { if (par >= 0.02) pts.push(cu.GetPointAtParam(par - 0.01)); if (par <= (cu.EndParam - 0.02)) pts.push(cu.GetPointAtParam(par + 0.01)); } } return pts; } //判断点点是否全部都在封闭区域内或者在曲线上 function IsPtsAllInOrOnReg(sourceReg, pts) { return pts.every(pt => { //是否点在封闭曲线内 return sourceReg.PtOnCurve(pt) || sourceReg.PtInCurve(pt); }); } let cache$1 = new WeakMap(); const COMBINE_FUZZ = 1e-2; class Contour { SetCurve(cu) { if (cu instanceof Polyline) { if (cu.Area2 < 0) cu.Reverse(); } this._Curve = cu; } /**会将传入的闭合轮廓改为逆时针 */ static CreateContour(cus, needLink = true) { if (cus instanceof Curve) { if (cus.IsClose) { let c = new Contour(); c.SetCurve(cus); return c; } return; } let closeCurve = Contour.Combine(cus, needLink, COMBINE_FUZZ); if (closeCurve && closeCurve.IsClose) { if (closeCurve instanceof Polyline && closeCurve.CloseMark === false) { closeCurve.CloseMark = true; closeCurve.RemoveVertexAt(closeCurve.NumberOfVertices - 1); } let c = new Contour(); c.SetCurve(closeCurve); return c; } } get Curve() { return this._Curve; } get Area() { return this._Curve.Area; } get BoundingBox() { return this._Curve.BoundingBox; } /** * 不等比例缩放 * @param {number} ref 缩放参考值,大于该值的点缩放 * @param {number} dist 缩放距离 * @param {string} dir x y z */ UnEqualProportionScale(ref, dist, dir) { let cu = this._Curve; if (cu instanceof Polyline) { let lineData = cu.LineData; let length = lineData.length; let p = cu.Position[dir]; let moveIndexs = []; for (let i = 0; i < length; i++) { if (lineData[i].pt[dir] + p > ref) moveIndexs.push(i); } let moveVec = new Vector3(); moveVec[dir] = dist; cu.MoveStretchPoints(moveIndexs, moveVec); return true; } return false; } Clone() { return Contour.CreateContour([this._Curve.Clone()]); } //交集:结果数组为空则失败 IntersectionBoolOperation(target) { if (!IntersectBox2(this.BoundingBox, target.BoundingBox)) return []; let resultCus = this.GetIntersetAndUnionList(target); return Contour.GetAllContour(resultCus.intersectionList); } //并集:结果轮廓数组长度大于2,则失败.等于1则成功. UnionBoolOperation(target) { let resultCus = this.GetIntersetAndUnionList(target); //快速 if (resultCus.unionList.every(c => c.IsClose)) return { contours: Contour.GetAllContour(resultCus.unionList), holes: [], }; //并集后的线段表如果有共线的直接合并起来 let cus = []; for (let pl of resultCus.unionList) { if (pl instanceof Polyline) cus.push(...pl.Explode()); else cus.push(pl); } let cuGroups = curveLinkGroup(cus); for (let g of cuGroups) { for (let i = 0; i < g.length; i++) { let c1 = g[i]; let nextI = FixIndex$1(i + 1, g); let c2 = g[nextI]; let status = c1.Join(c2); if (status === Status.True) { g.splice(nextI, 1); i--; } else if (status === Status.ConverToCircle) { g.length = 0; let a = c1; g.push(new Circle(a.Center, a.Radius)); break; } } } let allContour = Contour.GetAllContour(cuGroups); if (allContour.length < 2) { return { contours: allContour, holes: [], }; } else { let cache = new WeakMap(); for (let c of allContour) cache.set(c, c.Area); allContour.sort((a, b) => cache.get(b) - cache.get(a)); return { contours: [allContour[0]], holes: allContour.slice(1) }; } } //差集:等于0完全被减去 SubstactBoolOperation(target) { let subtractList = this.GetSubtractList(target); //纯网洞 if (subtractList.every(c => c.IsClose)) return Contour.GetAllContour(subtractList); let regParse = new RegionParse(subtractList, 2); let contours = []; //分析封闭包围区域 const parseRoute = (routeSet) => { for (let routes of routeSet) { let cs = routes.map(r => r.curve); let c = Contour.CreateContour(cs, false); if (c && !equalCurve(c.Curve, this.Curve) && !equalCurve(c.Curve, target.Curve) && c.Area > 1e-3) contours.push(c); } }; parseRoute(regParse.RegionsOutline); parseRoute(regParse.RegionsInternal); return contours; } /** * 计算与目标轮廓布尔运算后的结果曲线. */ GetIntersetAndUnionList(target) { //同心圆 if (this._Curve instanceof Circle && target._Curve instanceof Circle && equalv2(this._Curve.Center, target._Curve.Center, 1e-3)) { if (this._Curve.Radius > target._Curve.Radius) return { intersectionList: [target._Curve], unionList: [this._Curve] }; else return { intersectionList: [this._Curve], unionList: [target._Curve] }; } let intersectionList = []; let unionList = []; let sourceOutline = this._Curve; let targetOutline = target.Curve; let isEqualNormal = equalv3(sourceOutline.Normal, targetOutline.Normal, 1e-3); //可能会有提升,但是好像不大(并且还有更慢的趋势) // if (!sourceOutline.BoundingBox.intersectsBox(targetOutline.BoundingBox, 1e-3)) // return { intersectionList, unionList }; let interPts = sourceOutline.IntersectWith2(targetOutline, IntersectOption.OnBothOperands, COMBINE_FUZZ); let sourceContainerTarget; let targetContainerSource; if (sourceOutline.Area > targetOutline.Area) { sourceContainerTarget = interPts.length === 0 ? fastCurveInCurve(sourceOutline, targetOutline) : this.CuInOutline(targetOutline); targetContainerSource = false; } else { sourceContainerTarget = false; targetContainerSource = interPts.length === 0 ? fastCurveInCurve(targetOutline, sourceOutline) : target.CuInOutline(sourceOutline); } //包含.相交.分离(三种状态) if (sourceContainerTarget) //源包含目标 { intersectionList.push(targetOutline); unionList.push(sourceOutline); } else if (targetContainerSource) //目标包含源 { unionList.push(targetOutline); intersectionList.push(sourceOutline); } else if (interPts.length <= 1) //分离 { unionList.push(sourceOutline, targetOutline); } else //相交 interPts.length > 0 { let pars1 = interPts.map(r => r.thisParam); let pars2 = interPts.map(r => r.argParam); let sourceCus = sourceOutline.GetSplitCurves(pars1); let targetCus = targetOutline.GetSplitCurves(pars2); for (let pl of sourceCus) { let hasEqualCus = false; for (let i = 0; i < targetCus.length; i++) { let cu = targetCus[i]; hasEqualCus = fastEqualCurve(cu, pl); if (hasEqualCus) { //方向相同 if (equalv3(cu.GetFistDeriv(cu.EndParam * 0.5).normalize(), pl.GetFistDeriv(pl.EndParam * 0.5).normalize(), 1e-3) === isEqualNormal) { unionList.push(pl); intersectionList.push(pl); } targetCus.splice(i, 1); break; } } if (hasEqualCus) continue; if (fastCurveInCurve(targetOutline, pl)) intersectionList.push(pl); else unionList.push(pl); } for (let pl of targetCus) { if (fastCurveInCurve(sourceOutline, pl)) intersectionList.push(pl); else unionList.push(pl); } //特殊的分离 if (intersectionList.length === 0 && unionList.length === (sourceCus.length + targetCus.length)) { return { intersectionList, unionList: [sourceOutline, targetOutline] }; } } return { intersectionList, unionList }; } GetSubtractList(target) { let sourceOutline = this._Curve; let targetOutline = target.Curve; let isEqualNormal = equalv3(sourceOutline.Normal, targetOutline.Normal, 1e-3); let interPts = sourceOutline.IntersectWith2(targetOutline, IntersectOption.OnBothOperands, COMBINE_FUZZ); if (interPts.length <= 1) { //反包含 if (fastCurveInCurve2(targetOutline, sourceOutline) || equalCurve(targetOutline, sourceOutline)) return []; //包含 if (fastCurveInCurve2(sourceOutline, targetOutline)) return [sourceOutline, targetOutline]; else //分离 return [sourceOutline]; } //相交 let subtractList = []; let sourceCus = sourceOutline.GetSplitCurves(interPts.map(r => r.thisParam)); let targetCus = targetOutline.GetSplitCurves(interPts.map(r => r.argParam)); for (let pl of sourceCus) { let plMidParam = pl.MidParam; let plDir = pl.GetFistDeriv(plMidParam).normalize(); let index = targetCus.findIndex(cu => fastEqualCurve(cu, pl)); if (index !== -1) { let cu = targetCus[index]; let cuMidParam = cu.MidParam; let cuDir = cu.GetFistDeriv(cuMidParam).normalize(); if (isEqualNormal === !equalv3(cuDir, plDir, 1e-3)) //不同向 subtractList.push(pl); targetCus.splice(index, 1); continue; } if (!fastCurveInCurve(targetOutline, pl)) subtractList.push(pl); } //源对象没有被破坏 let sourceNotBreak = subtractList.length === sourceCus.length; for (let pl of targetCus) if (fastCurveInCurve(sourceOutline, pl)) subtractList.push(pl); if (sourceNotBreak && subtractList.length === sourceCus.length) return [sourceOutline]; return subtractList; } GetSubtractListByMoreTargets(targets) { let { holes, subtractList } = this.GetSubListWithCus(targets); //纯网洞 if (subtractList.every(c => c.IsClose)) return { holes: holes.map(h => Contour.CreateContour(h)), outlines: Contour.GetAllContour(subtractList) }; let regParse = new RegionParse(subtractList, 2); let contours = []; //分析封闭包围区域 const parseRoute = (routeSet) => { var _a; for (let routes of routeSet) { let cs = routes.map(r => r.curve); let c = (_a = Contour.CreateContour(cs, false)) !== null && _a !== void 0 ? _a : CreateContour2(cs); if (c && !equalCurve(c.Curve, this.Curve) && targets.every(target => !equalCurve(c.Curve, target.Curve)) && c.Area > 1e-3) contours.push(c); } }; parseRoute(regParse.RegionsOutline); parseRoute(regParse.RegionsInternal); return { holes: holes.map(h => Contour.CreateContour(h)), outlines: contours }; } GetSubListWithCus(targets) { let sourceOutline = this._Curve; let subtractList = []; let holes = []; let intPars = []; let cuMap = new Map(); let outBox = sourceOutline.BoundingBox; for (let con of targets) { const targetOutline = con.Curve; if (!IntersectBox2(outBox, targetOutline.BoundingBox)) continue; let pts = sourceOutline.IntersectWith2(con.Curve, IntersectOption.OnBothOperands, COMBINE_FUZZ); if (pts.length <= 1) { //反包含 if (fastCurveInCurve2(targetOutline, sourceOutline) || equalCurve(targetOutline, sourceOutline)) return { holes, subtractList }; //包含 if (fastCurveInCurve2(sourceOutline, targetOutline)) holes.push(targetOutline); } else { intPars.push(...pts.map(r => r.thisParam)); cuMap.set(targetOutline, pts.map(r => r.argParam)); } } intPars.sort((a, b) => a - b); arrayRemoveDuplicateBySort(intPars, (e1, e2) => equaln(e1, e2, 1e-8)); let sourceCus = sourceOutline.GetSplitCurves(intPars); let targetCus = []; let targetMap = new WeakMap(); let isEqualNormal; for (let [c, pars] of cuMap) { let cus = c.GetSplitCurves(pars); cus.forEach(cu => targetMap.set(cu, c)); targetCus.push(...cus); } for (let pl of sourceCus) { let plMidParam = pl.MidParam; let plDir = pl.GetFistDeriv(plMidParam).normalize(); let index = targetCus.findIndex(cu => fastEqualCurve(cu, pl, 0.05)); if (index !== -1) { let cu = targetCus[index]; isEqualNormal = equalv3(sourceOutline.Normal, targetMap.get(cu).Normal, 1e-3); let cuMidParam = cu.MidParam; let cuDir = cu.GetFistDeriv(cuMidParam).normalize(); if (isEqualNormal === !equalv3(cuDir, plDir, 1e-3)) //不同向 subtractList.push(pl); targetCus.splice(index, 1); continue; } if (targets.every(t => !fastCurveInCurve(t.Curve, pl))) subtractList.push(pl); } //源对象没有被破坏 let sourceNotBreak = subtractList.length === sourceCus.length; for (let pl of targetCus) if (fastCurveInCurve(sourceOutline, pl)) subtractList.push(pl); if (sourceNotBreak && subtractList.length === sourceCus.length) return { subtractList: [sourceOutline], holes }; return { subtractList, holes }; } /** * 获得全部闭合曲线 * @若传入二维曲线数据,将默认子数组为闭合曲线段 */ static GetAllContour(cus) { if (cus.length === 0) return []; let cuGroups; if (Array.isArray(cus[0])) cuGroups = cus; else cuGroups = curveLinkGroup(cus); let contours = []; for (let g of cuGroups) contours.push(Contour.CreateContour(g, false)); return contours.filter(c => c !== undefined && !equaln(c.Area, 0, 1e-6)); } /** * 合并曲线组成为多段线 * @param cus 曲线组 * @param [needLink=true] 需要解析成首尾连接状态 * @returns 单一曲线,如果返回超过1个,其他的将被遗弃. */ static Combine(cus, needLink = true, tolerance = 1e-3) { if (cus.length === 0) return undefined; let groups = needLink ? curveLinkGroup(cus) : [cus]; for (let g of groups) { if (g.length === 1) return g[0].Clone(); else { if (cache$1.has(g)) return cache$1.get(g); let gclone = g.map(c => c.Clone()); arrayRemoveDuplicateBySort(gclone, (cu1, cu2) => cu1.Join(cu2, false, tolerance) === Status.True); if (gclone.length > 1 && gclone[0].Join(arrayLast(gclone), false, tolerance)) gclone.pop(); let pl = Polyline.Combine(gclone, tolerance); cache$1.set(g, pl); return pl; } } } get Shape() { return this._Curve.Shape; } CuInOutline(targetCur) { return isTargetCurInOrOnSourceCur(this._Curve, targetCur); } Equal(tar) { return equalCurve(this._Curve, tar._Curve); } } /** * 对于轮廓切割后的曲线判断相同,使用这个函数进行快速判断 */ function fastEqualCurve(c1, c2, tolerance = 1e-3) { let sp1 = c1.StartPoint; let ep1 = c1.EndPoint; let sp2 = c2.StartPoint; let ep2 = c2.EndPoint; if (!((equalv3(sp1, sp2, tolerance) && equalv3(ep1, ep2, tolerance)) || (equalv3(sp1, ep2, tolerance) && equalv3(ep1, sp2, tolerance)))) return false; return equalv3(c1.Midpoint, c2.Midpoint, tolerance); } //对于双多段线互相切割后的结果,快速判断曲线是否在另一条曲线内部 //也许有一天这个中点算法需要改一下, 使用 src\Geometry\ExtrudeEdgeGeometry2.ts->CenterPoint 会比较稳妥 function fastCurveInCurve(sourceCu, targetCu) { return sourceCu.PtInCurve(targetCu.GetPointAtParam(targetCu.EndParam * 0.5)); } function fastCurveInCurve2(sourceCu, targetCu) { return sourceCu.PtInCurve(targetCu.StartPoint) && sourceCu.PtInCurve(targetCu.GetPointAtParam(targetCu.EndParam * 0.5)); } class CurveTreeNode { constructor(curve, box) { this.curve = curve; this.box = box || curve.BoundingBox; } TrimBy(contour, box) { if (IntersectsBox(box, this.box)) { if (this.children !== undefined) { for (let c of this.children) c.TrimBy(contour, box); } else { if (contour.Curve instanceof Circle && this.curve instanceof Arc) { if (equalv3(contour.Curve.Center, this.curve.Center)) { if (contour.Curve.Radius > this.curve.Radius + 1e-4) this.children = []; return; } } //交点参数列表 let iParams = this.curve.IntersectWith(contour.Curve, IntersectOption.OnBothOperands) .map(p => this.curve.GetParamAtPoint2(p)); let cus = this.curve.GetSplitCurves(iParams); if (cus.length === 0) { let p = this.curve.GetPointAtParam(0.5); if (box.containsPoint(p) && (contour.Curve.PtInCurve(p) && !contour.Curve.PtOnCurve(p))) this.children = []; } else { this.children = []; for (let c of cus) { let p = c.GetPointAtParam(0.5); if (CurveIsFine(c) && (!(box.containsPoint(p) && contour.Curve.PtInCurve(p)) || contour.Curve.PtOnCurve(p))) this.children.push(new CurveTreeNode(c)); } if (this.children.length === cus.length) this.children = undefined; } } } } get Nodes() { if (!this.children) return [this]; else { let cus = []; for (let c of this.children) cus.push(...c.Nodes); return cus; } } } class OffsetPolyline { constructor(_Polyline, _OffsetDist, _ToolPath = false, _OffsetDistSq = (_OffsetDist ** 2) * 2.1 //对直角走刀不进行圆弧过度 ) { this._Polyline = _Polyline; this._OffsetDist = _OffsetDist; this._ToolPath = _ToolPath; this._OffsetDistSq = _OffsetDistSq; } Do() { this._OffsetDistSign = Math.sign(this._OffsetDist); this._TrimPolylineContours = []; this._TrimCircleContours = []; this._TrimArcContours = []; this._RetCurves = []; this._CurveTreeNodes = []; this.InitSubCurves(); if (this._SubCurves.length === 0) return this._RetCurves; this.GeneralCirclesAndVertexs(); this.OffsetSubCurves(); this.LinkSubCurves(); if (this._SubOffsetedCurves.length === 0) { this._SubOffsetedCurves.push({ curve: this._Circles[0], index: 0, paddingCurve: this._Circles.slice(1) }); this._TrimPolylineContours.push(...this._Circles.map(c => Contour.CreateContour(c, false)), ...this._SubCurves.map(c => Contour.CreateContour([c, new Line(c.StartPoint, c.EndPoint)], false))); } else this.GeneralTrimContours(); this.TrimByContours(); this.FilterInvalidCurve(); this.JoinCollinear(); this.LinkResultPolyline(); this.RepairResultPolylineClosemark(); return this._RetCurves; } InitSubCurves() { this._CacheOCS = this._Polyline.OCS; this._IsClose = this._Polyline.IsClose; this._Polyline.OCS = IdentityMtx4; this._SubCurves = this._Polyline.Explode().filter(c => c.Length > 1e-4); this._Polyline.OCS = this._CacheOCS; return this; } GeneralCirclesAndVertexs() { this._Vertexs = this._SubCurves.map(c => c.StartPoint); let lastCu = arrayLast(this._SubCurves); if (!equalv3(lastCu.EndPoint, this._Vertexs[0], 1e-3)) this._Vertexs.push(lastCu.EndPoint); let radius = Math.abs(this._OffsetDist); this._Circles = this._Vertexs.map(p => new Circle(p, radius)); } OffsetSubCurves() { this._SubOffsetedCurves = []; for (let index = 0; index < this._SubCurves.length; index++) { let curveOld = this._SubCurves[index]; if (curveOld.Length > 1e-6) { let curve = curveOld.GetOffsetCurves(this._OffsetDist)[0]; if (curve) this._SubOffsetedCurves.push({ curve, index }); else this._TrimArcContours.push(Contour.CreateContour([curveOld, new Line(curveOld.StartPoint, curveOld.EndPoint)], false)); } } } //连接(延伸)曲线,或者补(圆弧,直线) LinkSubCurves() { let count = this._SubOffsetedCurves.length; if (!this._IsClose) count--; for (let i = 0; i < count; i++) { let curveResNow = this._SubOffsetedCurves[i]; let iNext = FixIndex$1(i + 1, this._SubOffsetedCurves); let curveResNext = this._SubOffsetedCurves[iNext]; let curveNow = curveResNow.curve; let curveNext = curveResNext.curve; let isNeighbor = FixIndex$1(curveResNow.index + 1, this._SubCurves) === curveResNext.index; if (isNeighbor) { let sp = curveNow.EndPoint; let ep = curveNext.StartPoint; //直连 if (equalv3(sp, ep, 1e-3)) continue; let iPts = curveNow.IntersectWith(curveNext, IntersectOption.ExtendBoth); let tPts = iPts.filter(p => curveNow.PtOnCurve3(p) && curveNext.PtOnCurve3(p)); let code = EntityEncode2(curveNow, curveNext); let tp; if (code === 1) { if (tPts.length > 0) //不走刀或者有真交点 this._ToolPath === false || tp = iPts[0]; else { if (iPts.length > 0 && curveNow.GetParamAtPoint(iPts[0]) > 1) { let refP = this._Vertexs[curveResNext.index]; let distSq = iPts[0].distanceToSquared(refP); if (this._ToolPath && distSq > this._OffsetDistSq) { curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)]; this._TrimCircleContours.push(this._Circles[curveResNext.index]); } else tp = iPts[0]; } // else // curveResNow.paddingCurve = [new Line(sp, ep)]; } } else { let refP = this._Vertexs[curveResNext.index]; if (tPts.length > 0) //ipts = 1 or ipts = 2 tp = SelectNearP(iPts, refP); else //补单圆 或者尝试连接 { let arc = this.CreateArc(refP, sp, ep); if (iPts.length > 0 && !this._ToolPath && this.IsSharpCorner(curveResNow, curveResNext, refP)) { //设置新的连接点,并且备份旧点 let oldp; if (curveResNow.sp) { oldp = curveNow.StartPoint; curveNow.StartPoint = curveResNow.sp; } let oldp2; if (curveResNext.ep) { oldp2 = curveNext.EndPoint; curveNext.EndPoint = curveResNext.ep; } let p; if (code === 2 && iPts.length === 2) { let c = curveNow; let minArc = new Arc(c.Center, c.Radius, c.EndAngle, 0, c.IsClockWise); let p1 = iPts[0]; let a1 = minArc.GetAngleAtPoint(p1); let anAll1 = c.ParamOnCurve(c.GetParamAtAngle(a1)) ? Infinity : minArc.ComputeAnlge(a1); let p2 = iPts[1]; let a2 = minArc.GetAngleAtPoint(p2); let anAll2 = c.ParamOnCurve(c.GetParamAtAngle(a2)) ? Infinity : minArc.ComputeAnlge(a2); if (anAll2 < anAll1) p = p2; else p = p1; } else p = SelectNearP(iPts, refP); let onPre; let param = curveNow.GetParamAtPoint2(p); if (curveNow instanceof Line) onPre = param > 1; else onPre = param < 0 || param > 1; let onNext = false; if (onPre) { let param2 = curveNext.GetParamAtPoint2(p); if (curveNext instanceof Line) onNext = param2 < 0; else onNext = param2 < 0 || param2 > 1; } if (curveResNow.sp) curveNow.StartPoint = oldp; if (curveResNext.ep) curveNext.EndPoint = oldp2; if (onPre && onNext) tp = p; else curveResNow.paddingCurve = [arc]; } else curveResNow.paddingCurve = [arc]; this._TrimCircleContours.push(this._Circles[curveResNext.index]); } } if (tp) { curveResNow.ep = tp; curveResNext.sp = tp; curveResNow.nextArc = curveNext; curveResNext.preArc = curveNow; } } else { let padCirs = []; for (let s = FixIndex$1(curveResNow.index + 1, this._Circles); ; s = FixIndex$1(s + 1, this._Circles)) { let c = this._Circles[s]; this._TrimCircleContours.push(c); padCirs.push(c); if (s === curveResNext.index) break; } curveResNow.paddingCurve = padCirs; } } } IsSharpCorner(curveResNow, curveResNext, refP) { let v1 = this._SubCurves[curveResNow.index].GetPointAtParam(0.9); let v2 = this._SubCurves[curveResNext.index].GetPointAtParam(0.1); v1.subVectors(refP, v1); v2.sub(refP); v1.cross(v2); return Math.sign(v1.z) === this._OffsetDistSign; } GeneralTrimContours() { for (let d of this._SubOffsetedCurves) { let cu2 = d.curve; if (d.sp && d.ep) { let param1 = cu2.GetParamAtPoint(d.sp); let param2 = cu2.GetParamAtPoint(d.ep); if (cu2.ParamOnCurve(param1) && cu2.ParamOnCurve(param2) && param1 > param2) [d.sp, d.ep] = [d.ep, d.sp]; } if (d.sp) cu2.StartPoint = d.sp; if (d.ep) cu2.EndPoint = d.ep; //这是极端情况,圆弧被压缩成0长度圆弧,本质是空圆弧(我们会在下面判断它)(因为精度的问题) //因为精度的问题,这种0圆心角的圆弧会被当成全圆,但是偏移算法中,应该不可能出现全圆弧的圆弧,所以我们压扁它 if (cu2 instanceof Arc && equaln(cu2.StartAngle, cu2.EndAngle, 1e-6) // && !equaln((this._SubCurves[d.index]).AllAngle, Math.PI * 2, 1e-3) 应该不会出现 ) { if (cu2.IsClockWise) cu2.StartAngle = cu2.EndAngle + 1e-6; else cu2.EndAngle = cu2.StartAngle + 1e-6; } } for (let d of this._SubOffsetedCurves) { let cu1 = this._SubCurves[d.index]; let cu2 = d.curve; let [p1, p2, p3, p4] = [cu1.StartPoint, cu2.StartPoint, cu1.EndPoint, cu2.EndPoint]; let l1 = new Line(p1, p2); let l2 = new Line(p3, p4); let ipts = l1.IntersectWith(l2, IntersectOption.OnBothOperands, 1e-8); if (ipts.length > 0) { let p = ipts[0]; l1.EndPoint = p; l2.EndPoint = p; let cus = [cu1, l1, l2]; let contour = Contour.CreateContour(cus, false); if (contour) { this._TrimPolylineContours.push(contour); continue; } else { console.error("未预料到的错误,构建轮廓失败" + this._OffsetDist); } } //真理1:针脚线不可能同时被两个圆弧所切割 let l1Intact = true; let l2Intact = true; if (cu2 instanceof Arc) { if (Math.sign(cu2.Bul) !== this._OffsetDistSign) { let ipts1 = cu2.IntersectWith(l1, IntersectOption.OnBothOperands); let ipts2 = cu2.IntersectWith(l2, IntersectOption.OnBothOperands); let sp; let ep; if (ipts1.length === 2) sp = SelectNearP(ipts1, p1); if (ipts2.length === 2) ep = SelectNearP(ipts2, p3); if (sp || ep) cu2 = cu2.Clone(); if (sp) { l1.EndPoint = sp; cu2.StartPoint = sp; l1Intact = false; } if (ep) { l2.EndPoint = ep; cu2.EndPoint = ep; l2Intact = false; } } } let l1PadArc; let l2PadArc; //真理2:隔壁的圆弧不可能破坏当前的圆弧,只能破坏当前的针脚 if (l1Intact && d.preArc && d.preArc instanceof Arc) { let a = d.preArc; if (Math.sign(a.Bul) !== this._OffsetDistSign && a.AllAngle > 1e-6) { let ipts = a.IntersectWith(l1, IntersectOption.OnBothOperands); if (ipts.length === 2) { let sp = SelectNearP(ipts, p1); l1.EndPoint = sp; l1PadArc = a.Clone(); l1PadArc.StartPoint = sp; } } } if (l2Intact && d.nextArc && d.nextArc instanceof Arc) { let a = d.nextArc; if (Math.sign(a.Bul) !== this._OffsetDistSign && a.AllAngle > 1e-6) { let ipts = a.IntersectWith(l2, IntersectOption.OnBothOperands); if (ipts.length === 2) { let ep = SelectNearP(ipts, p3); l2.EndPoint = ep; l2PadArc = a.Clone(); l2PadArc.EndPoint = ep; } } } let pl = new Polyline(); let cus = [cu1, l1]; if (l1PadArc) cus.push(l1PadArc); cus.push(cu2, l2); if (l2PadArc) cus.push(l2PadArc); for (let c of cus) pl.Join(c); let contour = Contour.CreateContour(pl, false); if (contour) this._TrimPolylineContours.push(contour); else console.error("未预料到的错误,构建轮廓失败" + this._OffsetDist); } if (!this._IsClose) { if (this._TrimCircleContours[0] !== this._Circles[0]) this._TrimCircleContours.push(this._Circles[0]); let lastTrimCircle = arrayLast(this._TrimCircleContours); let lastCircle = arrayLast(this._Circles); if (lastTrimCircle !== lastCircle) this._TrimCircleContours.push(lastCircle); if (this._SubOffsetedCurves[0].index !== 0) this._TrimCircleContours.push(this._Circles[this._SubOffsetedCurves[0].index]); let lastIndex = this._Circles.length - 1; let lastD = arrayLast(this._SubOffsetedCurves); if (lastIndex !== lastD.index) this._TrimCircleContours.push(this._Circles[lastD.index + 1]); } this._TrimPolylineContours.push(...this._TrimCircleContours.map(c => Contour.CreateContour(c, false)), ...this._TrimArcContours); } // 通过构建的轮廓对偏移曲线进行裁剪 TrimByContours() { for (let d of this._SubOffsetedCurves) { let c = d.curve; if (CurveIsFine(c)) this._CurveTreeNodes.push(new CurveTreeNode(c)); if (d.paddingCurve) this._CurveTreeNodes.push(...d.paddingCurve.map(c => new CurveTreeNode(c))); } for (let i = 0; i < this._TrimPolylineContours.length; i++) { let c = this._TrimPolylineContours[i]; let cbox = c.BoundingBox; for (let curveNode of this._CurveTreeNodes) curveNode.TrimBy(c, cbox); } } //过滤方向相反和0长度线 FilterInvalidCurve() { this._CurveTrimedTreeNodes = []; for (let n of this._CurveTreeNodes) { let ns = n.Nodes; for (let sn of ns) { if (this.CheckPointDir(sn.curve.GetPointAtParam(0.5))) this._CurveTrimedTreeNodes.push(sn); } } } //合并共线 JoinCollinear() { for (let i = 0; i < this._CurveTrimedTreeNodes.length; i++) { let n = this._CurveTrimedTreeNodes[i]; if (n.used) continue; let sp = n.curve.StartPoint; for (let j = i + 1; j < this._CurveTrimedTreeNodes.length; j++) { let n2 = this._CurveTrimedTreeNodes[j]; if (n2.used) continue; let status = n.curve.Join(n2.curve); if (status === Status.ConverToCircle) { n.used = true; n2.used = true; let circle = new Circle(n.curve.Center, n.curve.Radius); n.curve = circle; this._RetCurves.push(ConverCircleToPolyline(circle).ApplyMatrix(this._CacheOCS)); } else if (status === Status.True) { if (equalv3(sp, n.curve.StartPoint)) n2.used = true; else { n.used = true; n2.curve = n.curve; break; } } } } } //连接结果曲线,返回最终多段线 LinkResultPolyline() { let used = new Set(); let cuMap = new CurveMap(1); for (let n of this._CurveTrimedTreeNodes) { if (!n.used) cuMap.AddCurveToMap(n.curve); } let preP; let searchNext = (s, pl) => { let minDist = Infinity; let minR; for (let r of s.routes) { if (used.has(r.curve)) continue; if (preP) { let d = r.s.distanceToSquared(preP); if (d < minDist) { minR = r; minDist = d; } } else { minR = r; break; } } if (minR) { used.add(minR.curve); preP = minR.e; let status = pl.Join(minR.curve, false, 8e-2); if (status !== Status.True) console.warn("连接失败"); return minR.to; } }; for (let s of cuMap.Stands) { preP = undefined; let pl = new Polyline(); let ss = s; while (ss && !pl.IsClose) ss = searchNext(ss, pl); ss = s; while (ss && !pl.IsClose) ss = searchNext(ss, pl); if (pl.NumberOfVertices > 1) { //避免0长度的线 if (pl.NumberOfVertices === 2 && pl.Length < 1e-6) continue; let d = pl.LineData; let ld = arrayLast(d); if (equalv2(d[0].pt, ld.pt, 1e-2)) ld.pt.copy(d[0].pt); this._RetCurves.push(pl.ApplyMatrix(this._CacheOCS)); } } } RepairResultPolylineClosemark() { if (!this._RetCurves.length) return; if (this._Polyline.CloseMark) { if (!equalv2(this._Polyline.LineData[0].pt, arrayLast(this._Polyline.LineData).pt, 8e-2)) //缺省一个点 { for (let pl of this._RetCurves) { if (pl.IsClose && //封闭 equaln(arrayLast(pl.LineData).bul, 0, 1e-5) && //是直线 equalv2(pl.LineData[0].pt, arrayLast(pl.LineData).pt, 8e-2)) //首尾重复(一般已经是了) { pl.LineData.pop(); //移除最后一点 pl.CloseMark = true; } } } else { for (let pl of this._RetCurves) { if (pl.IsClose) pl.CloseMark = true; } } } else if (this._IsClose) { for (let pl of this._RetCurves) { let firstP = pl.LineData[0].pt; let lastP = arrayLast(pl.LineData).pt; if (equalv2(firstP, lastP, 8e-2)) lastP.copy(firstP); } } } CheckPointDir(pt) { return this.GetPointAtCurveDir(pt) === this._OffsetDistSign; } GetPointAtCurveDir(pt) { let minIndex = Infinity; let minDist = Infinity; let minCp; for (let i = 0; i < this._SubCurves.length; i++) { let c = this._SubCurves[i]; let cp = c.GetClosestPointTo(pt, false); if (equalv3(cp, pt, 1e-5)) return 0; let dist = cp.distanceToSquared(pt); if (dist < minDist) { minDist = dist; minIndex = i; minCp = cp; } } let c = this._SubCurves[minIndex]; let param = c.GetParamAtPoint(minCp); if (equaln(param, 0) && ((minIndex === 0) ? this._IsClose : true)) { let preIndex = FixIndex$1(minIndex - 1, this._SubCurves); let preCurve = this._SubCurves[preIndex]; if (!equalv3(c.GetFistDeriv(0).normalize(), preCurve.GetFistDeriv(1).normalize())) { let p = c.StartPoint; let l1 = c.Length; let l2 = preCurve.Length; let minLength = Math.min(l1, l2) * 0.2; let nextP; let preP; if (c instanceof Arc) nextP = c.GetPointAtDistance(minLength); else nextP = c.EndPoint; if (preCurve instanceof Arc) preP = preCurve.GetPointAtDistance(l2 - minLength); else preP = preCurve.StartPoint; let arc = new Arc(p, 1, angle(preP.sub(p)), angle(nextP.sub(p))); let dir = arc.PtOnCurve3(pt) ? -1 : 1; return dir; } } else if (equaln(param, 1) && ((minIndex === this._SubCurves.length - 1) ? this._IsClose : true)) { let nextIndex = FixIndex$1(minIndex + 1, this._SubCurves); let nextCurve = this._SubCurves[nextIndex]; if (!equalv3(c.GetFistDeriv(1).normalize(), nextCurve.GetFistDeriv(0).normalize())) { let p = c.EndPoint; let l1 = c.Length; let l2 = nextCurve.Length; let minLength = Math.min(l1, l2) * 0.2; let nextP; let preP; if (c instanceof Arc) preP = c.GetPointAtDistance(l1 - minLength); else preP = c.StartPoint; if (nextCurve instanceof Arc) nextP = nextCurve.GetPointAtDistance(minLength); else nextP = nextCurve.EndPoint; let arc = new Arc(p, 1, angle(preP.sub(p)), angle(nextP.sub(p))); let dir = arc.PtOnCurve3(pt) ? -1 : 1; return dir; } } let dri = c.GetFistDeriv(param); let cross = dri.cross(pt.clone().sub(minCp)); return -Math.sign(cross.z); } CreateArc(center, startP, endP) { let sa = angle(startP.clone().sub(center)); let ea = endP ? angle(endP.clone().sub(center)) : sa; let arc = new Arc(center, Math.abs(this._OffsetDist), sa, ea, this._OffsetDist < 0); return arc; } } function EntityEncode(c) { if (c instanceof Line) return 1; else return 2; } function EntityEncode2(c1, c2) { return EntityEncode(c1) & EntityEncode(c2); } //表示这个是一个正常的曲线,不是0长度的线,也不是0长度的圆弧 function CurveIsFine(curve) { if (curve instanceof Arc && curve.AllAngle < 2e-6) return false; return curve.Length > 5e-5; } /** * 判断点在多段线内外 * @param pl 多段线 * @param pt 点 * @returns 点在多段线内部 */ function IsPointInPolyLine(pl, pt) { let crossings = 0; let insLine = new Line(pt, pt.clone().add(new Vector3(0, 10, 0))); for (let i = 0; i < pl.EndParam; i++) { if (equaln(pl.GetBuilgeAt(i), 0, 5e-6)) //直线 { let sp = pl.GetPointAtParam(i); let ep = pl.GetPointAtParam(i + 1); //点位于线上面 if (pt.y > Math.max(sp.y, ep.y)) continue; //线垂直Y轴 let derX = ep.x - sp.x; if (equaln(derX, 0, 5e-6)) continue; //起点 if (equaln(sp.x, pt.x, 5e-6)) { if (sp.y > pt.y && derX < 0) crossings++; continue; } //终点 if (equaln(ep.x, pt.x, 5e-6)) { if (ep.y > pt.y && derX > 0) crossings++; continue; } //快速求交,只验证有没有交点 let [x1, x2] = sp.x > ep.x ? [ep.x, sp.x] : [sp.x, ep.x]; if (pt.x > x1 && pt.x < x2) { let derY = ep.y - sp.y; let k = derY / derX; if ((pt.x - sp.x) * k + sp.y > pt.y) crossings++; } } else //圆弧 { let arc = pl.GetCurveAtIndex(i); let sp = arc.StartPoint; let ep = arc.EndPoint; //如果相切 if (equaln(Math.abs(pt.x - arc.Center.x), arc.Radius)) { //当点和起点或者终点和点相切时 if (equaln(sp.x, pt.x) && sp.y > pt.y) { if (ep.x - sp.x < -1e-5) crossings++; } else if (equaln(ep.x, pt.x) && ep.y > pt.y) { if (ep.x - sp.x > 1e-5) crossings++; } continue; } if (equaln(sp.x, pt.x) && sp.y > pt.y) { let der = arc.GetFistDeriv(0); if (der.x < -1e-5) crossings++; } if (equaln(ep.x, pt.x) && ep.y > pt.y) { let der = arc.GetFistDeriv(1); if (der.x > 1e-5) crossings++; } for (let pti of arc.IntersectWith(insLine, IntersectOption.ExtendArg)) { if (pti.y < pt.y || equalv3(sp, pti, 1e-5) || equalv3(ep, pti, 1e-5)) continue; let der = arc.GetFistDeriv(pti); if (!equaln(der.x, 0)) //相切. crossings++; } } } return (crossings % 2) === 1; } var DragPointType; (function (DragPointType) { DragPointType[DragPointType["Grip"] = 0] = "Grip"; DragPointType[DragPointType["Stretch"] = 1] = "Stretch"; })(DragPointType || (DragPointType = {})); var Polyline_1; let Polyline = Polyline_1 = class Polyline extends Curve { constructor(_LineData = []) { super(); this._LineData = _LineData; this._ClosedMark = false; } UpdateMatrixTo(m) { this.WriteAllObjectRecord(); let p = new Vector3().setFromMatrixPosition(m); p.applyMatrix4(this.OCSInv); if (equaln(p.z, 0)) { let dir = Math.sign(this.Area2); let tm = matrixAlignCoordSys(this.OCS, m); for (let p of this._LineData) { let p3 = AsVector3(p.pt); p3.applyMatrix4(tm); p.pt.set(p3.x, p3.y); } this.OCS = m; let newDir = Math.sign(this.Area2); if (dir !== newDir) for (let p of this._LineData) p.bul *= -1; } } /** * 原地翻转,仅改变法向量 */ Flip() { this.WriteAllObjectRecord(); let x = new Vector3(); let y = new Vector3(); let z = new Vector3(); this._Matrix.extractBasis(x, y, z); z.negate(); y.crossVectors(z, x); let p = this.Position; this._Matrix.makeBasis(x, y, z).setPosition(p); for (let d of this._LineData) { d.pt.y *= -1; d.bul *= -1; } this.Update(); return this; } //翻转曲线,首尾调换 Reverse() { if (this._LineData.length === 0) return this; this.WriteAllObjectRecord(); let pts = []; let buls = []; for (let data of this._LineData) { pts.push(data.pt); buls.push(-data.bul); } let lastBul = buls.pop(); buls.reverse(); buls.push(lastBul); pts.reverse(); if (this._ClosedMark && !equalv2(pts[0], arrayLast(pts))) { pts.unshift(pts.pop()); buls.unshift(buls.pop()); } for (let i = 0; i < pts.length; i++) { let d = this._LineData[i]; d.pt = pts[i]; d.bul = buls[i]; } return this; } set LineData(data) { this.WriteAllObjectRecord(); this._LineData = data; this.Update(); } get LineData() { return this._LineData; } get NumberOfVertices() { return this._LineData.length; } /** * 在指定位置插入点. * 例如: * pl.AddVertexAt(pl.NumberOfVerticesk,p);//在末尾插入一个点 * * @param {number} index 索引位置 * @param {Vector2} pt 点 * @returns {this} * @memberof Polyline */ AddVertexAt(index, pt) { this.WriteAllObjectRecord(); let pts; if (Array.isArray(pt)) { pts = pt.map(p => { return { pt: p.clone(), bul: 0 }; }); } else pts = [{ pt: pt.clone(), bul: 0 }]; this._LineData.splice(index, 0, ...pts); this.Update(); return this; } RemoveVertexAt(index) { if (index < this._LineData.length) { this.WriteAllObjectRecord(); this._LineData.splice(index, 1); this.Update(); } return this; } RemoveVertexIn(from, to) { if (from + 1 < this._LineData.length && to > from) { this.WriteAllObjectRecord(); this._LineData.splice(from + 1, to - from - 1); this.Update(); } return this; } /** * 重设闭合多段线的起点 * @param index 起始index,如果index非整数,将用最接近的整数作为起始索引 */ ResetStartPoint(index) { if (!this.IsClose || index >= this.EndParam) return false; if (equalv2(this._LineData[0].pt, arrayLast(this._LineData).pt)) this._LineData.pop(); changeArrayStartIndex(this._LineData, Math.floor(index + 0.5)); this._LineData.push({ pt: this._LineData[0].pt.clone(), bul: 0 }); return true; } GetPoint2dAt(index) { if (index >= 0 && this._LineData.length > index) return this._LineData[index].pt.clone(); } /** * 设置指定点的位置 * * @param {number} index * @param {Vector2} pt * @memberof Polyline */ SetPointAt(index, pt) { let d = this._LineData[index]; if (d) { this.WriteAllObjectRecord(); d.pt.copy(pt); this.Update(); } return this; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); for (let i = 0; i <= this.EndParam; i++) { let p = this.GetPointAtParam(i); p.applyMatrix4(m).applyMatrix4(this.OCSInv); this.SetPointAt(i, AsVector2(p)); } return this; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let oldPts = this.GetStretchPoints(); reviseMirrorMatrix(this._Matrix); for (let i = 0; i < oldPts.length; i++) { let newP = oldPts[i].applyMatrix4(this.OCSInv); let newBul = -this.GetBuilgeAt(i); this.SetPointAt(i, AsVector2(newP)); this.SetBulgeAt(i, newBul); } this.Reverse(); return this; } SetBulgeAt(index, bul) { let d = this._LineData[index]; if (d) { this.WriteAllObjectRecord(); d.bul = bul; this.Update(); } return this; } GetBuilgeAt(index) { return this._LineData[index].bul; } Rectangle(length, height) { this.LineData = [ { pt: new Vector2(), bul: 0 }, { pt: new Vector2(length), bul: 0 }, { pt: new Vector2(length, height), bul: 0 }, { pt: new Vector2(0, height), bul: 0 } ]; this.CloseMark = true; return this; } RectangleFrom2Pt(p1, p2) { let box = new Box3(); box.setFromPoints([p2, p1].map((p) => p.clone().applyMatrix4(this.OCSInv))); let px1 = AsVector2(box.min); let px3 = AsVector2(box.max); let px2 = new Vector2(px3.x, px1.y); let px4 = new Vector2(px1.x, px3.y); this.LineData = [ { pt: px1, bul: 0 }, { pt: px2, bul: 0 }, { pt: px3, bul: 0 }, { pt: px4, bul: 0 } ]; this.CloseMark = true; return this; } //多段线起点 get StartPoint() { if (this._LineData.length > 0) return AsVector3(this._LineData[0].pt).applyMatrix4(this.OCS); return new Vector3(); } set StartPoint(p) { this.WriteAllObjectRecord(); p = p.clone().applyMatrix4(this.OCSInv); if (this._LineData.length === 0) this.AddVertexAt(0, AsVector2(p)); else if (this._LineData.length === 1) this.SetPointAt(0, AsVector2(p)); else { let bul = this.GetBuilgeAt(0); if (bul !== 0) { let arc = this.GetCurveAtParam(0); arc.StartPoint = p; //前面线的凸度调整 this.SetBulgeAt(0, Math.tan(arc.AllAngle / 4) * Math.sign(bul)); } this.SetPointAt(0, AsVector2(p)); } } get EndPoint() { if (this._ClosedMark) return this.StartPoint; if (this._LineData.length > 0) return AsVector3(this._LineData[this.EndParam].pt).applyMatrix4(this.OCS); return new Vector3(); } set EndPoint(p) { if (this._LineData.length < 2 || this.CloseMark) return; this.WriteAllObjectRecord(); p = p.clone().applyMatrix4(this.OCSInv); let bul = this.GetBuilgeAt(this.EndParam - 1); if (bul !== 0) { let arc = this.GetCurveAtParam(this.EndParam - 1); arc.ApplyMatrix(this.OCSInv); arc.EndPoint = p; //前面线的凸度调整 this.SetBulgeAt(this.EndParam - 1, Math.tan(arc.AllAngle / 4) * Math.sign(bul)); } this.SetPointAt(this.EndParam, AsVector2(p)); } get CurveCount() { return this.EndParam; } get StartParam() { return 0; } /** * 表示最后一条曲线的终止参数,使用该参数可以直接遍历到多段线的所有子线段. for(i 1 && (equalv3(this.StartPoint, this.EndPoint, 1e-4))); } set CloseMark(v) { this.WriteAllObjectRecord(); this._ClosedMark = v; this.Update(); } DigestionCloseMark() { if (this._ClosedMark && this._LineData.length > 1) { this.WriteAllObjectRecord(); this._ClosedMark = false; if (!equalv2(this._LineData[0].pt, arrayLast(this._LineData).pt)) this._LineData.push({ pt: AsVector2(this._LineData[0].pt), bul: 0 }); } } get Length() { return this.Explode().reduce((l, cu) => l + cu.Length, 0); } /** * 获得指定参数所在的点. * 当曲线存在闭合标志时,参数必须在曲线内部. * 当曲线不存在闭合标志时,参数允许延伸出曲线. * * @param {number} param 参数 * @returns {Vector3} 三维点,可为空 * @memberof Polyline */ GetPointAtParam(param) { if (param === Math.floor(param) && this.ParamOnCurve(param)) return AsVector3(this.GetPoint2dAt(FixIndex$1(param, this.NumberOfVertices))).applyMatrix4(this.OCS); let cu = this.GetCurveAtParam(param); if (cu) return cu.GetPointAtParam(this.GetCurveParamAtParam(param)); return undefined; } GetDistAtParam(param) { if (this._ClosedMark && !this.ParamOnCurve(param)) return NaN; //参数 整数 let paramFloor = Math.floor(param); //需要计算的曲线个数 let cuCout = paramFloor > this.EndParam ? this.EndParam : paramFloor; let dist = 0; //首先计算完整曲线的长度 for (let i = 0; i < cuCout; i++) { dist += this.GetCurveAtIndex(i).Length; } //参数已经大于索引,证明参数在线外. if (paramFloor !== cuCout) { dist += this.GetCurveAtParam(param).GetDistAtParam(param - cuCout); } else if (param > paramFloor) { let lastParam = param - paramFloor; dist += this.GetCurveAtParam(param).GetDistAtParam(lastParam); } return dist; } GetPointAtDistance(dist) { let param = this.GetParamAtDist(dist); return this.GetPointAtParam(param); } /** * 返回参数所在的点. 如果曲线不闭合,会试图返回延伸点参数 * * @param {Vector3} pt * @returns {number} * @memberof Polyline */ GetParamAtPoint(pt) { let cus = this.Explode(); if (cus.length === 0) return NaN; for (let i = 0; i < cus.length; i++) { let cu = cus[i]; let param = cu.GetParamAtPoint(pt); if (cu.ParamOnCurve(param)) return i + param; //返回点在曲线内部的参数 } //当曲线闭合时,不需要延伸首尾去判断参数 if (this._ClosedMark) return NaN; //起点终点参数集合 let seParams = []; //点在第一条曲线上的参数 let startParam = cus[0].GetParamAtPoint(pt); if (!isNaN(startParam) && startParam < 0) seParams.push(startParam); //点在最后一条线上的参数 let endParam = cus[cus.length - 1].GetParamAtPoint(pt); if (!isNaN(endParam) && endParam > 0) seParams.push(endParam + this.EndParam - 1); if (seParams.length == 1) { return seParams[0]; } else if (seParams.length == 2) { //返回较近的参数 if (pt.distanceToSquared(this.StartPoint) < pt.distanceToSquared(this.EndPoint)) return seParams[0]; else return seParams[1]; } return NaN; } GetParamAtDist(dist) { let cus = this.Explode(); for (let i = 0; i < cus.length; i++) { let cu = cus[i]; let len = cu.Length; if (dist <= len) return i + cu.GetParamAtDist(dist); else if (equaln(dist, len, 1e-8)) return i + 1; dist -= len; } if (!this._ClosedMark) return cus.length + cus[cus.length - 1].GetParamAtDist(dist); return NaN; } GetDistAtPoint(pt) { let param = this.GetParamAtPoint(pt); if (!this.ParamOnCurve(param)) return NaN; return this.GetDistAtParam(param); } /** * 返回曲线的一阶导数. * 当曲线闭合(标志)且点不在曲线上. * 或者曲线不闭合(标志) 且点不在曲线上也不在延伸上 * * @param {(number | Vector3)} param * @returns {Vector3} * @memberof Polyline */ GetFistDeriv(param) { if (param instanceof Vector3) param = this.GetParamAtPoint(param); if (isNaN(param)) return undefined; let cu = this.GetCurveAtParam(param); if (!cu) return undefined; return cu.GetFistDeriv(this.GetCurveParamAtParam(param)); } GetSplitCurves(param) { //参数需要转化为参数数组 let params; if (typeof param == "number") params = [param]; else params = param; //校验参数在曲线中,修正参数 let endParam = this.EndParam; params = params.filter(p => this.ParamOnCurve(p) && p > -1e-6) .map(a => { if (a < 0) return 0; if (a > endParam) return endParam; if (equaln(a, Math.floor(a + 0.5), 1e-8)) return Math.floor(a + 0.5); return a; }); //排序 params.sort((a, b) => a - b); let hasEndParam = arrayLast(params) === this.EndParam; //必须加入最后一个参数,保证切割后的曲线完整 if (!hasEndParam) params.push(this.EndParam); arrayRemoveDuplicateBySort(params, (e1, e2) => equaln(e1, e2, 1e-8)); params = params.filter(p => this.ParamOnCurve(p)); if (params.length === 0) return []; //判断是否存在0参数 let hasZeroParam = params[0] === 0; if (hasZeroParam) params.shift(); let { pts, buls } = this.PtsBuls; //返回的多段线集合 let pls = []; let len = 0; //已经走过的参数长度(整数) //上一个切割参数的位置 0-1 let prePa = 0; for (let pa of params) { //参数所在点 let pt = AsVector2(this.GetPointAtParam(pa).applyMatrix4(this.OCSInv)); pa -= len; let pafloor = Math.floor(pa); len += pafloor; let plData = []; //添加点 for (let i = 0; i < pafloor; i++) { if (i === 0 && !equaln(buls[0], 0, 1e-8)) { buls[0] = Math.tan((1 - prePa) * Math.atan(buls[0])); } plData.push({ pt: pts[0], bul: buls[0] }); pts.shift(); buls.shift(); } if (equaln(pa, pafloor, 1e-8)) //如果pa在点上 { plData.push({ pt: pts[0].clone(), bul: buls[0] }); } else //在曲线上 { let bul = buls[0]; if (!equaln(bul, 0, 1e-6)) bul = Math.tan((pa - pafloor - (0 === pafloor ? prePa : 0)) * Math.atan(buls[0])); //->凸度 //加入顶点+凸度 plData.push({ pt: pts[0].clone(), bul }); //终点 plData.push({ pt, bul: 0 }); //修正剩余的点表和凸度表 pts[0].copy(pt); } prePa = pa - pafloor; if (plData.length > 1) { let pl = new Polyline_1(plData).ApplyMatrix(this.OCS); pl.ColorIndex = this.ColorIndex; pls.push(pl); } } //当曲线为闭合曲线,并且不存在0切割参数时,首尾连接曲线 if (this._ClosedMark && !hasZeroParam && !hasEndParam) { let lastPl = pls[pls.length - 1]; if (equalv2(arrayLast(lastPl._LineData).pt, pls[0]._LineData[0].pt)) lastPl._LineData.pop(); lastPl._LineData.push(...pls[0]._LineData); pls.shift(); } return pls; } //未完善 GetCurveAtParamRange(startParam, endParam) { let sfloor = Math.floor(startParam + 0.5); if (equaln(sfloor, startParam, 1e-8)) startParam = sfloor; else sfloor = Math.floor(startParam); let efloor = Math.floor(endParam + 0.5); if (equaln(efloor, endParam, 1e-8)) endParam = efloor; else efloor = Math.floor(efloor); const GetCurve = (index) => { let d = this._LineData[index]; let next = this._LineData[index + 1]; if (!equaln(d.bul, 0, 1e-8)) return new Arc().ParseFromBul(d.pt, next.pt, d.bul); else return new Line(AsVector3(d.pt), AsVector3(next.pt)); }; let lined = []; if (startParam === sfloor) { let d = this._LineData[sfloor]; lined.push({ pt: d.pt.clone(), bul: d.bul }); } else { let d = this._LineData[sfloor]; let cu = GetCurve(sfloor); let remParam = startParam - sfloor; let p = cu.GetPointAtParam(remParam); let bul = d.bul; if (!equaln(bul, 0)) bul = Math.tan(Math.atan(bul) * (1 - remParam)); lined.push({ pt: AsVector2(p), bul: bul }); } for (let i = sfloor + 1; i < efloor; i++) { let d = this._LineData[i]; lined.push({ pt: d.pt.clone(), bul: d.bul }); } if (efloor !== endParam) { let d = this.LineData[efloor]; let remParam = endParam - efloor; let cu = GetCurve(efloor); let p = cu.GetPointAtParam(remParam); let bul = d.bul; if (!equaln(bul, 0)) { arrayLast(lined).bul = Math.tan(Math.atan(bul) * remParam); bul = Math.tan(Math.atan(bul) * (1 - remParam)); } lined.push({ pt: AsVector2(p), bul }); } let pl = new Polyline_1(lined); pl.OCS = this.OCSNoClone; return; } Extend(newParam) { if (this.CloseMark || this.ParamOnCurve(newParam)) return; this.WriteAllObjectRecord(); let ptIndex; let bulIndex; if (newParam < 0) { ptIndex = 0; bulIndex = 0; } else if (newParam > this.EndParam) { ptIndex = this.EndParam; bulIndex = ptIndex - 1; } //修改顶点 this._LineData[ptIndex].pt = AsVector2(this.GetPointAtParam(newParam).applyMatrix4(this.OCSInv)); //修改凸度 let oldBul = this._LineData[bulIndex].bul; if (oldBul != 0) this._LineData[bulIndex].bul = Math.tan(Math.atan(oldBul) * (1 + newParam - ptIndex)); this.Update(); } //const this MatrixAlignTo2(toMatrix) { if (!matrixIsCoplane(this._Matrix, toMatrix, 1e-4)) return this.PtsBuls; let m = matrixAlignCoordSys(this._Matrix, toMatrix); let z1 = this.Normal; let z2 = new Vector3().setFromMatrixColumn(toMatrix, 2); let isMirror = equalv3(z1, z2.negate()); let pts = []; let buls = []; for (let d of this._LineData) { let p = AsVector2(AsVector3(d.pt).applyMatrix4(m)); pts.push(p); buls.push(isMirror ? -d.bul : d.bul); } return { pts, buls }; } Join(cu, allowGap = false, tolerance = 1e-4) { this.WriteAllObjectRecord(); if (this._ClosedMark) return Status.False; let [sp, ep, cuSp, cuEp] = [this.StartPoint, this.EndPoint, cu.StartPoint, cu.EndPoint]; let ocsInv = this.OCSInv; let [cuSp2, cuEp2] = [cuSp, cuEp].map(p => AsVector2(p.clone().applyMatrix4(ocsInv))); if (this._LineData.length === 0) { if (cu instanceof Line) { this._LineData.push({ pt: cuSp2, bul: 0 }); this._LineData.push({ pt: cuEp2, bul: 0 }); } else if (cu instanceof Arc) { this._LineData.push({ pt: cuSp2, bul: cu.Bul }); this._LineData.push({ pt: cuEp2, bul: 0 }); } else if (cu instanceof Polyline_1) { let f = new CADFiler(); cu.WriteFile(f); this.ReadFile(f); } else return Status.False; } else { let LinkType; (function (LinkType) { LinkType[LinkType["None"] = 0] = "None"; LinkType[LinkType["SpSp"] = 1] = "SpSp"; LinkType[LinkType["SpEp"] = 2] = "SpEp"; LinkType[LinkType["EpSp"] = 3] = "EpSp"; LinkType[LinkType["EpEp"] = 4] = "EpEp"; })(LinkType || (LinkType = {})); let spspDisSq = cuSp.distanceToSquared(sp); let spepDisSq = cuSp.distanceToSquared(ep); let epspDisSq = cuEp.distanceToSquared(sp); let epepDisSq = cuEp.distanceToSquared(ep); let minDis = tolerance * tolerance; let linkType = LinkType.None; if (spspDisSq < minDis) { linkType = LinkType.SpSp; minDis = spspDisSq; } if (spepDisSq < minDis) { linkType = LinkType.SpEp; minDis = spepDisSq; } if (epspDisSq < minDis) { linkType = LinkType.EpSp; minDis = epspDisSq; } if (epepDisSq < minDis) linkType = LinkType.EpEp; if (linkType === LinkType.None) return Status.False; if (cu instanceof Line) { if (linkType === LinkType.SpSp) { this._LineData.unshift({ pt: cuEp2, bul: 0 }); } else if (linkType === LinkType.SpEp) { this._LineData.push({ pt: cuEp2, bul: 0 }); } else if (linkType === LinkType.EpSp) { this._LineData.unshift({ pt: cuSp2, bul: 0 }); } else if (linkType === LinkType.EpEp) { this._LineData.push({ pt: cuSp2, bul: 0 }); } } else if (cu instanceof Arc) { let dir = equalv3(this.Normal, cu.Normal.negate()) ? -1 : 1; let bul = cu.Bul * dir; if (linkType === LinkType.SpSp) { this._LineData.unshift({ pt: cuEp2, bul: -bul }); } else if (linkType === LinkType.SpEp) { arrayLast(this._LineData).bul = bul; this._LineData.push({ pt: cuEp2, bul: 0 }); } else if (linkType === LinkType.EpSp) { this._LineData.unshift({ pt: cuSp2, bul: bul }); } else if (linkType === LinkType.EpEp) { arrayLast(this._LineData).bul = -bul; this._LineData.push({ pt: cuSp2, bul: 0 }); } } else if (cu instanceof Polyline_1) { if (cu.CloseMark) return Status.False; let { pts, buls } = this.PtsBuls; if (linkType === LinkType.SpSp) { cu.Reverse(); let cuPtsBul = cu.MatrixAlignTo2(this.OCS); cuPtsBul.pts.pop(); cuPtsBul.buls.pop(); pts = cuPtsBul.pts.concat(pts); buls = cuPtsBul.buls.concat(buls); } else if (linkType === LinkType.SpEp) { pts.pop(); buls.pop(); let cuPtsBul = cu.MatrixAlignTo2(this.OCS); pts = pts.concat(cuPtsBul.pts); buls = buls.concat(cuPtsBul.buls); } else if (linkType === LinkType.EpSp) { let cuPtsBul = cu.MatrixAlignTo2(this.OCS); cuPtsBul.pts.pop(); cuPtsBul.buls.pop(); pts = cuPtsBul.pts.concat(pts); buls = cuPtsBul.buls.concat(buls); } else if (linkType === LinkType.EpEp) { pts.pop(); buls.pop(); cu.Reverse(); let cuPtsBul = cu.MatrixAlignTo2(this.OCS); pts = pts.concat(cuPtsBul.pts); buls = buls.concat(cuPtsBul.buls); } this._LineData.length = 0; for (let i = 0; i < pts.length; i++) { this._LineData.push({ pt: pts[i], bul: buls[i] }); } } else return Status.False; } //在上面的其他分支已经返回了假 所以这里直接返回真. this.Update(); return Status.True; } /** * 将曲线数组组合成多段线 * @param curves 已经使用CurveLinked的数组,总是首尾相连 * @returns */ static Combine(curves, tolerance = 1e-5) { if (!curves || curves.length === 0) return; let pl = new Polyline_1; pl.OCS = ComputerCurvesNormalOCS(curves); for (let cu of curves) pl.Join(cu, false, tolerance); let d = pl.LineData; if (d.length > 1) { let ld = arrayLast(d).pt; if (equalv2(d[0].pt, ld, tolerance)) ld.copy(d[0].pt); } return pl; } /**首尾相连的曲线直接连接 */ static FastCombine(curves, tolerance = 1e-5) { if (!curves || curves.length === 0) return; let pl = new Polyline_1; pl.OCS = ComputerCurvesNormalOCS(curves); let ocsInv = pl.OCSInv; let normal = pl.Normal; let lineData = []; for (let i = 0; i < curves.length; i++) { let cu = curves[i]; let bul = 0; if (cu instanceof Arc) { let dir = equalv3(normal, cu.Normal.negate(), 1e-3) ? -1 : 1; bul = cu.Bul * dir; } lineData.push({ pt: AsVector2(cu.StartPoint.applyMatrix4(ocsInv)), bul }); if (i === curves.length - 1) { lineData.push({ pt: AsVector2(cu.EndPoint.applyMatrix4(ocsInv)), bul: 0 }); } } if (lineData.length > 1) { let ld = arrayLast(lineData).pt; if (equalv2(lineData[0].pt, ld, tolerance)) ld.copy(lineData[0].pt); } pl.LineData = lineData; return pl; } PtOnCurve(pt) { for (let i = 0; i < this.EndParam; i++) { let c = this.GetCurveAtIndex(i); if (c.PtOnCurve(pt)) return true; } return false; } //点在曲线上,已经确定点在曲线的延伸线上 PtOnCurve3(p, fuzz = 1e-6) { for (let i = 0; i < this.EndParam; i++) { let c = this.GetCurveAtIndex(i); if (c.PtOnCurve3(p, fuzz)) return true; } return false; } PtInCurve(pt) { return this.IsClose && IsPointInPolyLine(this, pt); } GetClosestPointTo(pt, extend) { return this.GetClosestPointTo2(pt, extend ? ExtendType.Both : ExtendType.None); } GetClosestPointTo2(pt, extType) { //当曲线空时,返回空 if (this.EndParam < 1) return undefined; //当有闭合标志时,曲线在任何位置都不延伸 if (this._ClosedMark) extType = ExtendType.None; //最近点 let ptC = undefined; //最近点的距离 let ptCDist = Infinity; for (let i = 0; i < this.EndParam; i++) { let cu = this.GetCurveAtIndex(i); //前延伸 if (i === 0 && (extType & ExtendType.Front) > 0) { let ptCFirst = cu.GetClosestPointTo(pt, true); if (cu.GetParamAtPoint(ptCFirst) <= 1) { ptC = ptCFirst; ptCDist = ptC.distanceToSquared(pt); } if (extType === ExtendType.Front) continue; } let ptCloseNew; //新的最近点 //后延伸 (此处与前延伸分开if 如果线只有一段,那么前后延伸都能同时触发) if (i === (this.EndParam - 1) && (extType & ExtendType.Back) > 0) { let ptCLast = cu.GetClosestPointTo(pt, true); if (cu.GetParamAtPoint(ptCLast) >= 0) ptCloseNew = ptCLast; else //如果延伸之后并不在曲线或者曲线的后延伸上 ptCloseNew = cu.EndPoint; } else { ptCloseNew = cu.GetClosestPointTo(pt, false); } let newDist = ptCloseNew.distanceToSquared(pt); if (newDist < ptCDist) { ptC = ptCloseNew; ptCDist = newDist; } } return ptC; } //偏移 GetOffsetCurves(offsetDist) { if (equaln(offsetDist, 0)) return []; let polyOffestUtil = new OffsetPolyline(this, offsetDist); let curves = polyOffestUtil.Do(); for (let cu of curves) cu.ColorIndex = this.ColorIndex; return curves; } GetFeedingToolPath(offsetDist, offsetDistSq = (offsetDist ** 2) * 2.1) { if (equaln(offsetDist, 0)) return []; let polyOffestUtil = new OffsetPolyline(this, offsetDist, true, offsetDistSq); return polyOffestUtil.Do(); } /** * 分解 */ Explode() { let exportCus = []; for (let i = 0; i < this.EndParam; i++) { exportCus.push(this.GetCurveAtIndex(i)); } return exportCus; } /** * 根据参数得到参数所在的子曲线. * * 当曲线存在闭合标志时,参数必须在曲线内部,否则返回空. * * @param {number} param 参数值 * @returns {Curve} 曲线(直线或者圆弧) 或空 * @memberof Polyline */ GetCurveAtParam(param) { if (this._ClosedMark && !this.ParamOnCurve(param)) return undefined; if (param < 0) return this.GetCurveAtIndex(0); else if (param >= this.EndParam) return this.GetCurveAtIndex(this.EndParam - 1); else return this.GetCurveAtIndex(Math.floor(param)); } /** * 得到参数在子曲线中的表示 * * @param {number} param 参数在多段线中表示 * @returns {number} 参数在子曲线中表示 * @memberof Polyline */ GetCurveParamAtParam(param) { if (param >= this.EndParam) param -= this.EndParam - 1; else if (param > 0) param -= Math.floor(param); return param; } /** * 获得曲线,来自索引位置. * @param {number} i 索引位置 整数 */ GetCurveAtIndex(i) { if (i >= this._LineData.length) return undefined; if (!this.ParamOnCurve(i)) return undefined; if (!this._ClosedMark && i === this._LineData.length - 1) return undefined; let d1 = this._LineData[i]; let d2 = this._LineData[FixIndex$1(i + 1, this._LineData)]; let curve; if (equaln(d1.bul, 0, 1e-8)) curve = new Line(AsVector3(d1.pt), AsVector3(d2.pt)).ApplyMatrix(this.OCS); else curve = new Arc().ParseFromBul(d1.pt, d2.pt, d1.bul).ApplyMatrix(this.OCS); curve.ColorIndex = this._Color; return curve; } IntersectWith2(curve, intType, tolerance = 1e-5) { return IntersectPolylineAndCurve(this, curve, intType, tolerance); } //计算自交点. IntersectSelf() { let cus = this.Explode(); if (cus.length === 0) return []; let intParams = []; for (let i = 0; i < cus.length; i++) { let c = cus[i]; for (let j = i + 2; j < cus.length; j++) { let c2 = cus[j]; let pts = c.IntersectWith(c2, IntersectOption.OnBothOperands); for (let p of pts) { intParams.push(i + c.GetParamAtPoint(p)); intParams.push(j + c2.GetParamAtPoint(p)); } } } return intParams; } IsIntersectSelf() { let cus = this.Explode().filter(c => !equaln(c.Length, 0, 1e-3)); for (let i = 0; i < cus.length - 1; i++) { let c1 = cus[i]; let c1IsLine = c1 instanceof Line; let d1 = c1.GetFistDeriv(c1IsLine ? 0 : 1).normalize(); for (let j = i + 1; j < cus.length; j++) { let c2 = cus[j]; let c2IsLine = c2 instanceof Line; let d2 = c2.GetFistDeriv(0).normalize(); if (j === i + 1) { if (c1IsLine === c2IsLine) { if (c1IsLine) { if (equalv3(d1, d2.negate())) return true; continue; } else { if (equalv3(d1, d2.negate()) && equalv3(c1.Center, c2.Center)) return true; } } } let intPts = c1.IntersectWith2(c2, 0); let intPtsLen = intPts.length; if (intPtsLen > 0) { if (intPtsLen === 2 && equalv3(intPts[0].pt, intPts[1].pt, 1e-3)) { intPtsLen = 1; intPts.pop(); } if (intPtsLen === 2 && j === i + 1 && cus.length === 2) { if (intPts.every(r => equaln(r.thisParam, 0, 1e-3) || equaln(r.thisParam, 1, 1e-3))) continue; } if (j === i + 1 && intPtsLen === 1) continue; if (this.IsClose && i === 0 && j === cus.length - 1 && intPtsLen === 1) continue; return true; } } } return false; } get BoundingBox() { let box = new Box3Ext(); for (let i = 0; i < this.EndParam; i++) { let cu = this.GetCurveAtIndex(i); box.union(cu.BoundingBox); } return box; } /** * 得到曲线有用的点表和凸度(闭合曲线首尾重复) */ get PtsBuls() { let pts = []; let buls = []; if (this._LineData.length === 0) return { pts, buls }; for (let data of this._LineData) { pts.push(data.pt.clone()); buls.push(data.bul); } //闭合且起点不等于终点 if (this._ClosedMark && !this._LineData[0].pt.equals(arrayLast(this._LineData).pt)) { pts.push(pts[0].clone()); buls.push(buls[0]); } return { pts, buls }; } get IsBulge() { if (!this.IsClose) return false; let refDir = Math.sign(this.Area2); let c1; let c2; for (let i = 0; i < this.EndParam; i++) { c1 = this.GetCurveAtIndex(i); c2 = this.GetCurveAtIndex(FixIndex$1(i + 1, this.EndParam)); let len1 = c1.Length; let len2 = c2.Length; let minLen = Math.min(len1, len2) * 0.2; let p = c1.EndPoint; let p1; let p2; if (c1 instanceof Arc) { let dir = c1.IsClockWise ? -1 : 1; if (dir !== refDir) return false; p1 = c1.GetPointAtDistance(len1 - minLen); } else p1 = c1.StartPoint; if (c2 instanceof Arc) { let dir = c2.IsClockWise ? -1 : 1; if (dir !== refDir) return false; p2 = c2.GetPointAtDistance(minLen); } else p2 = c2.EndPoint; let vec1 = p.clone().sub(p1); let vec2 = p2.sub(p); let dir = Math.sign(vec1.cross(vec2).z); if (dir !== 0 && dir !== refDir) return false; } return true; } get Shape() { let { pts, buls } = this.PtsBuls; let curve = CreateBoardUtil.CreatePath(pts, buls); return curve; } get SVG() { let sp = this.StartPoint; let str = `M${sp.x} ${sp.y} `; for (let i = 1; i <= this.EndParam; i++) { let bul = this.GetBuilgeAt(i - 1); let p = this.GetPointAtParam(i); if (bul === 0) str += `L${p.x} ${p.y} `; else { let arc = this.GetCurveAtIndex(i - 1); str += `A ${arc.Radius} ${arc.Radius} 0 ${Math.abs(bul) >= 1 ? 1 : 0} ${arc.IsClockWise ? 0 : 1} ${p.x} ${p.y}`; } } return str; } GetDragPointCount(drag) { if (drag === DragPointType.Grip) { let count = this.EndParam * 2 + 1; if (this.CloseMark) count--; return count; } else { return this._LineData.length; } } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); case ObjectSnapMode.Mid: let midPts = []; let enParam = this.EndParam; for (let i = 0.5; i < enParam; i++) { let p = this.GetPointAtParam(i); p && midPts.push(p); } return midPts; case ObjectSnapMode.Nea: { let nea = []; for (let cu of this.Explode()) { let neaa = cu.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); if (neaa) nea.push(...neaa); } return nea; } case ObjectSnapMode.Ext: { let cp = this.GetClosestPointTo(pickPoint, true); if (cp) return [cp]; break; } case ObjectSnapMode.Cen: let cenPts = []; for (let i = 0; i < this._LineData.length; i++) { let data = this._LineData[i]; if (!equaln(data.bul, 0)) { let cu = this.GetCurveAtIndex(i); if (cu) //end bul !== 0 但是并没有圆弧 cenPts.push(cu.Center); } } return cenPts; case ObjectSnapMode.Per: if (lastPoint) { let cp = this.GetClosestPointTo(pickPoint, false); if (!cp) return []; let cparam = this.GetParamAtPoint(cp); let cu = this.GetCurveAtParam(cparam); if (cu) { let closestPt = cu.GetClosestPointTo(lastPoint, true); if (closestPt && this.PtOnCurve(closestPt)) return [closestPt]; } } case ObjectSnapMode.Tan: if (lastPoint) { let clostPt = this.GetClosestPointTo(pickPoint, false); if (!clostPt) return []; let par = this.GetParamAtPoint(clostPt); let cu = this.GetCurveAtParam(par); if (cu instanceof Arc) return cu.GetObjectSnapPoints(snapMode, pickPoint, lastPoint); return []; } } return []; } GetGripPoints() { let ptList = []; if (this._LineData.length < 2) return ptList; let enParam = this.EndParam; if (this.CloseMark) enParam -= 0.5; for (let i = 0; i < enParam + 0.5; i += 0.5) { let p = this.GetPointAtParam(i); ptList.push(p); } return ptList; } MoveGripPoints(indexList, moveVec) { this.WriteAllObjectRecord(); let moveVLoc = AsVector2(moveVec.clone().applyMatrix4(new Matrix4().extractRotation(this.OCSInv))); let calcIndexList = indexList; if (indexList.length > 1) { let centerIndexes = indexList.filter(i => i % 2 === 0); if (centerIndexes.length > 0) calcIndexList = centerIndexes; } for (let index of calcIndexList) { if (index % 2 === 0) { let cuIndex = index / 2; let ptCout = this._LineData.length; let frontIndex = cuIndex - 1; if (this._ClosedMark) frontIndex = FixIndex$1(frontIndex, ptCout); if (frontIndex >= 0 && this.GetBuilgeAt(frontIndex)) { let arc = this.GetCurveAtIndex(frontIndex); arc.MoveGripPoints([2], moveVec); this._LineData[frontIndex].bul = arc.Bul; } if ((cuIndex !== ptCout - 1) && this.GetBuilgeAt(cuIndex)) { let arc = this.GetCurveAtIndex(cuIndex); arc.MoveGripPoints([0], moveVec); this._LineData[cuIndex].bul = arc.Bul; } this._LineData[cuIndex].pt.add(moveVLoc); } else { let ptIndex = (index - 1) / 2; let nextIndex = (FixIndex$1(ptIndex + 1, this._LineData)); let d = this._LineData[ptIndex]; if (d.bul == 0) { this._LineData[ptIndex].pt.add(moveVLoc); this._LineData[nextIndex].pt.add(moveVLoc); } else { let arc = this.GetCurveAtIndex(ptIndex); arc.MoveGripPoints([1], moveVec); this._LineData[ptIndex].bul = arc.Bul; } } } this.Update(); } GetStretchPoints() { let iswcs = MatrixIsIdentityCS(this._Matrix); let pts = []; for (let data of this._LineData) { let p = AsVector3(data.pt); if (!iswcs) p.applyMatrix4(this._Matrix); pts.push(p); } return pts; } /** * 范围拉伸(stretch),对夹点进行拉伸. * 如果对圆弧的一侧进行拉伸,那么修改bul * * @param {Array} indexList * @param {Vector3} vec */ MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); //本地坐标系移动向量 let moveVLoc = vec.clone().applyMatrix4(new Matrix4().extractRotation(this.OCSInv)); let ptCout = this._LineData.length; for (let index of indexList) { if (index >= ptCout) throw "在拉伸多段线顶点时,尝试拉伸不存在的顶点!(通常是因为模块中的板轮廓被破坏,导致的顶点丢失!)"; let frontIndex = index - 1; let nextIndex = index + 1; if (this._ClosedMark) { frontIndex = FixIndex$1(frontIndex, ptCout); nextIndex = FixIndex$1(nextIndex, ptCout); } /** * 根据新的拉伸点修改凸度. * * @param {number} nextIndex 隔壁点索引 * @param {number} bulIndex 需要修改凸度位置的索引 * @returns */ const ChangeBul = (nextIndex, bulIndex) => { //需要修改的点的数据 let d = this._LineData[bulIndex]; if (d === undefined || d.bul == 0) return; //如果隔壁点不在拉伸列表中 if (indexList.indexOf(nextIndex) === -1) { let needChangeP = this.GetPointAtParam(index); let notChangeP = this.GetPointAtParam(nextIndex); //原先的弦长的一半 let oldChordLengthHalf = needChangeP.distanceTo(notChangeP) * 0.5; //弓高 let arcHeight = oldChordLengthHalf * d.bul; needChangeP.add(vec); let newChordLengthHalf = needChangeP.distanceTo(notChangeP) * 0.5; d.bul = arcHeight / newChordLengthHalf; } }; ChangeBul(frontIndex, frontIndex); ChangeBul(nextIndex, index); //修改顶点 this._LineData[index].pt.add(AsVector2(moveVLoc)); } this.Update(); } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._LineData.length = 0; let cout = file.Read(); for (let i = 0; i < cout; i++) { let v = new Vector2().fromArray(file.Read()); let bul = file.Read(); this._LineData.push({ pt: v, bul: bul }); } if (ver > 1) this._ClosedMark = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._LineData.length); for (let l of this._LineData) { file.Write(l.pt.toArray()); file.Write(l.bul); } file.Write(this._ClosedMark); } }; Polyline = Polyline_1 = __decorate([ Factory ], Polyline); const TempPolyline = new Polyline(); /** * 相交延伸选项. * * @export * @enum {number} */ var IntersectOption; (function (IntersectOption) { /** * 两者都不延伸 */ IntersectOption[IntersectOption["OnBothOperands"] = 0] = "OnBothOperands"; /** * 延伸自身 */ IntersectOption[IntersectOption["ExtendThis"] = 1] = "ExtendThis"; /** * 延伸参数 */ IntersectOption[IntersectOption["ExtendArg"] = 2] = "ExtendArg"; /** * 延伸两者 */ IntersectOption[IntersectOption["ExtendBoth"] = 3] = "ExtendBoth"; })(IntersectOption || (IntersectOption = {})); //延伸自身还是参数反转 function reverseIntersectOption(intType) { if (intType === IntersectOption.ExtendThis) intType = IntersectOption.ExtendArg; else if (intType === IntersectOption.ExtendArg) intType = IntersectOption.ExtendThis; return intType; } /** * 校验相交点是否满足延伸选项 * 算法会计算无限延伸状态下的曲线交点,调用该方法进行校验返回校验后的点表 * * @param {Vector3[]} intRes 相交点.曲线当作完全状态下的相交点 * @param {Curve} c1 曲线1 由this参数传入 * @param {Curve} c2 曲线2 由arg 参数传入 * @param {Intersect} extType 延伸选项. * @returns {Array} 校验完成后的点表 */ function CheckPointOnCurve(intRes, c1, c2, extType, tolerance = 1e-6) { return intRes.filter(r => { if (!(extType & IntersectOption.ExtendThis)) if (!c1.ParamOnCurve(r.thisParam, tolerance / c1.Length)) return false; if (!(extType & IntersectOption.ExtendArg)) if (!c2.ParamOnCurve(r.argParam, tolerance / c2.Length)) return false; return true; }); } function IntersectCircleAndCircle(cu1, cu2) { if (!cu1.IsCoplaneTo(cu2)) return []; let c1OcsInv = cu1.OCSInv; let c1Ocs = cu1.OCS; let center1 = cu1.Center.applyMatrix4(c1OcsInv); let center2 = cu2.Center.applyMatrix4(c1OcsInv); let radius1 = cu1.Radius; let radius2 = cu2.Radius; let pts = []; let dist = center2.distanceTo(center1); if (dist < Math.abs(radius1 - radius2) - 1e-3 || dist > (radius1 + radius2 + 1e-3)) return pts; if (equaln(dist, 0, 1e-6)) return pts; let dstsqr = dist * dist; let r1sqr = radius1 * radius1; let r2sqr = radius2 * radius2; let a = (dstsqr - r2sqr + r1sqr) / (2 * dist); let h = Math.sqrt(Math.abs(r1sqr - (a * a))); let ratio_a = a / dist; let ratio_h = h / dist; let dx = center2.x - center1.x; let dy = center2.y - center1.y; let phix = center1.x + (ratio_a * dx); let phiy = center1.y + (ratio_a * dy); dx *= ratio_h; dy *= ratio_h; let p1 = new Vector3(phix + dy, phiy - dx); let p2 = new Vector3(phix - dy, phiy + dx); p1.applyMatrix4(c1Ocs); p2.applyMatrix4(c1Ocs); pts.push({ pt: p1, thisParam: cu1.GetParamAtPoint(p1), argParam: cu2.GetParamAtPoint(p1), }); if (!equalv3(p1, p2)) //防止点重复 pts.push({ pt: p2, thisParam: cu1.GetParamAtPoint(p2), argParam: cu2.GetParamAtPoint(p2), }); return pts; } /** * 计算圆与圆弧的交点. * * @export * @param {Circle} circle 圆 * @param {Arc} arc 圆弧 * @param {IntersectOption} extType 延伸选项 * @returns 交点集合 */ function IntersectCircleAndArc(circle, arc, extType, tolerance = 1e-6) { let pts = IntersectCircleAndCircle(circle, arc); return CheckPointOnCurve(pts, circle, arc, extType | IntersectOption.ExtendThis, tolerance); } /** * 计算圆弧与圆弧的交点 * * @export * @param {Arc} arc1 圆弧 * @param {Arc} arc2 圆弧 * @param {IntersectOption} extType 延伸选项 * @returns 交点集合 */ function IntersectArcAndArc(arc1, arc2, extType, tolerance = 1e-5) { let pts = IntersectCircleAndCircle(arc1, arc2); return CheckPointOnCurve(pts, arc1, arc2, extType, tolerance); } function IntersectEllipseAndLine(l, el, extType, tolerance = 1e-6) { let pts = IntersectLineAndEllipseFor2D(l, el); return CheckPointOnCurve(pts, l, el, extType, tolerance); } /** * 通用方法:计算直线与圆的交点,默认延伸全部 * * @export * @param {Line} line 直线 * @param {(Circle | Arc)} circle 圆或圆弧 * @returns 交点集合 */ function IntersectLineAndCircleOrArc(line, circle) { let lineOrg = line.StartPoint; let lineDirection = line.EndPoint.sub(lineOrg); let dirLen = lineDirection.length(); if (equaln(dirLen, 0)) return []; lineDirection.divideScalar(dirLen); let diff = lineOrg.clone().sub(circle.Center); let a0 = diff.dot(diff) - circle.Radius ** 2; let a1 = lineDirection.dot(diff); let discr = a1 ** 2 - a0; if (equaln(discr, 0, 1e-7)) { let pt = lineOrg.add(lineDirection.multiplyScalar(-a1)); return [{ pt, thisParam: -a1 / dirLen, argParam: circle.GetParamAtPoint(pt) }]; } else if (discr > 0) { let root = Math.sqrt(discr); let p1 = lineOrg.clone().add(lineDirection.clone().multiplyScalar(-a1 + root)); let p2 = lineOrg.add(lineDirection.multiplyScalar(-a1 - root)); return [ { pt: p1, thisParam: (-a1 + root) / dirLen, argParam: circle.GetParamAtPoint(p1) }, { pt: p2, thisParam: (-a1 - root) / dirLen, argParam: circle.GetParamAtPoint(p2) } ]; } return []; } //直线和圆 function IntersectLineAndCircle(line, circle, extType, tolerance = 1e-6) { let ptArr = IntersectLineAndCircleOrArc(line, circle); return CheckPointOnCurve(ptArr, line, circle, extType | IntersectOption.ExtendArg); } //直线和圆弧 function IntersectLineAndArc(line, arc, extType, tolerance = 1e-6) { let ptArr = IntersectLineAndCircleOrArc(line, arc); return CheckPointOnCurve(ptArr, line, arc, extType, tolerance); } function IntersectLAndLFor2D2(p1, p2, p3, p4) { let dx1 = p1.x - p2.x; let dx2 = p3.x - p4.x; let dx3 = p4.x - p2.x; let dy1 = p1.y - p2.y; let dy2 = p3.y - p4.y; let dy3 = p4.y - p2.y; let det = (dx2 * dy1) - (dy2 * dx1); if (equaln(det, 0.0, 1e-5)) { if (equaln(dx2 * dy3, dy2 * dx3, 1e-5)) return [p1, p2, p3, p4]; return []; } let pt = new Vector3; let ratio = ((dx1 * dy3) - (dy1 * dx3)) / det; pt.x = (ratio * dx2) + p4.x; pt.y = (ratio * dy2) + p4.y; return [pt]; } /** * 三维中两行之间最短的直线 * ref:https://stackoverflow.com/questions/2316490/the-algorithm-to-find-the-point-of-intersection-of-two-3d-line-segment * ref:http://paulbourke.net/geometry/pointlineplane/ * ref:http://paulbourke.net/geometry/pointlineplane/calclineline.cs * * @export * @param {Vector3} p1 l1.start * @param {Vector3} p2 l1.end * @param {Vector3} p3 l2.start * @param {Vector3} p4 l2.end * @returns 交点集合 */ function ShortestLine3AndLine3(p1, p2, p3, p4, epsilon = 1e-6) { let p43 = p4.clone().sub(p3); if (p43.lengthSq() < epsilon) return; let p21 = p2.clone().sub(p1); if (p21.lengthSq() < epsilon) return; let p13 = p1.clone().sub(p3); let d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; let d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; let d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; let d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; let d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; let denom = d2121 * d4343 - d4321 * d4321; if (Math.abs(denom) < epsilon) return; let numer = d1343 * d4321 - d1321 * d4343; let mua = numer / denom; let mub = (d1343 + d4321 * (mua)) / d4343; let resultSegmentPoint1 = new Vector3(); resultSegmentPoint1.x = p1.x + mua * p21.x; resultSegmentPoint1.y = p1.y + mua * p21.y; resultSegmentPoint1.z = p1.z + mua * p21.z; let resultSegmentPoint2 = new Vector3(); resultSegmentPoint2.x = p3.x + mub * p43.x; resultSegmentPoint2.y = p3.y + mub * p43.y; resultSegmentPoint2.z = p3.z + mub * p43.z; return [resultSegmentPoint1, resultSegmentPoint2]; } //直线和直线 function IntersectLineAndLine(l1, l2, extType, fuzz = 1e-4) { let [pt1, pt2, pt3, pt4] = [l1.StartPoint, l1.EndPoint, l2.StartPoint, l2.EndPoint]; let ipts; if (equaln(pt1.z, 0, fuzz) && equaln(pt2.z, 0, fuzz) && equaln(pt3.z, 0, fuzz) && equaln(pt4.z, 0, fuzz)) { ipts = IntersectLAndLFor2D2(pt1, pt2, pt3, pt4); ipts.sort(comparePoint("xy")); arrayRemoveDuplicateBySort(ipts, (p1, p2) => equalv3(p1, p2, fuzz)); } else { ipts = ShortestLine3AndLine3(pt1, pt2, pt3, pt4); if (!ipts) return []; if (ipts.length === 2) ipts.pop(); } let ints = []; for (let pt of ipts) { let { closestPt: p1, param: param1 } = l1.GetClosestAtPoint(pt, true); if (!equalv3(pt, p1, fuzz)) return []; if (!(extType & IntersectOption.ExtendThis)) if (!(l1.ParamOnCurve(param1, 0) || equalv3(pt1, pt, fuzz) || equalv3(pt2, pt, fuzz))) return []; let { closestPt: p2, param: param2 } = l2.GetClosestAtPoint(pt, true); if (!equalv3(pt, p2, fuzz)) return []; if (!(extType & IntersectOption.ExtendArg)) if (!(l2.ParamOnCurve(param2, 0) || equalv3(pt3, pt, fuzz) || equalv3(pt4, pt, fuzz))) return []; ints.push({ pt, thisParam: param1, argParam: param2 }); } return ints; } function IntersectPolylineAndCurve(pl, cu, extType, tolerance = 1e-6) { let cus = pl.Explode(); let cus2; if (cu instanceof Polyline) cus2 = cu.Explode(); else cus2 = [cu]; let intRes = []; for (let i = 0; i < cus.length; i++) { let cu1 = cus[i]; for (let j = 0; j < cus2.length; j++) { let cu2 = cus2[j]; let ext = extType; let isStart = i === 0; let isEnd = i === cus.length - 1; let isStart2 = j === 0; let isEnd2 = j === cus2.length - 1; //当曲线闭合时,或者当前的子曲线不是起始和不是结束,那么不延伸曲线. if (pl.CloseMark || !(isStart || isEnd)) ext = ext & ~IntersectOption.ExtendThis; if ((cu instanceof Polyline && cu.CloseMark) || !(isStart2 || isEnd2)) ext = ext & ~IntersectOption.ExtendArg; let ptPars = cu1.IntersectWith2(cu2, ext, tolerance).filter(r1 => intRes.every(r2 => !equalv3(r1.pt, r2.pt))); //校验延伸 if (IntersectOption.ExtendThis & ext) { //如果曲线是起始又是结束,那么不校验. if (isStart && isEnd); else if (isStart) { ptPars = ptPars.filter(res => res.thisParam <= 1); } else if (isEnd) { ptPars = ptPars.filter(res => res.thisParam >= 0); } } if (IntersectOption.ExtendArg & ext) { //如果曲线是起始又是结束,那么不校验. if (isStart2 && isEnd2); else if (isStart2) { ptPars = ptPars.filter(res => res.argParam + j <= cu2.EndParam); } else if (isEnd2) { ptPars = ptPars.filter(res => res.argParam + j >= 0); } } intRes.push(...ptPars.map(r => { return { pt: r.pt, thisParam: i + r.thisParam, argParam: j + r.argParam, }; })); } } return intRes; } function IntersectLineAndEllipseFor2D(l, el) { if (!l.IsCoplaneTo(el)) return []; let mat = new Matrix4().makeRotationZ(-el.Rotation).multiply(el.OCSInv); let a = el.RadX; let b = el.RadY; let sp = l.StartPoint.applyMatrix4(mat); let ep = l.EndPoint.applyMatrix4(mat); let pts = []; if (equaln(sp.x, ep.x)) { let c = sp.x; let j = (b ** 2) * (1 - (c ** 2) / (a ** 2)); if (equaln(j, 0)) { pts = [new Vector3(sp.x, 0)]; } else if (j < 0) return []; else { let y1 = Math.sqrt(j); let y2 = -Math.sqrt(j); pts = [ new Vector3(c, y1), new Vector3(c, y2) ]; } } else { let k = (sp.y - ep.y) / (sp.x - ep.x); let c = sp.y - sp.x * k; let j = (2 * a * a * k * c) * (2 * a * a * k * c) - 4 * (b * b + a * a * k * k) * a * a * (c * c - b * b); if (equaln(j, 0)) { let x1 = -2 * k * c * a * a / (2 * (b * b + a * a * k * k)); let y1 = k * x1 + c; pts = [new Vector3(x1, y1)]; } else if (j < 0) return []; else { let x1 = (-2 * k * c * a * a + Math.sqrt(j)) / (2 * (b * b + a * a * k * k)); let y1 = k * x1 + c; let x2 = (-2 * k * c * a * a - Math.sqrt(j)) / (2 * (b * b + a * a * k * k)); let y2 = k * x2 + c; pts = [ new Vector3(x1, y1), new Vector3(x2, y2) ]; } } let matInv = new Matrix4().getInverse(mat); return pts.map(p => { let pt = p.applyMatrix4(matInv); return { pt, thisParam: l.GetParamAtPoint(pt), argParam: el.GetParamAtPoint(pt) }; }); } function IntersectEllipseAndCircleOrArc(el, cir, type) { if (!el.IsCoplaneTo(cir)) return []; let a = Math.max(el.RadX, el.RadY); let dist = el.Center.distanceTo(cir.Center); let disVail = dist > (a + cir.Radius); if (disVail) return []; if (equalv3(el.Center, cir.Center)) { let a = el.RadX; let b = el.RadY; let r = cir.Radius; let j = ((a * b) ** 2 - (b * r) ** 2) / (a ** 2 - b ** 2); let pts = []; if (equaln(j, 0) || equaln(j, r ** 2)) { if (equaln(j, 0)) pts = [ new Vector3(a, 0), new Vector3(-a, 0) ]; else pts = [ new Vector3(0, r), new Vector3(0, -r) ]; } else if (j < 0) return []; else { let y1 = Math.sqrt(j); let y2 = -Math.sqrt(j); let n = r ** 2 - j; let x1 = Math.sqrt(n); let x2 = -Math.sqrt(n); pts = [ new Vector3(x1, y1), new Vector3(x1, y2), new Vector3(x2, y1), new Vector3(x2, y2), ]; } let ro = new Matrix4().makeRotationZ(el.Rotation); let res = pts.map(p => { let pt = p.applyMatrix4(ro).applyMatrix4(el.OCS); return { pt, thisParam: el.GetParamAtPoint(pt), argParam: cir.GetParamAtPoint(pt) }; }); return CheckPointOnCurve(res, el, cir, type); } else { let pts = el.Shape.getPoints(60); let lineData = pts.map(p => { return { pt: p, bul: 0 }; }); let pl = new Polyline(lineData); let cirClone = cir.Clone().ApplyMatrix(el.OCSInv); if (type === IntersectOption.ExtendBoth) type = IntersectOption.ExtendArg; else if (type !== IntersectOption.ExtendArg) type = IntersectOption.OnBothOperands; let intPts = IntersectPolylineAndCurve(pl, cirClone, type); intPts.forEach(r => r.pt.applyMatrix4(el.OCS)); return intPts; } } function IntersectEllipse(el1, el2, type) { if (!el1.IsCoplaneTo(el2)) return []; let isEqul = equalv3(el1.Center, el2.Center) && equaln(el1.RadX, el2.RadX) && equaln(el1.RadY, el2.RadY) && equalv3(el1.StartPoint, el2.StartPoint); if (isEqul) return []; let a1 = Math.max(el1.RadX, el1.RadY); let a2 = Math.max(el2.RadX, el2.RadY); let dist = el1.Center.distanceToSquared(el2.Center); if (dist > (a1 + a2) ** 2) { return []; } if (!el1.BoundingBox.intersectsBox(el2.BoundingBox)) return []; let diffMat = el1.OCSInv.multiply(el2.OCS); let pts1 = el1.Shape.getPoints(60); let pts2 = el2.Shape.getPoints(60); let lineData1 = pts1.map(p => { return { pt: p, bul: 0 }; }); let lineData2 = pts2.map(p => { return { pt: p, bul: 0 }; }); let pl1 = new Polyline(lineData1); let pl2 = new Polyline(lineData2).ApplyMatrix(diffMat); let intPts = pl1.IntersectWith2(pl2, 0); intPts.forEach(r => r.pt.applyMatrix4(el1.OCS)); return intPts; } var Arc_1; /** * 圆弧实体类 * 与ACAD不同,这个类加入了时针变量,并且默认构造的圆弧为顺时针圆弧. * * 关于时针圆弧: * 起始圆弧到终止圆弧总是在0-2PI之间.(一个完整的圆). * 圆弧的绘制从起始圆弧绘制到终止圆弧. 按照时针绘制. * 参考计算圆弧的完整角度方法查看该计算方式. */ let Arc = Arc_1 = class Arc extends Curve { constructor(center = new Vector3(), radius = 0.1, startAngle = 0.1, endAngle = 0, clockwise = true) { super(); /** * 曲线为顺时针 */ this._Clockwise = true; this._Matrix.setPosition(center); this._Radius = radius; this._StartAngle = clampRad(startAngle); this._EndAngle = clampRad(endAngle); this._Clockwise = clockwise; } get Shape() { let sp = new Shape2(); sp.absarc(0, 0, this._Radius, this._StartAngle, this._EndAngle, this._Clockwise); return sp; } get Center() { return this.Position; } set Center(v) { this.Position = v; } get Normal() { return new Vector3().setFromMatrixColumn(this._Matrix, 2); } set Normal(v) { this.WriteAllObjectRecord(); matrixSetVector(this._Matrix, 2, v); this.Update(); } get Area() { return 0.5 * this.AllAngle * this.Radius * this.Radius; } //获得曲线的面积,逆时针为正,顺时针为负. get Area2() { let clockwise = this._Clockwise ? -1 : 1; return 0.5 * this.AllAngle * this.Radius * this.Radius * clockwise; } get IsClose() { return false; } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { let pts = [ polar(new Vector3(), this._StartAngle, this._Radius), polar(new Vector3(), this._EndAngle, this._Radius), ]; if (this.ParamOnCurve(this.GetParamAtAngle(0))) pts.push(new Vector3(this._Radius, 0)); if (this.ParamOnCurve(this.GetParamAtAngle(Math.PI / 2))) pts.push(new Vector3(0, this._Radius)); if (this.ParamOnCurve(this.GetParamAtAngle(Math.PI))) pts.push(new Vector3(-this._Radius, 0)); if (this.ParamOnCurve(this.GetParamAtAngle(Math.PI * 3 / 2))) pts.push(new Vector3(0, -this._Radius)); return new Box3Ext().setFromPoints(pts); } get Radius() { return this._Radius; } set Radius(v) { this.WriteAllObjectRecord(); this._Radius = v <= 0 ? 1e-19 : v; this.Update(); } get IsClockWise() { return this._Clockwise; } set IsClockWise(v) { if (v !== this._Clockwise) { this.WriteAllObjectRecord(); this._Clockwise = v; this.Update(); } } get StartAngle() { return this._StartAngle; } set StartAngle(v) { this.WriteAllObjectRecord(); this._StartAngle = v; this.Update(); } get EndAngle() { return this._EndAngle; } set EndAngle(v) { this.WriteAllObjectRecord(); this._EndAngle = v; this.Update(); } //******************** Curve function start*****************// get StartPoint() { return polar(new Vector3(), this._StartAngle, this._Radius).applyMatrix4(this.OCS); } set StartPoint(v) { let vTemp = v.clone().applyMatrix4(this.OCSInv); this.StartAngle = angle(vTemp); } get EndPoint() { return polar(new Vector3(), this._EndAngle, this._Radius).applyMatrix4(this.OCS); } set EndPoint(v) { let vTemp = v.clone().applyMatrix4(this.OCSInv); this.EndAngle = angle(vTemp); } get StartParam() { return 0; } get EndParam() { return 1; } get Length() { return this.AllAngle * this._Radius; } GetParamAtPoint2(pt) { return this.GetParamAtAngle(this.GetAngleAtPoint(pt)); } //点在曲线上,已经确定点在曲线的延伸线上 PtOnCurve3(p, fuzz = 1e-6) { let param = this.GetParamAtPoint2(p); return this.ParamOnCurve(param, fuzz); } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); this.Center = this.Center.applyMatrix4(m); this.Radius = this.Radius * m.getMaxScaleOnAxis(); return this; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let sp = this.StartPoint; let ep = this.EndPoint; reviseMirrorMatrix(this._Matrix); this._Clockwise = !this._Clockwise; this.StartPoint = sp; this.EndPoint = ep; return this; } GetPointAtParam(param) { let an = this.GetAngleAtParam(param); return polar(new Vector3(), an, this._Radius).applyMatrix4(this.OCS); } GetPointAtDistance(distance) { let len = this.Length; if (len == 0) return; return this.GetPointAtParam(distance / len); } GetDistAtParam(param) { return Math.abs(param * this.Length); } GetDistAtPoint(pt) { let param = this.GetParamAtPoint(pt); return this.GetDistAtParam(param); } GetParamAtPoint(pt) { if (this._Radius == 0 || this.AllAngle == 0 || !equaln(pt.distanceTo(this.Center), this._Radius, 1e-6)) return NaN; return this.GetParamAtAngle(this.GetAngleAtPoint(pt)); } /** * 利用角度计算该角度在圆弧中代表的参数. * 如果角度在圆弧内,那么返回0-1 * 如果角度不在圆弧内,那么尝试返回离圆弧起始或者结束的较近的参数 * * @param {number} an * @returns * @memberof Arc */ GetParamAtAngle(an) { //如果以pt为终点,那么所有的角度为 let ptAllAn = this.ComputeAnlge(an); let allAn = this.AllAngle; //减去圆弧角度,剩余角度的一半 let surplusAngleHalf = Math.PI - allAn / 2; if (ptAllAn > allAn + surplusAngleHalf) //返回负数 return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn; else //返回正数 return ptAllAn / allAn; } /** * 根据角度获得参数,不过在这里我们可以指定我们是要获取前面的参数还是后面的参数(正负) * @param an * @param [isStart] true:返回负数,false 返回正数 * @returns */ GetParamAtAngle2(an, isStart = true) { //如果以pt为终点,那么所有的角度为 let ptAllAn = this.ComputeAnlge(an); let allAn = this.AllAngle; //减去圆弧角度,剩余角度的一半 let surplusAngleHalf = Math.PI - allAn / 2; if (isStart) //返回负数 return ((ptAllAn - allAn) - (surplusAngleHalf * 2)) / allAn; else //返回正数 return ptAllAn / allAn; } GetAngleAtPoint(pt) { let ptTmp = pt.clone().applyMatrix4(this.OCSInv); return angle(ptTmp); } GetAngleAtParam(param) { return clampRad(this._StartAngle + param * this.AllAngle * (this._Clockwise ? -1 : 1)); } GetSplitCurves(param) { let params = this.SplitParamSort(param); //角度列表 let ans = params.map(p => this.GetAngleAtParam(p)); //返回圆弧表 let arcs = []; for (let i = 0; i < ans.length - 1; i++) { let arc = this.Clone(); arc.ColorIndex = this.ColorIndex; arc.StartAngle = ans[i]; arc.EndAngle = ans[i + 1]; arcs.push(arc); } return arcs; } GetOffsetCurves(offsetDist) { if (this._Clockwise) offsetDist *= -1; if ((offsetDist + this._Radius) > 0) { let arc = this.Clone(); arc.Radius = offsetDist + this._Radius; return [arc]; } return []; } Extend(newParam) { this.WriteAllObjectRecord(); if (newParam < 0) { this._StartAngle = this.GetAngleAtParam(newParam); } else if (newParam > 1) { this._EndAngle = this.GetAngleAtParam(newParam); } this.Update(); } Join(cu) { if (cu instanceof Arc_1) { //非常小的圆弧直接结束 if (cu.AllAngle < 5e-6) return Status.False; if (equalv3(cu.Center, this.Center) && equaln(cu._Radius, this._Radius)) { this.WriteAllObjectRecord(); let [sa, ea] = [cu.StartAngle, cu.EndAngle]; if (cu._Clockwise != this._Clockwise) [sa, ea] = [ea, sa]; let allAn = this.AllAngle; let saAllan = this.ComputeAnlge(sa); let eaAllan = this.ComputeAnlge(ea); if (equaln(sa, this._StartAngle)) //this起点对起点 { if (eaAllan > allAn) this.EndAngle = ea; return Status.True; } else if (equaln(sa, this._EndAngle)) //this终点对起点 { if (eaAllan < allAn || equaln(ea, this._StartAngle)) return Status.ConverToCircle; else this.EndAngle = ea; return Status.True; } else if (equaln(ea, this.StartAngle)) //this起点对终点 { if (saAllan < allAn) return Status.ConverToCircle; else this.StartAngle = sa; return Status.True; } else if (equaln(ea, this._EndAngle)) //this终点对终点 { if (saAllan > allAn) this.StartAngle = sa; return Status.True; } else if (this.ParamOnCurve(this.GetParamAtAngle(sa))) { if (eaAllan < saAllan) return Status.ConverToCircle; else if (eaAllan > allAn) this.EndAngle = ea; return Status.True; } else if (this.ParamOnCurve(this.GetParamAtAngle(ea))) { this.StartAngle = sa; return Status.True; } //使用按负方向去计算它的参数 let saParam; if (saAllan > allAn) saParam = (saAllan - Math.PI * 2) / allAn; else saParam = saAllan / allAn; let eaParam; if (eaAllan > saAllan && saAllan > allAn) eaParam = (eaAllan - Math.PI * 2) / allAn; else eaParam = eaAllan / allAn; let pMin = Math.max(0, saParam); let pMax = Math.min(1, eaParam); if (pMin <= pMax + 1e-5) { if (saParam < 0) this.StartAngle = sa; if (eaParam > 1) this.EndAngle = ea; return Status.True; } } } return Status.False; } Reverse() { this.WriteAllObjectRecord(); this._Clockwise = !this._Clockwise; [this._StartAngle, this._EndAngle] = [this._EndAngle, this._StartAngle]; return this; } IntersectWith2(curve, intType, tolerance = 1e-4) { if (curve instanceof Arc_1) { return IntersectArcAndArc(this, curve, intType); } if (curve instanceof Line) { return SwapParam(IntersectLineAndArc(curve, this, reverseIntersectOption(intType), tolerance)); } if (curve instanceof Circle) { return SwapParam(IntersectCircleAndArc(curve, this, reverseIntersectOption(intType), tolerance)); } if (curve instanceof Polyline) return SwapParam(IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType), tolerance)); if (curve instanceof Ellipse) return SwapParam(IntersectEllipseAndCircleOrArc(curve, this, intType)); return []; } /** * 计算出圆弧所包含的角度 * * @readonly * @type {number} * @memberof Arc */ get AllAngle() { return this.ComputeAnlge(this._EndAngle); } get Bul() { if (equaln(this.AllAngle, Math.PI * 2)) return 1; return Math.tan(this.AllAngle * 0.25) * (this.IsClockWise ? -1 : 1); } /** * 计算所包含的角度 * * @private * @param {number} endAngle 结束的角度 * @returns * @memberof Arc */ ComputeAnlge(endAngle) { //顺时针 if (this._Clockwise) { if (this._StartAngle > endAngle) return this.StartAngle - endAngle; else //越过0点绘制圆弧 return (Math.PI * 2) - (endAngle - this._StartAngle); } else { if (endAngle > this._StartAngle) return endAngle - this._StartAngle; else return (Math.PI * 2) - (this._StartAngle - endAngle); } } /** * 解析两点和凸度所构成的圆弧 * ref http://www.lee-mac.com/bulgeconversion.html * @param {Vector2} p1 * @param {Vector2} p2 * @param {number} bul 凸度,在cad中,凸度为 <(四分之一圆心角)的正切值> */ ParseFromBul(p1, p2, bul) { if (p1 instanceof Vector2) p1 = AsVector3(p1); if (p2 instanceof Vector2) p2 = AsVector3(p2); let ocsInv = this.OCSInv; p1 = p1.clone().applyMatrix4(ocsInv); p2 = p2.clone().applyMatrix4(ocsInv); //a (* 2 (atan b)) let a = Math.atan(bul) * 2; //r (/ (distance p1 p2) 2 (sin a)) let r = p1.distanceTo(p2) / 2 / Math.sin(a); //c (polar p1 (+ (- (/ pi 2) a) (angle p1 p2)) r) let c = polar(p1.clone(), Math.PI / 2 - a + angle(p2.clone().sub(p1)), r); this._Radius = Math.abs(r); this._StartAngle = angle(p1.sub(c)); this._EndAngle = angle(p2.sub(c)); this._Clockwise = bul < 0; this.Center = c.applyMatrix4(this.OCSNoClone); return this; } FromThreePoint(pt1, pt2, pt3) { if (!(pt1 && pt2 && pt3)) return; let ocsInv = this.OCSInv; pt1 = pt1.clone().applyMatrix4(ocsInv).setZ(0); pt2 = pt2.clone().applyMatrix4(ocsInv).setZ(0); pt3 = pt3.clone().applyMatrix4(ocsInv).setZ(0); let center = getCircleCenter(pt1, pt2, pt3); this.Center = center.clone().applyMatrix4(this.OCS); //用圆心和其中一个点求距离得到半径: this._Radius = center.distanceTo(pt1); //起始角度 端点角度 this._StartAngle = angle(pt1.clone().sub(center)); this._EndAngle = angle(pt3.clone().sub(center)); //求出向量p1->p2,p1->p3 let p1 = pt2.clone().sub(pt1); let p2 = pt3.clone().sub(pt1); this._Clockwise = p1.cross(p2).z < 0; return this; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return [this.StartPoint, this.EndPoint]; case ObjectSnapMode.Mid: return [this.GetPointAtParam(0.5)]; case ObjectSnapMode.Nea: return getArcOrCirNearPts(this, pickPoint, viewXform) .filter(p => this.PtOnCurve(p)); case ObjectSnapMode.Ext: return [this.GetClosestPointTo(pickPoint, true)]; case ObjectSnapMode.Cen: return [this.Center]; case ObjectSnapMode.Per: if (lastPoint) { if (equaln(lastPoint.distanceToSquared(this.Center), 0, 1e-10)) return []; let l = new Line(this.Center, lastPoint); return l.IntersectWith(this, IntersectOption.ExtendBoth).filter(p => this.PtOnCurve(p)); } case ObjectSnapMode.Tan: let pts = GetTanPtsOnArcOrCircle(this, lastPoint); if (pts) return pts.filter(p => this.PtOnCurve(p)); } return []; } GetGripPoints() { return [ this.StartPoint, this.GetPointAtParam(0.5), this.EndPoint, this.Center.clone(), ]; } MoveGripPoints(indexList, vec) { if (indexList.length > 0) { this.WriteAllObjectRecord(); let ptsArr = this.GetGripPoints(); let index = indexList[0]; let p = ptsArr[index]; if (p) { p.add(vec); if (index > 2) this.Center = this.Center.add(vec); else this.FromThreePoint(ptsArr[0], ptsArr[1], ptsArr[2]); this.Update(); } } } GetStretchPoints() { return [this.StartPoint, this.EndPoint]; } MoveStretchPoints(indexList, vec) { if (indexList.length === 0) return; this.WriteAllObjectRecord(); if (indexList.length === 2) this.ApplyMatrix(MoveMatrix(vec)); else for (let index of indexList) { let pts = [this.StartPoint, this.EndPoint]; let [sp, ep] = pts; let oldChordLengthHalf = sp.distanceTo(ep) * 0.5; let arcHeight = oldChordLengthHalf * this.Bul; pts[index].add(vec); let newChordLengthHalf = sp.distanceTo(ep) * 0.5; let newBul = arcHeight / newChordLengthHalf; //根据凸度构造新的弧 this.ParseFromBul(sp, ep, newBul); this.Update(); } } GetParamAtDist(d) { return d / this.Length; } GetFistDeriv(pt) { let an; if (typeof pt === "number") an = this.GetAngleAtParam(pt); else an = angle(pt.clone().applyMatrix4(this.OCSInv)); an += Math.PI * 0.5 * (this._Clockwise ? -1 : 1); let ocs = new Matrix4().extractRotation(this.OCS); return polar(new Vector3(), an, this._Radius).applyMatrix4(ocs); } GetClosestPointTo(pt, extend) { pt = pt.clone().applyMatrix4(this.OCSInv); if (equalv2(pt, ZeroVec, 1e-8)) return this.GetPointAtParam(0); let a = angle(pt); let param = this.GetParamAtAngle(a); if (extend || this.ParamOnCurve(param)) return polar(new Vector3, a, this._Radius).applyMatrix4(this._Matrix); if (param < 0) return this.GetPointAtParam(0); else return this.GetPointAtParam(1); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); if (ver === 1) { this.Center = new Vector3().fromArray(file.Read()); this.Normal = new Vector3().fromArray(file.Read()); } this._Radius = file.Read(); this._StartAngle = file.Read(); this._EndAngle = file.Read(); this._Clockwise = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._Radius); file.Write(this._StartAngle); file.Write(this._EndAngle); file.Write(this._Clockwise); } }; Arc = Arc_1 = __decorate([ Factory ], Arc); /** * 一个简单的计数器实现,本质是使用一个Map来保存元素的个数 * * 例: * let count = new Count(); * count.AddCount("Test", 1); * count.GetCount("Test");//现在 Test 的个数为1 */ class Count { constructor() { this.m_CountMap = new WeakMap(); } GetCount(obj) { let count = this.m_CountMap.get(obj); if (!count) { this.m_CountMap.set(obj, 0); count = 0; } return count; } AddCount(obj, add) { this.m_CountMap.set(obj, this.GetCount(obj) + add); } } /** * 轨道控制的数学类,观察向量和角度的互相转换 * 当x当抬头或者低头到90度时,触发万向锁. */ class Orbit { constructor() { //抬头低头 正数抬头 负数低头 this.phi = 0; //Φ //身体旋转 0为正右边 逆时针旋转 this.theta = 0; //θ } get RoX() { return this.phi; } set RoX(v) { this.phi = MathUtils.clamp(v, Math.PI * -0.49, Math.PI * 0.49); } /** * 使用旋转角度 计算观察向量 * @param [outDirection] 引用传入,如果传入,那么就不构造新的向量 * @returns 返回观察向量 */ UpdateDirection(outDirection = new Vector3()) { outDirection.z = Math.sin(this.phi); //归一化专用. let d = Math.abs(Math.cos(this.phi)); outDirection.x = Math.cos(this.theta) * d; outDirection.y = Math.sin(this.theta) * d; return outDirection; } /** * 使用观察向量,计算旋转角度 * @param dir 这个向量会被修改成单位向量. */ SetFromDirection(dir) { dir.normalize(); this.phi = Math.asin(dir.z); if (equaln(dir.x, 0) && equaln(dir.y, 0)) if (dir.z > 0) this.theta = Math.PI * -0.5; else this.theta = Math.PI * 0.5; else this.theta = Math.atan2(dir.y, dir.x); } /** * 参考任意轴坐标系算法. * http://help.autodesk.com/view/ACD/2017/CHS/?guid=GUID-E19E5B42-0CC7-4EBA-B29F-5E1D595149EE */ static ComputUpDirection(n, ay = new Vector3(), ax = new Vector3()) { n.normalize(); if (Math.abs(n.x) < 0.015625 && Math.abs(n.y) < 0.015625) ax.crossVectors(YAxis, n); else ax.crossVectors(ZAxis, n); ay.crossVectors(n, ax); ax.normalize(); ay.normalize(); return ay; } } //3点获取圆心 function getCircleCenter(pt1, pt2, pt3) { if (!(pt1 && pt2 && pt3)) return; let A1 = pt1.x - pt2.x; let B1 = pt1.y - pt2.y; let C1 = (Math.pow(pt1.x, 2) - Math.pow(pt2.x, 2) + Math.pow(pt1.y, 2) - Math.pow(pt2.y, 2)) / 2; let A2 = pt3.x - pt2.x; let B2 = pt3.y - pt2.y; let C2 = (Math.pow(pt3.x, 2) - Math.pow(pt2.x, 2) + Math.pow(pt3.y, 2) - Math.pow(pt2.y, 2)) / 2; //令temp = A1*B2 - A2*B1 let temp = A1 * B2 - A2 * B1; let center = new Vector3(); //判断三点是否共线 if (temp === 0) { //共线则将第一个点pt1作为圆心 center.x = pt1.x; center.y = pt1.y; } else { //不共线则求出圆心: center.x = (C1 * B2 - C2 * B1) / temp; center.y = (A1 * C2 - A2 * C1) / temp; } return center; } //行列式 function getDeterminantFor2V(v1, v2) { return v1.x * v2.y - v1.y * v2.x; } /** * 曲线根据连接来分组,每组都是一条首尾相连的曲线表. * * @export * @param {Curve[]} cus 传入的分组的曲线表 * @returns {Array>} 返回如下 * [ * [c1,c2,c3...],//后面的曲线的起点总是等于上一个曲线的终点 * [c1,c2,c3...], * ] */ function curveLinkGroup(cus) { //返回的曲线组 let groupCus = new Array(); //将封闭的曲线先提取出来 cus = cus.filter(c => { let isClose = c.IsClose; if (isClose) groupCus.push([c]); return !isClose; }); if (cus.length === 0) return groupCus; //曲线节点图 let cuMap = new CurveMap(); cus.forEach(c => cuMap.AddCurveToMap(c)); //曲线站点表 let stands = cuMap.Stands; //曲线使用计数 let cuCount = new Count(); /** * 从站点的路线中任意取一条,加入到曲线数组中. * * @param {Curve[]} cus 已经连接的曲线列表 * @param {boolean} isEndSeach true:从终点搜索,false:从起点搜索 * @returns {Stand} 如果站点中存在可以取得的曲线,返回下个站点,否则返回undefined */ function linkCurve(stand, cus, isEndSeach) { for (let route of stand.routes) { let cu = route.curve; if (cuCount.GetCount(cu) === 0) { if (isEndSeach) { //保证曲线总是从起点连接到终点 if (!equalv3(cu.StartPoint, stand.position)) cu.Reverse(); cus.push(cu); } else { //保证曲线总是从起点连接到终点 if (!equalv3(cu.EndPoint, stand.position)) cu.Reverse(); cus.unshift(cu); } cuCount.AddCount(cu, 1); return route.to; } } } for (let stand of stands) { let startStand = stand; let cus = []; //形成合并轮廓的曲线组 while (startStand) startStand = linkCurve(startStand, cus, true); if (cus.length > 0) { startStand = cuMap.GetOnlyVertice(cus[0].StartPoint); while (startStand) startStand = linkCurve(startStand, cus, false); } if (cus.length > 0) groupCus.push(cus); } return groupCus; } function equalCurve(cu1, cu2, tolerance = 1e-4) { if ((cu1 instanceof Polyline) && (cu2 instanceof Polyline)) { if (cu1.IsClose !== cu2.IsClose || !isParallelTo(cu1.Normal, cu2.Normal)) return false; let area1 = cu1.Area2; let area2 = cu2.Area2; if (!equaln(Math.abs(area1), Math.abs(area2), 0.1)) return false; let ptsBuls1 = cu1.PtsBuls; let ptsBuls2 = cu2.PtsBuls; let pts1 = ptsBuls1.pts; let pts2 = ptsBuls2.pts; let buls1 = ptsBuls1.buls; let buls2 = ptsBuls2.buls; let isEqualArea = equaln(area1, area2, 0.1); if (!equalv3(cu1.Normal, cu2.Normal)) { if (isEqualArea) { pts2.reverse(); buls2.reverse(); buls2.push(buls2.shift()); } else buls2 = buls2.map(bul => -bul); } else if (!isEqualArea) { pts2.reverse(); buls2.reverse(); buls2 = buls2.map(bul => -bul); buls2.push(buls2.shift()); } if (cu1.IsClose && equalv2(pts1[0], arrayLast(pts1), tolerance)) { pts1.pop(); buls1.pop(); } if (cu2.IsClose && equalv2(pts2[0], arrayLast(pts2), tolerance)) { pts2.pop(); buls2.pop(); } let cu1Sp = AsVector2(cu1.StartPoint.applyMatrix4(cu2.OCSInv)); let index = pts2.findIndex(p => equalv2(cu1Sp, p, tolerance)); changeArrayStartIndex(buls2, index); changeArrayStartIndex(pts2, index); return equalArray(buls1, buls2, equaln) && equalArray(pts1, pts2, (p1, p2) => equalv3(AsVector3(p1).applyMatrix4(cu1.OCS), AsVector3(p2).applyMatrix4(cu2.OCS), tolerance)); } else if (cu1 instanceof Circle && cu2 instanceof Circle) { return equalv3(cu1.Center, cu2.Center) && equaln(cu1.Radius, cu2.Radius, 1e-6); } else if (cu1 instanceof Arc && cu2 instanceof Arc) { if (!equalv3(cu1.StartPoint, cu2.EndPoint)) cu1.Reverse(); return equalv3(cu1.Center, cu2.Center) && equaln(cu1.Radius, cu2.Radius, 1e-6) && equaln(cu1.StartAngle, cu2.StartAngle) && equaln(cu1.EndAngle, cu2.EndAngle); } else if (cu1 instanceof Ellipse && cu2 instanceof Ellipse) { return equalv3(cu1.Center, cu2.Center) && equaln(cu1.RadX, cu2.RadX) && equaln(cu1.RadY, cu2.RadY) && equalv3(cu1.StartPoint, cu2.StartPoint); } else if (cu1 instanceof Line && cu2 instanceof Line) { let ps1 = [cu1.StartPoint, cu1.EndPoint]; let ps2 = [cu2.StartPoint, cu2.EndPoint]; return ps1.every(p => ps2.some(p1 => equalv3(p1, p))); } return false; } /** * 计算点在曲线前进方向的方位,左边或者右边 * * @param {Curve} cu * @param {Vector3} pt * @returns {boolean} 左边为-1,右边为1 */ function GetPointAtCurveDir(cu, pt) { if (cu instanceof Circle) return cu.PtInCurve(pt) ? -1 : 1; else if (cu instanceof Polyline) { let u = new OffsetPolyline(cu, 1); u.InitSubCurves(); return u.GetPointAtCurveDir(pt.clone().applyMatrix4(cu.OCSInv).setZ(0)); } else if (cu instanceof Spline) return GetPointAtCurveDir(cu.Convert2Polyline(), pt); //最近点 let cp = cu.GetClosestPointTo(pt, false); if (equalv3(cp, pt, 1e-6)) return 0; //最近点参数 let cparam = cu.GetParamAtPoint(cp); let dri = cu.GetFistDeriv(cparam); let cross = dri.cross(pt.clone().sub(cp)).applyMatrix4(cu.OCSInv); return -Math.sign(cross.z); } function ConverCircleToPolyline(cir) { //该写法不支持三维坐标系 // let pl = new Polyline(); // let bul = Math.tan(Math.PI * 0.125); // for (let i = 0; i < 4; i++) // { // let p = cir.GetPointAtParam(i * 0.25); // pl.AddVertexAt(i, Vec3DTo2D(p)); // pl.SetBulgeAt(i, bul); // } // pl.CloseMark = true; // return pl; let arcs = cir.GetSplitCurves([0, 0.5]); let pl = new Polyline(); pl.OCS = cir.OCS; pl.Join(arcs[0]); pl.Join(arcs[1]); return pl; } function GetTanPtsOnArcOrCircle(cu, lastPoint) { if (lastPoint) { //ref:wykobi let ocsInv = cu.OCSInv; let v = lastPoint.clone().applyMatrix4(ocsInv); let lengthSq = v.lengthSq(); let radiusSq = cu.Radius ** 2; if (lengthSq >= radiusSq) { let ratio = 1 / lengthSq; let deltaDist = Math.sqrt(lengthSq - radiusSq); let pts = [ new Vector3(cu.Radius * (cu.Radius * v.x - v.y * deltaDist) * ratio, cu.Radius * (cu.Radius * v.y + v.x * deltaDist) * ratio), new Vector3(cu.Radius * (cu.Radius * v.x + v.y * deltaDist) * ratio, cu.Radius * (cu.Radius * v.y - v.x * deltaDist) * ratio), ]; for (let p of pts) p.applyMatrix4(cu.OCS); return pts; } } } function getArcOrCirNearPts(cu, pickPoint, viewXform) { let viewNormal = new Vector3().fromArray(viewXform.elements, 2 * 3); let plane = new PlaneExt(cu.Normal, cu.Center); let pickLocal = plane.intersectLine(new Line3(pickPoint, pickPoint.clone().add(viewNormal)), new Vector3(), true); if (pickLocal) { let x = new Vector3().fromArray(viewXform.elements, 0).add(pickLocal); let y = new Vector3().fromArray(viewXform.elements, 3).add(pickLocal); x = plane.intersectLine(new Line3(x, x.clone().add(viewNormal)), new Vector3(), true); y = plane.intersectLine(new Line3(y, y.clone().add(viewNormal)), new Vector3(), true); let lx = new Line(pickLocal, x); let ly = new Line(pickLocal, y); let ins = cu.IntersectWith(lx, IntersectOption.ExtendBoth); ins.push(...cu.IntersectWith(ly, IntersectOption.ExtendBoth)); return ins; } else { let ptLocal = plane.projectPoint(pickPoint, new Vector3()); let lz = new Line(ptLocal, ptLocal.clone().add(viewNormal)); return cu.IntersectWith(lz, IntersectOption.ExtendBoth); } } function getTanPtsOnEllipse(cu, lastPoint) { return []; } function IsRect(cu) { if (cu instanceof Polyline) { if (!cu.IsClose) return { isRect: false }; let pts = cu.GetStretchPoints(); if (pts.length < 4) return { isRect: false }; let xVec; let p1 = pts[0]; for (let i = 1; i < pts.length; i++) { xVec = pts[i].clone().sub(p1).normalize(); if (!equalv3(xVec, ZeroVec)) break; } if (!xVec) return { isRect: false }; let zVec = cu.Normal; let yVec = zVec.clone().cross(xVec).normalize(); let rectOCS = new Matrix4().makeBasis(xVec, yVec, zVec); let rectOCSInv = new Matrix4().getInverse(rectOCS); for (let p of pts) p.applyMatrix4(rectOCSInv); let box = new Box3().setFromPoints(pts); let size = box.getSize(new Vector3); if (equaln(size.x * size.y, cu.Area, 0.1)) { return { isRect: true, size, box, OCS: rectOCS, }; } } return { isRect: false }; } function MergeCurvelist(cus) { for (let i = 0; i < cus.length; i++) { let c1 = cus[i]; let nextI = FixIndex$1(i + 1, cus); let c2 = cus[nextI]; let status = equaln(c2.Length, 0, LINK_FUZZ) ? Status.True : c1.Join(c2, false, LINK_FUZZ); if (status === Status.True) { cus.splice(nextI, 1); i--; } else if (status === Status.ConverToCircle) { cus.length = 0; let a = c1; cus.push(new Circle(a.Center, a.Radius)); break; } } return cus; } function SwapParam(res) { for (let r of res) [r.thisParam, r.argParam] = [r.argParam, r.thisParam]; return res; } function ComputerCurvesNormalOCS(curves, allowAutoCalc = true) { if (!curves || curves.length === 0) return; const IsNorZeroVector = (v) => v && !equalv3(v, ZeroVec, 1e-3); //准备计算多段线的法向量 let normal; let firstV; for (let c of curves) { if (c instanceof Arc || c instanceof Circle) { normal = c.Normal; break; } else if (firstV) { let v = c.GetFistDeriv(0); if (IsNorZeroVector(v)) { v.normalize().cross(firstV); if (IsNorZeroVector(v)) //避免平行向量 { normal = v.normalize(); break; } } } else { let cus = c.Explode(); let ocs = ComputerCurvesNormalOCS(cus, false); if (ocs) return ocs; let fv = c.GetFistDeriv(0); if (IsNorZeroVector(fv)) //先判断零向量 firstV = fv.normalize(); //再归一化 } } if (!normal && !allowAutoCalc) return; let x = new Vector3(); let y = new Vector3(); if (!normal) { if (!firstV) return curves[0].OCS; normal = firstV.normalize(); Orbit.ComputUpDirection(normal, y, x); [x, y, normal] = [normal, x, y]; } else { if (equalv3(normal, curves[0].Normal.negate(), 1e-5)) normal.negate(); Orbit.ComputUpDirection(normal, y, x); } return new Matrix4().makeBasis(x, y, normal).setPosition(curves[0].StartPoint); } function Pts2Polyline(pts, isClose) { let pl = new Polyline(); for (let i = 0; i < pts.length; i += 2) { let p1 = AsVector3(pts[i]); let arc; let p2; let p3; if (isClose) { p2 = AsVector3(pts[FixIndex$1(i + 1, pts.length)]); p3 = AsVector3(pts[FixIndex$1(i + 2, pts.length)]); } else { if (i >= pts.length - 2) break; p2 = AsVector3(pts[i + 1]); p3 = AsVector3(pts[i + 2]); } let v1 = p1.clone().sub(p2); let v2 = p2.clone().sub(p3); if (equaln(v1.angleTo(v2), 0)) arc = new Line(p1, p3); else arc = new Arc().FromThreePoint(p1, p2, p3); pl.Join(arc); } return pl; } const PolylineSpliteRectFuzz = 1e-3; /**封闭多段线 分割成矩形 */ function PolylineSpliteRect(outline) { if (!outline.IsClose || IsRect(outline).isRect) return [outline]; let firstDerv = outline.GetFistDeriv(0).normalize(); if (!isParallelTo(firstDerv, XAxis, PolylineSpliteRectFuzz) && !isParallelTo(firstDerv, YAxis, PolylineSpliteRectFuzz)) return [outline]; let cus = outline.Explode(); let yCus = []; for (let c of cus) { if (c instanceof Arc) return [outline]; let derv = c.GetFistDeriv(0).normalize(); if (isParallelTo(derv, YAxis, PolylineSpliteRectFuzz)) yCus.push(c); else if (!isParallelTo(derv, XAxis, PolylineSpliteRectFuzz)) { return [outline]; } } yCus.sort((c1, c2) => c1.StartPoint.x - c2.StartPoint.x); let rects = []; for (let i = 0; i < yCus.length - 1; i++) { let c1 = yCus[i]; let c2 = yCus[i + 1]; let x1 = c1.StartPoint.x; let x2 = c2.StartPoint.x; if (equaln(x1, x2)) continue; let y1; let y2; let res = c1.IntersectWith2(outline, IntersectOption.ExtendThis); let res2 = c2.IntersectWith2(outline, IntersectOption.ExtendThis); let pars = [...res.map(r => Math.floor(r.argParam)), ...res2.map(r => Math.floor(r.argParam))]; pars = [...new Set(pars)]; pars.sort((a, b) => a - b); let ys = []; for (let par of pars) { let c = outline.GetCurveAtParam(par); let derv = c.GetFistDeriv(0).normalize(); if (isParallelTo(derv, XAxis, PolylineSpliteRectFuzz)) { let x3 = c.StartPoint.x; let x4 = c.EndPoint.x; if (x3 > x4) [x3, x4] = [x4, x3]; if (isIntersect(x1, x2, x3, x4, -PolylineSpliteRectFuzz)) ys.push(c.StartPoint.y); } } if (ys.length < 2) return [outline]; ys.sort((a, b) => a - b); y1 = ys[0]; y2 = arrayLast(ys); rects.push(new Polyline().RectangleFrom2Pt(new Vector3(x1, y1), new Vector3(x2, y2))); } return rects; } var Circle_1; let circleGeometry; function GetCircleGeometry() { if (!circleGeometry) circleGeometry = BufferGeometryUtils.CreateFromPts(new EllipseCurve(0, 0, 1, 1, 0, 2 * Math.PI, false, 0).getPoints(360).map(AsVector3)); return circleGeometry; } let Circle = Circle_1 = class Circle extends Curve { constructor(center, radius = 1e-6) { super(); center && this._Matrix.setPosition(center); this._Radius = radius; } get Shape() { let sp = new Shape2(); sp.ellipse(0, 0, this._Radius, this._Radius, 0, 2 * Math.PI, false, 0); return sp; } get Center() { return new Vector3().setFromMatrixPosition(this._Matrix); } set Center(v) { this.WriteAllObjectRecord(); this._Matrix.setPosition(v); this.Update(); } get Radius() { return this._Radius; } set Radius(v) { this.WriteAllObjectRecord(); this._Radius = clamp(v, 1e-9, 1e19); this.Update(); } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); this.Center = this.Center.applyMatrix4(m); this.Radius = this.Radius * m.getMaxScaleOnAxis(); return this; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); reviseMirrorMatrix(this._Matrix); return this; } //******************** Curve function start*****************// get StartPoint() { return this.GetPointAtParam(0); } get StartParam() { return 0; } get EndPoint() { return this.GetPointAtParam(0); } get EndParam() { return 1; } PtInCurve(pt) { return pt.distanceToSquared(this.Center) < Math.pow(this.Radius, 2); } get Area() { return Math.PI * this._Radius ** 2; } get Area2() { return Math.PI * this._Radius ** 2; } get Length() { return Math.PI * 2 * this._Radius; } get IsClose() { return true; } //曲线为顺时针 get IsClockWise() { return false; } GetPointAtParam(param) { return polar(new Vector3(), param * 2 * Math.PI, this._Radius).applyMatrix4(this._Matrix); } GetPointAtDistance(distance) { let param = distance / (Math.PI * 2 * this._Radius); return this.GetPointAtParam(param); } GetDistAtParam(param) { return Math.PI * 2 * this._Radius * param; } GetDistAtPoint(pt) { let param = this.GetParamAtPoint(pt); return this.GetDistAtParam(param); } GetParamAtDist(d) { return d / (Math.PI * 2 * this._Radius); } GetSplitCurves(param) { let params; if (param instanceof Array) { params = param.filter(p => this.ParamOnCurve(p)); params.sort((a1, a2) => a2 - a1); //从大到小 arrayRemoveDuplicateBySort(params); if (params.length < 2) return []; } else //圆不能被单个参数切割 return []; //补上最后一个到第一个的弧 params.unshift(arrayLast(params)); let anglelist = params.map(param => Math.PI * 2 * param); let curvelist = new Array(); for (let i = 0; i < anglelist.length - 1; i++) { let sa = anglelist[i]; let ea = anglelist[i + 1]; if (!equaln(sa, ea, 1e-6)) { let arc = new Arc(new Vector3(), this._Radius, ea, sa, false); arc.ColorIndex = this.ColorIndex; arc.ApplyMatrix(this.OCS); curvelist.push(arc); } } return curvelist; } GetParamAtPoint(pt) { if (!this.PtOnCurve(pt)) return NaN; return angle(pt.clone().applyMatrix4(this.OCSInv)) / (Math.PI * 2); } PtOnCurve(pt) { return equaln(pt.distanceToSquared(this.Center), this._Radius * this._Radius, 1e-5); } GetOffsetCurves(offsetDist) { if ((offsetDist + this._Radius) > 0) { let circle = this.Clone(); circle.Radius = this._Radius + offsetDist; return [circle]; } return []; } IntersectWith2(curve, intType) { if (curve instanceof Arc) { return IntersectCircleAndArc(this, curve, intType); } if (curve instanceof Line) { return SwapParam(IntersectLineAndCircle(curve, this, reverseIntersectOption(intType))); } if (curve instanceof Circle_1) { return IntersectCircleAndCircle(this, curve); } if (curve instanceof Ellipse) { return SwapParam(IntersectEllipseAndCircleOrArc(curve, this, intType)); } if (curve instanceof Polyline) return SwapParam(IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType))); return []; } //******************** Curve function end*****************// get BoundingBox() { return new Box3Ext().setFromPoints(this.GetGripPoints()); } InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); let cirGeo = GetCircleGeometry(); if (renderType === RenderType.WireframePrint) { let geometry = new LineGeometry().setPositions(cirGeo.attributes.position.array); obj.add(new Line2(geometry, ColorMaterial.PrintLineMatrial)); } else { let line = new Line$1(cirGeo, ColorMaterial.GetLineMaterial(this._Color)); obj.add(line); } this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(type, obj) { obj.children[0].scale.set(this._Radius, this._Radius, this._Radius); obj.children[0].updateMatrix(); } UpdateDrawObjectMaterial(type, obj, material) { if (type === RenderType.WireframePrint); else { let m = obj.children[0]; m.material = material ? material : ColorMaterial.GetLineMaterial(this._Color); return obj; } } GetDragPointCount(drag) { if (drag === DragPointType.Grip) return 5; else return 1; } GetGripPoints() { let pts = [ new Vector3(), new Vector3(0, this._Radius), new Vector3(0, -this._Radius), new Vector3(-this._Radius, 0), new Vector3(this._Radius, 0), ]; let ocs = this.OCS; pts.forEach(p => p.applyMatrix4(ocs)); return pts; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.Nea: { return getArcOrCirNearPts(this, pickPoint, viewXform); } case ObjectSnapMode.Cen: return [this.Center]; case ObjectSnapMode.Per: if (lastPoint) { if (equaln(lastPoint.distanceToSquared(this.Center), 0, 1e-10)) return []; let l = new Line(this.Center, lastPoint); return l.IntersectWith(this, IntersectOption.ExtendBoth); } case ObjectSnapMode.Tan: let pts = GetTanPtsOnArcOrCircle(this, lastPoint); if (pts) return pts; case ObjectSnapMode.End: { let pts = this.GetGripPoints(); pts.shift(); return pts; } } return []; } MoveGripPoints(indexList, vec) { let pts = this.GetGripPoints(); if (indexList.length > 0) { let index = indexList[0]; let p = pts[index]; if (p) { if (index > 0) { p.add(vec); this.Radius = p.distanceTo(this.Center); } else { this.Center = this.Center.add(vec); } } } } GetStretchPoints() { let pts = [new Vector3()]; let ocs = this.OCS; pts.forEach(p => p.applyMatrix4(ocs)); return pts; } MoveStretchPoints(indexList, vec) { if (indexList.length > 0) { let mat = MoveMatrix(vec); this.ApplyMatrix(mat); } } GetFistDeriv(pt) { if (typeof pt === "number") pt = this.GetPointAtParam(pt); else pt = pt.clone(); pt.applyMatrix4(this.OCSInv); let an = angle(pt) + Math.PI * 0.5; return polar(new Vector3(), an, 1).applyMatrix4(new Matrix4().extractRotation(this.OCS)); } GetClosestPointTo(pt, extend) { pt = pt.clone().applyMatrix4(this.OCSInv); if (equalv2(pt, ZeroVec, 1e-8)) return this.GetPointAtParam(0); let a = Math.atan2(pt.y, pt.x); return polar(new Vector3, a, this._Radius).applyMatrix4(this._Matrix); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); file.Read(); this._Radius = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(1); file.Write(this._Radius); } }; Circle = Circle_1 = __decorate([ Factory ], Circle); const _LogInjectFunctions = []; function Log(message, ...optionalParams) { for (let f of _LogInjectFunctions) f(message, ...optionalParams); } let x = new Vector3(); let y = new Vector3(); let z = new Vector3(); function IsMirror(mtx) { mtx.extractBasis(x, y, z); // for a true orthogonal, non-mirrored base, u.cross(v) == w // If they have an opposite direction then we are mirroring const mirrorvalue = x.cross(y).dot(z); const ismirror = (mirrorvalue < 0); return ismirror; } /** Epsilon used during determination of near zero distances. * @default */ const EPS = 5e-2; // Tag factory: we can request a unique tag through CSG.getTag() let staticTag = 1; const getTag = () => staticTag++; /** Class Vector3D * Represents a 3D vector with X, Y, Z coordinates. */ class Vector3D extends Vector3 { clone() { return new Vector3D(this.x, this.y, this.z); } // find a vector that is somewhat perpendicular to this one randomNonParallelVector() { let x = Math.abs(this.x); let y = Math.abs(this.y); let z = Math.abs(this.z); if (x <= y && x <= z) return new Vector3D(1, 0, 0); else if (y <= x && y <= z) return new Vector3D(0, 1, 0); else return new Vector3D(0, 0, 1); } toString() { return ("(" + this.x.toFixed(5) + ", " + this.y.toFixed(5) + ", " + this.z.toFixed(5) + ")"); } } class Vector2D extends Vector2 { // extend to a 3D vector by adding a z coordinate: toVector3D(z) { return new Vector3D(this.x, this.y, z); } clone() { return new Vector2D(this.x, this.y); } // returns the vector rotated by 90 degrees clockwise normal() { return new Vector2D(this.y, -this.x); } cross(a) { return this.x * a.y - this.y * a.x; } } // # class Vertex // Represents a vertex of a polygon. Use your own vertex class instead of this // one to provide additional features like texture coordinates and vertex // colors. Custom vertex classes need to provide a `pos` property // `flipped()`, and `interpolate()` methods that behave analogous to the ones // FIXME: And a lot MORE (see plane.fromVector3Ds for ex) ! This is fragile code // defined by `Vertex`. class Vertex3D { constructor(pos, uv = new Vector2D()) { this.pos = pos; this.uv = uv; } clone() { return new Vertex3D(this.pos.clone(), this.uv.clone()); } // Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the // orientation of a polygon is flipped. flipped() { return this; } getTag() { let result = this.tag; if (!result) { result = getTag(); this.tag = result; } return result; } // Create a new vertex between this vertex and `other` by linearly // interpolating all properties using a parameter of `t`. Subclasses should // override this to interpolate additional properties. interpolate(other, t) { let pos = this.pos.clone().lerp(other.pos, t); let uv = this.uv.clone().lerp(other.uv, t); return new Vertex3D(pos, uv); } // Affine transformation of vertex. Returns a new Vertex transform(matrix4x4) { const newpos = this.pos.clone().applyMatrix4(matrix4x4); return new Vertex3D(newpos, this.uv); } } // # class Plane // Represents a plane in 3D space. class Plane { constructor(normal, w) { this.normal = normal; this.w = w; } flipped() { return new Plane(this.normal.clone().negate(), -this.w); } getTag() { if (!this.tag) this.tag = getTag(); return this.tag; } coplanarTo(plane) { return equalv3(this.normal, plane.normal, 1e-4) && equaln(this.w, plane.w, 1e-4); } transform(matrix4x4) { // get two vectors in the plane: let r = this.normal.randomNonParallelVector(); let u = this.normal.clone().cross(r); let v = this.normal.clone().cross(u); // get 3 points in the plane: let point1 = this.normal.clone().multiplyScalar(this.w); let point2 = u.add(point1); let point3 = v.add(point1); // transform the points: point1.applyMatrix4(matrix4x4); point2.applyMatrix4(matrix4x4); point3.applyMatrix4(matrix4x4); // and create a new plane from the transformed points: let newplane = Plane.fromVector3Ds(point1, point2, point3); if (IsMirror(matrix4x4)) { // the transform is mirroring // We should mirror the plane: newplane = newplane.flipped(); } return newplane; } splitLineBetweenPoints(p1, p2) { let direction = p2.pos.clone().sub(p1.pos); let labda = (this.w - this.normal.dot(p1.pos)) / this.normal.dot(direction); if (isNaN(labda)) labda = 0; if (labda > 1) labda = 1; if (labda < 0) labda = 0; let pos = p1.pos.clone().add(direction.multiplyScalar(labda)); let uv = p1.uv.clone().lerp(p2.uv, labda); return new Vertex3D(pos, uv); } static fromVector3Ds(a, b, c) { let n = b.clone() .sub(a) .cross(c.clone().sub(a)) .normalize(); return new Plane(n, n.dot(a)); } } var Type; (function (Type) { Type[Type["CoplanarFront"] = 0] = "CoplanarFront"; Type[Type["CoplanarBack"] = 1] = "CoplanarBack"; Type[Type["Front"] = 2] = "Front"; Type[Type["Back"] = 3] = "Back"; Type[Type["Spanning"] = 4] = "Spanning"; })(Type || (Type = {})); /** Class Polygon * Represents a convex polygon. The vertices used to initialize a polygon must * be coplanar and form a convex loop. They do not have to be `Vertex` * instances but they must behave similarly (duck typing can be used for * customization). *
* Each convex polygon has a `shared` property, which is shared between all * polygons that are clones of each other or were split from the same polygon. * This can be used to define per-polygon properties (such as surface color). *
* The plane of the polygon is calculated from the vertex coordinates if not provided. * The plane can alternatively be passed as the third argument to avoid calculations. * *表示凸多边形。 用于初始化多边形的顶点必须共面并形成凸环。 *多边形是彼此克隆或从同一多边形分割的多边形。 *这可用于定义每个多边形属性(例如表面颜色)。 */ class Polygon { constructor(vertices, plane) { this.vertices = vertices; this.plane = plane; if (!plane) this.plane = Plane.fromVector3Ds(vertices[0].pos, vertices[1].pos, vertices[2].pos); } /** Check whether the polygon is convex. (it should be, otherwise we will get unexpected results)*/ checkIfConvex() { return Polygon.verticesConvex(this.vertices, this.plane.normal); } // returns an array with a Vector3D (center point) and a radius boundingSphere() { if (!this.cachedBoundingSphere) { let box = this.boundingBox(); let middle = box[0].clone().add(box[1]).multiplyScalar(0.5); let radius3 = box[1].clone().sub(middle); let radius = radius3.length(); this.cachedBoundingSphere = [middle, radius]; } return this.cachedBoundingSphere; } // returns an array of two Vector3Ds (minimum coordinates and maximum coordinates) boundingBox() { if (!this.cachedBoundingBox) { let minpoint; let maxpoint; let vertices = this.vertices; let numvertices = vertices.length; if (numvertices === 0) minpoint = new Vector3D(0, 0, 0); else minpoint = vertices[0].pos.clone(); maxpoint = minpoint.clone(); for (let i = 1; i < numvertices; i++) { let point = vertices[i].pos; minpoint.min(point); maxpoint.max(point); } this.cachedBoundingBox = [minpoint, maxpoint]; } return this.cachedBoundingBox; } flipped() { let newvertices = this.vertices.map(v => v.flipped()); newvertices.reverse(); let newplane = this.plane.flipped(); return new Polygon(newvertices, newplane); } // Affine transformation of polygon. Returns a new Polygon transform(matrix4x4) { let newvertices = this.vertices.map(v => v.transform(matrix4x4)); let newplane = this.plane.transform(matrix4x4); if (IsMirror(matrix4x4)) { // need to reverse the vertex order // in order to preserve the inside/outside orientation: newvertices.reverse(); } return new Polygon(newvertices, newplane); } splitByPlane(plane) { let result = { type: null, front: null, back: null }; // cache in local lets (speedup): let planeNormal = plane.normal; let vertices = this.vertices; let numVertices = vertices.length; if (this.plane.coplanarTo(plane)) { result.type = Type.CoplanarFront; } else { let thisW = plane.w; let hasFront = false; let hasBack = false; let vertexIsBack = []; let MINEPS = -EPS; for (let i = 0; i < numVertices; i++) { let t = planeNormal.dot(vertices[i].pos) - thisW; let isBack = t < 0; vertexIsBack.push(isBack); if (t > EPS) hasFront = true; if (t < MINEPS) hasBack = true; } if (!hasFront && !hasBack) { // all points coplanar let t = planeNormal.dot(this.plane.normal); result.type = t >= 0 ? Type.CoplanarFront : Type.CoplanarBack; } else if (!hasBack) result.type = Type.Front; else if (!hasFront) result.type = Type.Back; else { result.type = Type.Spanning; let frontVertices = []; let backVertices = []; let isBack = vertexIsBack[0]; for (let vertexIndex = 0; vertexIndex < numVertices; vertexIndex++) { let vertex = vertices[vertexIndex]; let nextVertexindex = vertexIndex + 1; if (nextVertexindex >= numVertices) nextVertexindex = 0; let nextIsBack = vertexIsBack[nextVertexindex]; if (isBack === nextIsBack) { // line segment is on one side of the plane: if (isBack) backVertices.push(vertex); else frontVertices.push(vertex); } else { let intersectionVertex = plane.splitLineBetweenPoints(vertex, vertices[nextVertexindex]); if (isBack) { backVertices.push(vertex); backVertices.push(intersectionVertex); frontVertices.push(intersectionVertex); } else { frontVertices.push(vertex); frontVertices.push(intersectionVertex); backVertices.push(intersectionVertex); } } isBack = nextIsBack; } // for vertexindex // remove duplicate vertices: arrayRemoveDuplicateBySort(backVertices, (v1, v2) => equalv3(v1.pos, v2.pos, EPS)); if (backVertices.length > 2 && equalv3(backVertices[0].pos, arrayLast(backVertices).pos, EPS)) backVertices.pop(); arrayRemoveDuplicateBySort(frontVertices, (v1, v2) => equalv3(v1.pos, v2.pos, EPS)); if (frontVertices.length > 2 && equalv3(frontVertices[0].pos, arrayLast(frontVertices).pos, EPS)) frontVertices.pop(); if (frontVertices.length >= 3) result.front = new Polygon(frontVertices, this.plane); if (backVertices.length >= 3) result.back = new Polygon(backVertices, this.plane); } } return result; } /** * 是一个凸多边形 * @param vertices 顶点列表 * @param planenormal 平面法线 * @returns true:是凸多边形 false:不是凸多边形 */ static verticesConvex(vertices, planenormal) { let count = vertices.length; if (count < 3) return false; let prevPrevPos = vertices[count - 2].pos; let prevPos = vertices[count - 1].pos; for (let i = 0; i < count; i++) { let pos = vertices[i].pos; if (!Polygon.isConvexPoint(prevPrevPos, prevPos, pos, planenormal)) return false; prevPrevPos = prevPos; prevPos = pos; } return true; } // 计算3点是否凸角 static isConvexPoint(prevpoint, point, nextpoint, normal) { let crossproduct = point.clone().sub(prevpoint).cross(nextpoint.clone().sub(point)); let crossdotnormal = crossproduct.dot(normal); return crossdotnormal >= 0; } } // # class PolygonTreeNode // This class manages hierarchical splits of polygons // At the top is a root node which doesn hold a polygon, only child PolygonTreeNodes // Below that are zero or more 'top' nodes; each holds a polygon. The polygons can be in different planes // splitByPlane() splits a node by a plane. If the plane intersects the polygon, two new child nodes // are created holding the splitted polygon. // getPolygons() retrieves the polygon from the tree. If for PolygonTreeNode the polygon is split but // the two split parts (child nodes) are still intact, then the unsplit polygon is returned. // This ensures that we can safely split a polygon into many fragments. If the fragments are untouched, // getPolygons() will return the original unsplit polygon instead of the fragments. // remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated // since they are no longer intact. // constructor creates the root node: //此类管理多边形的层次分割 //顶部是一个根节点,它不包含多边形,只有子PolygonTreeNodes //下面是零个或多个“顶部”节点; 每个都有一个多边形。 多边形可以位于不同的平面中 // splitByPlane()按平面拆分节点。 如果平面与多边形相交,则会有两个新的子节点 //创建持有分割多边形。 // getPolygons()从树中检索多边形。 如果对于PolygonTreeNode,则多边形被拆分但是 //两个分割部分(子节点)仍然完好无损,然后返回未分割的多边形。 //这确保我们可以安全地将多边形拆分为多个片段。 如果碎片未受影响, // getPolygons()将返回原始的未分割多边形而不是片段。 // remove()从树中删除多边形。 删除多边形后,父多边形将失效 //因为它们不再完好无损 //构造函数创建根节点: class PolygonTreeNode { constructor(polygon) { this.children = []; this.removed = false; this.polygon = polygon; } // fill the tree with polygons. Should be called on the root node only; child nodes must // always be a derivate (split) of the parent node. addPolygons(polygons) { // new polygons can only be added to root node; children can only be splitted polygons if (!this.isRootNode()) throw new Error("Assertion failed"); for (let polygon of polygons) this.addChild(polygon); } // remove a node // - the siblings become toplevel nodes // - the parent is removed recursively remove() { if (this.removed) return; this.removed = true; // remove ourselves from the parent's children list: let parentschildren = this.parent.children; let i = parentschildren.indexOf(this); if (i < 0) throw new Error("Assertion failed"); parentschildren.splice(i, 1); // invalidate the parent's polygon, and of all parents above it: this.parent.recursivelyInvalidatePolygon(); } isRemoved() { return this.removed; } isRootNode() { return !this.parent; } // invert all polygons in the tree. Call on the root node invert() { if (!this.isRootNode()) throw new Error("Assertion failed"); // can only call this on the root node this.invertSub(); } getPolygon() { if (!this.polygon) throw new Error("Assertion failed"); // doesn't have a polygon, which means that it has been broken down return this.polygon; } getPolygons(outPolygons = []) { let children = [this]; let queue = [children]; for (let i = 0; i < queue.length; ++i) { // queue size can change in loop, don't cache length children = queue[i]; for (let node of children) { if (node.polygon) // the polygon hasn't been broken yet. We can ignore the children and return our polygon: outPolygons.push(node.polygon); else // our polygon has been split up and broken, so gather all subpolygons from the children queue.push(node.children); } } return outPolygons; } // split the node by a plane; add the resulting nodes to the frontnodes and backnodes array // If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays // If the plane does intersect the polygon, two new child nodes are created for the front and back fragments, // and added to both arrays. splitByPlane(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes) { if (this.children.length) { let queue = [this.children]; for (let i = 0; i < queue.length; i++) { // queue.length can increase, do not cache let nodes = queue[i]; for (let j = 0, l = nodes.length; j < l; j++) { // ok to cache length let node = nodes[j]; if (node.children.length) queue.push(node.children); else { // no children. Split the polygon: node.splitByPlaneNotChildren(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes); } } } } else { this.splitByPlaneNotChildren(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes); } } // only to be called for nodes with no children // 仅用于没有子节点的节点 splitByPlaneNotChildren(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes) { if (!this.polygon) return; let polygon = this.polygon; let bound = polygon.boundingSphere(); let sphereradius = bound[1] + EPS; // FIXME Why add imprecision? let planenormal = plane.normal; let spherecenter = bound[0]; let d = planenormal.dot(spherecenter) - plane.w; if (d > sphereradius) frontNodes.push(this); else if (d < -sphereradius) backNodes.push(this); else { let splitresult = polygon.splitByPlane(plane); switch (splitresult.type) { case Type.CoplanarFront: coplanarFrontNodes.push(this); break; case Type.CoplanarBack: coplanarBackNodes.push(this); break; case Type.Front: frontNodes.push(this); break; case Type.Back: backNodes.push(this); break; case Type.Spanning: if (splitresult.front) { let frontNode = this.addChild(splitresult.front); frontNodes.push(frontNode); } if (splitresult.back) { let backNode = this.addChild(splitresult.back); backNodes.push(backNode); } break; } } } // add child to a node // this should be called whenever the polygon is split // a child should be created for every fragment of the split polygon // returns the newly created child addChild(polygon) { let newchild = new PolygonTreeNode(polygon); newchild.parent = this; this.children.push(newchild); return newchild; } invertSub() { let queue = [[this]]; for (let i = 0; i < queue.length; i++) { let children = queue[i]; for (let j = 0, l = children.length; j < l; j++) { let node = children[j]; if (node.polygon) node.polygon = node.polygon.flipped(); queue.push(node.children); } } } recursivelyInvalidatePolygon() { let node = this; while (node.polygon) { node.polygon = null; if (node.parent) node = node.parent; } } } // # class Tree // This is the root of a BSP tree // We are using this separate class for the root of the tree, to hold the PolygonTreeNode root // The actual tree is kept in this.rootnode class Tree { constructor(polygons) { this.polygonTree = new PolygonTreeNode(); this.rootNode = new Node(null); this.addPolygons(polygons); } invert() { this.polygonTree.invert(); this.rootNode.invert(); } // Remove all polygons in this BSP tree that are inside the other BSP tree /** * this 减去 tree 删除此BSP树中位于其他BSP树内的所有多边形 * @param tree 不会被修改 * @param [alsoRemovecoplanarFront=false] 同时删除共面 */ clipTo(tree, alsoRemovecoplanarFront = false) { this.rootNode.clipTo(tree, alsoRemovecoplanarFront); } allPolygons() { return this.polygonTree.getPolygons(); } addPolygons(polygons) { if (polygons.length > 1e4) return; let polygonTreeNodes = polygons.map((p) => this.polygonTree.addChild(p)); this.rootNode.addPolygonTreeNodes(polygonTreeNodes); } } // # class Node // Holds a node in a BSP tree. A BSP tree is built from a collection of polygons // by picking a polygon to split along. // Polygons are not stored directly in the tree, but in PolygonTreeNodes, stored in // this.polygontreenodes. Those PolygonTreeNodes are children of the owning // Tree.polygonTree // This is not a leafy BSP tree since there is // no distinction between internal and leaf nodes. class Node { constructor(parent) { this.polygonTreeNodes = []; this.parent = parent; } // Convert solid space to empty space and empty space to solid space. invert() { let queue = [this]; for (let i = 0; i < queue.length; i++) { let node = queue[i]; if (node.plane) node.plane = node.plane.flipped(); if (node.front) queue.push(node.front); if (node.back) queue.push(node.back); let temp = node.front; node.front = node.back; node.back = temp; } } // clip polygontreenodes to our plane // calls remove() for all clipped PolygonTreeNodes //将polygontreenodes剪辑到我们的飞机上 //为所有剪切的PolygonTreeNodes调用remove() clipPolygons(polygonTreeNodes, alsoRemoveCoplanarFront) { let args = { node: this, polygonTreeNodes }; let stack = []; do { let node = args.node; let polygonTreeNodes1 = args.polygonTreeNodes; // begin "function" if (node.plane) { let backnodes = []; let frontnodes = []; let coplanarfrontnodes = alsoRemoveCoplanarFront ? backnodes : frontnodes; let plane = node.plane; for (let node1 of polygonTreeNodes1) { if (!node1.isRemoved()) node1.splitByPlane(plane, coplanarfrontnodes, backnodes, frontnodes, backnodes); } if (node.front && frontnodes.length > 0) stack.push({ node: node.front, polygonTreeNodes: frontnodes }); let numbacknodes = backnodes.length; if (node.back && numbacknodes > 0) stack.push({ node: node.back, polygonTreeNodes: backnodes }); else { // there's nothing behind this plane. Delete the nodes behind this plane: // 这架飞机背后什么也没有。 删除此平面后面的节点: for (let i = 0; i < numbacknodes; i++) backnodes[i].remove(); } } args = stack.pop(); } while (args); } // Remove all polygons in this BSP tree that are inside the other BSP tree // `tree`. clipTo(tree, alsoRemovecoplanarFront) { let node = this; let stack = []; do { if (node.polygonTreeNodes.length > 0) { tree.rootNode.clipPolygons(node.polygonTreeNodes, alsoRemovecoplanarFront); } if (node.front) stack.push(node.front); if (node.back) stack.push(node.back); node = stack.pop(); } while (node); } addPolygonTreeNodes(polygonTreeNodes) { let args = { node: this, polygontreenodes: polygonTreeNodes }; let stack = []; let doCount = 0; do { if (doCount > 1e3) throw "程序内部错误:尝试对平面进行切割失败!进入死循环!"; doCount++; let node = args.node; polygonTreeNodes = args.polygontreenodes; if (polygonTreeNodes.length === 0) { args = stack.pop(); continue; } if (!node.plane) { let bestplane = polygonTreeNodes[Math.floor(polygonTreeNodes.length / 2)].getPolygon().plane; node.plane = bestplane; } let frontNodes = []; let backNodes = []; for (let i = 0, n = polygonTreeNodes.length; i < n; ++i) { polygonTreeNodes[i].splitByPlane(node.plane, node.polygonTreeNodes, backNodes, frontNodes, backNodes); } if (frontNodes.length > 0) { if (!node.front) node.front = new Node(node); stack.push({ node: node.front, polygontreenodes: frontNodes }); } if (backNodes.length > 0) { if (!node.back) node.back = new Node(node); stack.push({ node: node.back, polygontreenodes: backNodes }); } args = stack.pop(); } while (args); } getParentPlaneNormals(normals, maxdepth) { if (maxdepth > 0) { if (this.parent) { normals.push(this.parent.plane.normal); this.parent.getParentPlaneNormals(normals, maxdepth - 1); } } } } // ////////////////////////////// // ## class fuzzyFactory // This class acts as a factory for objects. We can search for an object with approximately // the desired properties (say a rectangle with width 2 and height 1) // The lookupOrCreate() method looks for an existing object (for example it may find an existing rectangle // with width 2.0001 and height 0.999. If no object is found, the user supplied callback is // called, which should generate a new object. The new object is inserted into the database // so it can be found by future lookupOrCreate() calls. // Constructor: // numdimensions: the number of parameters for each object // for example for a 2D rectangle this would be 2 // tolerance: The maximum difference for each parameter allowed to be considered a match class FuzzyFactory { constructor(numdimensions, tolerance) { this.lookuptable = {}; this.multiplier = 1.0 / tolerance; } // let obj = f.lookupOrCreate([el1, el2, el3], function(elements) {/* create the new object */}); // Performs a fuzzy lookup of the object with the specified elements. // If found, returns the existing object // If not found, calls the supplied callback function which should create a new object with // the specified properties. This object is inserted in the lookup database. lookupOrCreate(els, object) { let hash = ""; let multiplier = this.multiplier; for (let el of els) { let valueQuantized = Math.round(el * multiplier); hash += valueQuantized + "/"; } if (hash in this.lookuptable) return this.lookuptable[hash]; else { let hashparts = els.map(el => { let q0 = Math.floor(el * multiplier); let q1 = q0 + 1; return ["" + q0 + "/", "" + q1 + "/"]; }); let numelements = els.length; let numhashes = 1 << numelements; for (let hashmask = 0; hashmask < numhashes; ++hashmask) { let hashmaskShifted = hashmask; hash = ""; hashparts.forEach(hashpart => { hash += hashpart[hashmaskShifted & 1]; hashmaskShifted >>= 1; }); this.lookuptable[hash] = object; } return object; } } } class FuzzyCSGFactory { constructor() { this.vertexfactory = new FuzzyFactory(3, EPS); this.planefactory = new FuzzyFactory(4, EPS); } getVertex(sourcevertex) { let elements = [sourcevertex.pos.x, sourcevertex.pos.y, sourcevertex.pos.z]; let result = this.vertexfactory.lookupOrCreate(elements, sourcevertex); return result; } getPlane(sourceplane) { let elements = [sourceplane.normal.x, sourceplane.normal.y, sourceplane.normal.z, sourceplane.w]; let result = this.planefactory.lookupOrCreate(elements, sourceplane); return result; } getPolygon(sourcePolygon, outputPolygon = sourcePolygon) { let newPlane = this.getPlane(sourcePolygon.plane); let newVertices = sourcePolygon.vertices.map(vertex => this.getVertex(vertex)); // two vertices that were originally very close may now have become // truly identical (referring to the same Vertex object). // Remove duplicate vertices: let newVerticesDedup = []; //新的顶点列表(已过滤重复) if (newVertices.length > 0) { let prevVertexTag = newVertices[newVertices.length - 1].getTag(); for (let vertex of newVertices) { let vertextag = vertex.getTag(); if (vertextag !== prevVertexTag) newVerticesDedup.push(vertex); prevVertexTag = vertextag; } } // If it's degenerate, remove all vertices: if (newVerticesDedup.length < 3) newVerticesDedup = []; outputPolygon.vertices = newVertices; outputPolygon.plane = newPlane; return outputPolygon; } } /** * Returns a cannoicalized version of the input csg : ie every very close * points get deduplicated * * 返回删除重复点的csg,重复点将被合并 */ function canonicalizeCSG(csg) { const factory = new FuzzyCSGFactory(); let result = CSGFromCSGFuzzyFactory(factory, csg); result.isCanonicalized = true; result.isRetesselated = csg.isRetesselated; return result; } function CSGFromCSGFuzzyFactory(factory, sourcecsg) { let newpolygons = sourcecsg.polygons.filter(poly => { return factory.getPolygon(poly).vertices.length >= 3; }); return new CSG(newpolygons); } /** * Returns an array of Vector3D, providing minimum coordinates and maximum coordinates * of this solid. * @example * let bounds = A.getBounds() * let minX = bounds[0].x */ function bounds(csg) { if (!csg.cachedBoundingBox) { let minpoint; let maxpoint; let polygons = csg.polygons; let numpolygons = polygons.length; for (let i = 0; i < numpolygons; i++) { let polygon = polygons[i]; let bounds = polygon.boundingBox(); if (i === 0) { minpoint = bounds[0].clone(); maxpoint = bounds[1].clone(); } else { minpoint.min(bounds[0]); maxpoint.max(bounds[1]); } } // FIXME: not ideal, we are mutating the input, we need to move some of it out csg.cachedBoundingBox = [minpoint, maxpoint]; } return csg.cachedBoundingBox; } function fnNumberSort(a, b) { return a - b; } function insertSorted(array, element, comparefunc) { let leftbound = 0; let rightbound = array.length; while (rightbound > leftbound) { let testindex = Math.floor((leftbound + rightbound) / 2); let testelement = array[testindex]; let compareresult = comparefunc(element, testelement); if (compareresult > 0) // element > testelement leftbound = testindex + 1; else rightbound = testindex; } array.splice(leftbound, 0, element); } // Get the x coordinate of a point with a certain y coordinate, interpolated between two // points (CSG.Vector2D). // Interpolation is robust even if the points have the same y coordinate function interpolateBetween2DPointsForY(point1, point2, y) { let f1 = y - point1.y; let f2 = point2.y - point1.y; if (f2 < 0) { f1 = -f1; f2 = -f2; } let t; if (f1 <= 0) t = 0.0; else if (f1 >= f2) t = 1.0; else if (f2 < 1e-10) // FIXME Should this be CSG.EPS? t = 0.5; else t = f1 / f2; let result = point1.x + t * (point2.x - point1.x); return result; } /** class Line2D * Represents a directional line in 2D space * A line is parametrized by its normal vector (perpendicular to the line, rotated 90 degrees counter clockwise) * and w. The line passes through the point .times(w). * Equation: p is on line if normal.dot(p)==w */ class Line2D { constructor(normal, w) { this.normal = normal.clone(); let l = this.normal.length(); w *= l; this.normal.normalize(); this.w = w; } direction() { return this.normal; } static fromPoints(p1, p2) { let direction = p2.clone().sub(p1); let normal = direction .normal() .negate() .normalize(); let w = p1.dot(normal); return new Line2D(normal, w); } } /** class OrthoNormalBasis * Reprojects points on a 3D plane onto a 2D plane * or from a 2D plane back onto the 3D plane */ class OrthoNormalBasis { constructor(plane, rightVector = plane.normal.randomNonParallelVector()) { this.plane = plane; this.v = plane.normal.clone().cross(rightVector).normalize(); this.u = this.v.clone().cross(plane.normal); this.plane = plane; this.planeorigin = plane.normal.clone().multiplyScalar(plane.w); } to2D(vec3) { return new Vector2D(vec3.dot(this.u), vec3.dot(this.v)); } to3D(vec2) { return this.planeorigin.clone() .add(this.u.clone().multiplyScalar(vec2.x)) .add(this.v.clone().multiplyScalar(vec2.y)); } } //一组共面多边形的Retesselation函数。 请参阅此文件顶部的介绍。 function reTesselateCoplanarPolygons(sourcePolygons, destpolygons = []) { let numPolygons = sourcePolygons.length; if (numPolygons < 2) { destpolygons.push(...sourcePolygons); return; } let plane = sourcePolygons[0].plane; let orthobasis = new OrthoNormalBasis(plane); // let xcoordinatebins = {} let yCoordinateBins = {}; //整数map let yCoordinateBinningFactor = (1.0 / EPS) * 10; let polygonVertices2d = []; // (Vector2[])[]; let polygonTopVertexIndexes = []; // 每个多边形最顶层顶点的索引数组 minIndex let topY2PolygonIndexes = {}; // Map let yCoordinateToPolygonIndexes = {}; // Map > Y坐标映射所有的多边形 //将多边形转换为2d点表 polygonVertices2d //建立y对应的多边形Map yCoordinateToPolygonIndexes for (let polygonIndex = 0; polygonIndex < numPolygons; polygonIndex++) { let poly3d = sourcePolygons[polygonIndex]; let numVertices = poly3d.vertices.length; if (numVertices === 0) continue; let vertices2d = []; //Vector2d[]; let minIndex = -1; let miny, maxy; for (let i = 0; i < numVertices; i++) { let pos2d = orthobasis.to2D(poly3d.vertices[i].pos); // perform binning of y coordinates: If we have multiple vertices very // close to each other, give them the same y coordinate: let yCoordinatebin = Math.floor(pos2d.y * yCoordinateBinningFactor); let newy; if (yCoordinatebin in yCoordinateBins) newy = yCoordinateBins[yCoordinatebin]; else if (yCoordinatebin + 1 in yCoordinateBins) newy = yCoordinateBins[yCoordinatebin + 1]; else if (yCoordinatebin - 1 in yCoordinateBins) newy = yCoordinateBins[yCoordinatebin - 1]; else { newy = pos2d.y; yCoordinateBins[yCoordinatebin] = pos2d.y; } pos2d = new Vector2D(pos2d.x, newy); vertices2d.push(pos2d); if (i === 0 || newy < miny) { miny = newy; minIndex = i; } if (i === 0 || newy > maxy) maxy = newy; if (!(newy in yCoordinateToPolygonIndexes)) yCoordinateToPolygonIndexes[newy] = {}; yCoordinateToPolygonIndexes[newy][polygonIndex] = true; } //退化多边形,所有顶点都具有相同的y坐标。 从现在开始忽略它: if (miny >= maxy) continue; if (!(miny in topY2PolygonIndexes)) topY2PolygonIndexes[miny] = []; topY2PolygonIndexes[miny].push(polygonIndex); // reverse the vertex order: vertices2d.reverse(); minIndex = numVertices - minIndex - 1; polygonVertices2d.push(vertices2d); polygonTopVertexIndexes.push(minIndex); } //所有的y坐标,从小到大排序 let yCoordinates = []; for (let ycoordinate in yCoordinateToPolygonIndexes) yCoordinates.push(ycoordinate); yCoordinates.sort(fnNumberSort); //迭代y坐标 从低到高 // activepolygons :'active'的源多边形,即与y坐标相交 // 多边形是从左往右排序的 // activepolygons 中的每个元素都具有以下属性: // polygonindex 源多边形的索引(即sourcepolygons的索引 和polygonvertices2d数组) // leftvertexindex 左边 在当前y坐标处或刚好在当前y坐标之上 // rightvertexindex 右边 // topleft bottomleft 与当前y坐标交叉的多边形左侧的坐标 // topright bottomright 与当前y坐标交叉的多边形右侧的坐标 let activePolygons = []; let prevOutPolygonRow = []; //上一个输出多边形行? for (let yindex = 0; yindex < yCoordinates.length; yindex++) { let yCoordinateStr = yCoordinates[yindex]; let yCoordinate = Number(yCoordinateStr); // 用当前的y 更新 activePolygons // - 删除以y坐标结尾的所有多边形 删除polygon maxy = y 的多边形 // - 更新 leftvertexindex 和 rightvertexindex (指向当前顶点索引) // 在多边形的左侧和右侧 // 迭代在Y坐标处有一个角的所有多边形 let polygonIndexeSwithCorner = yCoordinateToPolygonIndexes[yCoordinateStr]; for (let activePolygonIndex = 0; activePolygonIndex < activePolygons.length; activePolygonIndex++) { let activepolygon = activePolygons[activePolygonIndex]; let polygonindex = activepolygon.polygonindex; if (!polygonIndexeSwithCorner[polygonindex]) //如果不在角内 continue; //多边形在此y坐标处有一个角 let vertices2d = polygonVertices2d[polygonindex]; let numvertices = vertices2d.length; let newleftvertexindex = activepolygon.leftvertexindex; let newrightvertexindex = activepolygon.rightvertexindex; //看看我们是否需要增加 leftvertexindex 或减少 rightvertexindex : while (true) { let nextleftvertexindex = newleftvertexindex + 1; if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0; if (vertices2d[nextleftvertexindex].y !== yCoordinate) break; newleftvertexindex = nextleftvertexindex; } //减少 rightvertexindex let nextrightvertexindex = newrightvertexindex - 1; if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1; if (vertices2d[nextrightvertexindex].y === yCoordinate) newrightvertexindex = nextrightvertexindex; if (newleftvertexindex !== activepolygon.leftvertexindex //有向上更新 && newleftvertexindex === newrightvertexindex //指向同一个点 ) { // We have increased leftvertexindex or decreased rightvertexindex, and now they point to the same vertex // This means that this is the bottom point of the polygon. We'll remove it: //我们增加了leftvertexindex或减少了rightvertexindex,现在它们指向同一个顶点 //这意味着这是多边形的底点。 我们将删除它: activePolygons.splice(activePolygonIndex, 1); --activePolygonIndex; } else { activepolygon.leftvertexindex = newleftvertexindex; activepolygon.rightvertexindex = newrightvertexindex; activepolygon.topleft = vertices2d[newleftvertexindex]; activepolygon.topright = vertices2d[newrightvertexindex]; let nextleftvertexindex = newleftvertexindex + 1; if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0; activepolygon.bottomleft = vertices2d[nextleftvertexindex]; let nextrightvertexindex = newrightvertexindex - 1; if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1; activepolygon.bottomright = vertices2d[nextrightvertexindex]; } } let nextYCoordinate; // number y if (yindex >= yCoordinates.length - 1) { // last row, all polygons must be finished here: // 最后一行,所有多边形必须在这里完成: activePolygons = []; } else // yindex < ycoordinates.length-1 { nextYCoordinate = Number(yCoordinates[yindex + 1]); let middleYCoordinate = 0.5 * (yCoordinate + nextYCoordinate); // update activepolygons by adding any polygons that start here: // 添加从这里开始的多边形 到 activePolygons let startingPolygonIndexes = topY2PolygonIndexes[yCoordinateStr]; for (let polygonindex_key in startingPolygonIndexes) { let polygonindex = startingPolygonIndexes[polygonindex_key]; let vertices2d = polygonVertices2d[polygonindex]; let numvertices = vertices2d.length; let topVertexIndex = polygonTopVertexIndexes[polygonindex]; // the top of the polygon may be a horizontal line. In that case topvertexindex can point to any point on this line. // Find the left and right topmost vertices which have the current y coordinate: // 顶部可以是一条直线,寻找最左边的点和最右边的点 let topleftvertexindex = topVertexIndex; while (true) { let i = topleftvertexindex + 1; if (i >= numvertices) i = 0; if (vertices2d[i].y !== yCoordinate) break; if (i === topVertexIndex) break; // should not happen, but just to prevent endless loops topleftvertexindex = i; } let toprightvertexindex = topVertexIndex; while (true) { let i = toprightvertexindex - 1; if (i < 0) i = numvertices - 1; if (vertices2d[i].y !== yCoordinate) break; if (i === topleftvertexindex) break; // should not happen, but just to prevent endless loops toprightvertexindex = i; } let nextleftvertexindex = topleftvertexindex + 1; if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0; let nextrightvertexindex = toprightvertexindex - 1; if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1; let newactivepolygon = { polygonindex: polygonindex, leftvertexindex: topleftvertexindex, rightvertexindex: toprightvertexindex, topleft: vertices2d[topleftvertexindex], topright: vertices2d[toprightvertexindex], bottomleft: vertices2d[nextleftvertexindex], bottomright: vertices2d[nextrightvertexindex] }; //二分插入 insertSorted(activePolygons, newactivepolygon, function (el1, el2) { let x1 = interpolateBetween2DPointsForY(el1.topleft, el1.bottomleft, middleYCoordinate); let x2 = interpolateBetween2DPointsForY(el2.topleft, el2.bottomleft, middleYCoordinate); if (x1 > x2) return 1; if (x1 < x2) return -1; return 0; }); } } //#region // if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) ) // if(true) // { let newOutPolygonRow = []; //输出多边形 // Build the output polygons for the next row in newOutPolygonRow: //现在 activepolygons 是最新的 //为 newOutPolygonRow 中的下一行构建输出多边形: for (let activepolygonKey in activePolygons) { let activepolygon = activePolygons[activepolygonKey]; let x = interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, yCoordinate); let topleft = new Vector2D(x, yCoordinate); x = interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, yCoordinate); let topright = new Vector2D(x, yCoordinate); x = interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, nextYCoordinate); let bottomleft = new Vector2D(x, nextYCoordinate); x = interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, nextYCoordinate); let bottomright = new Vector2D(x, nextYCoordinate); let outPolygon = { topleft: topleft, topright: topright, bottomleft: bottomleft, bottomright: bottomright, leftline: Line2D.fromPoints(topleft, bottomleft), rightline: Line2D.fromPoints(bottomright, topright) }; if (newOutPolygonRow.length > 0) { let prevoutpolygon = newOutPolygonRow[newOutPolygonRow.length - 1]; let d1 = outPolygon.topleft.distanceTo(prevoutpolygon.topright); let d2 = outPolygon.bottomleft.distanceTo(prevoutpolygon.bottomright); if (d1 < EPS && d2 < EPS) { // we can join this polygon with the one to the left: outPolygon.topleft = prevoutpolygon.topleft; outPolygon.leftline = prevoutpolygon.leftline; outPolygon.bottomleft = prevoutpolygon.bottomleft; newOutPolygonRow.splice(newOutPolygonRow.length - 1, 1); } } newOutPolygonRow.push(outPolygon); } if (yindex > 0) { // try to match the new polygons against the previous row: //尝试将新多边形与上一行匹配: let prevContinuedIndexes = {}; let matchedIndexes = {}; for (let i = 0; i < newOutPolygonRow.length; i++) { let thispolygon = newOutPolygonRow[i]; for (let ii = 0; ii < prevOutPolygonRow.length; ii++) { if (!matchedIndexes[ii]) { // not already processed? // We have a match if the sidelines are equal or if the top coordinates // are on the sidelines of the previous polygon let prevpolygon = prevOutPolygonRow[ii]; if (prevpolygon.bottomleft.distanceTo(thispolygon.topleft) < EPS) { if (prevpolygon.bottomright.distanceTo(thispolygon.topright) < EPS) { // Yes, the top of this polygon matches the bottom of the previous: matchedIndexes[ii] = true; // Now check if the joined polygon would remain convex: let d1 = thispolygon.leftline.direction().x - prevpolygon.leftline.direction().x; let d2 = thispolygon.rightline.direction().x - prevpolygon.rightline.direction().x; let leftlinecontinues = Math.abs(d1) < EPS; let rightlinecontinues = Math.abs(d2) < EPS; let leftlineisconvex = leftlinecontinues || d1 >= 0; let rightlineisconvex = rightlinecontinues || d2 >= 0; if (leftlineisconvex && rightlineisconvex) { // yes, both sides have convex corners: // This polygon will continue the previous polygon thispolygon.outpolygon = prevpolygon.outpolygon; thispolygon.leftlinecontinues = leftlinecontinues; thispolygon.rightlinecontinues = rightlinecontinues; prevContinuedIndexes[ii] = true; } break; } } } // if(!prevcontinuedindexes[ii]) } // for ii } // for i for (let ii = 0; ii < prevOutPolygonRow.length; ii++) { if (!prevContinuedIndexes[ii]) { // polygon ends here // Finish the polygon with the last point(s): let prevpolygon = prevOutPolygonRow[ii]; prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright); if (prevpolygon.bottomright.distanceTo(prevpolygon.bottomleft) > EPS) { // polygon ends with a horizontal line: prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft); } // reverse the left half so we get a counterclockwise circle: prevpolygon.outpolygon.leftpoints.reverse(); let points2d = prevpolygon.outpolygon.rightpoints.concat(prevpolygon.outpolygon.leftpoints); let vertices = points2d.map(v => new Vertex3D(orthobasis.to3D(v))); let polygon = new Polygon(vertices, plane); destpolygons.push(polygon); } } } for (let i = 0; i < newOutPolygonRow.length; i++) { let thispolygon = newOutPolygonRow[i]; if (!thispolygon.outpolygon) { // polygon starts here: thispolygon.outpolygon = { leftpoints: [], rightpoints: [] }; thispolygon.outpolygon.leftpoints.push(thispolygon.topleft); if (thispolygon.topleft.distanceTo(thispolygon.topright) > EPS) { // we have a horizontal line at the top: thispolygon.outpolygon.rightpoints.push(thispolygon.topright); } } else { // continuation of a previous row if (!thispolygon.leftlinecontinues) { thispolygon.outpolygon.leftpoints.push(thispolygon.topleft); } if (!thispolygon.rightlinecontinues) { thispolygon.outpolygon.rightpoints.push(thispolygon.topright); } } } prevOutPolygonRow = newOutPolygonRow; // } //#endregion } // for yindex } function reTesselate(csg) { if (csg.isRetesselated) return csg; let polygonsPerPlane = {}; let isCanonicalized = csg.isCanonicalized; let fuzzyfactory = new FuzzyCSGFactory(); for (let polygon of csg.polygons) { let plane = polygon.plane; if (!isCanonicalized) { // in order to identify polygons having the same plane, we need to canonicalize the planes // We don't have to do a full canonizalization (including vertices), to save time only do the planes and the shared data: plane = fuzzyfactory.getPlane(plane); } let tag = plane.getTag(); if (!(tag in polygonsPerPlane)) polygonsPerPlane[tag] = [polygon]; else polygonsPerPlane[tag].push(polygon); } let destpolygons = []; for (let planetag in polygonsPerPlane) { let sourcepolygons = polygonsPerPlane[planetag]; reTesselateCoplanarPolygons(sourcepolygons, destpolygons); } let resultCSG = new CSG(destpolygons); resultCSG.isRetesselated = true; return resultCSG; } /** Class CSG * Holds a binary space partition tree representing a 3D solid. Two solids can * be combined using the `union()`, `subtract()`, and `intersect()` methods. * @constructor */ class CSG { constructor(polygons = []) { this.polygons = polygons; /** # 是否已精简重复点 */ this.isCanonicalized = false; /** # 是否已合并轮廓 */ this.isRetesselated = false; } /** * Return a new CSG solid representing the space in either this solid or * in the given solids. Neither this solid nor the given solids are modified. * @param {CSG[]} csg - list of CSG objects * @returns {CSG} new CSG object * @example * let C = A.union(B) * @example * +-------+ +-------+ * | | | | * | A | | | * | +--+----+ = | +----+ * +----+--+ | +----+ | * | B | | | * | | | | * +-------+ +-------+ */ union(csg) { let csgs; if (csg instanceof Array) { csgs = csg.slice(0); csgs.push(this); } else csgs = [this, csg]; let i; // combine csg pairs in a way that forms a balanced binary tree pattern for (i = 1; i < csgs.length; i += 2) { csgs.push(csgs[i - 1].unionSub(csgs[i])); } return csgs[i - 1].reTesselated().canonicalized(); } unionSub(csg, retesselate = false, canonicalize = false) { if (!this.mayOverlap(csg)) return this.unionForNonIntersecting(csg); let a = new Tree(this.polygons); let b = new Tree(csg.polygons); a.clipTo(b); // b.clipTo(a, true); // ERROR: this doesn't work b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); let newpolygons = [...a.allPolygons(), ...b.allPolygons()]; let resultCSG = new CSG(newpolygons); if (retesselate) resultCSG = resultCSG.reTesselated(); if (canonicalize) resultCSG = resultCSG.canonicalized(); return resultCSG; } // Like union, but when we know that the two solids are not intersecting // Do not use if you are not completely sure that the solids do not intersect! unionForNonIntersecting(csg) { let newpolygons = [...this.polygons, ...csg.polygons]; let result = new CSG(newpolygons); result.isCanonicalized = this.isCanonicalized && csg.isCanonicalized; result.isRetesselated = this.isRetesselated && csg.isRetesselated; return result; } /** * Return a new CSG solid representing space in this solid but * not in the given solids. Neither this solid nor the given solids are modified. * @returns new CSG object * @example * let C = A.subtract(B) * @example * +-------+ +-------+ * | | | | * | A | | | * | +--+----+ = | +--+ * +----+--+ | +----+ * | B | * | | * +-------+ */ subtract(csg) { let csgs; if (csg instanceof Array) csgs = csg; else csgs = [csg]; let result = this; for (let i = 0; i < csgs.length; i++) { let islast = i === csgs.length - 1; result = result.subtractSub(csgs[i], islast, islast); } return result; } subtractSub(csg, retesselate = false, canonicalize = false) { let a = new Tree(this.polygons); let b = new Tree(csg.polygons); a.invert(); a.clipTo(b); b.clipTo(a, true); a.addPolygons(b.allPolygons()); a.invert(); let result = new CSG(a.allPolygons()); // if (retesselate) result = result.reTesselated(); // if (canonicalize) result = result.canonicalized(); return result; } /** * Return a new CSG solid representing space in both this solid and * in the given solids. Neither this solid nor the given solids are modified. * let C = A.intersect(B) * @returns new CSG object * @example * +-------+ * | | * | A | * | +--+----+ = +--+ * +----+--+ | +--+ * | B | * | | * +-------+ */ intersect(csg) { let csgs; if (csg instanceof Array) csgs = csg; else csgs = [csg]; let result = this; for (let i = 0; i < csgs.length; i++) { let islast = i === csgs.length - 1; result = result.intersectSub(csgs[i], islast, islast); } return result; } intersectSub(csg, retesselate = false, canonicalize = false) { let a = new Tree(this.polygons); let b = new Tree(csg.polygons); a.invert(); b.clipTo(a); b.invert(); a.clipTo(b); b.clipTo(a); a.addPolygons(b.allPolygons()); a.invert(); let result = new CSG(a.allPolygons()); // if (retesselate) result = result.reTesselated(); // if (canonicalize) result = result.canonicalized(); return result; } /** * Return a new CSG solid with solid and empty space switched. * This solid is not modified. */ invert() { let flippedpolygons = this.polygons.map(p => p.flipped()); return new CSG(flippedpolygons); } // Affine transformation of CSG object. Returns a new CSG object transform1(matrix4x4) { let newpolygons = this.polygons.map(p => { return p.transform(matrix4x4); }); let result = new CSG(newpolygons); result.isCanonicalized = this.isCanonicalized; result.isRetesselated = this.isRetesselated; return result; } /** * Return a new CSG solid that is transformed using the given Matrix. * Several matrix transformations can be combined before transforming this solid. * @param {CSG.Matrix4x4} matrix4x4 - matrix to be applied * @returns {CSG} new CSG object * @example * var m = new CSG.Matrix4x4() * m = m.multiply(CSG.Matrix4x4.rotationX(40)) * m = m.multiply(CSG.Matrix4x4.translation([-.5, 0, 0])) * let B = A.transform(m) */ transform(matrix4x4) { let ismirror = IsMirror(matrix4x4); let transformedvertices = {}; let transformedplanes = {}; let newpolygons = this.polygons.map(p => { let newplane; let plane = p.plane; let planetag = plane.getTag(); if (planetag in transformedplanes) { newplane = transformedplanes[planetag]; } else { newplane = plane.transform(matrix4x4); transformedplanes[planetag] = newplane; } let newvertices = p.vertices.map(v => { let newvertex; let vertextag = v.getTag(); if (vertextag in transformedvertices) { newvertex = transformedvertices[vertextag]; } else { newvertex = v.transform(matrix4x4); transformedvertices[vertextag] = newvertex; } return newvertex; }); if (ismirror) newvertices.reverse(); return new Polygon(newvertices, newplane); }); let result = new CSG(newpolygons); result.isRetesselated = this.isRetesselated; result.isCanonicalized = this.isCanonicalized; return result; } canonicalized() { if (this.isCanonicalized) return this; return canonicalizeCSG(this); } reTesselated() { if (this.isRetesselated) return this; return reTesselate(this); } //如果两个实体有可能重叠,返回true mayOverlap(csg) { if (this.polygons.length === 0 || csg.polygons.length === 0) return false; let mybounds = bounds(this); let otherbounds = bounds(csg); if (mybounds[1].x < otherbounds[0].x) return false; if (mybounds[0].x > otherbounds[1].x) return false; if (mybounds[1].y < otherbounds[0].y) return false; if (mybounds[0].y > otherbounds[1].y) return false; if (mybounds[1].z < otherbounds[0].z) return false; if (mybounds[0].z > otherbounds[1].z) return false; return true; } toTriangles() { let polygons = []; for (let poly of this.polygons) { let firstVertex = poly.vertices[0]; for (let i = poly.vertices.length - 3; i >= 0; i--) { polygons.push(new Polygon([ firstVertex, poly.vertices[i + 1], poly.vertices[i + 2] ], poly.plane)); } } return polygons; } } function Geometry2CSG(geometry) { if (geometry instanceof BufferGeometry) geometry = new Geometry().fromBufferGeometry(geometry); let polygons = []; for (let i = 0; i < geometry.faces.length; i++) { let face = geometry.faces[i]; let faceVertexUvs = geometry.faceVertexUvs[0][i]; let vertices = []; if (face instanceof Face3) { let uv = faceVertexUvs ? faceVertexUvs[0].clone() : null; let vertex1 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.a]), new Vector2D(uv.x, uv.y)); vertices.push(vertex1); uv = faceVertexUvs ? faceVertexUvs[1].clone() : null; let vertex2 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.b]), new Vector2D(uv.x, uv.y)); vertices.push(vertex2); uv = faceVertexUvs ? faceVertexUvs[2].clone() : null; let vertex3 = new Vertex3D(Vector3ToVector3D(geometry.vertices[face.c]), new Vector2D(uv.x, uv.y)); vertices.push(vertex3); } let polygon = new Polygon(vertices); let normal = AsVector3(polygon.plane.normal); if (!isNaN(polygon.plane.w) && !equalv3(normal, new Vector3())) polygons.push(polygon); } return new CSG(polygons); } function Vector3ToVector3D(v) { return new Vector3D(v.x, v.y, v.z); } class BoardUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { var a_x = vertices[indexA * 3]; var a_y = vertices[indexA * 3 + 1]; var b_x = vertices[indexB * 3]; var b_y = vertices[indexB * 3 + 1]; var c_x = vertices[indexC * 3]; var c_y = vertices[indexC * 3 + 1]; return [ new Vector2(a_x, a_y), new Vector2(b_x, b_y), new Vector2(c_x, c_y) ]; } generateSideWallUV(geometry, vertices, indexA, indexB, indexC, indexD) { var a_x = vertices[indexA * 3]; var a_y = vertices[indexA * 3 + 1]; var a_z = vertices[indexA * 3 + 2]; var b_x = vertices[indexB * 3]; var b_y = vertices[indexB * 3 + 1]; var b_z = vertices[indexB * 3 + 2]; var c_x = vertices[indexC * 3]; var c_y = vertices[indexC * 3 + 1]; var c_z = vertices[indexC * 3 + 2]; var d_x = vertices[indexD * 3]; var d_y = vertices[indexD * 3 + 1]; var d_z = vertices[indexD * 3 + 2]; let pts; if (Math.abs(a_y - b_y) < 0.01) { pts = [ new Vector2(a_z - 1, a_x), new Vector2(b_z - 1, b_x), new Vector2(c_z - 1, c_x), new Vector2(d_z - 1, d_x) ]; } else { pts = [ new Vector2(a_z - 1, a_y), new Vector2(b_z - 1, b_y), new Vector2(c_z - 1, c_y), new Vector2(d_z - 1, d_y) ]; } return pts; } } class BoardUVGenerator2 extends BoardUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { var a_x = vertices[indexA * 3]; var a_y = vertices[indexA * 3 + 1]; var b_x = vertices[indexB * 3]; var b_y = vertices[indexB * 3 + 1]; var c_x = vertices[indexC * 3]; var c_y = vertices[indexC * 3 + 1]; return [ new Vector2(a_y, a_x), new Vector2(b_y, b_x), new Vector2(c_y, c_x) ]; } } let boardUVGenerator = new BoardUVGenerator(); let boardUVGenerator2 = new BoardUVGenerator2(); /** * 解决 THREEBSP(CSG) 产生的结果没有办法得到分裂的个数. * 本类分析了THREEBSP的组合情况. * * Example: * * let topology = new BSPGroupParse(csg); * topology.parse(); */ class BSPGroupParse { constructor(bsp, fractionDigits = 1) { this.fractionDigits = fractionDigits; this.map = new Map(); this.vecMap = new Map(); if (bsp) for (let poly of bsp.polygons) this.Add(poly); } Add(poly) { let strs = poly.vertices.map(p => this.GenerateP(p.pos)); let str0 = strs[0]; let s0 = this.Get(str0); for (let i = 1; i < strs.length; i++) { let stri = strs[i]; s0.add(stri); this.Get(stri).add(str0); } } /** * 返回组合点 */ Parse() { let set = new Set([...this.map.keys()]); let res = []; while (set.size > 0) { let fp = set[Symbol.iterator]().next().value; set.delete(fp); let cset = new Set(); cset.add(fp); this.GetPts(fp, cset, set); let pts = [...cset].map(str => { let v3 = this.vecMap.get(str); return new Vector3(v3.x, v3.y, v3.z); }); res.push(pts); } return res; } Get(vstr) { if (!this.map.has(vstr)) { let s = new Set(); this.map.set(vstr, s); return s; } return this.map.get(vstr); } GetPts(p, cset, oset) { let strs = this.map.get(p); for (let str of strs) { if (!cset.has(str)) { cset.add(str); oset.delete(str); this.GetPts(str, cset, oset); } } } GenerateP(v) { let str = [v.x, v.y, v.z].map(n => ToFixed(n, this.fractionDigits)).join(","); this.vecMap.set(str, v); return str; } } function ScaleUV(geo, scale = 1e-3) { for (let uvsg of geo.faceVertexUvs) { for (let uvs of uvsg) { for (let uv of uvs) { uv.multiplyScalar(scale); } } } } function ScaleUV2(geo, ocs, xScale = 1e-3, yScale = 1e-3, isInvert = false) { for (let uvsg of geo.faceVertexUvs) { for (let uvs of uvsg) { for (let uv of uvs) { let p = new Vector3(uv.x, uv.y).applyMatrix4(ocs); uv.x = p.x; uv.y = p.y; if (isInvert) { uv.x /= yScale; uv.y /= xScale; } else { uv.x /= xScale; uv.y /= yScale; } } } } } class Shape { constructor(out, hols) { this._Holes = []; this._Shape = new Shape$1(); this._Outline = out || new Contour(); hols && this._Holes.push(...hols); } get Outline() { return this._Outline; } get Holes() { return this._Holes; } get Area() { let outlineArea = this._Outline.Area; let holeArea = this._Holes.map(l => l.Area).reduce((a1, a2) => a1 + a2, 0); return outlineArea - holeArea; } get BoundingBox() { return this._Outline.BoundingBox; } set Outline(cus) { this._Outline = cus; this.UpdateShape(); } set Holes(cus) { this._Holes = cus; this.UpdateShape(); } get Shape() { this.UpdateShape(); return this._Shape; } get Position() { return this._Outline.Curve.Position; } set Position(p) { let vec = p.clone().sub(this._Outline.Curve.Position); this._Outline.Curve.Position = p; for (let h of this._Holes) h.Curve.Position = h.Curve.Position.add(vec); } Z0() { this._Outline.Curve.Z0(); for (let h of this._Holes) h.Curve.Z0(); return this; } MatrixPlanarizere() { this._Outline.Curve.MatrixPlanarizere(); for (let h of this._Holes) h.Curve.MatrixPlanarizere(); } ApplyMatrix(m) { this._Outline.Curve.ApplyMatrix(m); this._Holes.forEach(h => h.Curve.ApplyMatrix(m)); return this; } ApplyScaleMatrix(m) { let cu = this.Outline.Curve; let cus = this._Holes.map(h => h.Curve); cus.unshift(cu); for (let c of cus) { c.ApplyMatrix(c.OCS); c.ApplyMatrix(m); c.ApplyMatrix(c.OCSInv); } return this; } Explode() { let cus = []; let contours = [this._Outline, ...this._Holes]; for (let con of contours) { if (con.Curve instanceof Polyline) cus.push(...con.Curve.Explode()); else cus.push(con.Curve.Clone()); } return cus; } Clone() { let shape = new Shape(); shape.Outline = this._Outline.Clone(); shape.Holes = this.Holes.map(h => h.Clone()); return shape; } SetColor(color) { this._Outline.Curve.ColorIndex = color; this._Holes.forEach(h => h.Curve.ColorIndex = color); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Cen: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: { let cus = [this._Outline.Curve]; for (let h of this._Holes) { cus.push(h.Curve); } let pts = []; for (let c of cus) { pts.push(...c.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } return pts; } } return []; } GetGripPoints() { let pts = this.Outline.Curve.GetGripPoints(); for (let h of this._Holes) { pts.push(...h.Curve.GetGripPoints()); } return pts; } MoveGripPoints(indexList, vec) { let i = indexList[0]; let outlineIndex = this._Outline.Curve.GetGripPoints().length; let cu = this._Outline.Curve; if (i >= outlineIndex) { for (let h of this._Holes) { let len = h.Curve.GetGripPoints().length; if (indexList[0] < outlineIndex + len) { indexList = [indexList[0] - outlineIndex]; cu = h.Curve; break; } outlineIndex += len; } } cu.MoveGripPoints(indexList, vec); } GetStretchPoints() { let pts = this.Outline.Curve.GetStretchPoints(); for (let h of this._Holes) { pts.push(...h.Curve.GetStretchPoints()); } return pts; } MoveStretchPoints(indexList, vec) { let outlen = 0; for (let cu of [this._Outline.Curve, ...this._Holes.map(h => h.Curve)]) { let count = cu.GetStretchPoints().length; let refIndex = outlen + count; let curIndexs = []; while (indexList.length) { if (indexList[0] < refIndex) curIndexs.push(indexList.shift() - outlen); else break; } cu.MoveStretchPoints(curIndexs, vec); if (indexList.length === 0) break; outlen += count; } } //交集 如果成功返回一个面域 失败返回0个 IntersectionBoolOperation(targetShape) { let resOutlines = this._Outline.IntersectionBoolOperation(targetShape._Outline); let cus = this.targetOutlineSubHoleOutline(resOutlines, Shape.mergeContours([...this._Holes, ...targetShape._Holes])); return Shape.pairHoleAndOutline(cus); } //并集,如果成功返回1个形状,不成功返回2个形状 UnionBoolOperation(targetShape, checkIntersect = false) { if (checkIntersect && !this.BoundingBox.intersectsBox(targetShape.BoundingBox, 1e-3)) return [this, targetShape]; let { contours, holes } = this._Outline.UnionBoolOperation(targetShape._Outline); let shapes = []; //提取出所有的孔洞, 目标线段孔洞和原线段差,如果孔洞和目标相减后有被包围轮廓,应把这个单独提取出来作为形状 let unionHoles = []; //合并运算时提取出运算后的孔洞和形状 const pickUpHoleOrShape = (srcHoles, tarHoles, outline) => { srcHoles.forEach(cu => { let tmpContours = cu.SubstactBoolOperation(outline).sort((a, b) => b.Area - a.Area); let isAllContainered = tmpContours.length > 1 && tmpContours.slice(1).every((cu, index) => tmpContours[0].CuInOutline(cu.Curve)); //洞是否被最大的洞包含,是,则把被包含的洞都提取出来加入形状数组 if (isAllContainered) { shapes.push(...this.targetOutlinesSubHoles(tmpContours.slice(1).map(c => new Shape(c)), tarHoles.map(c => new Shape(c)))); } else unionHoles.push(...tmpContours); }); }; pickUpHoleOrShape(targetShape._Holes, this._Holes, this._Outline); pickUpHoleOrShape(this._Holes, targetShape._Holes, targetShape._Outline); targetShape._Holes.forEach(cu => { this._Holes.forEach(c => { unionHoles.push(...c.IntersectionBoolOperation(cu)); }); }); shapes.push(...this.targetOutlinesSubHoles(contours.map(c => new Shape(c, holes)), unionHoles.map(c => new Shape(c)))); return shapes; } /** * 如果完全被减掉,就返回0个.其他的返回1个或者n个 * @param targetShapes 已经是合并后的形状数组 */ SubstactBoolOperation(targetShapes) { let originOutline = this.Outline; let targetOutlines = targetShapes.map(s => s.Outline); const { holes, outlines } = originOutline.GetSubtractListByMoreTargets(targetOutlines); holes.push(...this.Holes); let newShapes = []; if (outlines.length === 1 && equaln(outlines[0].Area, originOutline.Area)) { newShapes = [new Shape(outlines[0], Shape.mergeContours(holes))]; } else if (holes.length === 0) { newShapes = outlines.map(o => new Shape(o)); } else { for (let outline of outlines) newShapes.push(...new Shape(outline).SubstactBoolOperation(holes.map(h => new Shape(h)))); } let holeShape = this.Holes.map(h => new Shape(h)); for (let target of targetShapes) { let tmpInterList = []; if (target.Holes.length === 0) continue; for (let hole of target.Holes) { let list = hole.IntersectionBoolOperation(originOutline); tmpInterList.push(...list); } for (let ot of tmpInterList) { let subShapes = []; subShapes.push(...holeShape); for (let t of targetShapes) { if (t !== target) subShapes.push(new Shape(t.Outline)); } newShapes.push(...new Shape(ot).SubstactBoolOperation(subShapes)); } } return newShapes; } Equal(targetShape) { if (this._Outline.Equal(targetShape._Outline)) { return this._Holes.length === targetShape._Holes.length && this._Holes.every(h1 => targetShape._Holes.some(h2 => h1.Equal(h2))); } return false; } targetOutlinesSubHoles(targetShapes, holeShapes) { let resultShapes = []; for (let ts of targetShapes) { let res = ts.SubstactBoolOperation(holeShapes); resultShapes.push(...res); } return resultShapes; } /** * 目标轮廓减去洞 * * @private * @param {Contour[]} tarContours 轮廓列表 * @param {Contour[]} holes 洞列表 * @returns {Contour[]} 新的轮廓列表 * @memberof Shape */ targetOutlineSubHoleOutline(tarContours, holes) { if (!holes.length) return tarContours; let resultContours = []; for (let minuendContour of tarContours) { //需要被差集的形状列表 let tmpContour = [minuendContour]; for (let hole of holes) { //缓存差集生成的轮廓 let tmps = []; tmpContour.forEach(r => { let cus = r.SubstactBoolOperation(hole); tmps.push(...cus); }); tmpContour = tmps; //使用新生成的进行下一轮计算 } resultContours.push(...tmpContour); } return resultContours; } //整理轮廓数组,匹配洞和外轮廓 static pairHoleAndOutline(contours) { let shapes = []; contours.sort((a, b) => b.Area - a.Area); while (contours.length) { //洞列表 let tmpHoles = []; let outline = contours.shift(); //取出包含的洞 arrayRemoveIf(contours, (con) => { let bisIn = outline.CuInOutline(con.Curve); if (bisIn) tmpHoles.push(con); return bisIn; }); let holes = Shape.removeBeContaineredHoles(tmpHoles); shapes.push(new Shape(outline, holes)); } return shapes; } /** * 合并洞,本质是使用(并集算法)将可以并集的洞合并在一起,减少洞的数量. * canSidewipe 用于走刀,擦边的,包含的,是否合并 */ static mergeContours(holes, canSidewipe = true) { if (holes.length <= 1) return holes; let rets = []; //返回的合并轮廓 let cache = new Map(); while (holes.length > 0) { let c = holes.shift(); //取第一个 let b1 = cache.get(c); if (!b1) { b1 = c.BoundingBox; cache.set(c, b1); } while (true) { //剩余的 不相交的形状表 remaining let remHoles = holes.filter(ic => { let b2 = cache.get(ic); if (!b2) { b2 = ic.BoundingBox; cache.set(ic, b2); } if (!IntersectBox2(b1, b2)) return true; let unions = c.UnionBoolOperation(ic); if (unions.holes.length > 0) console.warn("未知情况"); if (unions.contours.length === 1) //并集成功 { if (!canSidewipe) { if (equaln(c.Area + ic.Area, unions.contours[0].Area, 0.1)) return true; if (equaln(unions.contours[0].Area, Math.max(c.Area, ic.Area), 0.1)) return true; } c = unions.contours[0]; //更新c b1 = c.BoundingBox; cache.set(c, b1); } return unions.contours.length !== 1; //过滤出并集失败的形状 }); //如果c和剩余的轮廓都不相交,那么退出 if (remHoles.length === holes.length) { rets.push(c); //c已经是一个独立的轮廓,不和任意轮廓相交(不能合并了) break; //退出循环.下一个 } else holes = remHoles; //更新为剩下的轮廓列表 } } return rets; } /** * 移除被包含的洞.(移除无效的洞,已经被更大的洞包含) * * @private * @param {Contour[]} tmpHoles 洞列表 * @returns {Contour[]} 返回的洞列表都不会互相包含. * @memberof Shape */ static removeBeContaineredHoles(tmpHoles) { let holes = []; if (tmpHoles.length <= 1) return tmpHoles; tmpHoles.sort((a, b) => b.Area - a.Area); while (tmpHoles.length) { let srcHole = tmpHoles.shift(); holes.push(srcHole); //移除包含的洞 arrayRemoveIf(tmpHoles, h => srcHole.CuInOutline(h.Curve)); } return holes; } UpdateShape() { this._Shape = this.Outline.Shape; for (let h of this._Holes) { if (h.Curve instanceof Polyline) h.Curve.UpdateMatrixTo(this.Outline.Curve.OCS); if (h.Curve instanceof Circle) { let sp = new Path(); let cen = h.Curve.Center.applyMatrix4(this.Outline.Curve.OCSInv); sp.ellipse(cen.x, cen.y, h.Curve.Radius, h.Curve.Radius, 0, 2 * Math.PI, false, 0); this._Shape.holes.push(sp); } else this._Shape.holes.push(h.Shape); } } //读写文件 ReadFile(file) { file.Read(); //1 this._Outline = Contour.CreateContour([file.ReadObject()]); let count = file.Read(); for (let i = 0; i < count; i++) { this._Holes.push(Contour.CreateContour([file.ReadObject()])); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); //ver file.WriteObject(this._Outline.Curve); file.Write(this._Holes.length); this._Holes.forEach(h => file.WriteObject(h.Curve)); } } class ShapeManager { constructor() { this._ShapeList = []; } get ShapeList() { return this._ShapeList.slice(); } get ShapeCount() { return this._ShapeList.length; } get ShapeArea() { return this._ShapeList.map(s => s.Area).reduce((a1, a2) => a1 + a2, 0); } AppendShapeList(shapes) { Array.isArray(shapes) ? this._ShapeList.push(...shapes) : this._ShapeList.push(shapes); return this; } Clear() { this._ShapeList.length = 0; } BoolOper(otherMg, booltype) { switch (booltype) { case BoolOpeartionType.Intersection: return this.IntersectionBoolOperation(otherMg); case BoolOpeartionType.Union: return this.UnionBoolOperation(otherMg); case BoolOpeartionType.Subtract: return this.SubstactBoolOperation(otherMg); } } //交集 如果成功返回一个面域 失败返回0个 IntersectionBoolOperation(target) { let shapes = []; for (let srcShape of this._ShapeList) { for (let tarShape of target._ShapeList) { let tmpShapes = srcShape.IntersectionBoolOperation(tarShape); shapes.push(...tmpShapes); } } this.Clear(); this._ShapeList = shapes; return this._ShapeList.length > 0; } //并集,如果有一个形状并集成功,就成功 UnionBoolOperation(targetMg) { let isSuccess = false; let srcShapes = this._ShapeList; let tarShapes = targetMg._ShapeList; let alones = []; //孤立的形状 const boxCache = new WeakMap(); for (let src of srcShapes) { let notUnions = []; //未被合并的形状列表 来自tarShapes let srcBox = src.BoundingBox; for (let tar of tarShapes) { let tarBox = boxCache.get(tar); if (!tarBox) { tarBox = tar.BoundingBox; boxCache.set(tar, tarBox); } if (!IntersectBox2(srcBox, tarBox)) { notUnions.push(tar); continue; } let unions = src.UnionBoolOperation(tar); if (unions.length === 1) //并集成功 { isSuccess = true; src = unions[0]; //src设置为 合并完的形状 } else //并集失败 notUnions.push(tar); //设置为未计算 } //如果发现src和任意一个形状并集成功,那么 if (notUnions.length != tarShapes.length) { notUnions.push(src); //加入src 进行下一轮 tarShapes = notUnions; } else alones.push(src); //它是孤独的一个形状 } this._ShapeList = alones.concat(tarShapes); return isSuccess; } SubstactBoolOperation(target) { let newShapes = []; for (let s of this._ShapeList) { let ss = s.SubstactBoolOperation(target.ShapeList); newShapes.push(...ss); } this._ShapeList = newShapes; return true; } /** * 与region.ApplyMatrix不同的是,这个是直接操作内部对象. * 通常用来计算布尔运算时需要真实的移动这个位置. * 并且将不会刷新显示 * * @param {Matrix4} mat4 * @memberof ShapeManager */ ApplyMatrix(mat4) { for (let s of this._ShapeList) { s.Outline.Curve.ApplyMatrix(mat4); s.Holes.forEach(o => o.Curve.ApplyMatrix(mat4)); } } ReadFile(file) { file.Read(); //1 let cout = file.Read(); for (let i = 0; i < cout; i++) { let obj = new Shape(); obj.ReadFile(file); this._ShapeList.push(obj); } } WriteFile(file) { file.Write(1); //ver file.Write(this.ShapeList.length); for (let s of this.ShapeList) { s.WriteFile(file); } } } var Region_1; let Region = Region_1 = class Region extends Entity { constructor(_ShapeManager = new ShapeManager()) { super(); this._ShapeManager = _ShapeManager; } static CreateFromCurves(cus) { let shapes = Contour.GetAllContour(cus).map(out => new Shape(out)); if (shapes.length > 0) { let reg = new Region_1(); //MarkX:曲线同面域一起移动 reg.ApplyMatrix(shapes[0].Outline.Curve.OCS); reg.ShapeManager.AppendShapeList(shapes); return reg; } } //如果需要修改获取到的属性,需要Clone后进行操作,否则会对原实体进行破坏 get ShapeManager() { return this._ShapeManager; } get Area() { return this.ShapeManager.ShapeArea; } get BoundingBox() { let box = new Box3(); for (let s of this._ShapeManager.ShapeList) box.union(s.BoundingBox); return box; } Explode() { let shapeList = this._ShapeManager.ShapeList; if (shapeList.length <= 1) { return shapeList[0].Explode(); } else { let regs = []; shapeList.forEach(s => { let reg = new Region_1().ApplyMatrix(this.OCS); reg.ShapeManager.AppendShapeList(s); regs.push(reg); }); return regs; } } /** * 对于布尔操作,这个将会变换内部轮廓到对方坐标系. * 并且这个变换不会更新图形绘制. * @param {Matrix4} m * @memberof Region */ ShapeApplyMatrix(m) { this.WriteAllObjectRecord(); this._ShapeManager.ApplyMatrix(m); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetGripPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Cen: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: { let pts = []; for (let s of this._ShapeManager.ShapeList) { pts.push(...s.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } return pts; } } return []; } GetGripPoints() { let pts = []; for (let s of this._ShapeManager.ShapeList) pts.push(...s.GetStretchPoints()); return pts; } MoveGripPoints(indexList, moveVec) { this.WriteAllObjectRecord(); let moveVLoc = moveVec.clone().applyMatrix4(new Matrix4().extractRotation(this.OCSInv)); this.ApplyMatrix(MoveMatrix(moveVLoc)); } ApplyMatrix(m) { this.WriteAllObjectRecord(); //面域移动,组成面域的曲线也要移动 MarkX:曲线同面域一起移动 this._ShapeManager.ShapeList.forEach(s => s.ApplyMatrix(m)); return super.ApplyMatrix(m); } get Position() { return super.Position; } set Position(pt) { this.WriteAllObjectRecord(); let moveX = pt.x - this._Matrix.elements[12]; let moveY = pt.y - this._Matrix.elements[13]; let moveZ = pt.z - this._Matrix.elements[14]; this._Matrix.setPosition(pt); this._SpaceOCS.elements[12] += moveX; this._SpaceOCS.elements[13] += moveY; this._SpaceOCS.elements[14] += moveZ; let m = new Matrix4().setPosition(moveX, moveY, moveZ); for (let s of this.ShapeManager.ShapeList) s.ApplyMatrix(m); this.Update(UpdateDraw.Matrix); } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); for (let s of this._ShapeManager.ShapeList) s.ApplyScaleMatrix(m); this.Update(UpdateDraw.Geometry); return this; } //Z轴归0 Z0() { super.Z0(); for (let s of this._ShapeManager.ShapeList) s.Z0(); return this; } MatrixPlanarizere() { super.MatrixPlanarizere(); for (let s of this._ShapeManager.ShapeList) s.MatrixPlanarizere(); return this; } ApplyMirrorMatrix(m) { return this; } /** * 请注意:该计算会操作otherRegion的矩阵 * @param {Region} otherRegion * @param {BoolOpeartionType} boolType */ BooleanOper(otherRegion, boolType) { if (this.IsCoplaneTo(otherRegion)) { this.WriteAllObjectRecord(); let oldOcs = this.OCS; //把形状曲线转移到二维屏幕计算后还原回来 this.ShapeApplyMatrix(this.OCSInv); otherRegion.ShapeApplyMatrix(this.OCSInv); let isSuccess = this._ShapeManager.BoolOper(otherRegion._ShapeManager, boolType); this.ShapeApplyMatrix(oldOcs); this.Update(); return isSuccess; } return false; } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this.UpdateGeometry(); return this._MeshGeometry; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this.UpdateGeometry(); return this._EdgeGeometry; } UpdateGeometry() { let shapeList = this._ShapeManager.ShapeList; let edgePts = []; let meshGeoms = []; const AddEdgePts = (pts, diffMat) => { for (let i = 0; i < pts.length; i++) { let p = AsVector3(pts[i]); p.applyMatrix4(diffMat); edgePts.push(p); if (i !== 0 && i !== pts.length - 1) edgePts.push(p); } }; for (let i = 0; i < shapeList.length; i++) { let shape = shapeList[i]; let geometry = new ShapeGeometry(shape.Shape, 60); //60 可以优化. let diffMat = this.OCSInv.clone().multiply(shape.Outline.Curve.OCSNoClone); geometry.applyMatrix4(diffMat); ScaleUV(geometry); meshGeoms.push(new BufferGeometry().fromGeometry(geometry)); let shapeInfo = shape.Shape.extractPoints(60); let pts = shapeInfo.shape; AddEdgePts(pts, diffMat); let holePtss = shapeInfo.holes; for (let holePts of holePtss) AddEdgePts(holePts, diffMat); } this._EdgeGeometry = BufferGeometryUtils.CreateFromPts(edgePts); this._MeshGeometry = BufferGeometryUtils.MergeBufferGeometries(meshGeoms); this._MeshGeometry["IsMesh"] = true; this._MeshGeometry.computeVertexNormals(); } UpdateDrawGeometry() { this._EdgeGeometry = undefined; this._MeshGeometry = undefined; } InitDrawObject(renderType = RenderType.Wireframe) { if (renderType === RenderType.Wireframe) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)), new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex))); } else if (renderType === RenderType.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } else if (renderType === RenderType.Print) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(0)); } else if (renderType === RenderType.Physical2) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return new Object3D().add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)), mesh); } } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (renderType === RenderType.Wireframe) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual) { return obj.add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)), new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex))); } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.geometry = this.MeshGeometry; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Physical2) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return obj.add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)), mesh); } else if (renderType === RenderType.Print) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(0); } } /** * 当实体需要被更新时,更新实体材质 */ UpdateDrawObjectMaterial(type, obj, material) { if (type === RenderType.Wireframe || type === RenderType.Print) { let line = obj; line.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (type === RenderType.Conceptual) { for (let i = 0; i < obj.children.length; i++) { if (i % 2 === 0) { let l = obj.children[i]; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else { let mesh = obj.children[i]; mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } } } else { for (let m of obj.children) { let mesh = m; mesh.material = this.MeshMaterial; } } } _ReadFile(file) { super._ReadFile(file); file.Read(); //1 this._ShapeManager.Clear(); this._ShapeManager.ReadFile(file); } WriteFile(file) { super.WriteFile(file); file.Write(1); //ver this._ShapeManager.WriteFile(file); } }; Region = Region_1 = __decorate([ Factory ], Region); /** * 把板件炸开成面域,0,1为正反面,其余的为边面(没有圆弧面) */ function Board2Regions(br) { let ocs = br.OCS; let cu = br.ContourCurve.Clone(); if (cu instanceof Circle) cu = ConverCircleToPolyline(cu); let frontReg = Region.CreateFromCurves([cu.Clone()]); let regFrontOcs = ocs.clone(); regFrontOcs.setPosition(br.Position.add(br.Normal.multiplyScalar(br.Thickness))); frontReg.ApplyMatrix(regFrontOcs); let backReg = Region.CreateFromCurves([cu.Flip()]); backReg.ApplyMatrix(ocs); let resultRegs = [frontReg, backReg]; //edges let lines = cu.Explode().filter(c => c instanceof Line); for (let l of lines) { let rectPl = new Polyline().Rectangle(l.Length, br.Thickness); let reg = Region.CreateFromCurves([rectPl]); if (!reg) continue; let p = l.StartPoint.applyMatrix4(ocs); let x = l.GetFistDeriv(0).transformDirection(ocs); let y = br.Normal; let z = new Vector3().crossVectors(x, y); let mtx = new Matrix4().makeBasis(x, y, z).setPosition(p); reg.ApplyMatrix(mtx); resultRegs.push(reg); } return resultRegs; } /**统一板件属性key的命名,修改值会导致无法 .xxx该属性 */ var EBoardKeyList; (function (EBoardKeyList) { EBoardKeyList["Height"] = "height"; EBoardKeyList["Width"] = "width"; EBoardKeyList["Thick"] = "thickness"; EBoardKeyList["RoomName"] = "roomName"; EBoardKeyList["CabinetName"] = "cabinetName"; EBoardKeyList["BrMat"] = "boardName"; EBoardKeyList["Mat"] = "material"; EBoardKeyList["Color"] = "color"; EBoardKeyList["Lines"] = "lines"; EBoardKeyList["ProcessGroup"] = "ProcessGroup"; EBoardKeyList["BigHole"] = "bigHoleDir"; /** * 排钻类型,当没有定义每个边的排钻数据时,使用统一的排钻类型 */ EBoardKeyList["DrillType"] = "drillType"; EBoardKeyList["ComposingFace"] = "composingFace"; /** * 封边数组,定义每个边的封边信息 */ EBoardKeyList["HighSealed"] = "highSealed"; EBoardKeyList["UpSealed"] = "sealedUp"; EBoardKeyList["DownSealed"] = "sealedDown"; EBoardKeyList["LeftSealed"] = "sealedLeft"; EBoardKeyList["RightSealed"] = "sealedRight"; EBoardKeyList["KnifeRad"] = "knifeRadius"; EBoardKeyList["SpliteHeight"] = "spliteHeight"; EBoardKeyList["SpliteWidth"] = "spliteWidth"; EBoardKeyList["SpliteThickness"] = "spliteThickness"; EBoardKeyList["DrawNumber"] = "drawNumber"; })(EBoardKeyList || (EBoardKeyList = {})); /**序列化板件数据 */ function serializeBoardData(file, processData) { file.Write(processData[EBoardKeyList.RoomName]); file.Write(processData[EBoardKeyList.CabinetName]); file.Write(processData[EBoardKeyList.BrMat]); file.Write(processData[EBoardKeyList.Mat]); file.Write(processData[EBoardKeyList.Color]); file.Write(processData[EBoardKeyList.Lines]); file.Write(processData[EBoardKeyList.BigHole]); file.Write(processData[EBoardKeyList.DrillType]); file.Write(processData[EBoardKeyList.ComposingFace]); file.Write(processData[EBoardKeyList.HighSealed].length); for (let n of processData[EBoardKeyList.HighSealed]) { file.Write(n.size); } file.Write(processData[EBoardKeyList.UpSealed]); file.Write(processData[EBoardKeyList.DownSealed]); file.Write(processData[EBoardKeyList.LeftSealed]); file.Write(processData[EBoardKeyList.RightSealed]); file.Write(processData.spliteHeight); file.Write(processData.spliteWidth); file.Write(processData.spliteThickness); file.Write(processData.highDrill.length); for (let n of processData.highDrill) file.Write(n); file.Write(processData.frontDrill); file.Write(processData.backDrill); file.Write(processData.remarks.length); for (let d of processData.remarks) { file.Write(d[0]); file.Write(d[1]); } } //反序列化板件数据 function deserializationBoardData(file, processData, ver) { processData[EBoardKeyList.RoomName] = file.Read(); processData[EBoardKeyList.CabinetName] = file.Read(); processData[EBoardKeyList.BrMat] = file.Read(); processData[EBoardKeyList.Mat] = file.Read(); processData[EBoardKeyList.Color] = file.Read(); processData[EBoardKeyList.Lines] = file.Read(); processData[EBoardKeyList.BigHole] = file.Read(); processData[EBoardKeyList.DrillType] = file.Read(); processData[EBoardKeyList.ComposingFace] = file.Read(); let count = file.Read(); processData[EBoardKeyList.HighSealed].length = 0; for (let i = 0; i < count; i++) { let size = file.Read(); if (ver < 4) { file.Read(); } processData[EBoardKeyList.HighSealed].push({ size }); } processData[EBoardKeyList.UpSealed] = file.Read(); processData[EBoardKeyList.DownSealed] = file.Read(); processData[EBoardKeyList.LeftSealed] = file.Read(); processData[EBoardKeyList.RightSealed] = file.Read(); processData.spliteHeight = file.Read(); processData.spliteWidth = file.Read(); processData.spliteThickness = file.Read(); count = file.Read(); processData.highDrill = file.ReadArray(count); processData.frontDrill = file.Read(); processData.backDrill = file.Read(); if (ver >= 7) { let count = file.Read(); processData.remarks.length = 0; for (let i = 0; i < count; i++) { let d = ["", ""]; d[0] = file.Read(); d[1] = file.Read(); processData.remarks.push(d); } } } function SerializeBoard2DModeingData(file, modelList) { file.Write(modelList.length); for (let data of modelList) { file.WriteObject(data.path); file.Write(data.dir); file.Write(data.items.length); for (let item of data.items) { file.Write(item.depth); file.Write(item.offset); file.Write(item.knife.id); file.Write(item.knife.radius); file.Write(item.knife.angle); file.Write(item.knife.name); } } } function SerializeBoard3DModeingData(file, modelList) { file.Write(modelList.length); for (let item of modelList) { file.Write(item.path.length); for (let d of item.path) { file.Write(d.pt.toArray()); file.Write(d.bul); } file.Write(item.dir); file.Write(item.knife.id); file.Write(item.knife.radius); file.Write(item.knife.angle); file.Write(item.knife.name); } } //反序列化板件数据 function DeserializationBoard2DModeingData(file, data, ver) { data.length = 0; const count = file.Read(); for (let i = 0; i < count; i++) { let path = file.ReadObject(); let dir = file.Read(); let m = { path, dir, items: [] }; const itemCount = file.Read(); for (let j = 0; j < itemCount; j++) { let depth = file.Read(); let offset = file.Read(); let knifeId = file.Read(); let knifeRad = file.Read(); let knifeAngle = file.Read(); let knifeName = file.Read(); m.items.push({ depth, offset, knife: { id: knifeId, radius: knifeRad, angle: knifeAngle, name: knifeName } }); } data.push(m); } } //反序列化板件数据 function DeserializationBoard3DModeingData(file, data, ver) { data.length = 0; const count = file.Read(); for (let i = 0; i < count; i++) { let pathCount = file.Read(); let path = []; for (let i = 0; i < pathCount; i++) { let pt = new Vector3().fromArray(file.Read()); let bul = file.Read(); path.push({ pt, bul }); } let dir = file.Read(); let knifeId = file.Read(); let knifeRad = file.Read(); let knifeAngle = file.Read(); let knifeName = file.Read(); data.push({ path, dir, knife: { id: knifeId, radius: knifeRad, angle: knifeAngle, name: knifeName } }); } } let Hole = class Hole extends Entity { get Height() { return this._Height; } set Height(v) { if (this._Height !== v) { this.WriteAllObjectRecord(); this._Height = v; this.Update(); } } Clone() { let ent = super.Clone(); ent.OtherHalfTongKong = null; return ent; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 if (ver <= 4) { //临时兼容旧图纸排钻,更新旧图纸后去掉兼容代码 file['readIndex']--; } else { this._Height = file.Read(); this.FId = file.ReadSoftObjectId(); this.MId = file.ReadSoftObjectId(); } if (ver >= 6) { this.OtherHalfTongKong = file.ReadSoftObjectId(); } } WriteFile(file) { super.WriteFile(file); file.Write(6); //ver file.Write(this._Height); file.WriteSoftObjectId(this.FId); file.WriteSoftObjectId(this.MId); file.WriteSoftObjectId(this.OtherHalfTongKong); } }; __decorate([ AutoRecord ], Hole.prototype, "FId", void 0); __decorate([ AutoRecord ], Hole.prototype, "MId", void 0); __decorate([ AutoRecord ], Hole.prototype, "OtherHalfTongKong", void 0); Hole = __decorate([ Factory ], Hole); var CylinderHole_1; var GangDrillType; (function (GangDrillType) { /**偏心轮 */ GangDrillType[GangDrillType["Pxl"] = 0] = "Pxl"; /**连接杆 */ GangDrillType[GangDrillType["Ljg"] = 1] = "Ljg"; /**预埋件 */ GangDrillType[GangDrillType["Ymj"] = 2] = "Ymj"; /**层板钉 */ GangDrillType[GangDrillType["Nail"] = 3] = "Nail"; /** 木销 */ GangDrillType[GangDrillType["Wood"] = 4] = "Wood"; /** 通孔 */ GangDrillType[GangDrillType["TK"] = 5] = "TK"; GangDrillType[GangDrillType["WoodPXL"] = 6] = "WoodPXL"; })(GangDrillType || (GangDrillType = {})); let TempCircle1 = new Circle(); let TempCircle2 = new Circle(); let CylinderHole = CylinderHole_1 = class CylinderHole extends Hole { constructor() { super(); this._Radius = 1; this.type = GangDrillType.Pxl; this._Color = 1; } static CreateCylHole(radius, height, type) { let drill = new CylinderHole_1(); drill.Height = height; drill._Radius = radius; drill.type = type; return drill; } get Type() { return this.type; } set Type(t) { if (this.type !== t) { this.WriteAllObjectRecord(); this.type = t; } } set Radius(r) { if (r !== this._Radius) { this.WriteAllObjectRecord(); this._MeshGeometry = null; this._EdgeGeometry = null; this._Radius = r; this.Update(); } } get Height() { return super.Height; } set Height(v) { if (this._Height !== v) { this._MeshGeometry = null; this._EdgeGeometry = null; super.Height = v; } } get Radius() { return this._Radius; } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this._Matrix); } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { return new Box3Ext(new Vector3(-this._Radius, -this._Radius, 0), new Vector3(this._Radius, this._Radius, this._Height)); } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this._MeshGeometry = FastDrillingMeshGeometry(this.Radius, this.Height); return this._MeshGeometry; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this._EdgeGeometry = FastDrillingEdgeGeometry(this._Radius, this.Height); return this._EdgeGeometry; } GetGripPoints() { let cir = new Circle(new Vector3(), this._Radius); let pts = cir.GetGripPoints(); pts.push(...pts.map(p => p.clone().add(new Vector3(0, 0, this.Height)))); return pts.map(p => p.applyMatrix4(this.OCS)); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let pts = []; TempCircle1.Radius = this.Radius; TempCircle1.OCS = this._Matrix; TempCircle2.Radius = this.Radius; TempCircle2.OCS = this._Matrix; TempCircle2.Position = TempCircle2.Position.add(this.Normal.multiplyScalar(this.Height)); for (let c of [TempCircle2, TempCircle1]) { pts.push(...c.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } return pts; } Erase(isErase = true) { if (isErase === this.IsErase) return; super.Erase(isErase); if (!isErase) return; if (this.OtherHalfTongKong && !this.OtherHalfTongKong.IsErase) { let cy = this.OtherHalfTongKong.Object; cy.Type = GangDrillType.Ymj; cy.OtherHalfTongKong = null; } } InitDrawObject(renderType) { return this.GetObject3DByRenderType(renderType); } GetObject3DByRenderType(renderType) { if (renderType === RenderType.Wireframe) return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); else return new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)); } UpdateDrawObject(type, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); obj.add(this.GetObject3DByRenderType(type)); } UpdateDrawObjectMaterial(type, obj) { if (type === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else { let mesh = obj; mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 this._Radius = file.Read(); if (ver <= 4) { //临时兼容旧排钻 this._Height = file.Read(); this.type = file.Read(); this.FId = file.ReadSoftObjectId(); this.MId = file.ReadSoftObjectId(); } else { this.type = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(5); //ver file.Write(this._Radius); file.Write(this.type); } }; CylinderHole = CylinderHole_1 = __decorate([ Factory ], CylinderHole); let cache = new Map(); let ro = new Matrix4(); ro.makeRotationX(Math.PI / 2); function FastDrillingMeshGeometry(radius, height) { let key = `${radius},${height}`; if (cache.has(key)) return cache.get(key); let geo = new CylinderBufferGeometry(radius, radius, height, 8, 1); geo.applyMatrix4(ro); geo.translate(0, 0, height / 2); cache.set(key, geo); return geo; } let cache2 = new Map(); function FastDrillingEdgeGeometry(radius, height) { let key = `${radius},${height}`; if (cache2.has(key)) return cache2.get(key); let sp = new Shape$1(); sp.ellipse(0, 0, radius, radius, 0, 2 * Math.PI, false, 0); let pts = sp.getPoints(4); let geo = new BufferGeometry(); let coords = []; for (let i = 0; i < pts.length; i++) { let p = pts[i]; let np = pts[FixIndex$1(i + 1, pts.length)]; coords.push(p.x, p.y, 0, np.x, np.y, 0); //bottom coords.push(p.x, p.y, height, np.x, np.y, height); //top coords.push(p.x, p.y, 0, p.x, p.y, height); //edge } geo.setAttribute('position', new Float32BufferAttribute(coords, 3)); cache2.set(key, geo); return geo; } CADFactory.RegisterObjectAlias(CylinderHole, "GangDrill"); var BoardType; (function (BoardType) { BoardType[BoardType["Layer"] = 0] = "Layer"; BoardType[BoardType["Vertical"] = 1] = "Vertical"; BoardType[BoardType["Behind"] = 2] = "Behind"; //背板 })(BoardType || (BoardType = {})); //排钻类型 var DrillType; (function (DrillType) { DrillType["Yes"] = "\u6392"; DrillType["None"] = "\u4E0D\u6392"; DrillType["More"] = "**\u591A\u79CD**"; DrillType["Invail"] = "\u65E0\u6548\u914D\u7F6E"; })(DrillType || (DrillType = {})); //偏心轮类型 var FaceDirection; (function (FaceDirection) { FaceDirection[FaceDirection["Front"] = 0] = "Front"; FaceDirection[FaceDirection["Back"] = 1] = "Back"; })(FaceDirection || (FaceDirection = {})); //纹路类型 var LinesType; (function (LinesType) { /** 正纹 */ LinesType[LinesType["Positive"] = 0] = "Positive"; /** 反纹 */ LinesType[LinesType["Reverse"] = 1] = "Reverse"; /** 可翻转 */ LinesType[LinesType["CanReversal"] = 2] = "CanReversal"; })(LinesType || (LinesType = {})); // 排版面 var ComposingType; (function (ComposingType) { ComposingType[ComposingType["Positive"] = 0] = "Positive"; ComposingType[ComposingType["Reverse"] = 1] = "Reverse"; ComposingType[ComposingType["Arbitrary"] = 2] = "Arbitrary"; //任意 })(ComposingType || (ComposingType = {})); /** *背板靠上还是靠下 * * @export * @enum {number} */ var BehindHeightPositon; (function (BehindHeightPositon) { BehindHeightPositon["ForTop"] = "top"; BehindHeightPositon["ForBottom"] = "bottom"; BehindHeightPositon["AllHeight"] = "all"; //总高 })(BehindHeightPositon || (BehindHeightPositon = {})); /** *板件相对位置 * * @export * @enum {number} */ var BrRelativePos; (function (BrRelativePos) { BrRelativePos["Front"] = "front"; BrRelativePos["Back"] = "back"; BrRelativePos["Top"] = "top"; BrRelativePos["Bottom"] = "bottom"; BrRelativePos["Left"] = "left"; BrRelativePos["Right"] = "right"; BrRelativePos["Div"] = "div"; })(BrRelativePos || (BrRelativePos = {})); var StripType; (function (StripType) { StripType["H"] = "h"; StripType["V"] = "v"; })(StripType || (StripType = {})); var CurtailType; (function (CurtailType) { CurtailType["PerBr"] = "0"; CurtailType["Total"] = "1"; })(CurtailType || (CurtailType = {})); var BoardOpenDir; (function (BoardOpenDir) { BoardOpenDir[BoardOpenDir["Left"] = 1] = "Left"; BoardOpenDir[BoardOpenDir["Right"] = 2] = "Right"; BoardOpenDir[BoardOpenDir["Up"] = 3] = "Up"; BoardOpenDir[BoardOpenDir["Down"] = 4] = "Down"; BoardOpenDir[BoardOpenDir["None"] = 0] = "None"; })(BoardOpenDir || (BoardOpenDir = {})); const SCALAR = 0.1; function CyHoleInBoard(cys, br, ocs) { if (cys.length === 1 && cys[0].Type === GangDrillType.Ymj) return true; const outline = br.ContourCurve; let box = new Box3(); let pxl; let ljg; let ymj; let wood; let woodPXL; let pxl2; for (let cy of cys) { box.union(cy.BoundingBox); if (cy.Type === GangDrillType.Pxl) { if (pxl) pxl2 = cy; else pxl = cy; } else if (cy.Type === GangDrillType.Ljg) ljg = cy; else if (cy.Type === GangDrillType.Wood) wood = cy; else if (cy.Type === GangDrillType.WoodPXL) woodPXL = cy; else ymj = cy; } box.applyMatrix4(ocs); let outlineBox = outline.BoundingBox; outlineBox.max.setZ(br.Thickness); if (!box.intersectsBox(outlineBox)) return false; let nor = new Vector3(); if (ljg) nor.copy(ljg.Normal); else if (ymj) nor.copy(ymj.Normal); else if (wood) nor.copy(wood.Normal); nor.applyMatrix4(ocs.clone().setPosition(new Vector3)); if (isParallelTo(nor, ZAxis)) { if (ymj) { let center = ymj.Position.applyMatrix4(ocs).setZ(0); let cir = new Circle(center, ymj.Radius - SCALAR); return outline.IntersectWith(cir, 0).length === 0 && outline.PtInCurve(center); } } else { if (pxl) { let plxs = [pxl]; if (pxl2) plxs.push(pxl2); if (plxs.every(cy => { let center = cy.Position.applyMatrix4(ocs).setZ(0); let cir = new Circle(center, pxl.Radius - SCALAR); if (userConfig.forceFilterPxl) return outline.IntersectWith(cir, 0).length > 0 || !outline.PtInCurve(center); else return outline.IntersectWith(cir, 0).length <= 1 && !outline.PtInCurve(center); })) return false; } if (woodPXL) { let center = woodPXL.Position.applyMatrix4(ocs).setZ(0); let cir = new Circle(center, woodPXL.Radius - SCALAR); if (outline.IntersectWith(cir, 0).length > 0 || !outline.PtInCurve(center)) return false; } if (ljg) { let c1 = ljg.Position.applyMatrix4(ocs).setZ(0); let minPt = c1.clone().add(nor.clone().multiplyScalar(ljg.Height / 2)); let c2 = c1.clone().add(nor.clone().multiplyScalar(ljg.Height - SCALAR)); c1.add(nor.clone().multiplyScalar(SCALAR)); rotatePoint(nor, Math.PI / 2); c1.add(nor.multiplyScalar(ljg.Radius)); c2.add(nor.negate()); let rect = new Polyline().RectangleFrom2Pt(c1, c2); let intPtsLen = outline.IntersectWith(rect, 0).length; if (intPtsLen > 2 || (intPtsLen === 0 && !outline.PtInCurve(minPt))) return false; } if (wood) { let c1 = wood.Position.applyMatrix4(ocs).setZ(0); let c2 = c1.clone().add(nor.clone().multiplyScalar(wood.Height)); rotatePoint(nor, Math.PI / 2); let dir = nor.multiplyScalar(wood.Radius); let p1 = c1.clone().add(dir); let p2 = c2.clone().add(dir); let p3 = c1.clone().add(dir.negate()); let p4 = c2.clone().add(dir); let l1 = new Line(p1, p2); let l2 = new Line(p3, p4); if (l1.IntersectWith(outline, 0).length !== 1 || l2.IntersectWith(outline, 0).length !== 1) return false; } } return true; } const TempRectHoleOption = { up: "", down: "", left: "", right: "", }; /**分析上下左右排钻 */ function InitRectBoardHoleOption(br, option) { let dir = Math.sign(br.ContourCurve.Area2); let hightDrill = br.BoardProcessOption.highDrill; let cus = br.ContourCurve.Explode(); for (let i = 0; i < cus.length; i++) { let c = cus[i]; let derv = c.GetFistDeriv(0).multiplyScalar(dir); let an = angle(derv); if (equaln(an, 0) || (an < Math.PI / 4 + 1e-8 && an > Math.PI * 7 / 4)) option.down = hightDrill[i]; else if (an > Math.PI / 4 && an < Math.PI * 3 / 4 + 1e-8) option.right = hightDrill[i]; else if (an > Math.PI * 3 / 4 && an < Math.PI * 5 / 4 + 1e-8) option.up = hightDrill[i]; else option.left = hightDrill[i]; } } function ExtureHoleInBoard(holes, board, ocs) { //TODO:自定义排钻判断 return true; } function HoleInBoard(holes, br, ocs) { if (holes.length === 0) return false; if (holes[0] instanceof CylinderHole) { return CyHoleInBoard(holes, br, ocs !== null && ocs !== void 0 ? ocs : br.OCSInv); } else { return ExtureHoleInBoard(holes, br, ocs !== null && ocs !== void 0 ? ocs : br.OCSInv); } } /**上下左右排钻写入板件 */ function SetRectHighHole(br, option) { let dir = Math.sign(br.ContourCurve.Area2); let highDrill = br.BoardProcessOption.highDrill; let cus = br.ContourCurve.Explode(); highDrill.length = 0; for (let i = 0; i < cus.length; i++) { let c = cus[i]; let derv = c.GetFistDeriv(0).multiplyScalar(dir); let an = angle(derv); if (equaln(an, 0) || (an < Math.PI / 4 + 1e-8 && an > Math.PI * 7 / 4)) highDrill.push(option.down); else if (an > Math.PI / 4 && an < Math.PI * 3 / 4 + 1e-8) highDrill.push(option.right); else if (an > Math.PI * 3 / 4 && an < Math.PI * 5 / 4 + 1e-8) highDrill.push(option.up); else highDrill.push(option.left); } let types = new Set(highDrill); if (types.size === 1 && highDrill[0] !== DrillType.None) br.BoardProcessOption[EBoardKeyList.DrillType] = highDrill[0]; else if (types.size > 1) br.BoardProcessOption[EBoardKeyList.DrillType] = DrillType.More; } class PointShapeUtils { //方形点表 static SquarePts(size) { return [ new Vector3(-size, -size), new Vector3(size, -size), new Vector3(size, size), new Vector3(-size, size), new Vector3(-size, -size), ]; } //方形外圈十字直线点表 static OutsideLinePts(squareSize, lineLength) { return [ //-X new Vector3(-squareSize, 0), new Vector3(-lineLength, 0), //X new Vector3(squareSize, 0), new Vector3(lineLength, 0), //Y new Vector3(0, squareSize), new Vector3(0, lineLength), //-Y new Vector3(0, -squareSize), new Vector3(0, -lineLength), ]; } //十字直线点表 static CrossLinePts(lineLength) { return [ new Vector3(0, -lineLength), new Vector3(0, lineLength), new Vector3(lineLength, 0), new Vector3(-lineLength, 0), ]; } static CrossLine3DPts(lineLength) { return [ [new Vector3(lineLength, 0), new Vector3(-lineLength / 2, 0)], [new Vector3(0, -lineLength / 2), new Vector3(0, lineLength)], [new Vector3(0, 0, -lineLength / 2), new Vector3(0, 0, lineLength)], ]; } static TrianglePts(size) { return [ new Vector3(size, -size), new Vector3(0, size), new Vector3(-size, -size), new Vector3(size, -size), ]; } static CirclePts(size) { let pts = []; let a = Math.PI * 2 / 8; for (let i = 0; i < 9; i++) pts.push(new Vector3(Math.sin(a * i) * size, Math.cos(a * i) * size)); return pts; } static ObliqueCrossPts(size) { return [new Vector3(-size, size), new Vector3(size, -size), new Vector3(-size, -size), new Vector3(size, size)]; } static ObliqueCrossLinePts(size) { return [new Vector3(-size, size), new Vector3(size, -size), new Vector3(), new Vector3(-size, -size), new Vector3(size, size)]; } static SandClockPts(size) { return [ new Vector3(size, size), new Vector3(-size, size), new Vector3(size, -size), new Vector3(-size, -size), new Vector3(size, size), ]; } static TangentPts(size) { let pts = [ new Vector3(-size, size), new Vector3(size, size), new Vector3(size / 2, size), ]; let a = Math.PI * 2 / 8; for (let i = 0; i < 9; i++) pts.push(new Vector3(Math.sin(a * i + Math.PI / 2) * size, Math.cos(a * i + Math.PI / 2) * size)); return pts; } static PerPts(size) { return [ new Vector3(-size, size), new Vector3(-size, -size), new Vector3(size, -size), new Vector3(0, -size), new Vector3(0, 0), new Vector3(-size, 0), ]; } static LinesDirPts(len, width, lineType) { if (lineType === LinesType.Reverse) { return [ new Vector3(-len / 2), new Vector3(-len / 2 + width / 2, width / 2), new Vector3(-len / 2), new Vector3(-len / 2 + width / 2, -width / 2), new Vector3(-len / 2), new Vector3(len / 2), new Vector3(len / 2), new Vector3(len / 2 - width / 2, width / 2), new Vector3(len / 2), new Vector3(len / 2 - width / 2, -width / 2), ]; } else if (lineType === LinesType.Positive) return [ new Vector3(0, -len / 2), new Vector3(-width / 2, -len / 2 + width / 2), new Vector3(0, -len / 2), new Vector3(width / 2, -len / 2 + width / 2), new Vector3(0, -len / 2), new Vector3(0, len / 2), new Vector3(0, len / 2), new Vector3(-width / 2, len / 2 - width / 2), new Vector3(0, len / 2), new Vector3(width / 2, len / 2 - width / 2), ]; else { let w1 = Math.min(len, width) / 5; return [ new Vector3(0, len / 2), new Vector3(0, -len / 2), new Vector3(-width / 2), new Vector3(width / 2), new Vector3(-width / 2), new Vector3(-width / 2 + w1, w1), new Vector3(-width / 2), new Vector3(-width / 2 + w1, -w1), new Vector3(width / 2), new Vector3(width / 2 - w1, w1), new Vector3(width / 2), new Vector3(width / 2 - w1, -w1), new Vector3(0, len / 2), new Vector3(-w1, len / 2 - w1), new Vector3(0, len / 2), new Vector3(w1, len / 2 - w1), new Vector3(0, -len / 2), new Vector3(-w1, -len / 2 + w1), new Vector3(0, -len / 2), new Vector3(w1, -len / 2 + w1), ]; } } } var Intent; (function (Intent) { Intent["NONE"] = "none"; Intent["PRIMARY"] = "primary"; Intent["SUCCESS"] = "success"; Intent["WARNING"] = "warning"; Intent["DANGER"] = "danger"; })(Intent || (Intent = {})); const ToasterInjectFunctions = []; function Toaster(option) { for (let f of ToasterInjectFunctions) f(option); } var EWineRackType; (function (EWineRackType) { EWineRackType[EWineRackType["Oblique"] = 0] = "Oblique"; EWineRackType[EWineRackType["Upright"] = 1] = "Upright"; })(EWineRackType || (EWineRackType = {})); var EWRackArrayType; (function (EWRackArrayType) { EWRackArrayType[EWRackArrayType["ByWidth"] = 0] = "ByWidth"; EWRackArrayType[EWRackArrayType["ByCount"] = 1] = "ByCount"; EWRackArrayType[EWRackArrayType["Fixed"] = 2] = "Fixed"; })(EWRackArrayType || (EWRackArrayType = {})); /**铺满方式 */ var EFullType; (function (EFullType) { EFullType[EFullType["ByHeight"] = 0] = "ByHeight"; EFullType[EFullType["ByWidth"] = 1] = "ByWidth"; EFullType[EFullType["Symmetry"] = 2] = "Symmetry"; })(EFullType || (EFullType = {})); /**高度优先时靠左还是靠右 */ var EFullDir; (function (EFullDir) { EFullDir[EFullDir["Left"] = 0] = "Left"; EFullDir[EFullDir["Right"] = 1] = "Right"; })(EFullDir || (EFullDir = {})); var EFindType; (function (EFindType) { EFindType[EFindType["Find"] = 0] = "Find"; EFindType[EFindType["Modify"] = 1] = "Modify"; EFindType[EFindType["FindMaxSize"] = 2] = "FindMaxSize"; EFindType[EFindType["FindSplite"] = 3] = "FindSplite"; EFindType[EFindType["GetOption"] = 4] = "GetOption"; EFindType[EFindType["RemoveModeling"] = 5] = "RemoveModeling"; EFindType[EFindType["RemoveSpecialShape"] = 6] = "RemoveSpecialShape"; EFindType[EFindType["RemoveModelingAndSpecial"] = 7] = "RemoveModelingAndSpecial"; EFindType[EFindType["ModifyHardware"] = 8] = "ModifyHardware"; EFindType[EFindType["FindMinSize"] = 9] = "FindMinSize"; })(EFindType || (EFindType = {})); var ECompareType; (function (ECompareType) { ECompareType["Equal"] = "="; ECompareType["UnEqual"] = "!="; ECompareType["Greater"] = ">="; ECompareType["Less"] = "<="; })(ECompareType || (ECompareType = {})); var ELatticeArrayType; (function (ELatticeArrayType) { ELatticeArrayType[ELatticeArrayType["ByWidth"] = 0] = "ByWidth"; ELatticeArrayType[ELatticeArrayType["ByCount"] = 1] = "ByCount"; })(ELatticeArrayType || (ELatticeArrayType = {})); //门板位置类型 var DoorPosType; (function (DoorPosType) { DoorPosType[DoorPosType["Out"] = 0] = "Out"; DoorPosType[DoorPosType["In"] = 1] = "In"; })(DoorPosType || (DoorPosType = {})); var HandleHorPos; (function (HandleHorPos) { HandleHorPos[HandleHorPos["Left"] = 0] = "Left"; HandleHorPos[HandleHorPos["Right"] = 1] = "Right"; HandleHorPos[HandleHorPos["Mid"] = 2] = "Mid"; })(HandleHorPos || (HandleHorPos = {})); var HandleVePos; (function (HandleVePos) { HandleVePos[HandleVePos["Top"] = 0] = "Top"; HandleVePos[HandleVePos["Bottom"] = 1] = "Bottom"; HandleVePos[HandleVePos["Mid"] = 2] = "Mid"; })(HandleVePos || (HandleVePos = {})); //门板开门类型 var DoorOpenDir; (function (DoorOpenDir) { DoorOpenDir["Left"] = "lf"; DoorOpenDir["Right"] = "rt"; DoorOpenDir["Top"] = "tp"; DoorOpenDir["Bottom"] = "bm"; DoorOpenDir["None"] = "none"; })(DoorOpenDir || (DoorOpenDir = {})); var EMetalsType; (function (EMetalsType) { EMetalsType["Metals"] = "\u4E94\u91D1"; EMetalsType["Comp"] = "\u7EC4\u4EF6"; })(EMetalsType || (EMetalsType = {})); const DefaultLayerBoardConfig = { version: 2, type: BoardType.Layer, name: "层板", frontShrink: 0, leftShrink: 0, rightShrink: 0, calcHeight: "W", isTotalLength: true, boardRelative: BrRelativePos.Div, thickness: 18, count: 1, spaceSize: 300, isActive: false, calcSpaceSize: "0", calcFrontShrink: "0", calcLeftShrink: "0", calcRightShrink: "0", }; Object.freeze(DefaultLayerBoardConfig); const DefaultVerticalBoardConfig = { version: 2, type: BoardType.Vertical, name: "立板", frontShrink: 0, bottomShrink: 0, calcWidth: "W", calcHeight: "H", isTotalLength: true, isTotalWidth: true, boardRelative: BrRelativePos.Div, thickness: 18, count: 1, spaceSize: 0, calcSpaceSize: "0", calcBottomShrink: "0", calcFrontShrink: "0", }; Object.freeze(DefaultVerticalBoardConfig); const DefaultBehindBoardConfig = { version: 2, type: BoardType.Behind, name: "背板", leftExt: 0, rightExt: 0, topExt: 0, bottomExt: 0, thickness: 18, boardPosition: BehindHeightPositon.AllHeight, calcHeight: "H", moveDist: 0, boardRelative: BrRelativePos.Back, spaceSize: 0, count: 1, calcSpaceSize: "0", calcMoveDist: "0", }; Object.freeze(DefaultBehindBoardConfig); const DefaultWineRackConfig = { version: 1, type: EWineRackType.Oblique, arrayType: EWRackArrayType.ByWidth, fullType: EFullType.ByWidth, isFull: false, isLock: false, fullDir: EFullDir.Left, heightCount: 3.5, widthCount: 3.5, isTotalDepth: true, depth: 0, gripWidth: 100, calcDepth: "W", boardThick: 18, grooveWidthAdd: 0, leftEdge: 1, rightEdge: 1, topEdge: 1, bottomEdge: 1, frontCut: 0, leftCut: 0, rightCut: 0, topCut: 0, grooveLengthAdd: 3, isDrawLy: false, isDrawVer: false, brThick2: 18, }; Object.freeze(DefaultWineRackConfig); const DefaultTopBoardOption = { version: 2, type: BoardType.Layer, name: "顶板", isDraw: true, thickness: 18, frontDist: 0, behindDistance: 0, isWrapSide: false, useLFData: true, leftExt: 0, rightExt: 0, offset: 0, }; Object.freeze(DefaultTopBoardOption); const DefaultBottomBoardOption = { version: 2, type: BoardType.Layer, name: "底板", isDraw: true, thickness: 18, frontDist: 0, behindDistance: 0, isWrapSide: false, useLFData: true, leftExt: 0, rightExt: 0, offset: 80, footThickness: 18, isDrawFooter: true, footBehindShrink: 0, isDrawBackFooter: false, isDrawStrengthenStrip: false, footerOffset: 0, divCount: 1, }; Object.freeze(DefaultBottomBoardOption); const DefaultSideBoardOption = { version: 2, type: BoardType.Vertical, name: "", height: 2000, width: 600, thickness: 18, spaceSize: 1200, leftShrink: 0, rightShrink: 0, }; Object.freeze(DefaultSideBoardOption); const DefaultModifyTextsOption = { changeTexts: Array.from({ length: 5 }, () => ["", ""]), }; Object.freeze(DefaultModifyTextsOption); const DefaultSingleBoardOption = { version: 1, name: "层板", type: BoardType.Layer, height: 1200, width: 600, thickness: 18, rotateX: 0, rotateY: 0, rotateZ: 0, drawNumber: 1 }; Object.freeze(DefaultSingleBoardOption); const DefaultClosingStripOption = { version: 2, type: BoardType.Vertical, name: "收口条", striptype: StripType.H, boardRelative: BrRelativePos.Left, width: 54, thickness: 18, frontShrink: 0, isDrawFuZhu: true, fzWidth: 80, fzThickness: 18, }; Object.freeze(DefaultClosingStripOption); const DefaultBoardFindOption = { version: 5, condition: { layer: false, height: false, width: false, thickness: false, useWood: false, useDrill: false, useNail: false, useDoor: false, useDim: false, useSpecial: false, useModeling: false, roomName: false, cabinetName: false, brName: false, material: false, lines: false, bigHoleDir: false, drillType: false, useKeyWord: false, composingFace: false, sealedUp: false, sealedDown: false, sealedLeft: false, sealedRight: false, upDrill: false, downDrill: false, leftDrill: false, rightDrill: false, useZhengFanDrill: false, useChaidan: false, [EBoardKeyList.KnifeRad]: false, }, compareType: { height: ECompareType.Equal, width: ECompareType.Equal, thickness: ECompareType.Equal, roomName: ECompareType.Equal, cabinetName: ECompareType.Equal, brName: ECompareType.Equal, [EBoardKeyList.Mat]: ECompareType.Equal, [EBoardKeyList.Color]: ECompareType.Equal, [EBoardKeyList.BrMat]: ECompareType.Equal, lines: ECompareType.Equal, bigHoleDir: ECompareType.Equal, drillType: ECompareType.Equal, composingFace: ECompareType.Equal, [EBoardKeyList.KnifeRad]: ECompareType.Equal, }, tolerance: { height: "", width: "", thickness: "", [EBoardKeyList.KnifeRad]: "", }, layer: "0", height: "", width: "", thickness: "", roomName: "", cabinetName: "", brName: "", [EBoardKeyList.BrMat]: "", material: "", color: "", lines: LinesType.Positive, bigHoleDir: FaceDirection.Front, drillType: "", composingFace: ComposingType.Positive, sealedUp: "", sealedDown: "", sealedLeft: "", sealedRight: "", highDrill: [], upDownDrill: [true, true], isClose: false, remarks: Array.from({ length: 10 }, () => ["", ""]), isChaidan: false, [EBoardKeyList.KnifeRad]: "", }; Object.freeze(DefaultBoardFindOption); const DefaultLatticOption = { version: 1, arrayType: ELatticeArrayType.ByWidth, gripWidth: 100, gripDepth: 100, widthCount: 3, depthCount: 4, knifeRad: 3, thickness: 18, arcLen: 50, downDist: 0, space: 0.2, grooveAddWidth: 0.2, upSealed: 1, downSealed: 1, leftSealed: 1, rightSealed: 1, isAuto: true, isChange: true, isOpenCut: false, upCut: 0, downCut: 4, }; Object.freeze(DefaultLatticOption); const DefaultDoorOption = { version: 4, col: 2, row: 1, isAllSelect: true, topOffset: 0, bottomOffset: 0, doorPosType: DoorPosType.Out, offset: 0, topExt: 18, bottomExt: 18, leftExt: 18, rightExt: 18, topSpace: 2, bottomSpace: 2, leftSpace: 2, rightSpace: 2, midSpace: 2, thickness: 18, depth: 0, isAuto: true, boardName: "", doorThickness: 18, topBrSeal: 1, bottomBrSeal: 1, leftBrSeal: 1, rightBrSeal: 1, topDoorSeal: 1, bottomDoorSeal: 1, leftDoorSeal: 1, rightDoorSeal: 1, handleAngle: 0, handleHorPos: HandleHorPos.Right, horSpacing: 50, handleVePos: HandleVePos.Mid, veSpacing: 10, hingeCount: 0, hindeTopDist: 0, hindeBottomDist: 0, downOffsetExpr: "0", upOffsetExpr: "0", useRule: false, changeTemplateBoardNameOfOpenDir: true, // }; Object.freeze(DefaultDoorOption); const DefaultDrawerOption = { version: 4, col: 1, row: 1, isAllSelect: true, topOffset: 0, bottomOffset: 0, doorPosType: DoorPosType.Out, offset: 0, topExt: 18, bottomExt: 18, leftExt: 18, rightExt: 18, topSpace: 2, bottomSpace: 2, leftSpace: 2, rightSpace: 2, midSpace: 2, thickness: 18, depth: 0, isAuto: true, boardName: "", handleAngle: 90, handleHorPos: HandleHorPos.Mid, horSpacing: 10, handleVePos: HandleVePos.Mid, veSpacing: 10, drawerTotalDepth: 0, trackDepth: 0, isAutoSelectTrack: true, isLockTopOffset: false, isLockBottomOffset: false, downOffsetExpr: "0", upOffsetExpr: "0", }; Object.freeze(DefaultDrawerOption); const DefaultBoardBatchCurtailOption = { version: 1, type: CurtailType.Total, front: 0, back: 0, left: 0, right: 0, moveBrs: false, }; Object.freeze(DefaultBoardBatchCurtailOption); const DefaultLatticeConfig = { arrayType: ELatticeArrayType.ByWidth, gripWidth: 100, gripDepth: 100, widthCount: 3, depthCount: 4, knifeRad: 3, thickness: 18, arcLen: 50, downDist: 0, space: 0.5, grooveAddWidth: 0, upSealed: 1, downSealed: 0, leftSealed: 0, rightSealed: 0, isAuto: true, isChange: true, isOpenCut: false, upCut: 0, downCut: 4, }; Object.freeze(DefaultLatticeConfig); const DefaultNailOption = { version: 1, isDraw: true, addCount: 0, dist: 50, isGroup: false, isInBack: false, front: 50, behind: 50, count: 2, rad: 2.5, length: 34, depth: 11 }; Object.freeze(DefaultNailOption); const DefaultCylinederMetalsOption = { version: 2, rad: 50, height: 200, name: "圆柱体", unit: "", roomName: "", cabinetName: "", costExpr: "L*R*R*3.14", actualExpr: "L*R*R*3.14*3", model: "X-1", factory: "晨丰", brand: "晨丰", spec: "个", count: "1", comments: "", isHole: true, }; Object.freeze(DefaultCylinederMetalsOption); const DefaultExtruderMetalsOption = { version: 1, thickness: 100, knifeRad: 0, isHole: true, addLen: 0, name: "拉伸实体", unit: "", roomName: "", cabinetName: "", costExpr: "L*W*H*100", actualExpr: "L*W*H*200", model: "X-1", factory: "晨丰", brand: "晨丰", spec: "个", count: "1", comments: "", }; Object.freeze(DefaultExtruderMetalsOption); const DefaultCompositeMetalsOption = { version: 2, type: EMetalsType.Metals, isSplite: false, isSplitePrice: false, name: "复合实体", unit: "", roomName: "", cabinetName: "", costExpr: "L*W*H*100", actualExpr: "L*W*H*300", model: "X-1", factory: "晨丰", brand: "晨丰", spec: "个", count: "1", color: "", material: "", comments: "", isHole: true, }; Object.freeze(DefaultCompositeMetalsOption); const DefaultToplineMetalsOption = { version: 3, name: "顶线", unit: "毫米", roomName: "", cabinetName: "", costExpr: "", actualExpr: "", model: "", factory: "", brand: "", spec: "", comments: "", addLen: "0", isHole: false, }; Object.freeze(DefaultToplineMetalsOption); const DefaultBoardProcessOption = { version: 3, roomName: "", cabinetName: "", boardName: "", material: "", color: "", lines: LinesType.Positive, bigHoleDir: FaceDirection.Front, drillType: "", composingFace: ComposingType.Arbitrary, highSealed: [], sealedUp: "1", sealedDown: "1", sealedLeft: "1", sealedRight: "1", spliteHeight: "", spliteWidth: "", spliteThickness: "", highDrill: [], frontDrill: true, backDrill: true, remarks: [], useBoardProcessOption: true, }; Object.freeze(DefaultBoardProcessOption); const DefaultCurve2RecOption = { version: 1, isSaveMax: false, isSaveSmall: true, width: 90, isAnaly: true, gap: 3, forceUseUCS: false, ForceUseFrontViewCS: false, }; Object.freeze(DefaultCurve2RecOption); const DefaultUpdateInfoOption = { version: 1, [EBoardKeyList.RoomName]: "", [EBoardKeyList.CabinetName]: "", [EBoardKeyList.Lines]: LinesType.Positive, [EBoardKeyList.BigHole]: FaceDirection.Front, [EBoardKeyList.DrillType]: "", [EBoardKeyList.ComposingFace]: ComposingType.Arbitrary, upDownDrill: [true, true], [EBoardKeyList.UpSealed]: "1", [EBoardKeyList.DownSealed]: "1", [EBoardKeyList.LeftSealed]: "1", [EBoardKeyList.RightSealed]: "1", [EBoardKeyList.KnifeRad]: "3", remarks: Array.from({ length: 10 }, () => ["", ""]), [EBoardKeyList.BrMat]: "", [EBoardKeyList.Mat]: "", [EBoardKeyList.Color]: "", grooveAddDepth: "0", grooveAddLength: "0", grooveAddWidth: "0", highDrill: [], isChaiDan: true, condition: { [EBoardKeyList.RoomName]: false, [EBoardKeyList.CabinetName]: false, [EBoardKeyList.Lines]: true, [EBoardKeyList.BigHole]: true, [EBoardKeyList.DrillType]: true, [EBoardKeyList.ComposingFace]: true, [EBoardKeyList.UpSealed]: true, [EBoardKeyList.DownSealed]: true, [EBoardKeyList.LeftSealed]: true, [EBoardKeyList.RightSealed]: true, useZhengFanDrill: true, remarks: true, [EBoardKeyList.KnifeRad]: true, [EBoardKeyList.Mat]: true, grooveAddDepth: true, grooveAddLength: true, grooveAddWidth: true, upDrill: true, downDrill: true, leftDrill: true, rightDrill: true, isChaiDan: true, } }; Object.freeze(DefaultUpdateInfoOption); const DefaultKuGanOption = { count: 1, isHor: false, depth: 0, isDefault: true, leftDist: 0, rightDist: 0, upDist: 0, downDist: 0, }; Object.freeze(DefaultKuGanOption); const DefaultParseBoardNameOPtion = { version: 1, verticalBrShrink: 0, layerBrShrink: 0, topBrShrink: 0, bottomBrShrink: 0, groundLineBrShrink: 0, farLeftVerticalBrName: "左侧板", farRightVerticalBrName: "右侧板", topMostLayerBrName: "顶板", bottomMostLayerBrName: "底板", bottomMostBackBrName: "地脚线", stripeBrName: "收口条", cabinetName: "", isfarLeftVerticalBrName: true, isfarRightVerticalBrName: true, istopMostLayerBrName: true, isbottomMostLayerBrName: true, isbottomMostBackBrName: true, isstripeBrName: true, iscabinetName: false, isMultiBackBr: false, isBack: true, backName: "背板", }; Object.freeze(DefaultParseBoardNameOPtion); const DefaultR2bOption = { version: 2, cabinetDeep: 400, cabinetBrThick: 18, cabinetCurtail: 0, backBrThick: 18, backBrBiggerThanHeight: 200, backBrBiggerThanWidth: 200, backBrFrontMove: 0, backBrLeftExtend: 0, backBrRightExtend: 0, backBrUpExtend: 0, backBrDownExtend: 0, ...DefaultParseBoardNameOPtion, grooveOption: { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "0", knifeRadius: "3", }, roomName: "", boardMatName: "", material: "", color: "", drillType: "", sealedDown: "1", sealedLeft: "1", sealedRight: "1", sealedUp: "1", backBrUseTemplate: false, backBrTemplate: null, remarks: Array.from({ length: 12 }, () => ["", ""]), maxThickness: 20, useBrName: true, configName: "", backBrName: "背板", behindIsRelative: false, footerThickness: 18, }; Object.freeze(DefaultR2bOption); const DefaultR2b2Option = { version: 1, depthExpr: "W", drillType: "", sealedDown: "1", sealedLeft: "1", sealedRight: "1", sealedUp: "1", remarks: Array.from({ length: 12 }, () => ["", ""]), maxThickness: 20, layerShrink: 0, vertialShrink: 0, }; Object.freeze(DefaultR2b2Option); function FixIndex(index, arr) { let count = (arr instanceof Array) ? arr.length : arr; if (index < 0) return count + index; else if (index >= count) return index - count; else return index; } /** * 使用轮廓和扫描路径构建扫描几何体,实现衣柜中的顶线或者地脚线之类的实体. * 该几何体需要轮廓和路径的起始截面垂直,否则构造的实体将会错误. */ class SweepGeometry extends Geometry { constructor(contour, path) { super(); this.edgePts = []; this.AddShape(contour, path); this.computeVertexNormals(); this.computeFaceNormals(); } AddShape(contour, path) { //路径点表 let pathPts2d = path.Shape.getPoints(4); let pathPts = pathPts2d.map(AsVector3); arrayRemoveDuplicateBySort(pathPts, equalv3); for (let p of pathPts) p.applyMatrix4(path.OCS); let shapePts2d = contour.Shape.getPoints(4); if (!ShapeUtils.isClockWise(shapePts2d)) shapePts2d.reverse(); //轮廓点表 let shapePts3d = shapePts2d.map(AsVector3); for (let p of shapePts3d) p.applyMatrix4(contour.OCS); let isClosePath = path.IsClose; let verts = []; //所有路径上的轮廓点 //计算所有需要的几何点,本质是不断的投影 if (isClosePath) verts.push(ProjectionToPlane(shapePts3d, path.Normal, pathPts[0], pathPts[pathPts.length - 2], pathPts[1])); else verts.push(ProjectionToPlane(shapePts3d, path.Normal, pathPts[0], undefined, pathPts[1])); //遍历所有的路径节点进行顶点投射 for (let i = 1; i < pathPts.length; i++) { if (i === pathPts.length - 1) { if (isClosePath) verts.push(ProjectionToPlane(shapePts3d, path.Normal, pathPts[i], pathPts[i - 1], pathPts[1])); else verts.push(ProjectionToPlane(shapePts3d, path.Normal, pathPts[i], pathPts[i - 1])); } else { verts.push(ProjectionToPlane(shapePts3d, path.Normal, pathPts[i], pathPts[i - 1], pathPts[i + 1])); } } this.BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts); if (!isClosePath) this.BuildLid(shapePts2d, verts); } BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts) { let addCount = 0; //补充个数 shapePts2d[0]["_mask_"] = true; for (let p of shapePts2d) if (p["_mask_"]) addCount++; let sumCount = addCount + shapePts2d.length; //实际个数 const f4 = (a, b, c, d, uvs) => { let f1 = new Face3(a, b, c); let f2 = new Face3(b, d, c); this.faces.push(f1, f2); this.faceVertexUvs[0].push([uvs[0].clone(), uvs[1].clone(), uvs[2].clone()], [uvs[1].clone(), uvs[3].clone(), uvs[2].clone()]); }; let vs = [0]; //vs 对应 y轴 for (let i = 1; i < shapePts2d.length; i++) vs.push((vs[i - 1] + shapePts2d[i].distanceTo(shapePts2d[i - 1]) * 1e-3)); let lastStartX = 0; for (let pathIndex = 0; pathIndex < verts.length; pathIndex++) { let pts = verts[pathIndex]; let pts2 = verts[FixIndex(pathIndex + 1, verts)]; let startIndex = this.vertices.length; let pBase = pts[0]; let p1 = pathPts[pathIndex]; let p2 = pathPts[FixIndex(pathIndex + 1, pathPts.length)]; let p1Dir = p2.clone().sub(p1).normalize(); let tempStartX = 0; for (let contourIndex = 0; contourIndex < shapePts2d.length; contourIndex++) { let p1 = pts[contourIndex]; let p2 = pts2[contourIndex]; let p2d = shapePts2d[contourIndex]; if (pathIndex !== verts.length - 1) if (contourIndex === 0 || p2d["_mask_"]) this.edgePts.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z); if (contourIndex === 0 || p2d["_mask_"]) this.vertices.push(p1); //补点 if (pathIndex !== verts.length - 1) { let curIndex = this.vertices.length; let nextIndex = startIndex + FixIndex(curIndex - startIndex + 1, sumCount); let curIndex2 = curIndex + sumCount; let nextIndex2 = nextIndex + sumCount; let x1 = lastStartX + p1.clone().sub(pBase).dot(p1Dir) * 1e-3; let x2 = lastStartX + pts[FixIndex(contourIndex + 1, shapePts2d)].clone().sub(pBase).dot(p1Dir) * 1e-3; let x3 = lastStartX + p2.clone().sub(pBase).dot(p1Dir) * 1e-3; let x4 = lastStartX + pts2[FixIndex(contourIndex + 1, shapePts2d)].clone().sub(pBase).dot(p1Dir) * 1e-3; if (contourIndex === 0) tempStartX = x3; let v1 = vs[contourIndex]; let v2 = vs[FixIndex(contourIndex + 1, vs)]; let uvs = [ new Vector2(v1, x1), new Vector2(v2, x2), new Vector2(v1, x3), new Vector2(v2, x4), ]; f4(curIndex, nextIndex, curIndex2, nextIndex2, uvs); } this.vertices.push(p1); } lastStartX = tempStartX; if (pathPts2d[FixIndex(pathIndex + 1, verts)]["_mask_"]) { for (let contourIndex = 0; contourIndex < shapePts2d.length; contourIndex++) { let p1 = pts2[contourIndex]; let p2d = shapePts2d[contourIndex]; if (contourIndex === 0 || p2d["_mask_"]) this.vertices.push(p1); //补点 this.vertices.push(p1); let p2 = pts2[FixIndex(contourIndex + 1, pts2)]; this.edgePts.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z); } } } } BuildLid(shapePts2d, verts) { //轮廓三角网格索引 let faces = ShapeUtils.triangulateShape(shapePts2d, []); for (let v of shapePts2d) v.multiplyScalar(1e-3); //作为uvs let lastIndex = this.vertices.length; this.vertices.push(...verts[0].map(p => p.clone())); this.vertices.push(...verts[verts.length - 1].map(p => p.clone())); for (let i = 0; i < faces.length; i++) { let [a, b, c] = faces[i]; this.faces.push(new Face3(lastIndex + a, lastIndex + b, lastIndex + c)); let uvs = faces[i].map(index => shapePts2d[index].clone()); this.faceVertexUvs[0].push(uvs); this.faces.push(new Face3(lastIndex + verts[0].length + c, lastIndex + verts[0].length + b, lastIndex + verts[0].length + a)); this.faceVertexUvs[0].push(uvs.concat().reverse().map(v => v.clone())); } //构建线框 for (let i = 0; i < shapePts2d.length; i++) { let nextIndex = FixIndex(i + 1, shapePts2d); let pts1 = verts[0]; let p0 = pts1[i]; let p1 = pts1[nextIndex]; this.edgePts.push(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); let pts2 = verts[verts.length - 1]; p0 = pts2[i]; p1 = pts2[nextIndex]; this.edgePts.push(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); } } } /** * 将轮廓变换到`路径上某个点`. * * @param {Vector3[]} contourPts 原始的轮廓点(在世界坐标系) * @param {Vector3} normal 路径法向量 * @param {Vector3} curP 路径上当前点 * @param {Vector3} [preP] 路径的前一个点 * @param {Vector3} [nextP] 路径下一个点 * @returns 变换后的轮廓点表 */ function ProjectionToPlane(contourPts, normal, curP, preP, nextP) { let pts; if (!preP && nextP) { let mat = ContourTransfromToPath(curP, normal, nextP.clone().sub(curP)); pts = contourPts.map(p => p.clone().applyMatrix4(mat)); } else if (!nextP && preP) { let mat = ContourTransfromToPath(curP, normal, curP.clone().sub(preP)); pts = contourPts.map(p => p.clone().applyMatrix4(mat)); } else if (nextP && preP) { let dir = curP.clone().sub(preP).normalize(); let v2 = nextP.clone().sub(curP).normalize(); //角平分线向量 let v = dir.clone().sub(v2); //v1v2pm向量 let nv1v2 = dir.clone().cross(v2); let norm = nv1v2.cross(v); //角平分线的平面 let plane = new PlaneExt(norm, curP); let mat = ContourTransfromToPath(preP, normal, dir); pts = contourPts.map(p => p.clone().applyMatrix4(mat)); pts = pts.map(p => plane.intersectLine(new Line3(p, p.clone().add(dir)), new Vector3(), true)); } return pts; } /** * 计算轮廓变换到`路径上某个点`的矩阵 * * @param {Vector3} pt 路径上的点 * @param {Vector3} norm 曲线法向量 * @param {Vector3} dir 点前进的方向. * @returns {Matrix4} */ function ContourTransfromToPath(pt, norm, dir) { let vy = norm; let vz = dir.normalize(); let vx = vz.clone().cross(vy); let mat = new Matrix4(); mat.makeBasis(vx, vy, vz); mat.setPosition(pt); return mat; } // Quote from: // https://github.com/Mugen87/yume/blob/master/src/javascript/engine/etc/OBB.js // 即obb.js(本项目中已存在) // Reference material: //https://stackoverflow.com/questions/28499800/oriented-box-intersection-in-threejs //http://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.html //https://github.com/Mugen87/yume/blob/master/src/javascript/engine/etc/OBB.js class OBB { constructor(ocs, halfSizes) { this.ocs = ocs; this.halfSizes = halfSizes; this._EPSILON = 1e-3; this.center = halfSizes.clone().applyMatrix4(ocs); } intersectsOBB(obb, is2D, ucsInv) { let newCenter; let newObbCenter; let cs; let obbcs; if (is2D) { let mtx1 = new Matrix4().multiplyMatrices(ucsInv, this.ocs); let mtx2 = new Matrix4().multiplyMatrices(ucsInv, obb.ocs); cs = mtx1; obbcs = mtx2; cs.elements[14] = 0; obbcs.elements[14] = 0; newCenter = this.halfSizes.clone().applyMatrix4(cs); newObbCenter = obb.halfSizes.clone().applyMatrix4(obbcs); } let xAxisA = new Vector3(); let yAxisA = new Vector3(); let zAxisA = new Vector3(); let xAxisB = new Vector3(); let yAxisB = new Vector3(); let zAxisB = new Vector3(); let translation = new Vector3(); let vector = new Vector3(); let axisA = []; let axisB = []; let rotationMatrix = [[], [], []]; let rotationMatrixAbs = [[], [], []]; let halfSizeA, halfSizeB; let t, i; // extract each axis (cs !== null && cs !== void 0 ? cs : this.ocs).extractBasis(xAxisA, yAxisA, zAxisA); (obbcs !== null && obbcs !== void 0 ? obbcs : obb.ocs).extractBasis(xAxisB, yAxisB, zAxisB); // push basis vectors into arrays, so you can access them via indices axisA.push(xAxisA, yAxisA, zAxisA); axisB.push(xAxisB, yAxisB, zAxisB); // get displacement vector vector.subVectors(newObbCenter !== null && newObbCenter !== void 0 ? newObbCenter : obb.center, newCenter !== null && newCenter !== void 0 ? newCenter : this.center); // express the translation vector in the coordinate frame of the current // OBB (this) for (i = 0; i < 3; i++) { translation.setComponent(i, vector.dot(axisA[i])); } // generate a rotation matrix that transforms from world space to the // OBB's coordinate space for (i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { rotationMatrix[i][j] = axisA[i].dot(axisB[j]); rotationMatrixAbs[i][j] = Math.abs(rotationMatrix[i][j]) + this._EPSILON; } } // test the three major axes of this OBB for (i = 0; i < 3; i++) { vector.set(rotationMatrixAbs[i][0], rotationMatrixAbs[i][1], rotationMatrixAbs[i][2]); halfSizeA = this.halfSizes.getComponent(i); halfSizeB = obb.halfSizes.dot(vector); if (Math.abs(translation.getComponent(i)) > halfSizeA + halfSizeB) { return false; } } // test the three major axes of other OBB for (i = 0; i < 3; i++) { vector.set(rotationMatrixAbs[0][i], rotationMatrixAbs[1][i], rotationMatrixAbs[2][i]); halfSizeA = this.halfSizes.dot(vector); halfSizeB = obb.halfSizes.getComponent(i); vector.set(rotationMatrix[0][i], rotationMatrix[1][i], rotationMatrix[2][i]); t = translation.dot(vector); if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } } // test the 9 different cross-axes // A.x B.x halfSizeA = this.halfSizes.y * rotationMatrixAbs[2][0] + this.halfSizes.z * rotationMatrixAbs[1][0]; halfSizeB = obb.halfSizes.y * rotationMatrixAbs[0][2] + obb.halfSizes.z * rotationMatrixAbs[0][1]; t = translation.z * rotationMatrix[1][0] - translation.y * rotationMatrix[2][0]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.x < cross> B.y halfSizeA = this.halfSizes.y * rotationMatrixAbs[2][1] + this.halfSizes.z * rotationMatrixAbs[1][1]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[0][2] + obb.halfSizes.z * rotationMatrixAbs[0][0]; t = translation.z * rotationMatrix[1][1] - translation.y * rotationMatrix[2][1]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.x B.z halfSizeA = this.halfSizes.y * rotationMatrixAbs[2][2] + this.halfSizes.z * rotationMatrixAbs[1][2]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[0][1] + obb.halfSizes.y * rotationMatrixAbs[0][0]; t = translation.z * rotationMatrix[1][2] - translation.y * rotationMatrix[2][2]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.y B.x halfSizeA = this.halfSizes.x * rotationMatrixAbs[2][0] + this.halfSizes.z * rotationMatrixAbs[0][0]; halfSizeB = obb.halfSizes.y * rotationMatrixAbs[1][2] + obb.halfSizes.z * rotationMatrixAbs[1][1]; t = translation.x * rotationMatrix[2][0] - translation.z * rotationMatrix[0][0]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.y B.y halfSizeA = this.halfSizes.x * rotationMatrixAbs[2][1] + this.halfSizes.z * rotationMatrixAbs[0][1]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[1][2] + obb.halfSizes.z * rotationMatrixAbs[1][0]; t = translation.x * rotationMatrix[2][1] - translation.z * rotationMatrix[0][1]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.y B.z halfSizeA = this.halfSizes.x * rotationMatrixAbs[2][2] + this.halfSizes.z * rotationMatrixAbs[0][2]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[1][1] + obb.halfSizes.y * rotationMatrixAbs[1][0]; t = translation.x * rotationMatrix[2][2] - translation.z * rotationMatrix[0][2]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.z B.x halfSizeA = this.halfSizes.x * rotationMatrixAbs[1][0] + this.halfSizes.y * rotationMatrixAbs[0][0]; halfSizeB = obb.halfSizes.y * rotationMatrixAbs[2][2] + obb.halfSizes.z * rotationMatrixAbs[2][1]; t = translation.y * rotationMatrix[0][0] - translation.x * rotationMatrix[1][0]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.z B.y halfSizeA = this.halfSizes.x * rotationMatrixAbs[1][1] + this.halfSizes.y * rotationMatrixAbs[0][1]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[2][2] + obb.halfSizes.z * rotationMatrixAbs[2][0]; t = translation.y * rotationMatrix[0][1] - translation.x * rotationMatrix[1][1]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // A.z B.z halfSizeA = this.halfSizes.x * rotationMatrixAbs[1][2] + this.halfSizes.y * rotationMatrixAbs[0][2]; halfSizeB = obb.halfSizes.x * rotationMatrixAbs[2][1] + obb.halfSizes.y * rotationMatrixAbs[2][0]; t = translation.y * rotationMatrix[0][2] - translation.x * rotationMatrix[1][2]; if (Math.abs(t) > halfSizeA + halfSizeB) { return false; } // no separating axis exists, so the two OBB don't intersect return true; } } var SweepSolid_1; let SweepSolid = SweepSolid_1 = class SweepSolid extends Entity { constructor(contour, pathCurve) { super(); this._Contour = contour; this._PathCurve = pathCurve; if (this._Contour && this._Contour.Id) this._Contour = this._Contour.Clone(); if (this._Contour && this._PathCurve) { this.TransfromPathToWCS(); this.OCS = this._PathCurve.OCS; this._SpaceOCS.copy(this._PathCurve.OCS); this._PathCurve.ApplyMatrix(this._PathCurve.OCSInv); } } Explode() { return [this._Contour.Clone(), this._PathCurve.Clone()]; } get Contour() { return this._Contour; } get Path() { return this._PathCurve; } Reverse() { this.WriteAllObjectRecord(); this._PathCurve.Reverse(); this.Update(); } /**保持路径左下角在0点 */ PathTo0() { let min = this._PathCurve.BoundingBox.min; this._PathCurve.Position = this._PathCurve.Position.sub(min); this.OCS = this.OCS.multiply(MoveMatrix(min)); } /** * 将轮廓变换到wcs空间,当用户选定某个与扫描线起点相切的轮廓时. */ TransfromPathToWCS() { if (equalv3(this._Contour.Normal, new Vector3(0, 0, 1))) return; let fDir = this._PathCurve.GetFistDeriv(0); if (isParallelTo(fDir, this._Contour.Normal)) { //构建回家的矩阵 let toWcsMat4Inv = new Matrix4(); let zv = fDir.normalize(); let yv = this._PathCurve.Normal; let xv = zv.clone().cross(yv); toWcsMat4Inv.makeBasis(xv, yv, zv); toWcsMat4Inv.setPosition(this._PathCurve.StartPoint); let toWcsMat4 = new Matrix4().getInverse(toWcsMat4Inv); this._Contour.ApplyMatrix(toWcsMat4); let z = this._Contour.StartPoint.z; if (IsPointInPolyLine(this._Contour, new Vector3(0, 0, z))) { let z = this._Contour.StartPoint.z; this._Contour.ApplyMatrix(MoveMatrix(new Vector3(0, 0, -z))); return; } else this._Contour.ApplyMatrix(toWcsMat4Inv); } let lDir = this._PathCurve.GetFistDeriv(this._PathCurve.EndParam); if (isParallelTo(lDir, this._Contour.Normal)) { //再次构建回家的矩阵 let toWcsMat4Inv = new Matrix4(); let zv = lDir.negate().normalize(); let yv = this._PathCurve.Normal; let xv = zv.clone().cross(yv); toWcsMat4Inv.makeBasis(xv, yv, zv); toWcsMat4Inv.setPosition(this._PathCurve.EndPoint); let toWcsMat4 = new Matrix4().getInverse(toWcsMat4Inv); this._Contour.ApplyMatrix(toWcsMat4); let z = this._Contour.StartPoint.z; if (IsPointInPolyLine(this._Contour, new Vector3(0, 0, z))) { let z = this._Contour.StartPoint.z; this._Contour.ApplyMatrix(MoveMatrix(new Vector3(0, 0, -z))); this._PathCurve.Reverse(); return; } else this._Contour.ApplyMatrix(toWcsMat4); } Log("错误:提供的轮廓没有和路径垂直!"); } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; try { let contour = this._Contour; if (SweepSolid_1.UseRectFakerContour && contour.EndParam > 10) { let box = contour.BoundingBox; contour = new Polyline().RectangleFrom2Pt(box.min, box.max); } this._MeshGeometry = new SweepGeometry(contour, this._PathCurve); this._EdgeGeometry = new BufferGeometry().setAttribute('position', new Float32BufferAttribute(this._MeshGeometry.edgePts, 3)); this.getLineGeo(this._MeshGeometry.edgePts); this._MeshGeometry.edgePts = undefined; return this._MeshGeometry; } catch (error) { return new BoxBufferGeometry(1000, 1000, 1000); } } getLineGeo(pts) { this._lineGeo = new LineGeometry(); let lineSegments = new Float32Array(pts); var instanceBuffer = new InstancedInterleavedBuffer(lineSegments, 6, 1); this._lineGeo.setAttribute('instanceStart', new InterleavedBufferAttribute(instanceBuffer, 3, 0)); this._lineGeo.setAttribute('instanceEnd', new InterleavedBufferAttribute(instanceBuffer, 3, 3)); } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this.MeshGeometry; return this._EdgeGeometry; } InitDrawObject(renderType) { if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical) return new Mesh(this.MeshGeometry, this.MeshMaterial); else if (renderType === RenderType.Print) { let mat2 = ColorMaterial.GetPrintConceptualMaterial(); let meshGeo = this.MeshGeometry; let mesh = new Mesh(meshGeo, mat2); let line = new Line2(this._lineGeo, ColorMaterial.PrintLineMatrial); return new Object3D().add(line, mesh); } else if (renderType === RenderType.Jig) { return new Object3D().add(this._PathCurve.DrawObject); } else if (renderType === RenderType.Physical2) { return new Object3D().add(new Mesh(this.MeshGeometry, this.MeshMaterial), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } } UpdateDrawGeometry() { this._EdgeGeometry = undefined; this._MeshGeometry = undefined; } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual) { Object3DRemoveAll(obj); return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.geometry = this.MeshGeometry; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Jig) { Object3DRemoveAll(obj); obj.add((this._PathCurve.DrawObject)); } else if (renderType === RenderType.Physical2) { Object3DRemoveAll(obj); return obj.add(new Mesh(this.MeshGeometry, this.MeshMaterial), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } } /** * 当实体需要被更新时,更新实体材质 */ UpdateDrawObjectMaterial(type, obj) { if (type === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (type === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } else if (type === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else { let mesh = obj; mesh.material = this.MeshMaterial; } } get BoundingBox() { if (!this.MeshGeometry.boundingBox) this.MeshGeometry.computeBoundingBox(); return this.MeshGeometry.boundingBox.clone().applyMatrix4(this._Matrix); } get OBB() { let box = this.BoundingBox; let size = box.getSize(new Vector3); return new OBB(MoveMatrix(box.min), size.multiplyScalar(0.5)); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetEndPoint(); case ObjectSnapMode.Mid: case ObjectSnapMode.Cen: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: { let contour = this._PathCurve.Clone(); contour.ApplyMatrix(this.OCS); let pts = contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); if (snapMode === ObjectSnapMode.Mid) return [...pts, ...this.GetMidPoints()]; return pts; } } return []; } GetGripPoints() { let pts = this._PathCurve.GetGripPoints(); for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } GetStretchPoints() { let pts = this._PathCurve.GetStretchPoints(); for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } UpdateEndMtx(dir, pos) { let y = this.Normal; let roMat = new Matrix4().extractRotation(this.OCS); let z = dir.applyMatrix4(roMat); let x = z.clone().cross(y); tempMatrix1.makeBasis(x, y, z); tempMatrix1.setPosition(pos.applyMatrix4(this.OCS)); } GetEndPoint() { let conPts = this._Contour.GetStretchPoints(); let cus; if (this._PathCurve instanceof Polyline) cus = this._PathCurve.Explode(); else cus = [this._PathCurve]; let roMat = new Matrix4().extractRotation(this.OCS); const pts = []; for (let i = 0; i < cus.length; i++) { let l1 = cus[i]; let l2; if (this._PathCurve.IsClose) { l2 = cus[FixIndex$1(i + 1, cus.length)]; } else { l2 = cus[i + 1]; if (i === 0) { this.UpdateEndMtx(l1.GetFistDeriv(0).normalize(), l1.StartPoint); pts.push(...conPts.map(p => p.clone().applyMatrix4(tempMatrix1))); } } let p = l1.EndPoint; let d1 = l1.GetFistDeriv(1).normalize(); this.UpdateEndMtx(d1.clone(), p); if (l2) { let d2 = l2.GetFistDeriv(0).normalize().applyMatrix4(roMat); d1.applyMatrix4(roMat); d2.add(d1).normalize(); if (isParallelTo(d1, d2)) { if (l1 instanceof Line && l2 instanceof Line); else { let ps = conPts.map(p => p.clone().applyMatrix4(tempMatrix1)); pts.push(...ps); } continue; } p.copy(l1.EndPoint); //角平分线的平面; let plane = new PlaneExt(d2, p.applyMatrix4(this.OCS)); let ps = conPts.map(p => p.clone().applyMatrix4(tempMatrix1)); pts.push(...ps.map(p => plane.intersectLine(new Line3(p.clone().sub(d1.clone().multiplyScalar(-100)), p.clone().add(d1)), new Vector3(), true))); } else { pts.push(...conPts.map(p => p.clone().applyMatrix4(tempMatrix1))); } } return pts; } GetMidPoints() { let conPts = this._Contour.GetStretchPoints(); const pts = []; for (let i = 0.5; i < this._PathCurve.EndParam; i++) { let p = this._PathCurve.GetPointAtParam(i); let d1 = this._PathCurve.GetFistDeriv(i).normalize(); this.UpdateEndMtx(d1, p); pts.push(...conPts.map(p => p.clone().applyMatrix4(tempMatrix1))); } return pts; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); this.IfPathIsLineThenZ0Vector(vec); this._PathCurve.MoveGripPoints(indexList, vec.clone().applyMatrix4(new Matrix4().extractRotation(this.OCSInv))); this.Update(); } //如果路径是直线,我们在这里避免vec传递z轴信息 IfPathIsLineThenZ0Vector(vec) { if (this._PathCurve instanceof Line) { let ocsinv = this._PathCurve.OCSInv.setPosition(0, 0, 0); vec.applyMatrix4(ocsinv).setZ(0); vec.applyMatrix4(this._PathCurve.OCSNoClone); } } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); this.IfPathIsLineThenZ0Vector(vec); this._PathCurve.MoveStretchPoints(indexList, vec.clone().applyMatrix4(new Matrix4().extractRotation(this.OCSInv))); this.Update(); } ApplyMatrix(m) { this.WriteAllObjectRecord(); if (equaln(m.getMaxScaleOnAxis(), 1)) { let xA = new Vector3(); let yA = new Vector3(); let zA = new Vector3(); m.extractBasis(xA, yA, zA); if (!equalv3(xA.clone().cross(yA).normalize(), zA)) this.ApplyMirrorMatrix(m); else { this._Matrix.multiplyMatrices(m, this._Matrix); this._SpaceOCS.multiplyMatrices(m, this._SpaceOCS); this.Update(UpdateDraw.Matrix); } } else { this.ApplyScaleMatrix(m); } return this; } ApplyMirrorMatrix(m) { if (!this.Id) { this._Matrix.multiplyMatrices(m, this._Matrix); return this; } let ocsInv = this.OCSInv; this._PathCurve.ApplyMatrix(this.OCS).ApplyMatrix(m).ApplyMatrix(ocsInv); let mtx = this._PathCurve.OCS; let mtxInv = new Matrix4().getInverse(mtx); this._PathCurve.ApplyMatrix(mtxInv); this._SpaceOCS.copy(this._Matrix); this._Matrix.multiplyMatrices(this._Matrix, mtx); this.Update(UpdateDraw.Geometry); return this; } _ReadFile(file) { super._ReadFile(file); file.Read(); //1 this._Contour = file.ReadObject(); this._PathCurve = file.ReadObject(); if (this._Contour instanceof Spline || this._PathCurve instanceof Spline) { this._isErase = true; Log("放样实体是样条线生成的,自动删除它!"); } } WriteFile(file) { super.WriteFile(file); file.Write(1); //ver file.WriteObject(this._Contour); file.WriteObject(this._PathCurve); } }; SweepSolid.UseRectFakerContour = false; SweepSolid = SweepSolid_1 = __decorate([ Factory ], SweepSolid); let HardwareTopline = class HardwareTopline extends SweepSolid { constructor() { super(...arguments); this.HardwareOption = { ...DefaultToplineMetalsOption }; this.DataList = []; this._contourRotation = 0; } get ContourRotation() { return this._contourRotation; } get Contours() { var _a, _b; let c = this.Path; let conBox = this.Contour.BoundingBox; let cMin = conBox.min; let cMax = conBox.max; let y = ZAxis; let z = c.GetFistDeriv(0).normalize(); let x = z.clone().cross(y); let mat = new Matrix4().makeBasis(x, y, z); mat.setPosition(c.StartPoint); [cMin, cMax].forEach(p => p.applyMatrix4(mat).setZ(0)); let firstCurve; if (c instanceof Polyline) firstCurve = c.GetCurveAtParam(0); else firstCurve = c; let closePt = firstCurve.GetClosestPointTo(cMin, false); let offset = cMin.distanceTo(closePt); let dir = GetPointAtCurveDir(firstCurve, cMin); let cus = this.Path.GetOffsetCurves(offset * dir); let l1 = (_a = cus[0]) !== null && _a !== void 0 ? _a : this.Path.Clone(); closePt = firstCurve.GetClosestPointTo(cMax, false); let offset2 = cMax.distanceTo(closePt); dir = GetPointAtCurveDir(firstCurve, cMax); cus = this.Path.GetOffsetCurves(offset2 * dir); let l2 = (_b = cus[0]) !== null && _b !== void 0 ? _b : this.Path.Clone(); this._ContourWidth = offset + offset2; return [l1, l2]; } /** *延伸取最大最小轮廓每段首尾到前面线段,取最长线段作为分段长 * */ get Segmentations() { const [l1, l2] = this.Contours; if (!(l1 instanceof Polyline)) return [l1]; let cus1 = l1.Explode(); let cus2 = l2.Explode(); [cus1, cus2] = cus1.length < cus2.length ? [cus2, cus1] : [cus1, cus2]; let sgs = []; const AddSgs = (c1, c2) => { sgs.push(c1.Length > c2.Length ? c1 : c2); }; const ExtendCurve = (c, refC, isPre) => { let pts = c.IntersectWith2(refC, IntersectOption.ExtendBoth).filter(r => { if (isPre) return r.thisParam < 0; else return r.thisParam > 1; }); if (isPre) { pts.sort((r1, r2) => r2.thisParam - r1.thisParam); if (pts.length > 0 && pts[0].thisParam < 0) c.Extend(pts[0].thisParam); } else { pts.sort((r1, r2) => r1.thisParam - r2.thisParam); if (pts.length > 0 && pts[0].thisParam > 1) c.Extend(pts[0].thisParam); } }; const IsNoRelativeCurve = (c1, c2) => { if ((c1 instanceof Line) !== (c2 instanceof Line)) return true; let midPt = c1.GetPointAtParam(0.5); let closePt = c2.GetClosestPointTo(midPt, false); return !closePt || !equaln(midPt.distanceTo(closePt), this._ContourWidth, 1e-3); }; const HasRelativeCurveAndChange = (target, cs, isChange = false) => { let index = cs.findIndex(c => !IsNoRelativeCurve(c, target)); if (index !== -1) { if (isChange && l1.IsClose) cs.unshift(...cs.splice(index)); return true; } return false; }; if (cus1.length !== cus2.length) arrayRemoveIf(cus2, c => !HasRelativeCurveAndChange(c, cus1)); for (let i = 0; i < cus1.length; i++) { let c1 = cus1[i]; let c2 = cus2[i]; if (cus1.length !== cus2.length) { if (IsNoRelativeCurve(c1, c2)) { sgs.push(c1); cus1.splice(i, 1); i--; continue; } } else { //第一段验证是否是关联段,不关联重置数组顺序 if (i === 0) { if (IsNoRelativeCurve(c1, c2)) { if (!HasRelativeCurveAndChange(c1, cus2, true)) { console.error("错误"); return cus1; } i--; continue; } } } let nextC1; if (l1.IsClose) { nextC1 = cus1[FixIndex(i + 1, cus1.length)]; } else { if (i < cus1.length - 1) { nextC1 = cus1[i + 1]; } } if (nextC1) { let derv = c1.GetFistDeriv(0).normalize(); let derv2 = nextC1.GetFistDeriv(0).normalize(); if (isPerpendicularityTo(derv, derv2) && isPerpendicularityTo(derv, c1.StartPoint.sub(c2.StartPoint).normalize())) { AddSgs(c1, c2); continue; } } let nextDerv1 = c1.GetFistDeriv(1).normalize(); let nextDerv2 = c2.GetFistDeriv(1).normalize(); let preDerv1 = c1.GetFistDeriv(0).normalize(); let preDerv2 = c2.GetFistDeriv(0).normalize(); [nextDerv1, nextDerv2, preDerv1, preDerv2].forEach(d => rotatePoint(d, Math.PI / 2)); let preRefLine1 = new Line(c1.StartPoint, c1.StartPoint.add(preDerv1)); let preRefLine2 = new Line(c2.StartPoint, c2.StartPoint.add(preDerv2)); let nextRefLine1 = new Line(c1.EndPoint, c1.EndPoint.add(nextDerv1)); let nextRefLine2 = new Line(c2.EndPoint, c2.EndPoint.add(nextDerv2)); ExtendCurve(c1, nextRefLine2, false); ExtendCurve(c2, nextRefLine1, false); ExtendCurve(c1, preRefLine2, true); ExtendCurve(c2, preRefLine1, true); AddSgs(c1, c2); } return sgs; } get MaxLength() { return this.Segmentations.reduce((len, c) => len + c.Length, 0); } set ContourRotation(ro) { if (ro === this._contourRotation) return; let diffRo = ro - this._contourRotation; this._contourRotation = ro; let mat = new Matrix4().makeRotationZ(diffRo); this.Contour.ApplyMatrix(mat); this.Update(); } _ReadFile(file) { super._ReadFile(file); file.Read(); //1 this._contourRotation = file.Read(); this.HardwareOption.addLen = file.Read(); this.HardwareOption.name = file.Read(); this.HardwareOption.roomName = file.Read(); this.HardwareOption.cabinetName = file.Read(); this.HardwareOption.costExpr = file.Read(); this.HardwareOption.actualExpr = file.Read(); this.HardwareOption.model = file.Read(); this.HardwareOption.factory = file.Read(); this.HardwareOption.brand = file.Read(); this.HardwareOption.spec = file.Read(); this.HardwareOption.comments = file.Read(); let count = file.Read(); this.DataList.length = 0; for (let i = 0; i < count; i++) { let d = ["", ""]; d[0] = file.Read(); d[1] = file.Read(); this.DataList.push(d); } } WriteFile(file) { super.WriteFile(file); file.Write(1); //ver file.Write(this._contourRotation); file.Write(this.HardwareOption.addLen); file.Write(this.HardwareOption.name); file.Write(this.HardwareOption.roomName); file.Write(this.HardwareOption.cabinetName); file.Write(this.HardwareOption.costExpr); file.Write(this.HardwareOption.actualExpr); file.Write(this.HardwareOption.model); file.Write(this.HardwareOption.factory); file.Write(this.HardwareOption.brand); file.Write(this.HardwareOption.spec); file.Write(this.HardwareOption.comments); file.Write(this.DataList.length); for (let data of this.DataList) { file.Write(data[0]); file.Write(data[1]); } } }; __decorate([ AutoRecord ], HardwareTopline.prototype, "HardwareOption", void 0); __decorate([ AutoRecord ], HardwareTopline.prototype, "DataList", void 0); HardwareTopline = __decorate([ Factory ], HardwareTopline); class LookOverBoardInfosTool { constructor() { this.drillTypeMap = new Map(); this.sealMap = new Map(); this.boardMap = new Map(); } GetCount(brs, options = null) { let drillCount = []; let sealCount = []; let hardwareCount = []; let areaCount = []; this.drillTypeMap.clear(); this.sealMap.clear(); this.Update(brs, options); if (this.drillTypeMap.size > 0) for (let [k, v] of this.drillTypeMap) { if (v[0] instanceof Hole) if (k === "木销") drillCount.push({ name: k, count: v.length }); else if (k === "层板钉") drillCount.push({ name: k, count: v.length }); else drillCount.push({ name: k, count: v.length }); else { this.ParseHardwareCount(k, v, hardwareCount); } } hardwareCount.sort((h1, h2) => h1.name.localeCompare(h2.name)); //加入封边信息 for (let [k, v] of this.sealMap) { sealCount.push({ name: k, count: v / 1000, unit: "米" }); } for (let [k, bs] of this.boardMap) { areaCount.push({ entity: bs[0], count: bs.length, count2: this.GetBoardsArea(bs) }); } return { drillCount, hardwareCount, sealCount, areaCount }; } ; Update(ens, options = null) { var _a; //计算排钻个数 const addDrillToMap = (spiteName, d) => { if (!this.drillTypeMap.has(spiteName)) this.drillTypeMap.set(spiteName, [d]); else { let ds = this.drillTypeMap.get(spiteName); if (!ds.includes(d)) ds.push(d); } }; const brsProps = []; const hardwares = []; for (let e of ens) { if (e instanceof Board) brsProps.push(e); else hardwares.push(e); } for (let h of hardwares) { let { name, unit, factory, spec, model, brand } = h.HardwareOption; addDrillToMap(`${name},${unit},${factory},${this.ParseSpec(h, spec)},${model},${brand}`, h); } this.UpdateBoardMap(brsProps); for (let b of brsProps) { let dlist = b.DrillList; if (equaln(b.ContourCurve.Area, 0)) { Toaster({ message: `${b.BoardProcessOption.roomName} ${b.BoardProcessOption.cabinetName} ${b.Name}轮廓有有问题,请检查`, timeout: 3000, intent: Intent.DANGER, }); continue; } for (let [id, idList] of dlist) { for (let ids of idList) { let holes = ids.map(id => id.Object); if (!holes[0] || !HoleInBoard(holes, b)) continue; let isTk = false; let spliteName; let hole; let pxlCount = 0; findHole: for (let objId of ids) { let gd = objId.Object; if (!gd || gd.IsErase) break; const group = (_a = gd.GroupId) === null || _a === void 0 ? void 0 : _a.Object; if (!group) { Toaster({ message: `柜名:${b.BoardProcessOption.cabinetName} 板名:${b.Name} 的排钻的编组丢失,统计排钻个数时会丢失该个数!`, timeout: 5000, intent: Intent.DANGER }); break; } if (gd instanceof CylinderHole) switch (gd.Type) { case GangDrillType.Pxl: pxlCount++; break; case GangDrillType.Ljg: case GangDrillType.Ymj: break; case GangDrillType.TK: isTk = true; break; case GangDrillType.Wood: case GangDrillType.WoodPXL: spliteName = "木销"; break; default: break findHole; } else { if (gd.isThrough) isTk = true; } if (!spliteName) spliteName = group.Name || "未命名"; if (!hole) hole = gd; } if (spliteName && hole) { if (isTk && userConfig.chaidanOption.statTk) { addDrillToMap("通孔" + spliteName, hole); } else if (pxlCount === 2 && userConfig.chaidanOption.statSt) { addDrillToMap("双头" + spliteName, hole); } else { addDrillToMap(spliteName, hole); } } } } // 被复制的层板钉暂未加入LayerNails数组 等做好关联后解除注释 if (b.LayerNails.length > 0) for (let objId of b.LayerNails) { if (!(objId === null || objId === void 0 ? void 0 : objId.IsErase)) addDrillToMap("层板钉", objId.Object); } //分析五金 for (const mId of b.RelativeHardware) { let metal = mId === null || mId === void 0 ? void 0 : mId.Object; if (metal && !metal.IsErase && metal.HardwareOption) { let { name, unit, factory, spec, model, brand } = metal.HardwareOption; addDrillToMap(`${name},${unit},${factory},${this.ParseSpec(metal, spec)},${model},${brand}`, metal); } } //封边 let sealData = Production.GetBoardSealingData(b); let color = b.BoardProcessOption[EBoardKeyList.Color]; for (let data of sealData) { if (equaln(0, data.size)) continue; let k = `${data.size}-${FixedNotZero(b.Thickness, 2)}-${color}`; if (options) { k += options.sealGruopKey(b); } let len = this.sealMap.get(k); if (!len) this.sealMap.set(k, data.length); else this.sealMap.set(k, len += data.length); } } } ; ParseSpec(en, spec, len) { let size = en instanceof Vector3 ? en : en.BoundingBoxInOCS.getSize(new Vector3); return ParseExpr(spec, { L: len !== null && len !== void 0 ? len : size.x, W: size.y, H: size.z }) || "[ 无 ]"; } ParseHardwareCount(k, v, hardwareCount) { if (v.length > 0) { if (!(v[0] instanceof HardwareTopline)) { let count2 = v.reduce((v, d) => { var _a; let size = d.BoundingBoxInOCS.getSize(new Vector3); let c = (_a = safeEval(d.HardwareOption.count, { L: size.x, W: size.y, H: size.z })) !== null && _a !== void 0 ? _a : 0; return v + c; }, 0); hardwareCount.push({ name: k.split(",")[0], count: v.length, entity: v[0], count2: FixedNotZero(count2, 2) }); } else { let map = new Map(); let name = k.split(",")[0]; let addLen = v[0].HardwareOption.addLen; for (let d of v) { let e = d; let cus = e.Segmentations; for (let cu of cus) { let len = parseFloat(FixedNotZero(cu.Length, 2)); if (map.has(len)) { map.set(len, map.get(len) + 1); } else { map.set(len, 1); } } } for (let [len, count] of map) { let count2 = parseFloat(FixedNotZero(len + parseFloat(addLen), 2)); hardwareCount.push({ name, count, entity: v[0], count2, length: count2 }); } } } } UpdateBoardMap(brs) { this.boardMap.clear(); for (let b of brs) { let thickness = this.GetBoardThickness(b); let brMat = b.BoardProcessOption[EBoardKeyList.BrMat]; let mat = b.BoardProcessOption[EBoardKeyList.Mat]; let color = b.BoardProcessOption[EBoardKeyList.Color]; let key = `${thickness}-${brMat}-${mat}-${color}`; let list = this.boardMap.get(key); if (!list) { list = []; this.boardMap.set(key, list); } list.push(b); } } GetBoardThickness(br) { let size = Production.GetSpiteSize(br); if (size) return FixedNotZero(size.spliteThickness, 2); else return FixedNotZero(br.Thickness, 2); } GetBoardsArea(brs) { return brs.reduce((area, b) => { let size = Production.GetSpiteSize(b); let ar; if (size) ar = size.spliteHeight * size.spliteWidth / 1e6; else ar = b.Width * b.Height / 1e6; ar = parseFloat(ar.toFixed(3)); return area + ar; }, 0).toFixed(2); } } const lookOverBoardInfosTool = new LookOverBoardInfosTool(); function IsDev() { return window.location.hostname === "localhost"; } const ReportFunctionList = []; function SendReport(msg) { for (let f of ReportFunctionList) f(msg); } const ShowObjectsFunctionList = []; function ShowSelectObjects(ens) { for (let f of ShowObjectsFunctionList) f(ens); } function GenerateExtrudeEdgeGeometry(contourPoints, height) { let pts = []; for (let cs of contourPoints) arrayPushArray(pts, GenerateExtrudeEdgeGeometryPoints(cs, height)); let geo = new BufferGeometry().setFromPoints(pts); return geo; } function GenerateExtrudeEdgeGeometryPoints(contourPoints, height) { if (contourPoints.length < 3) return []; if (equalv3(contourPoints[0], arrayLast(contourPoints))) contourPoints.pop(); let pts = []; let hpts = contourPoints.map(p => new Vector3(p.x, p.y, height)); let count = contourPoints.length; for (let i = 0; i < count; i++) { pts.push(contourPoints[i], contourPoints[FixIndex$1(i + 1, count)], hpts[i], hpts[FixIndex$1(i + 1, count)], contourPoints[i], hpts[i]); } return pts; } let ExtrudeHole = class ExtrudeHole extends Hole { constructor() { super(...arguments); this._contourCurve = new Polyline(); this._knifeRadius = 3; this.isHole = true; this.isThrough = false; } get KnifeRadius() { return this._knifeRadius; } set KnifeRadius(v) { if (!equaln(v, this._knifeRadius)) { this.WriteAllObjectRecord(); this._knifeRadius = v; } } Explode() { return [this.ContourCurve.Clone().ApplyMatrix(this.OCS)]; } get ContourCurve() { return this._contourCurve; } set ContourCurve(curve) { if (!curve.IsClose) return; if (curve instanceof Polyline) { curve.CloseMark = true; let pts = curve.LineData; if (equalv2(pts[0].pt, arrayLast(pts).pt)) pts.pop(); //如果曲线被旋转了,那么修正它的旋转矩阵,避免纹路错误 let ocs = curve.OCS; if (!equaln(ocs.elements[0], 1)) // || ocs.elements[9] || ocs.elements[10] { for (let p of pts) Vector2ApplyMatrix4(ocs, p.pt); curve.OCS = new Matrix4(); } curve.ClearDraw(); } this.WriteAllObjectRecord(); this._contourCurve = curve; this.CheckContourCurve(); this.Update(); } CheckContourCurve() { let box = this._contourCurve.BoundingBox; //修正轮廓基点 if (!equalv3(box.min, ZeroVec)) { this._contourCurve.Position = this._contourCurve.Position.sub(box.min); let v = box.min.applyMatrix4(this.OCS.setPosition(ZeroVec)); this._Matrix.setPosition(this.Position.add(v)); } } Erase(isErase = true) { if (isErase === this.IsErase) return; super.Erase(isErase); if (!isErase) return; if (this.OtherHalfTongKong && !this.OtherHalfTongKong.IsErase) { let cy = this.OtherHalfTongKong.Object; cy.isThrough = false; cy.OtherHalfTongKong = null; } } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let cu = this.ContourCurve; cu.ApplyMatrix(this.OCS); cu.ApplyMatrix(m); cu.ApplyMatrix(this.OCSInv); this.CheckContourCurve(); this.Update(); return this; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Cen: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: { let contour = this.ContourCurve.Clone(); contour.ApplyMatrix(this.OCS); let pts = contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); contour.Position = contour.Position.add(this.Normal.multiplyScalar(this.Height)); pts.push(...contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); if (snapMode === ObjectSnapMode.Mid) pts.push(...contour.GetStretchPoints().map(p => p.add(this.Normal.multiplyScalar(-this.Height / 2)))); return pts; } } return []; } get Shape() { let contour = Contour.CreateContour(this.ContourCurve.Clone(), false); return new Shape(contour); } get BoundingBoxInOCS() { let box = new Box3Ext().copy(this.ContourCurve.BoundingBox); box.max.add(new Vector3(0, 0, this.Height)); return box; } get BoundingBox() { let box = this.ContourCurve.BoundingBox; box.max.add(new Vector3(0, 0, this.Height)); box.applyMatrix4(this.OCS); return box; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; let pts = [this.ContourCurve.Shape.getPoints(6).map(AsVector3)]; this._EdgeGeometry = GenerateExtrudeEdgeGeometry(pts, this.Height).applyMatrix4(this._contourCurve.OCSNoClone); return this._EdgeGeometry; } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this._MeshGeometry = this.GeneralMeshGeometry(); return this._MeshGeometry; } GeneralMeshGeometry() { let extrudeSettings = { curveSegments: 6, steps: 1, bevelEnabled: false, depth: this.Height, }; let geo = new ExtrudeGeometry(this.ContourCurve.Shape, extrudeSettings); geo.applyMatrix4(this._contourCurve.OCS); return geo; } GetGripOrStretchPoints(dragType) { let isGrip = dragType === DragPointType.Grip; let pts = isGrip ? this.ContourCurve.GetGripPoints() : this.ContourCurve.GetStretchPoints(); let v = new Vector3(0, 0, this.Height); pts.push(...pts.map(p => p.clone().add(v))); pts.forEach(p => { p.applyMatrix4(this.OCS); }); return pts; } GetStrectchPointCountList(dragType) { return this.ContourCurve.GetDragPointCount(dragType) * 2; } MoveGripOrStretchPoints(indexList, vec, dragType) { this.WriteAllObjectRecord(); if (dragType === DragPointType.Stretch && indexList.length === this.GetStrectchPointCountList(dragType)) { this.Position = this.Position.add(vec); return; } arraySortByNumber(indexList); this.MoveGripOrStretchPointsOnly(indexList, vec, dragType); this.CheckContourCurve(); this.Update(); } IsStretchHeight(indexs) { let count = this.ContourCurve.GetStretchPoints().length; if (indexs.length === count) { let isF = indexs[0] < count; return indexs.every(i => isF === (i < count)); } return false; } MoveGripOrStretchPointsOnly(indexList, vec, dragType) { let stretchCount = this.ContourCurve.GetDragPointCount(dragType); if (dragType === DragPointType.Stretch) { //Move if (indexList.length === stretchCount * 2) { this.Position = this.Position.add(vec); return; } //判断是否拉伸厚度 if (this.IsStretchHeight(indexList)) { let isFront = indexList[0] < stretchCount; if (indexList.every(v => v < stretchCount === isFront)) { //Change thickness let lvec = vec.clone().applyMatrix4(this.OCSInv.setPosition(ZeroVec)); if (isFront) { this.Height -= lvec.z; //移动位置而不改变内部拉槽 let v = this.Normal.multiplyScalar(lvec.z); this._Matrix.elements[12] += v.x; this._Matrix.elements[13] += v.y; this._Matrix.elements[14] += v.z; } else { this.Height += lvec.z; } return; } } indexList = arrayClone(indexList); } //修正点的索引 for (let i = 0; i < indexList.length; i++) { let index = indexList[i]; if (index >= stretchCount) { index -= stretchCount; indexList[i] = index; } } indexList = [...new Set(indexList)]; let localVec = vec.clone().applyMatrix4(this.OCSInv.setPosition(ZeroVec)); if (dragType === DragPointType.Grip) { if (this.ContourCurve instanceof Polyline && indexList.length === 1 && indexList[0] % 2 === 1) { let param = indexList[0] / 2; if (this.ContourCurve.GetBuilgeAt(Math.floor(param)) === 0) { let der = this.ContourCurve.GetFistDeriv(param).normalize(); [der.x, der.y] = [der.y, -der.x]; let d = localVec.dot(der); localVec.copy(der).multiplyScalar(d); } } this.ContourCurve.MoveGripPoints(indexList, localVec); } else this.ContourCurve.MoveStretchPoints(indexList, localVec); } GetGripPoints() { return this.GetGripOrStretchPoints(DragPointType.Grip); } GetStretchPoints() { return this.GetGripOrStretchPoints(DragPointType.Stretch); } MoveGripPoints(indexList, vec) { this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Grip); } MoveStretchPoints(indexList, vec) { this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Stretch); } Convert2ExtrudeSolid() { let g = new ExtrudeSolid(); g.KnifeRadius = this.KnifeRadius; g.SetContourCurve(this.ContourCurve); g.Thickness = this.Height; g.ApplyMatrix(this.OCS); return g; } GetPrintObject3D() { let geometry = new LineGeometry(); let lineSegments = new Float32Array(this.EdgeGeometry.attributes.position.array); let instanceBuffer = new InstancedInterleavedBuffer(lineSegments, 6, 1); geometry.setAttribute('instanceStart', new InterleavedBufferAttribute(instanceBuffer, 3, 0)); geometry.setAttribute('instanceEnd', new InterleavedBufferAttribute(instanceBuffer, 3, 3)); let line = new Line2(geometry, ColorMaterial.PrintLineMatrial); let mesh = new Mesh(this.MeshGeometry, ColorMaterial.GetPrintConceptualMaterial()); return [line, mesh]; } InitDrawObject(renderType = RenderType.Wireframe) { if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex))); } else if (renderType === RenderType.Jig) { return new Object3D().add(...FastWireframe2(this)); } else if (renderType === RenderType.Print) { return new Object3D().add(...this.GetPrintObject3D()); } } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); if (renderType !== RenderType.Wireframe) Object3DRemoveAll(obj); this._EdgeGeometry = undefined; this._MeshGeometry = undefined; this.MeshGeometry; if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Print) { obj.add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical || renderType === RenderType.Physical2) { obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Jig) obj.add(...FastWireframe2(this)); return obj; } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType !== RenderType.Jig && renderType !== RenderType.Print) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } } get OBB() { let size = this.ContourCurve.BoundingBox.getSize(new Vector3).setZ(this.Height); return new OBB(this.OCS, size.multiplyScalar(0.5)); } ReadFile(file) { super.ReadFile(file); let ver = file.Read(); this._contourCurve = file.ReadObject(); this._knifeRadius = file.Read(); if (ver > 1) { this.isHole = file.Read(); } if (ver > 2) this.isThrough = file.Read(); this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(3); file.WriteObject(this._contourCurve); file.Write(this._knifeRadius); file.Write(this.isHole); file.Write(this.isThrough); } }; __decorate([ AutoRecord ], ExtrudeHole.prototype, "isHole", void 0); __decorate([ AutoRecord ], ExtrudeHole.prototype, "isThrough", void 0); ExtrudeHole = __decorate([ Factory ], ExtrudeHole); //将嵌入的实体绘制对象添加到当前的绘制对象(由于内嵌的实体可能被重复引用) function AddEntityDrawObject(obj, embedEntity, renderType = RenderType.Wireframe) { let embedObject = embedEntity.GetDrawObjectFromRenderType(renderType); if (embedObject.parent) obj.children.push(embedObject); //为了避免这个内嵌实体加入到不同的Object中(因为我们有PrintObject),这个写法能行,是因为我们会在其他地方更新它的矩阵 else obj.add(embedObject); } var CompositeEntity_1; let CompositeEntity = CompositeEntity_1 = class CompositeEntity extends Entity { constructor() { super(); //如果你需要修改内部实体,则需要写入记录 this.Entitys = []; } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { return this.GetBoundingBoxInMtx(this.OCSInv); } //因为复合实体特性的关系,所以Entity.GetBoundingBoxInMtx实现会错误,因为修改这个实体的矩阵并不会影响子实体的包围盒(在布局状态下,实体可能被复用),所以用重载看起来没问题 GetBoundingBoxInMtx(mtx) { return new Box3Ext().copy(this.BoundingBox).applyMatrix4(mtx); } //#region 绘制 // OnlyRenderType = true; //我们现在不需要这样,因为我们每个绘制类型的Object的子实体都有子实体的渲染类型(唯一的缺点可能是渲染速度变慢了?) /** * 初始化绘制的threejs实体,子类型重载该函数初始化绘制实体. */ Explode() { return this.Entitys.map(e => { let cloneE = e.Clone(); cloneE.Material = e.Material; return cloneE.ApplyMatrix(this.OCS); }); } Traverse(callback) { callback(this); for (let en of this.Entitys) { if (en instanceof CompositeEntity_1) en.Traverse(callback); else callback(en); } } //实体在被内嵌时,它绘制对象的世界矩阵会被影响,所以这里我们不直接计算它的盒子,而是用绘制对象的盒子来计算包围盒,避免错误 //例如 复合扫略实体 的ZoomObject在这个实现下是错误的(因为扫略实体也是直接取绘制对象的包围盒) // get BoundingBox() // { // let box = new Box3Ext(); // for (let e of this.Entitys) // box.union(e.BoundingBox); // return box.applyMatrix4(this.OCSNoClone); // } InitDrawObject(renderType = RenderType.Wireframe) { /** * 如果复合实体里面有圆,并且使用了拉伸夹点功能,在UpdateDrawObject时,会因为无法得到Jig对象而导致的错误. * 索性我们去掉Jig实体的功能. */ if (renderType === RenderType.Jig) return; let object = new Object3D(); this.UpdateDrawObject(renderType, object); return object; } UpdateDrawObject(renderType, obj) { Object3DRemoveAll(obj); for (let e of this.Entitys) { e.IsEmbedEntity = true; // //内嵌实体在某些时候可能被清理,修复它 // if (e.DrawObject.children.length === 0) // e.ClearDraw(); let o = e.GetDrawObjectFromRenderType(renderType); if (o) { o.traverse(obj => obj.userData = {}); AddEntityDrawObject(obj, e, renderType); } } } get ColorIndex() { return super.ColorIndex; } set ColorIndex(color) { if (color !== this._Color) { this.WriteAllObjectRecord(); this._Color = color; this.Traverse(e => { if (e === this) return; // if (e instanceof CompositeEntity) //有点奇怪 // e._Color = color; // else e.ColorIndex = color; }); } } get Material() { return super.Material; } set Material(id) { super.Material = id; for (let e of this.Entitys) e.Material = id; } UpdateDrawObjectMaterial(renderType, obj) { this.UpdateDrawObject(renderType, obj); } RestoreJigMaterial() { //我们不做任何事情,避免更新材质引起的重绘,因为我们没有实现Jig材质,所以我们也不需要还原它 } //#endregion //#region 交互操作 /** * * @param snapMode 捕捉模式(单一) * @param pickPoint const * @param lastPoint const * @param viewXform const 最近点捕捉需要这个变量 * @returns object snap points */ GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let pts = []; for (let e of this.Entitys) { pts.push(...e.Clone().ApplyMatrix(this.OCS).GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } return pts; } GetGripPoints() { return this.GetGripOrStretchPoints(DragPointType.Grip); } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Grip); } GetStretchPoints() { return this.GetGripOrStretchPoints(DragPointType.Stretch); } /** * 拉伸夹点,用于Stretch命令 * * @param {Array} indexList 拉伸点索引列表. * @param {Vector3} vec 移动向量 * @memberof Entity */ MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Stretch); } GetGripOrStretchPoints(type) { let pts = []; for (let e of this.Entitys) pts.push(...(type === DragPointType.Grip ? e.GetGripPoints() : e.GetStretchPoints())); for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } GetStrectchPointCountList(dragType) { let counts = this.Entitys.map(e => { return (dragType === DragPointType.Grip ? e.GetGripPoints() : e.GetStretchPoints()).length; }); return counts; } MoveGripOrStretchPoints(indexList, vec, dragType) { this.WriteAllObjectRecord(); let counts = this.GetStrectchPointCountList(dragType); if (dragType === DragPointType.Stretch && indexList.length === arraySum(counts)) { this.Position = this.Position.add(vec); return; } vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); arraySortByNumber(indexList); let i = 0; let j = 0; let icount = indexList.length; let offset = 0; for (let count of counts) { offset += count; let ilist = []; for (; i < icount; i++) { if (indexList[i] < offset) ilist.push(indexList[i] - offset + count); else break; } let ent = this.Entitys[j]; dragType === DragPointType.Grip ? ent.MoveGripPoints(ilist, vec) : ent.MoveStretchPoints(ilist, vec); if (ent instanceof ExtrudeSolid) //取消优化判断this.Objectid,因为这个实体可能被复合在另一个实体中,导致这个id是不存在的,所以我们无法判断它在拽拖. ent.CheckContourCurve(); ent.Update(); j++; } this.__UpdateVersion__++; //如何绘制对象是克隆的,那么我们将重绘它(避免无法更新) //我们也不大需要下面的判断,我们如果持续的更新它,其实并不会有多大的问题,因为我们总是从缓存里面拿绘制对象 // if (this._drawObject && this._drawObject.children[0]?.userData.IsClone) this.Update(); } CloneDrawObject(from) { for (let [type, obj] of from._CacheDrawObject) { let oldUserDaata = obj.userData; obj.userData = {}; let newObj = obj.clone(true); obj.userData = oldUserDaata; obj.userData.IsClone = true; newObj.matrix = this._Matrix; newObj.userData = { Entity: this }; newObj.userData.IsClone = true; this._CacheDrawObject.set(type, newObj); } this.NeedUpdateFlag = UpdateDraw.None; } ApplyMirrorMatrix(m) { if (this.Id) this.Update(UpdateDraw.Geometry); return this; } //#endregion //#region 文件序列化 _ReadFile(file) { file.Read(); super._ReadFile(file); let count = file.Read(); this.Entitys.length = 0; for (let i = 0; i < count; i++) { let ent = file.ReadObject(); if (ent) this.Entitys.push(ent); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.Entitys.length); for (let e of this.Entitys) file.WriteObject(e); } }; __decorate([ AutoRecord ], CompositeEntity.prototype, "Entitys", void 0); CompositeEntity = CompositeEntity_1 = __decorate([ Factory ], CompositeEntity); var HardwareCompositeEntity_1; let HardwareCompositeEntity = HardwareCompositeEntity_1 = class HardwareCompositeEntity extends CompositeEntity { constructor() { super(); this.HardwareOption = { ...DefaultCompositeMetalsOption }; this.DataList = []; this.RelevanceBoards = []; } GetAllEntity(isHole = false, filter) { let holes = []; for (let e of this.Entitys) { if (e instanceof HardwareCompositeEntity_1) { if (!isHole || e.HardwareOption.isHole) holes.push(...e.GetAllEntity(isHole, filter).map(h => h.ApplyMatrix(this.OCS))); } else { if (!filter || filter(e)) { holes.push(e.Clone().ApplyMatrix(this.OCS)); } } } return holes; } _ReadFile(file) { super._ReadFile(file); let v = file.Read(); this.HardwareOption.type = file.Read(); this.HardwareOption.isSplite = file.Read(); this.HardwareOption.isSplitePrice = file.Read(); this.HardwareOption.color = file.Read(); this.HardwareOption.material = file.Read(); this.HardwareOption.name = file.Read(); this.HardwareOption.roomName = file.Read(); this.HardwareOption.cabinetName = file.Read(); this.HardwareOption.costExpr = file.Read(); this.HardwareOption.actualExpr = file.Read(); this.HardwareOption.model = file.Read(); this.HardwareOption.factory = file.Read(); this.HardwareOption.brand = file.Read(); this.HardwareOption.spec = file.Read(); this.HardwareOption.count = file.Read(); this.HardwareOption.comments = file.Read(); this.HardwareOption.unit = file.Read(); let count = file.Read(); this.DataList.length = 0; for (let i = 0; i < count; i++) { let d = ["", ""]; d[0] = file.Read(); d[1] = file.Read(); this.DataList.push(d); } if (v > 1) this.HardwareOption.isHole = file.Read(); if (v >= 3) { let count = file.Read(); this.RelevanceBoards.length = 0; for (let i = 0; i < count; i++) { this.RelevanceBoards.push(file.ReadSoftObjectId()); } } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(3); file.Write(this.HardwareOption.type); file.Write(this.HardwareOption.isSplite); file.Write(this.HardwareOption.isSplitePrice); file.Write(this.HardwareOption.color); file.Write(this.HardwareOption.material); file.Write(this.HardwareOption.name); file.Write(this.HardwareOption.roomName); file.Write(this.HardwareOption.cabinetName); file.Write(this.HardwareOption.costExpr); file.Write(this.HardwareOption.actualExpr); file.Write(this.HardwareOption.model); file.Write(this.HardwareOption.factory); file.Write(this.HardwareOption.brand); file.Write(this.HardwareOption.spec); file.Write(this.HardwareOption.count); file.Write(this.HardwareOption.comments); file.Write(this.HardwareOption.unit); file.Write(this.DataList.length); for (let data of this.DataList) { file.Write(data[0]); file.Write(data[1]); } file.Write(this.HardwareOption.isHole); file.Write(this.RelevanceBoards.length); for (let id of this.RelevanceBoards) file.WriteSoftObjectId(id); } }; __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "HardwareOption", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "DataList", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "RelevanceBoards", void 0); HardwareCompositeEntity = HardwareCompositeEntity_1 = __decorate([ Factory ], HardwareCompositeEntity); const CanDrawHoleFuzz = 1e-2; var BoardFaceType; (function (BoardFaceType) { BoardFaceType[BoardFaceType["Side"] = 0] = "Side"; BoardFaceType[BoardFaceType["NoSide"] = 1] = "NoSide"; })(BoardFaceType || (BoardFaceType = {})); function GetSideFaceMtx(cu, inverseZ = false) { let x = cu.GetFistDeriv(0).normalize(); let y = ZAxis; let z = x.clone().cross(y); if (inverseZ) z.negate(); let basePt; if ((equaln(x.x, 0) && x.y > 0) || x.x < 0) { x.negate(); basePt = cu.EndPoint; } else basePt = cu.StartPoint; //构建面矩阵 return new Matrix4() .makeBasis(x, y, z) .setPosition(basePt); } let instanceMap = new Map(); /** * 构造单例类的静态类. * # Example: * class A extends Singleton(){}; * //获得单例 * let a = A.GetInstance(); */ class Singleton { constructor() { } //FIXME: https://github.com/Microsoft/TypeScript/issues/5863 static GetInstance() { if (instanceMap.has(this)) return instanceMap.get(this); //@ts-ignore let __instance__ = new this.prototype.constructor(); instanceMap.set(this, __instance__); return __instance__; } } /** * 优化走刀路径,连接偏移后的曲线数组 * @param offsetCus 偏移后的曲线组 * @param originShape 原始走刀形状 * @param rad 刀半径 * @returns tool path */ function OptimizeToolPath(offsetCus, originShape, rad) { var _a; // 去掉最外轮廓 let outline = offsetCus.shift(); let plList = []; let noCloseCus = []; for (let cu of offsetCus) { if (!cu.IsClose) { noCloseCus.push(cu); continue; } if (cu instanceof Polyline) { //轮廓朝下的逆时针轮廓需要翻转 //如果走刀不止一条,第一刀为顺时针,其余为逆时针 if (cu.IsClose) { if (offsetCus.length === 1) { if (cu.Normal.z * cu.Area2 < 0) cu.Reverse(); } else if ((cu.Normal.z * cu.Area2 < 0) === (cu !== offsetCus[0])) cu.Reverse(); } plList.push(cu); } else if (cu instanceof Circle) { let c = ConverCircleToPolyline(cu); if (offsetCus.length > 1 && cu === offsetCus[0]) c.Reverse(); c.ColorIndex = cu.ColorIndex; plList.push(c); } else console.warn("错误形状"); } if (noCloseCus.length > 0) { let culist = []; noCloseCus.forEach(c => { if (c instanceof Polyline) culist.push(...c.Explode()); else culist.push(c); }); //移除相等的曲线避免重复走刀 RempveEqualCurves(culist); let groups = curveLinkGroup(culist); for (let g of groups) { let pl = Polyline.Combine(g); pl.ColorIndex = noCloseCus[0].ColorIndex; plList.push(pl); } } let dir = GetCurveToInDir(outline); let cantIntCur = [outline]; cantIntCur.push(...GetOffsetCurves(outline, rad * dir)); if (originShape.Holes.length > 0) { for (let h of originShape.Holes) { let dir = Math.sign(h.Curve.Area2); if (h.Curve instanceof Circle) dir = 1; cantIntCur.push(h.Curve, ...GetOffsetCurves(h.Curve, rad * dir)); } } //曲线统一起点 ChangePlListStartPt(plList); //对多段线进行排序,按最起始点远近排序 SortPlByStartPt(plList); let result = []; let firstPl = plList[0]; firstPl.CloseMark = false; for (let i = 1; i < plList.length; i++) { let ePt = firstPl.EndPoint; let isDisVail; if (((_a = plList[i].TempData) === null || _a === void 0 ? void 0 : _a.isOut) && !equalv3(ePt, plList[i].StartPoint)) isDisVail = true; else { let refLine = new Line(ePt, plList[i].StartPoint); isDisVail = cantIntCur.some(c => c.IntersectWith(refLine, 0).length > 1); } if (isDisVail) { result.push(firstPl); firstPl = plList[i]; firstPl.CloseMark = false; } else { let alMat = matrixAlignCoordSys(plList[i].OCS, firstPl.OCS); let cuPtsBul = plList[i].PtsBuls; for (let i = 0; i < cuPtsBul.pts.length; i++) { //坐标系对齐 let p = cuPtsBul.pts[i]; p.copy(AsVector2(AsVector3(p).applyMatrix4(alMat))); firstPl.LineData.push({ pt: p, bul: cuPtsBul.buls[i] }); } } } result.push(firstPl); return result; } /** * 设定走刀路径起始点 * 为了统一刀路起点,最外轮廓左左点为起始点,其余轮廓以最接近最外轮廓起始点的点左起始点 * @param plList */ function ChangePlListStartPt(plList) { let firstPl = plList[0]; if (firstPl.IsClose) { let minP = undefined; let compare = comparePoint("xy"); for (let p of firstPl.GetStretchPoints()) { if (!minP) minP = p; else if (compare(minP, p) === 1) minP = p; } let par = firstPl.GetParamAtPoint(minP); firstPl.ResetStartPoint(par); } let firstSpt = firstPl.StartPoint; for (let i = 1; i < plList.length; i++) { let pl = plList[i]; if (pl.IsClose) { let pts = pl.GetStretchPoints().sort((p1, p2) => { let dist1 = p1.distanceToSquared(firstSpt); let dist2 = p2.distanceToSquared(firstSpt); return dist1 - dist2; }); let par = pl.GetParamAtPoint(pts[0]); pl.ResetStartPoint(par); } else { let sPt = pl.StartPoint; let ePt = pl.EndPoint; let dist1 = sPt.distanceToSquared(firstSpt); let dist2 = ePt.distanceToSquared(firstSpt); if (dist1 > dist2) pl.Reverse(); } } } /** * 排序多段线数组,按照起点之间的距离 */ function SortPlByStartPt(pls) { if (pls.length <= 1) return pls; let result = [pls[0]]; let usedPl = new WeakSet([pls[0]]); let p = pls[0].StartPoint; while (true) { if (pls.length === result.length) break; let vaildPl; let minDist = Infinity; for (let pl of pls) { if (usedPl.has(pl)) continue; let dist = pl.StartPoint.distanceToSquared(p); if (dist < minDist) { minDist = dist; vaildPl = pl; } } p = vaildPl.StartPoint; result.push(vaildPl); usedPl.add(vaildPl); } pls.length = 0; pls.push(...result); } function RempveEqualCurves(cus) { let needRemoveCurve = new Set(); for (let i = 0; i < cus.length; i++) { let cu1 = cus[i]; if (needRemoveCurve.has(cu1)) continue; for (let j = i + 1; j < cus.length; j++) { let cu2 = cus[j]; if (needRemoveCurve.has(cu2)) continue; if (equalCurve(cu1, cu2)) { needRemoveCurve.add(cu2); } } } arrayRemoveIf(cus, (c) => needRemoveCurve.has(c)); } /**获取内偏移的轮廓 */ function GetOffsetCurves(cu, dist, rectInfo) { if (cu instanceof Polyline) { if (rectInfo === null || rectInfo === void 0 ? void 0 : rectInfo.isRect) { let r = RectOffset(cu, rectInfo, Math.abs(dist)); return r ? [r] : []; } return cu.GetFeedingToolPath(dist).filter(c => !equaln(c.Length, 0, 1e-5)); } else return cu.GetOffsetCurves(dist); } /** 获得曲线内偏移方向*/ function GetCurveToInDir(cu) { return cu.IsClockWise ? 1 : -1; } /**矩形偏移,正为内偏移 */ function RectOffset(rect, res, dist) { if (!res.isRect || equaln(dist, 0)) return; let box = res.box; let size = res.size; let min = box.min.clone(); let max = box.max.clone(); if (dist > Math.min(size.x, size.y) / 2 + 1e-2) return; if (equaln(size.x / 2, dist, 1e-5)) { let x = (box.min.x + box.max.x) * 0.5; let sPt = new Vector3(x, box.min.y + dist); let ePt = new Vector3(x, box.max.y - dist); return new Polyline([{ pt: AsVector2(sPt), bul: 0 }, { pt: AsVector2(ePt), bul: 0 }]).ApplyMatrix(res.OCS); } else if (equaln(size.y / 2, dist, 1e-5)) { let y = (box.min.y + box.max.y) * 0.5; let sPt = new Vector3(box.min.x + dist, y); let ePt = new Vector3(box.max.x - dist, y); return new Polyline([{ pt: AsVector2(sPt), bul: 0 }, { pt: AsVector2(ePt), bul: 0 }]).ApplyMatrix(res.OCS); } else { min.add(new Vector3(dist, dist)); max.add(new Vector3(-dist, -dist)); return new Polyline().RectangleFrom2Pt(min, max).ApplyMatrix(res.OCS); } } /** *计算走刀工具类 */ class FeedingToolPath extends Singleton { /** * 处理形状,内偏移 * @param shape 造型Shape * @param knifRadius 刀半径/偏移距离 * @param [isOut=true] 是否是最外轮廓,如果是,洞需要外偏移一个刀半径,多段线偏移保留不闭合轮廓 */ HandleShape(shape, knifRadius, isOut = true) { let outline = shape.Outline.Curve; if (isOut) outline = outline.Clone(); let dir = GetCurveToInDir(outline); let offsetCus = [outline]; //获得形状外孔轮廓 let holes = []; /**用于判断孤岛是否与外轮廓相交 */ let holeOffsetCus = []; for (let h of shape.Holes) { if (!isOut) holes.push(h.Clone()); else { let dir = -GetCurveToInDir(h.Curve); let cus; if (h.Curve instanceof Circle) cus = h.Curve.GetOffsetCurves(knifRadius * dir); else cus = h.Curve.GetFeedingToolPath(knifRadius * dir); holeOffsetCus.push(...h.Curve.GetOffsetCurves(knifRadius * dir).filter(c => c.IsClose)); holes.push(...this.GetContours(cus, offsetCus)); } } let offsetDist = 0; let rectInfo = IsRect(outline); while (true) { if ((!isOut || offsetDist >= knifRadius) && rectInfo.isRect) offsetDist += knifRadius * 2; else offsetDist += knifRadius; //理论上最大的宽度为1220,所以2000已经是种仁慈. //更好的算法应该是, 如果线不在outline里面, 那么已经算是错误的,但是理论上如果线已经往外偏太多了,就一定会使得这个判断生效 if (offsetDist > 2000) throw "无法加工的造型!已经超过了最大的走刀范围2000!"; let retCus = []; let tempOffsetCus = GetOffsetCurves(outline, offsetDist * dir, rectInfo); retCus.push(...tempOffsetCus); //最后一次内偏移如果是矩形,需在偏移一个刀半径避免没切到中心 if (retCus.length === 0 && rectInfo.isRect && offsetDist > knifRadius) { offsetDist -= knifRadius; retCus.push(...GetOffsetCurves(outline, offsetDist * dir, rectInfo)); } if (retCus.length === 0) break; //是否和孤岛相交 let isInt = false; for (let c of retCus) { if (holes.length > 0) { isInt = holes.some(h => h.Curve.IntersectWith(c, 0).length > 0 || h.CuInOutline(c)); if (isInt) break; } if (isOut && offsetDist === knifRadius) c.TempData = { isOut: true }; offsetCus.push(c); } if (isInt) { //洞形状管理器 let holesMg = new ShapeManager(); if (isOut) holes = Shape.mergeContours(holes, false); //#I1MUQD 正好擦边的孔不合并 holesMg.AppendShapeList(holes.map(h => new Shape(h))); let shapeMg = new ShapeManager(); let cons = this.GetContours(retCus, offsetCus); shapeMg.AppendShapeList(cons.map(c => new Shape(c))); shapeMg.BoolOper(holesMg, BoolOpeartionType.Subtract); for (let s of shapeMg.ShapeList) { if (isOut && tempOffsetCus.length > 1) s.Outline.Curve.TempData = { isOut: true }; offsetCus.push(...this.HandleShape(s, knifRadius, false)); } break; } } let vailHoles = []; for (let i = 0; i < holes.length; i++) { let h = holes[i]; //如果加工洞外圈和最外轮廓相交,则去掉 if (h.Curve.IntersectWith(outline, 0).length > 0) continue; let isVail = true; //若最外轮廓内偏移一个刀半径的曲线 和最内轮廓相交或者被包含,则去掉.且不与洞曲线相等 if (isOut) { let outlineOffsetCus = outline.GetOffsetCurves(dir * knifRadius).filter(c => c.IsClose); let outlineCus = GetOffsetCurves(outline, dir * knifRadius).filter(c => c.IsClose); let ho = holeOffsetCus[i]; let maxArea = Math.max(...(outlineOffsetCus.map(c => c.Area))); for (let j = 0; j < outlineOffsetCus.length; j++) { let c = outlineOffsetCus[j]; if (h.Curve.IntersectWith(outlineCus[j], 0).length > 0) { if (!(equalCurve(ho, c) || isTargetCurInOrOnSourceCur(c, h.Curve))) { isVail = false; break; } else if (isTargetCurInOrOnSourceCur(h.Curve, c)) { offsetCus.push(c); isVail = false; break; } } else if (ho.Area > maxArea) { isVail = false; break; } } } if (isVail) vailHoles.push(h); } offsetCus.push(...vailHoles.map(h => h.Curve)); return offsetCus; } /**用于测试走刀路径 */ TestCalcPath(br, isCd = false) { let modelings = br.BoardModeling; let allModeling = GetModelingFromCustomDrill(br); modelings.push(...allModeling.modeling); if (isCd && userConfig.chaidanOption.useDefaultRad) modelings.forEach(m => m.knifeRadius = userConfig.chaidanOption.radius); if (isCd) arrayRemoveIf(modelings, m => { let c = m.shape.Outline.Curve; if (c instanceof Circle && c.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) return true; return false; }); return this.CalcPath(modelings, br); } /** * 计算走刀路径 */ CalcPath(modelings, br) { let cus = []; for (let m of modelings) { cus.push(...this.GetModelFeedPath(br, m)); } return cus; } GetModelFeedPath(br, m) { const brThickness = br.Thickness; let cus = []; let { shape, thickness, knifeRadius, addLen, addWidth, addDepth } = m; if (!knifeRadius) knifeRadius = 3; if (addDepth) thickness += addDepth; if (thickness < 1e-5) return cus; shape = shape.Clone(); shape.Z0(); this.GrooveAddSize(shape, addLen, addWidth); this.HandleThoughGroove(br.ContourCurve, shape, knifeRadius); //造型半径和刀半径相等,返回重合点的线 let outline = shape.Outline.Curve; if (outline instanceof Circle && equaln(outline.Radius, m.knifeRadius)) return [new Polyline([{ pt: AsVector2(outline.Center), bul: 0 }, { pt: AsVector2(outline.Center), bul: 0 }])]; if (thickness >= brThickness) { //通孔只切一刀 let dir = GetCurveToInDir(outline); let paths; if (outline instanceof Circle) outline = ConverCircleToPolyline(outline); paths = outline.GetFeedingToolPath(dir * knifeRadius); for (let path of paths) { if (dir < 0) path.Reverse(); // 有些走刀会变成一条线,或者某些地方退化成线,这个时候这个判断是错误的 // if (!path.IsClockWise) // throw "程序错误:全深网洞加工数据并不为逆时针!"; } cus.push(...paths); } else { let offsetCus = this.HandleShape(shape, knifeRadius); if (offsetCus.length > 1) cus.push(...OptimizeToolPath(offsetCus, shape, knifeRadius)); } return cus; } GrooveAddSize(shape, addLen, addWidth) { shape.Outline.Curve.Position = shape.Outline.Curve.Position.setZ(0); //若是矩形,应用槽加长 if (addLen > 0 || addWidth > 0) { let curveData = IsRect(shape.Outline.Curve); if (curveData.isRect) { let box = curveData.box; let size = curveData.size; if (size.x > size.y) { box.max.add(new Vector3(addLen / 2, addWidth / 2)); box.min.add(new Vector3(-addLen / 2, -addWidth / 2)); } else { box.max.add(new Vector3(addWidth / 2, addLen / 2)); box.min.add(new Vector3(-addWidth / 2, -addLen / 2)); } let pl = new Polyline().RectangleFrom2Pt(box.min, box.max).ApplyMatrix(curveData.OCS); shape.Outline = Contour.CreateContour(pl); } } } GetContours(cus, retCus) { let cons = []; for (let c of cus) { if (c.IsClose) { cons.push(Contour.CreateContour(c)); } else { let expCus = c.Explode(); let regParse = new RegionParse(expCus); //分析封闭包围区域 const parseRoute = (routeSet) => { for (let routes of routeSet) { let cs = routes.map(r => r.curve); let c = Contour.CreateContour(cs, false); if (c && c.Area > 1e-3) cons.push(c); } }; parseRoute(regParse.RegionsOutline); parseRoute(regParse.RegionsInternal); for (let c of expCus) { if (!regParse.GetCueveUsed(c)) { retCus.push(c); } } } } return cons; } CheckModeling(br) { let errorIndexs = []; let modelings = br.BoardModeling; for (let i = 0; i < modelings.length; i++) { if (userConfig.chaidanOption.useDefaultRad) modelings[i].knifeRadius = userConfig.chaidanOption.radius; let m = modelings[i]; let cu = m.shape.Outline.Curve; if (cu instanceof Circle && cu.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) continue; let cus = this.GetModelFeedPath(br, m); if (cus.length === 0) errorIndexs.push(i); } return errorIndexs; } CheckCustomHole(br) { let { modeling, sideModeling } = GetModelingFromCustomDrill(br); let errHoles = []; for (let m of [...modeling, ...sideModeling]) { let cu = m.shape.Outline.Curve; if (cu instanceof Circle && cu.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) continue; if (userConfig.chaidanOption.useDefaultRad) m.knifeRadius = userConfig.chaidanOption.radius; let cus = this.GetModelFeedPath(br, m); if (cus.length === 0) errHoles.push(m.originEn); } return errHoles; } HandleThoughGroove(brCon, shape, knifeRadius) { let outline = shape.Outline.Curve; if (outline instanceof Circle) return; let cus = outline.Explode(); MergeCurvelist(cus); let hasChange = false; let curveBak; for (let i = 0; i < cus.length; i++) { let c = cus[i]; if (c instanceof Line) { let mp = (curveBak !== null && curveBak !== void 0 ? curveBak : c).Midpoint; curveBak = undefined; if (brCon.PtOnCurve(mp)) { hasChange = true; let cs = c.GetOffsetCurves(knifeRadius); cus[i] = cs[0]; let fline = cus[FixIndex(i - 1, cus.length)]; let isAddLine = false; if (fline instanceof Line) { let intPts = fline.IntersectWith2(cs[0], 3); if (intPts.length === 0) { console.error("未知错误情况"); return; } if (intPts[0].thisParam >= 0 && intPts[0].argParam <= 1) { fline.EndPoint = intPts[0].pt; cs[0].StartPoint = intPts[0].pt; } else { isAddLine = true; } } else { isAddLine = true; } if (isAddLine) { let newLine = new Line(fline.EndPoint, cs[0].StartPoint); if (i === 0) { cus.push(newLine); } else { cus.splice(i, 0, newLine); i++; } } let backLine = cus[FixIndex(i + 1, cus.length)]; isAddLine = false; if (backLine instanceof Line) { let intPts = backLine.IntersectWith2(cs[0], 3); if (intPts.length === 0) { return; } if (intPts[0].thisParam <= 1 && intPts[0].argParam >= 0) { curveBak = backLine.Clone(); backLine.StartPoint = intPts[0].pt; cs[0].EndPoint = intPts[0].pt; } else { isAddLine = true; } } else { isAddLine = true; } if (isAddLine) { let newLine = new Line(cs[0].EndPoint, backLine.StartPoint); if (i + 1 === cus.length) { cus.unshift(newLine); } else { cus.splice(i + 1, 0, newLine); i++; } } } } } if (hasChange) { let con = Contour.CreateContour(Polyline.Combine(cus)); if (con) shape.Outline = con; else console.error("在造型走刀时构建轮廓失败了!(未知情况,未处理)"); } } } function GetModelingFromCustomDrill(br) { let normal = br.Normal; let outline = GetSealedBoardContour(br, true); let modeling = []; let sideModeling = []; const holes = []; let bbox = br.BoundingBoxInOCS; let holeBoxMap = new WeakMap(); for (let [, idss] of br.DrillList) { for (let ids of idss) { for (let id of ids) { if ((id === null || id === void 0 ? void 0 : id.Object) && !id.Object.IsErase && id.Object instanceof ExtrudeHole && id.Object.isHole) { if (!(id.Object.ContourCurve instanceof Circle)) { let en = id.Object; let enBox = en.GetBoundingBoxInMtx(br.OCSInv); holeBoxMap.set(en, enBox); if (enBox.clone().intersect(bbox).isSolid(0.1)) holes.push(id.Object); } } else break; } } } for (let en of holes) { let box = holeBoxMap.get(en); let max = box.max; let min = box.min; let dir; let shape = en.Shape; let diff = br.OCSInv.multiply(en.OCS); shape.ApplyMatrix(diff); let thickness; if (isParallelTo(normal, en.Normal)) { if (min.z > br.Thickness - 1e-6) continue; //在板件的世界,0.01的误差应该不能被看出来,所以我们允许0.01的容差(这样应该是没问题的) //也避免了一些二维转三维出现的缝隙排钻不能被拆解的问题 if (max.z >= br.Thickness - 1e-2) //较大的容差(0.01) { dir = FaceDirection.Front; shape.Position = shape.Position.setZ(min.z); thickness = br.Thickness - min.z; } else if (min.z < 1e-2) //较大的容差 { dir = FaceDirection.Back; thickness = max.z; } else continue; if (thickness > +1e-6 && isTargetCurInOrOnSourceCur(outline, shape.Outline.Curve.Clone().Z0())) { modeling.push({ shape, thickness, dir, knifeRadius: en.KnifeRadius, addLen: 0, originEn: en, }); } } else { if (min.z <= 0 || max.z >= br.Thickness) continue; let spt = en.Position.applyMatrix4(br.OCSInv).setZ(0); if (outline.PtOnCurve(spt)) continue; let line = new Line(spt, en.Position.add(en.Normal.multiplyScalar(en.Height)).applyMatrix4(br.OCSInv).setZ(0)); let pt = outline.IntersectWith(line, 0)[0]; if (!pt) continue; let index = Math.floor(outline.GetParamAtPoint(pt)); let thickness = line.StartPoint.distanceTo(pt); let shape = en.Shape.ApplyMatrix(en.OCS).ApplyMatrix(br.OCSInv); let vec = line.GetFistDeriv(0).normalize().multiplyScalar(thickness); shape.Position = shape.Position.add(vec); let cu = outline.GetCurveAtIndex(index); shape.ApplyMatrix(new Matrix4().getInverse(GetSideFaceMtx(cu))); sideModeling.push({ shape, thickness, dir: index, knifeRadius: en.KnifeRadius, addLen: 0, originEn: en, }); } } return { modeling, sideModeling }; } var Production; (function (Production) { /**获取板件拆单数据 */ function GetBoardSplitOrderData(br) { var _a, _b; let sealedContour = GetSealedBoardContour(br, true); if (!sealedContour || equaln(sealedContour.Area, 0)) { Toaster({ message: br.Name + " 轮廓错误,可能存在轮廓自交,请检查后重新拆单!(错误的板已经选中,您可以按住鼠标中键查看该板!)(使用FISC命令可以修复自交轮廓!)", timeout: 8000, intent: Intent.DANGER, }); Report([(_a = br.__OriginalEnt__) !== null && _a !== void 0 ? _a : br], br.Name + " 轮廓错误"); return undefined; } let outline = GetSealedBoardContour(br, false); if (!outline || equaln(outline.Area, 0)) { Toaster({ message: br.Name + "扣除封边轮廓有误,请检查后重新拆单!(错误的板已经选中,您可以按住鼠标中键查看该板!)", timeout: 8000, intent: Intent.DANGER, }); Report([(_b = br.__OriginalEnt__) !== null && _b !== void 0 ? _b : br], br.Name + "扣除封边轮廓有误"); return; } let offsetTanslation = outline.BoundingBox.min; outline.Position = outline.Position.sub(offsetTanslation); let outlinePtsBul = ConverToPolylineAndSplitArc(outline); //外轮廓去掉最后的闭合点 outlinePtsBul.pts.pop(); outlinePtsBul.buls.pop(); let size = outline.BoundingBox.getSize(new Vector3); //不扣除封边的轮廓信息 let originOutlinePtsBul = ConverToPolylineAndSplitArc(sealedContour); originOutlinePtsBul.pts.pop(); originOutlinePtsBul.buls.pop(); let { modeling, sideModeling } = GetBoardModelingData(br, offsetTanslation); let boardContour; if (GetSpiteSize(br)) boardContour = ConverToPolylineAndSplitArc(br.ContourCurve); return { info: GetBoardInfo(br, size), originOutlin: originOutlinePtsBul, outline: outlinePtsBul, sealing: GetBoardSealingData(br), modeling, holes: GetBoardHolesData(br, offsetTanslation, sealedContour), sideModeling, offsetTanslation, metalsData: GetBoardMetals(br), boardContour, modeling2D: Get2DModeing(br, offsetTanslation), modeling3D: Get3DModeing(br, offsetTanslation), }; } Production.GetBoardSplitOrderData = GetBoardSplitOrderData; function GetBoardInfo(br, size) { let data = br.BoardProcessOption; let spliteSize = Production.GetSpiteSize(br); let isRect = !!spliteSize || !br.IsSpecialShape; return { id: br.Id.Index, name: br.Name, [EBoardKeyList.RoomName]: data[EBoardKeyList.RoomName], [EBoardKeyList.CabinetName]: data[EBoardKeyList.CabinetName], [EBoardKeyList.Mat]: data[EBoardKeyList.Mat], [EBoardKeyList.BrMat]: data[EBoardKeyList.BrMat], [EBoardKeyList.Color]: data[EBoardKeyList.Color], [EBoardKeyList.Lines]: data[EBoardKeyList.Lines], [EBoardKeyList.DrillType]: data[EBoardKeyList.DrillType], spliteHeight: spliteSize ? spliteSize.spliteHeight.toString() : "", spliteThickness: spliteSize ? spliteSize.spliteThickness.toString() : "", spliteWidth: spliteSize ? spliteSize.spliteWidth.toString() : "", isRect, remarks: data.remarks.slice(), kaiLiaoWidth: size.x, kaiLiaoHeight: size.y, openDir: br.OpenDir, }; } Production.GetBoardInfo = GetBoardInfo; /** * 转换成多段线并且将圆弧打断(大于1/4的话) */ function ConverToPolylineAndSplitArc(cu, isOutline = true, isSplite = true) { let ptsBuls; if (cu instanceof Circle) { let pl = ConverCircleToPolyline(cu); ptsBuls = pl.PtsBuls; } else { if (isOutline && cu.IsClose && cu.Normal.z * cu.Area2 < 0) cu.Reverse(); if (isSplite) ptsBuls = SplitePolylineAtArc(cu); else ptsBuls = cu.PtsBuls; } let ocs = cu.OCS; if (!equaln(ocs.elements[0], 1) || !equaln(ocs.elements[9], 0) || !equaln(ocs.elements[10], 0)) { for (let i = 0; i < ptsBuls.pts.length; i++) { Vector2ApplyMatrix4(ocs, ptsBuls.pts[i]); ptsBuls.buls[i] *= cu.Normal.z; } } return ptsBuls; } Production.ConverToPolylineAndSplitArc = ConverToPolylineAndSplitArc; function ConverCircleToPolyline(cir) { let arcs = cir.GetSplitCurves([0, 0.25, 0.5, 0.75]); let pl = new Polyline(); pl.OCS = cir.OCS; for (let arc of arcs) pl.Join(arc); return pl; } Production.ConverCircleToPolyline = ConverCircleToPolyline; const SPLITBUL = Math.tan(Math.PI / 8); function GetSpliteCount(allAngle) { return Math.ceil(Math.abs(allAngle) / Math.PI * 2); } /** 打断多段线超过1/4圆的圆弧*/ function SplitePolylineAtArc(cu, isSplite = true) { let ptsBuls = cu.PtsBuls; let ocsInv = cu.OCSInv; let result = { pts: [], buls: [] }; if (ptsBuls.pts.length === 0) return result; for (let i = 0; i < ptsBuls.buls.length - 1; i++) { let bul = ptsBuls.buls[i]; if (Math.abs(bul) > SPLITBUL + 1e-8 && isSplite) { let allAngle = Math.atan(bul) * 4; let splitCount = GetSpliteCount(allAngle); let arc = cu.GetCurveAtIndex(i); let paramDiv = 1 / splitCount; let newBul = Math.tan((allAngle / splitCount) / 4); for (let i = 0; i < splitCount; i++) { let param = i * paramDiv; let p = arc.GetPointAtParam(param).applyMatrix4(ocsInv); let p2 = AsVector2(p); //暂时不处理0长度段 { result.pts.push(p2); result.buls.push(newBul); } } } else { //暂时不处理0长度段 { result.pts.push(ptsBuls.pts[i]); result.buls.push(ptsBuls.buls[i]); } } } result.pts.push(arrayLast(ptsBuls.pts)); result.buls.push(arrayLast(ptsBuls.buls)); //测试是否存在无效的边(0长度边) // for (let i = 1; i < result.pts.length; i++) // { // if (equalv2(result.pts[i], result.pts[i - 1], 0.01)) // alert("存在无效的边"); // } return result; } Production.SplitePolylineAtArc = SplitePolylineAtArc; function SplitetArc(arc, hasEnd = false) { let result = { pts: [], buls: [] }; let bul = arc.Bul; if (Math.abs(bul) > SPLITBUL + 1e-8) { let allAngle = Math.atan(bul) * 4; let splitCount = GetSpliteCount(allAngle); let paramDiv = 1 / splitCount; let newBul = Math.tan((allAngle / splitCount) / 4); for (let i = 0; i < splitCount; i++) { let param = i * paramDiv; let p = arc.GetPointAtParam(param); result.pts.push(p); result.buls.push(newBul); } } else { result.pts.push(arc.StartPoint); result.buls.push(bul); } if (hasEnd) { result.pts.push(arc.EndPoint); result.buls.push(0); } return result; } Production.SplitetArc = SplitetArc; /** * 获取封边数据 * 封边数据未统一逆时针顺序,用于拆单 * */ function GetBoardSealingData(br) { let sealCus = GetBoardSealingCurves(br); let highSeal = GetBoardHighSeal(br, sealCus); let sealData = []; for (let i = 0; i < sealCus.length; i++) { let sealCu = sealCus[i]; let data = highSeal[i]; let cus = []; if (sealCu instanceof Polyline) cus.push(...sealCu.Explode()); else cus.push(sealCu); for (let cu of cus) { if (cu instanceof Line) { sealData.push(Object.assign({}, data, { length: cu.Length })); } else if (cu instanceof Arc) { let splitCount = GetSpliteCount(cu.AllAngle); let len = 2 * Math.PI * cu.Radius / 4; for (let i = 0; i < splitCount; i++) { let arcLen = i !== splitCount - 1 ? len : cu.Length - (splitCount - 1) * len; if (!equaln(arcLen, 0)) sealData.push(Object.assign({}, data, { length: arcLen })); } } else if (cu instanceof Circle) { let length = 2 * Math.PI * cu.Radius / 4; sealData.push(...Array.from({ length: 4 }, () => { return { ...data, length }; })); } } } if (br.ContourCurve instanceof Polyline && br.ContourCurve.Area2 < 0) sealData.reverse(); return sealData; } Production.GetBoardSealingData = GetBoardSealingData; function GetMetalTotalEntitys(md, isHole = false, filter) { let holes = []; if (isHole && !md.HardwareOption.isHole) return []; for (let e of md.Entitys) { if (e instanceof HardwareCompositeEntity) { if (!isHole || md.HardwareOption.isHole) holes.push(...GetMetalTotalEntitys(e, isHole, filter).map(h => h.ApplyMatrix(md.OCS))); } else { if (!filter || filter(e)) { holes.push(e.Clone().ApplyMatrix(md.OCS)); } } } return holes; } Production.GetMetalTotalEntitys = GetMetalTotalEntitys; function GetOriginBoardModelingData(br) { const getModelings = (ms) => { var _a; let data = []; for (let m of ms) { let cu = m.shape.Outline.Curve; if (cu instanceof Circle && cu.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) continue; if (userConfig.chaidanOption.useDefaultRad) m.knifeRadius = userConfig.chaidanOption.radius; data.push({ outline: ConverToPolylineAndSplitArc(cu.Clone(), false, false), holes: m.shape.Holes.map(h => ConverToPolylineAndSplitArc(h.Curve.Clone(), false, false)), thickness: m.thickness + ((_a = m.addDepth) !== null && _a !== void 0 ? _a : 0), dir: m.dir, knifeRadius: m.knifeRadius, }); } return data; }; let allModeling = GetModelingFromCustomDrill(br); let modeling = getModelings([...br.BoardModeling, ...allModeling.modeling]); let sideModeling = getModelings(allModeling.sideModeling); return { modeling, sideModeling }; } Production.GetOriginBoardModelingData = GetOriginBoardModelingData; function GetBoardModelingData(br, offsetTanslation) { const tool = FeedingToolPath.GetInstance(); const tMtx = MoveMatrix(offsetTanslation.clone().negate()); const getModelings = (ms, isSide) => { var _a; let data = []; for (let m of ms) { let cu = m.shape.Outline.Curve; if (cu instanceof Circle && cu.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) continue; if (userConfig.chaidanOption.useDefaultRad) m.knifeRadius = userConfig.chaidanOption.radius; let paths = tool.GetModelFeedPath(br, m); if (!isSide) paths.forEach(path => path.ApplyMatrix(tMtx)); let feeding = paths.map((c) => ConverToPolylineAndSplitArc(c, false)); if (feeding.length > 0) data.push({ feeding, thickness: m.thickness + ((_a = m.addDepth) !== null && _a !== void 0 ? _a : 0), dir: m.dir, knifeRadius: m.knifeRadius, origin: { outline: ConverToPolylineAndSplitArc(cu.Clone(), false, false), holes: m.shape.Holes.map(h => ConverToPolylineAndSplitArc(h.Curve.Clone(), false, false)), addLen: m.addLen, addWidth: m.addWidth, addDepth: m.addDepth, } }); else { Toaster({ message: "板件有造型或者自定义排钻无法加工,请运行造型检测命令确认", timeout: 5000, intent: Intent.DANGER, key: "造型加工错误" }); } } return data; }; let allModeling = GetModelingFromCustomDrill(br); let modeling = getModelings([...br.BoardModeling, ...allModeling.modeling], false).filter(f => f.feeding.length > 0); let sideModeling = getModelings(allModeling.sideModeling, true).filter(f => f.feeding.length > 0); return { modeling, sideModeling }; } Production.GetBoardModelingData = GetBoardModelingData; /**获取板件的轮廓 *有拆单尺寸返回矩形 *用于拆单的轮廓统一逆时针 */ function GetSpliteOutline(br, isSplite) { let con = GetSpliteOutlineBySpliteSize(br); if (con) return con; con = br.ContourCurve; if (con instanceof Circle) return con.Clone(); //避免共线导致的侧面数据对应错误 let cus = con.Explode(); MergeCurvelist(cus); let pl = Polyline.FastCombine(cus, LINK_FUZZ); if (pl && isSplite && pl.Area2 < 0) pl.Reverse(); return pl; } Production.GetSpliteOutline = GetSpliteOutline; //获得拆单尺寸 function GetSpiteSize(br) { let param = { L: br.Height, W: br.Width, H: br.Thickness }; let spliteHeight = eval2(br.BoardProcessOption.spliteHeight, param, "L"); let spliteWidth = eval2(br.BoardProcessOption.spliteWidth, param, "W"); let spliteThickness = eval2(br.BoardProcessOption.spliteThickness, param, "H"); if (spliteHeight && spliteWidth && spliteThickness) return { spliteHeight, spliteWidth, spliteThickness }; else return; } Production.GetSpiteSize = GetSpiteSize; //获得拆单轮廓(如果没有,那么将返回空,如果有,返回多段线) function GetSpliteOutlineBySpliteSize(br) { let size = GetSpiteSize(br); if (size) return new Polyline().RectangleFrom2Pt(new Vector3, new Vector3(size.spliteWidth, size.spliteHeight)); return null; } Production.GetSpliteOutlineBySpliteSize = GetSpliteOutlineBySpliteSize; /**孔信息,侧孔的z 均为 从上到下距离 */ function GetBoardHolesData(br, offsetTanslation, sealedContour) { let data = { frontBackHoles: [], sideHoles: [] }; let brNormal = br.Normal; // 性能优化的解析板件网洞类 // new ParseBoardHoleData(br, offsetTanslation, sealedContour); for (let [, driss] of br.DrillList) { for (let dris of driss) { for (let dId of dris) { if (!dId || dId.IsErase) continue; let d = dId.Object; if (d instanceof ExtrudeHole) ParseExtrudeHoles(d, br, offsetTanslation, data, sealedContour); else ParseCylHoles(d, br, offsetTanslation, data, sealedContour); } } } if (br.RelativeHardware) { for (let dId of br.RelativeHardware) { if (dId.IsErase) continue; let d = dId.Object; let holes = []; if (d instanceof HardwareCompositeEntity) { holes.push(...GetMetalTotalEntitys(d, true, (e) => e instanceof Hole)); } for (let h of holes) { if (h instanceof ExtrudeHole) ParseExtrudeHoles(h, br, offsetTanslation, data, sealedContour, true); else ParseCylHoles(h, br, offsetTanslation, data, sealedContour); } } } let modelings = br.BoardModeling; for (let nid of br.LayerNails) { if (!nid || !nid.Object || nid.IsErase) continue; let nail = nid.Object; if (!isParallelTo(nail.Normal, brNormal)) continue; let sp = nail.Position.applyMatrix4(br.OCSInv); let nor = nail.Normal.multiplyScalar(nail.Height); let ep = nail.Position.add(nor).applyMatrix4(br.OCSInv); let [z0, z1] = sp.z < ep.z ? [sp.z, ep.z] : [ep.z, sp.z]; if (Math.max(z0, 0) < Math.min(z1, br.Thickness) - 1e-6 && br.ContourCurve.PtInCurve(sp.setZ(0)) && modelings.every(m => !m.shape.Outline.Curve.PtInCurve(sp))) { let face = !equalv3(nail.Normal, brNormal, CanDrawHoleFuzz) ? FaceDirection.Front : FaceDirection.Back; let depth = Math.min(z1, br.Thickness) - Math.max(z0, 0); data.frontBackHoles.push({ type: nail.Type, position: sp.sub(offsetTanslation), radius: nail.Radius, depth, face, }); } } for (let m of modelings) { let cu = m.shape.Outline.Curve; if (cu instanceof Circle && cu.Radius < userConfig.chaidanOption.modeling2HoleRad + 1e-6) { let center = cu.Center.setZ(0).sub(offsetTanslation); data.frontBackHoles.push({ type: GangDrillType.Pxl, position: center, radius: cu.Radius, depth: m.thickness, face: m.dir }); } } return data; } Production.GetBoardHolesData = GetBoardHolesData; /**拆单那边需要把侧孔 z 坐标转换为从上到下 */ function InvertPosition(pos, thickness) { pos.z = thickness - pos.z; } function HoleInBoard(center, radius, outline, isYMJ = false) { let cir = new Circle(center, radius - SCALAR); if (isYMJ) { return outline.IntersectWith(cir, 0).length === 0 && outline.PtInCurve(center); } else { if (userConfig.forceFilterPxl) return outline.IntersectWith(cir, 0).length === 0 && outline.PtInCurve(center); else return outline.IntersectWith(cir, 0).length > 1 || outline.PtInCurve(center); } } /**分析常规排钻 */ function ParseCylHoles(d, br, offsetTanslation, data, outline) { let processData = br.BoardProcessOption; let brNormal = br.Normal; let roMat = new Matrix4().extractRotation(br.OCSInv); let position = d.Position.applyMatrix4(br.OCSInv); let holes = data.frontBackHoles; let face; let isPush = false; let endPt; let depth = d.Height; let diffMat = br.OCSInv.multiply(d.OCS); let x = new Vector3().setFromMatrixColumn(diffMat, 0); let angle = angleTo(XAxis, x); let nor = d.Normal.applyMatrix4(roMat); let pos2 = position.clone().add(nor.multiplyScalar(depth)); if (d.Type === GangDrillType.Pxl || d.Type === GangDrillType.WoodPXL) { if (isParallelTo(d.Normal, brNormal, CanDrawHoleFuzz)) { if (!IsBetweenA2B(position.x, -d.Radius, br.Width + d.Radius, 1e-6) || !IsBetweenA2B(position.y, -d.Radius, br.Height + d.Radius, 1e-6) || !isIntersect2(0, br.Thickness, position.z, pos2.z, -CanDrawHoleFuzz) || !HoleInBoard(position.clone().setZ(0), d.Radius, outline)) return; position.sub(offsetTanslation); face = processData[EBoardKeyList.BigHole]; isPush = true; } } else if (d.Type === GangDrillType.Ljg || d.Type === GangDrillType.Wood) { if (!isParallelTo(d.Normal, brNormal, CanDrawHoleFuzz)) { let z = position.z; if (!IsBetweenA2B(z, -d.Radius, br.Thickness + d.Radius, 1e-6)) return; let sp = position.clone().setZ(0).add(d.Normal.multiplyScalar(-CanDrawHoleFuzz).applyMatrix4(roMat)); //加长线(以便加大容差) let ep = position.clone().setZ(0).add(d.Normal.multiplyScalar(d.Height + CanDrawHoleFuzz).applyMatrix4(roMat)); //加长线 let line = new Line(sp, ep); let pt = outline.IntersectWith(line, 0)[0]; if (!pt) { console.warn("侧孔与板无交点,无法加工该侧孔!"); return; } position = pt.clone().setZ(z); for (let p of [line.StartPoint, line.EndPoint]) { if (outline.PtInCurve(p)) { endPt = p.setZ(z); break; } } if (!endPt) { console.warn("排钻位置有问题"); return; } holes = data.sideHoles; face = Math.floor(outline.GetParamAtPoint(pt)); isPush = true; depth = position.distanceTo(endPt); angle = undefined; InvertPosition(position, br.Thickness); InvertPosition(endPt, br.Thickness); } else if (d.Type === GangDrillType.Wood) { if (!outline.PtInCurve(position.clone().setZ(0))) return; face = position.z > 0 ? FaceDirection.Front : FaceDirection.Back; holes = data.frontBackHoles; if (position.z > 0) { let z1 = position.z - d.Height; if (z1 > 0 && z1 < br.Thickness) { depth = br.Thickness - z1; isPush = true; } } else { let z1 = position.z + d.Height; if (z1 > 0 && z1 < br.Thickness) { depth = z1; isPush = true; } } position.sub(offsetTanslation); } } else { if (isParallelTo(d.Normal, brNormal, CanDrawHoleFuzz)) { if (!IsBetweenA2B(position.x, -d.Radius, br.Width + d.Radius, CanDrawHoleFuzz) || !IsBetweenA2B(position.y, -d.Radius, br.Height + d.Radius, CanDrawHoleFuzz) || !isIntersect2(0, br.Thickness, position.z, pos2.z, -CanDrawHoleFuzz) || !HoleInBoard(position.clone().setZ(0), d.Radius, outline, true)) return; position.sub(offsetTanslation); holes = data.frontBackHoles; face = !equalv3(d.Normal, brNormal, CanDrawHoleFuzz) ? FaceDirection.Front : FaceDirection.Back; isPush = true; } } isPush && holes.push({ type: d.Type, position, radius: d.Radius, depth, face, endPt, angle }); } /**分析自定义圆柱排钻 */ function ParseExtrudeHoles(d, br, offsetTanslation, data, outline, isCheckGroove = false) { if (!d.isHole) return; let brNormal = br.Normal; let cir = d.ContourCurve; if (cir instanceof Circle) { let diffMtx = br.OCSInv.multiply(d.OCS); let nor = d.Normal; let sp = cir.Center.applyMatrix4(diffMtx); let ep = cir.Center.add(new Vector3(0, 0, d.Height)).applyMatrix4(diffMtx); let x = new Vector3().setFromMatrixColumn(diffMtx, 0); //#I2DPFO 在挖穿造型内的五金不加工 const grooves = br.Grooves.filter(g => equaln(g.Thickness, br.Thickness)); const groovesOutlines = isCheckGroove ? grooves.map(g => g.ContourCurve.Clone().ApplyMatrix(g.OCS).ApplyMatrix(br.OCSInv).Z0()) : []; if (isParallelTo(nor, brNormal, CanDrawHoleFuzz)) { let z0 = Math.min(sp.z, ep.z); let z1 = Math.max(sp.z, ep.z); let p = sp.clone().setZ(0).sub(offsetTanslation); if (Math.max(z0, 0) < Math.min(z1, br.Thickness) - CanDrawHoleFuzz && outline.PtInCurve(p) && groovesOutlines.every(g => !g.PtInCurve(p))) { let depth = z0 < CanDrawHoleFuzz ? z1 : br.Thickness - z0; let angle = angleTo(XAxis, x); if (equaln(angle, Math.PI)) angle = 0; if (depth > CanDrawHoleFuzz) data.frontBackHoles.push({ type: d.isThrough ? GangDrillType.TK : GangDrillType.Ymj, position: z0 < CanDrawHoleFuzz ? p : p.setZ(br.Thickness), radius: cir.Radius, depth, face: z0 < CanDrawHoleFuzz ? FaceDirection.Back : FaceDirection.Front, angle: angle, }); } } else { let oldZ = sp.z; let [minX, maxX] = sp.x < ep.x ? [sp.x, ep.x] : [ep.x, sp.x]; let [minY, maxY] = sp.y < ep.y ? [sp.y, ep.y] : [ep.y, sp.y]; if (sp.z > -cir.Radius && sp.z < br.Thickness + cir.Radius && Math.max(minX, 0) < Math.min(br.Width, maxX) + 1e-6 && Math.max(minY, 0) < Math.min(br.Height, maxY) + 1e-6) { sp.setZ(0); ep.setZ(0); let line = new Line(sp, ep); let pt = outline.IntersectWith(line, 0)[0]; if (!pt) { console.error("排钻嵌在板件内部"); return; } let position = pt.clone().setZ(oldZ); let endPt; let face = Math.floor(outline.GetParamAtPoint(pt)); for (let p of [line.StartPoint, line.EndPoint]) { if (!equalv3(p.setZ(oldZ), position) && outline.PtInCurve(p)) { endPt = p.setZ(oldZ); break; } } if (!endPt) return; let depth = position.distanceTo(endPt); if (equaln(depth, 0, CanDrawHoleFuzz)) return; InvertPosition(position, br.Thickness); InvertPosition(endPt, br.Thickness); data.sideHoles.push({ type: GangDrillType.Ljg, endPt, position, radius: cir.Radius, depth, face, }); } } } } function GetBoardMetals(br) { let mids = br.RelativeHardware; let metalsData = { metals: 0, comp: 0 }; for (let id of mids) { if (!id || id.IsErase) continue; let metals = id.Object; if (!metals.HardwareOption) continue; if (metals.HardwareOption.type === EMetalsType.Metals) { metalsData.metals++; } else { metalsData.comp++; } } return metalsData; } function GetHardwareCompositeData(en) { let size = en.BoundingBoxInOCS.getSize(new Vector3); let data = { ...en.HardwareOption }; const actualVal = safeEval(data.actualExpr, { L: size.x, W: size.y, H: size.z }); data.actualExpr = actualVal ? actualVal.toString() : data.actualExpr; data.spec = ParseExpr(data.spec, { L: size.x, W: size.y, H: size.z }); data.count = (safeEval(data.count, { L: size.x, W: size.y, H: size.z }) || 0).toString(); let metalData = { metalsOption: data, dataList: en.DataList, children: [], size: size }; if (en instanceof HardwareCompositeEntity && (en.HardwareOption.isSplite || en.HardwareOption.isSplitePrice)) { if (en.Entitys.every(e => !(e instanceof HardwareCompositeEntity || e instanceof HardwareTopline))) return metalData; for (let e of en.Entitys) { if (e instanceof HardwareCompositeEntity) { let d = GetHardwareCompositeData(e); metalData.children.push(d); } else if (e instanceof HardwareTopline) { metalData.children.push(...GetHardwareToplineData(e)); } } } return metalData; } Production.GetHardwareCompositeData = GetHardwareCompositeData; function GetHardwareToplineData(en) { let data = { ...en.HardwareOption }; let datas = []; let map = new Map(); let addLen = en.HardwareOption.addLen; let cus = en.Segmentations; let size = en.BoundingBoxInOCS.getSize(new Vector3); for (let cu of cus) { let len = parseFloat(FixedNotZero(cu.Length, 2)); if (map.has(len)) { map.set(len, map.get(len) + 1); } else { map.set(len, 1); } } for (let [len, count] of map) { let totalLength = parseFloat(FixedNotZero(len + parseFloat(addLen), 2)); let width = parseFloat(FixedNotZero(size.y, 2)); let height = parseFloat(FixedNotZero(size.z, 2)); for (let i = 0; i < count; i++) { let d = { ...en.HardwareOption }; const actualVal = safeEval(data.actualExpr, { L: totalLength, W: width, H: height }); d.actualExpr = actualVal ? actualVal.toString() : d.actualExpr; d.spec = ParseExpr(data.spec, { L: totalLength, W: width, H: height }); datas.push({ metalsOption: d, dataList: en.DataList, length: totalLength, children: [], size }); } } return datas; } Production.GetHardwareToplineData = GetHardwareToplineData; /**获取排钻数量 */ function GetTotalDrillCount(brs) { return lookOverBoardInfosTool.GetCount(brs); } Production.GetTotalDrillCount = GetTotalDrillCount; function GetCabSize(brList) { let brMap = new Map(); //根据柜名房名分类 for (let b of brList) { let k = b.BoardProcessOption[EBoardKeyList.RoomName] + '-' + b.BoardProcessOption[EBoardKeyList.CabinetName]; if (brMap.has(k)) brMap.get(k).push(b); else brMap.set(k, [b]); } let sizeData = new Map(); for (let [k, brs] of brMap) { let ocsInv = brs[0].SpaceOCSInv; let box = new Box3(); let size = new Vector3(); for (let b of brs) { sizeData.set(b, size); box.union(b.GetBoundingBoxInMtx(ocsInv)); } box.getSize(size); } return sizeData; } Production.GetCabSize = GetCabSize; function Data2Polyline(data, isClose = true) { let pl = new Polyline(data.pts.map((p, i) => ({ pt: new Vector2(p.x, p.y), bul: data.buls[i] }))); if (isClose) pl.CloseMark = true; return pl; } Production.Data2Polyline = Data2Polyline; function Report(ens, msg) { if (IsDev()) return; ShowSelectObjects(ens); SendReport(msg); } Production.Report = Report; function Get2DModeing(br, offset) { let res = []; let tmtx = MoveMatrix(offset.clone().negate()); for (let m of br.Modeling2D) { let path = m.path.Clone().ApplyMatrix(tmtx); res.push({ path: ConverToPolylineAndSplitArc(path), dir: m.dir, items: m.items.map(item => ({ ...item })) }); } return res; } Production.Get2DModeing = Get2DModeing; function Get3DModeing(br, offset) { let res = []; for (let m of br.Modeling3D) { let d = { path: { pts: [], buls: [] }, knife: { ...m.knife }, dir: m.dir }; for (let i = 0; i < m.path.length - 1; i++) { let d1 = m.path[i]; let d2 = m.path[i + 1]; if (equaln(d1.bul, 0)) { let p = d1.pt.clone(); InvertPosition(p, br.Thickness); p.sub(offset); d.path.pts.push(p); d.path.buls.push(0); } else { let arc = new Arc().ParseFromBul(d1.pt.clone().sub(offset), d2.pt.clone().sub(offset), d1.bul); let r = SplitetArc(arc, false); r.pts.forEach(p => InvertPosition(p, br.Thickness)); d.path.pts.push(...r.pts); d.path.buls.push(...r.buls); } if (i === m.path.length - 2) { let p = d2.pt.clone(); InvertPosition(p, br.Thickness); p.sub(offset); d.path.pts.push(p); d.path.buls.push(0); } } res.push(d); } return res; } Production.Get3DModeing = Get3DModeing; function GetChaiDanFeedingPath(data) { const { thickness, boardContour, dir, addLen, addWidth, addDepth, knifeRadius, brThickness } = data; let brContour = Data2Polyline(boardContour); const tool = FeedingToolPath.GetInstance(); const outline = Contour.CreateContour(Data2Polyline(data.outline)); const holes = data.holes.map(h => Contour.CreateContour(Data2Polyline(h))); let shape = new Shape(outline, holes); const paths = tool.GetModelFeedPath({ Thickness: brThickness, ContourCurve: brContour }, { shape, thickness, dir, knifeRadius, addLen, addWidth, addDepth }); return paths.map((c) => ConverToPolylineAndSplitArc(c, false)); } Production.GetChaiDanFeedingPath = GetChaiDanFeedingPath; })(Production || (Production = {})); /** *曲线列表分段 * @l-arc-l,l-arc-arc-l,l-arc-l-arc-l.... */ function ParagraphCulist(cus) { let newCulist = []; let usedCu = new WeakSet(); //归类曲线,返回归类是否成功 const paragraph = (cu, originCu, cuList, isBack) => { const cuIsLine = cu instanceof Line; const originCuIsLine = originCu instanceof Line; if (usedCu.has(cu)) return false; if (originCuIsLine !== cuIsLine) { if (originCuIsLine) { if (isBack) { if (!isParallelTo(originCu.GetFistDeriv(0), cu.GetFistDeriv(0))) return false; } else { if (!isParallelTo(originCu.GetFistDeriv(0), cu.GetFistDeriv(1))) return false; } } if (cuIsLine) { if (isBack) { if (!isParallelTo(originCu.GetFistDeriv(1), cu.GetFistDeriv(0))) return false; } else { if (!isParallelTo(originCu.GetFistDeriv(0), cu.GetFistDeriv(0))) return false; } } } else if (cuIsLine) { //共线且相连的直线分为一组 #I11T1Z if (!isParallelTo(cu.GetFistDeriv(0).normalize(), originCu.GetFistDeriv(0).normalize())) return false; let pts = [originCu.StartPoint, originCu.EndPoint]; let pts2 = [cu.StartPoint, cu.EndPoint]; if (pts.every(p => pts2.every(p2 => !equalv3(p, p2, 1e-6)))) return false; } if (isBack) cuList.push(cu); else cuList.unshift(cu); usedCu.add(cu); return true; }; let caclCus = cus.slice().filter(c => !equaln(c.Length, 0)); while (caclCus.length > 0) { let originCu = caclCus.shift(); if (usedCu.has(originCu)) continue; let originCus = [originCu]; usedCu.add(originCu); //往后搜索 for (let i = 0; i < caclCus.length; i++) { if (!paragraph(caclCus[i], originCu, originCus, true)) break; originCu = caclCus[i]; } //只有第一条才需要往前搜索 if (caclCus.length === cus.length - 1) { originCu = originCus[0]; //往前搜索 for (let i = caclCus.length - 1; i >= 0; i--) { if (!paragraph(caclCus[i], originCu, originCus, false)) break; originCu = caclCus[i]; } } newCulist.push(originCus); } cus.length = 0; //同组多条曲线连接为多段线 for (let g of newCulist) { if (g.length === 1) cus.push(g[0]); else { let pl = new Polyline(); for (let c of g) { pl.Join(c); } cus.push(pl); } } } /** *计算封边 */ function CalcEdgeSealing(cus) { if (cus.length <= 1) return; let oldLine; let firstLine = cus[0].Clone(); let oldLen = cus.length; for (let i = 0; i < cus.length; i++) { let frontLine = cus[i]; let laterIndex = FixIndex$1(i + 1, cus); let laterLine = cus[laterIndex]; if (!frontLine || !laterLine || cus.length < 2) { return false; } let dist = frontLine.EndPoint.distanceToSquared(laterLine.StartPoint); if (dist < LINK_FUZZ ** 2) { if (frontLine instanceof Line && laterLine instanceof Line) { if (frontLine.PtOnCurve(laterLine.EndPoint)) { cus.splice(laterIndex, 1); if (laterIndex === 0) firstLine = cus[0].Clone(); i -= 2; } else if (laterLine.PtOnCurve(frontLine.StartPoint)) { cus.splice(i, 1); i -= 2; if (i < -1) i = -1; } } continue; } let refLine = oldLine !== null && oldLine !== void 0 ? oldLine : frontLine; let refLine2 = i === cus.length - 1 ? firstLine : laterLine; let iPts = refLine.IntersectWith(refLine2, IntersectOption.ExtendBoth); let tPts = iPts.filter(p => refLine.PtOnCurve(p) && refLine2.PtOnCurve(p)); let iPt = SelectNearP(tPts.length > 0 ? tPts : iPts, refLine.EndPoint); if (!iPt) { //没交点,如果删过线,则尝试继续连接 if (cus.length !== oldLen && cus.length > 2) { cus.splice(i, 1); i -= 2; if (i < -1) i = -1; continue; } else return false; } let par = refLine.GetParamAtPoint(iPt); //前面线的点无效直接删除 if (par < 1e-6) { cus.splice(i, 1); i -= 2; if (i < -1) i = -1; } else frontLine.EndPoint = iPt; oldLine = null; par = laterLine.GetParamAtPoint(iPt); if (cus.length < 2) return false; //后面线点无效,如果是起始线,则删除,否则缓存原始线,继续尝试连接 if (par > 1 - 1e-6) { if (laterIndex === 0) { cus.shift(); firstLine = cus[0].Clone(); i -= 2; continue; } else oldLine = laterLine.Clone(); } laterLine.StartPoint = iPt; } return true; } function GetBoardHighSeal(br, sealcus) { let highSeals = br.BoardProcessOption.highSealed.slice().filter(d => d.size !== null && d.size !== undefined); let sealDown = parseFloat(br.BoardProcessOption[EBoardKeyList.DownSealed]); let sealUp = parseFloat(br.BoardProcessOption[EBoardKeyList.UpSealed]); let sealLeft = parseFloat(br.BoardProcessOption[EBoardKeyList.LeftSealed]); let sealRight = parseFloat(br.BoardProcessOption[EBoardKeyList.RightSealed]); //若未设置高级封边,把上下左右封边存入高级封边 if (sealcus.length !== highSeals.length || !br.IsSpecialShape) { highSeals.length = 0; let dir = Math.sign(br.ContourCurve.Area2); for (let c of sealcus) { let derv = c.GetFistDeriv(0).multiplyScalar(dir); let an = angle(derv); if (equaln(an, 0) || (an < Math.PI / 4 + 1e-8 && an > Math.PI * 7 / 4)) highSeals.push({ size: sealDown }); else if (an > Math.PI / 4 && an < Math.PI * 3 / 4 + 1e-8) highSeals.push({ size: sealRight }); else if (an > Math.PI * 3 / 4 && an < Math.PI * 5 / 4 + 1e-8) highSeals.push({ size: sealUp }); else highSeals.push({ size: sealLeft }); } } return highSeals; } /**偏移前后曲线起点没改变 */ function OffsetOutlineSpNotChange(oldcu, newCu) { if (!newCu) return false; let sDerv = oldcu.GetFistDeriv(0).normalize(); let eDerv = oldcu.GetFistDeriv(oldcu.EndParam).normalize().negate(); sDerv.add(eDerv).normalize(); let mDerv = newCu.StartPoint.sub(oldcu.StartPoint).normalize(); return oldcu.EndParam === (newCu === null || newCu === void 0 ? void 0 : newCu.EndParam) && isParallelTo(mDerv, sDerv); } /** * 获取板件封边轮廓线段数组 * 消除共线的数据,不改变轮廓方向 * isOffset-是否偏移轮廓用于查看 * */ function GetBoardSealingCurves(br, isOffset = false) { let cu = Production.GetSpliteOutlineBySpliteSize(br); if (cu) return cu.Explode(); let cus = []; cu = Production.GetSpliteOutline(br, false); if (!cu) { Toaster({ message: "获取封边错误", timeout: 3000, intent: Intent.DANGER }); return []; } if (isOffset) { let dir = Math.sign(cu.Area2); let newCu = cu.GetOffsetCurves(-1 * dir)[0]; if (OffsetOutlineSpNotChange(cu, newCu)) cu = newCu; } if (cu instanceof Circle) return [cu.Clone()]; else { cus = cu.Explode(); if (br.IsSpecialShape) ParagraphCulist(cus); return cus; } } /** * 获取板件轮廓 * 结果轮廓拆单用,统一逆时针数据 * hasSealing 轮廓是否包含封边 * 用户计算拆单侧孔面id * TODO:如果封边一致,那么应该直接偏移!!! */ function GetSealedBoardContour(br, hasSealing) { var _a; let area2 = br.ContourCurve.Area2; if (Math.abs(area2) < 10) return; let offsetCus = []; let cus = GetBoardSealingCurves(br); let dir = Math.sign(area2); if (hasSealing) { for (let c of cus) { if (c instanceof Polyline) offsetCus.push(...c.Explode()); else offsetCus.push(c); } } else { let highSeals = GetBoardHighSeal(br, cus); if (cus[0] instanceof Circle) dir = 1; //所有的封边都一样时 if (highSeals.every(s => equaln(s.size, highSeals[0].size), 1e-3)) { let brContour = br.ContourCurve; let retPl; if (!highSeals[0].size || equaln(highSeals[0].size, 0, 1e-3)) { retPl = brContour.Clone(); if (dir < 0) retPl.Reverse(); } else { retPl = brContour.GetOffsetCurves(-highSeals[0].size * dir)[0]; if (retPl && !retPl.IsClose) { //某些情况下,这里会出现非闭合轮廓 retPl = (_a = CreateContour2([retPl])) === null || _a === void 0 ? void 0 : _a.Curve; } if (retPl && retPl.Area2 < 0) retPl.Reverse(); } if (retPl) return retPl; } for (let i = 0; i < cus.length; i++) { let cs; if (!highSeals[i].size) cs = [cus[i].Clone()]; else cs = cus[i].GetOffsetCurves(-highSeals[i].size * dir); for (let c of cs) { if (c instanceof Polyline) offsetCus.push(...c.Explode()); else offsetCus.push(c); } } } if (offsetCus.length === 1 && offsetCus[0] instanceof Circle) return offsetCus[0]; if (!CalcEdgeSealing(offsetCus)) return; let pl = Polyline.FastCombine(offsetCus, LINK_FUZZ); if (pl && dir < 0) pl.Reverse(); return pl; } /**处理常规板件封边数据和上下左右封边值 */ function HandleRectBoardSealingData(br, edges, cus) { let dir = Math.sign(br.ContourCurve.Area2); if (!cus) cus = br.ContourCurve.Explode(); let spliteHeight = safeEval(br.BoardProcessOption.spliteHeight); let spliteWidth = safeEval(br.BoardProcessOption.spliteWidth); let spliteThickness = safeEval(br.BoardProcessOption.spliteThickness); if ((spliteHeight && spliteWidth && spliteThickness) || !br.IsSpecialShape && cus.length == 4) { for (let i = 0; i < 4; i++) { let derv = cus[i].GetFistDeriv(0); if (isParallelTo(derv, XAxis)) { if (derv.x * dir > 0) br.BoardProcessOption[EBoardKeyList.DownSealed] = edges[i].size.toString(); else br.BoardProcessOption[EBoardKeyList.UpSealed] = edges[i].size.toString(); } else { if (derv.y * dir > 0) br.BoardProcessOption[EBoardKeyList.RightSealed] = edges[i].size.toString(); else br.BoardProcessOption[EBoardKeyList.LeftSealed] = edges[i].size.toString(); } } } } /** * V型刀走刀数据,第一刀直接扎进去,最后一刀提刀 * @param polyline * @param feedingDepth 走刀深度 * @param knifAngle 通常为60度.按弧度表示 */ function VKnifToolPath(polyline, feedingDepth, knifAngle) { let x = Math.abs(feedingDepth * Math.tan(knifAngle)); let cus = polyline.Explode(); arrayRemoveIf(cus, c => c.Length < 0.01); let ptsbul = []; let isClose = polyline.IsClose; for (let i = 0; i < cus.length; i++) { let nextIndex = FixIndex$1(i + 1, cus.length); let c1 = cus[i]; let c2 = cus[nextIndex]; let d = { pt: c1.StartPoint, bul: 0 }; let curP = c1.EndPoint; if (c1 instanceof Arc) { d.bul = c1.Bul; c1 = new Line(curP.clone().sub(c1.GetFistDeriv(1).multiplyScalar(100)), curP.clone()); } if (c2 instanceof Arc) c2 = new Line(curP.clone(), curP.clone().add(c2.GetFistDeriv(0).multiplyScalar(100))); ptsbul.push(d); if (!isClose && i === cus.length - 1) //最后一条 { ptsbul.push({ pt: c1.EndPoint, bul: 0 }); break; } //圆弧与直线相切,此时不要提刀 if (isParallelTo(c1.GetFistDeriv(0), c2.GetFistDeriv(0))) continue; //计算提刀部分: //向量与平分线,参照倒角代码 let derv1 = c1.GetFistDeriv(0).normalize(); let derv2 = c2.GetFistDeriv(0).normalize(); let bisectorVec = derv1.clone().negate().add(derv2).multiplyScalar(0.5); let co1 = c1.GetOffsetCurves(x * Math.sign(derv1.cross(bisectorVec).z))[0]; let co2 = c2.GetOffsetCurves(x * Math.sign(derv2.cross(bisectorVec).z))[0]; if (!co1 || !co2) continue; let ipts = co1.IntersectWith(co2, IntersectOption.ExtendBoth); if (ipts.length === 0) continue; if (co1.PtOnCurve(ipts[0])) continue; //抬刀路径 ptsbul.push({ pt: curP, bul: 0 }); ptsbul.push({ pt: ipts[0].setZ(feedingDepth), bul: 0 }); } if (isClose) { //第一刀 ptsbul.unshift(ptsbul[ptsbul.length - 1]); //, ptsbul[ptsbul.length - 2] } return ptsbul; } function VData2Curve(data) { let curves = []; for (let i = 0; i < data.length - 1; i++) { let p1 = new Vector3(data[i].pt.x, data[i].pt.y, data[i].pt.z); let p2 = new Vector3(data[i + 1].pt.x, data[i + 1].pt.y, data[i + 1].pt.z); if (equaln(data[i].bul, 0)) { curves.push(new Line(p1, p2)); } else { curves.push(new Arc().ParseFromBul(p1, p2, data[i].bul)); } } return curves; } //使用lineseg来生成拉伸的边框,避免生成过多的实体导致的drawcall次数增多 function FastWireframe(br, color = 0, divCount = 6, optArc = true) { color = color || br.ColorIndex; let material = ColorMaterial.GetLineMaterial(color); let thickness = br.Thickness; let cu = br.ContourCurve; let pts = cu.Shape.getPoints(divCount, optArc); let geo = new BufferGeometry(); let coords = []; let edgeCoords = []; for (let p of pts) { coords.push(p.x, p.y, 0); if (p["_mask_"]) edgeCoords.push(p.x, p.y, 0, p.x, p.y, thickness); } for (let p of pts) coords.push(p.x, p.y, thickness); let edgeGeo = new BufferGeometry(); edgeGeo.setAttribute('position', new Float32BufferAttribute(edgeCoords, 3)); geo.setAttribute('position', new Float32BufferAttribute(coords, 3)); let line = new Line$1(geo, material); line.applyMatrix4(cu.OCSNoClone); let edge = new LineSegments(edgeGeo, material); edge.applyMatrix4(cu.OCSNoClone); let result = [line, edge]; let ocsInv = br.OCSInv; if (br.Grooves.length < 100) for (let g of br.Grooves) { let m = ocsInv.clone().multiply(g.OCSNoClone); let lines = FastWireframe(g, color, 3, false); for (let l of lines) { l.applyMatrix4(m); result.push(l); } } return result; } let tempP = new Vector3; function FastExtrudeEdgeGeometry(ext, color = 0, divCount = 6, optArc = true, coords = [], inv = undefined) { color = color || ext.ColorIndex; let thickness = ext.Thickness; let cu = ext.ContourCurve; let pts = cu.Shape.getPoints(divCount, optArc); let startIndex = coords.length / 3; for (let i = 0; i < pts.length; i++) { let p = pts[i]; let nextp = pts[FixIndex$1(i + 1, pts)]; //底面 coords.push(p.x, p.y, 0, nextp.x, nextp.y, 0); //顶面 coords.push(p.x, p.y, thickness, nextp.x, nextp.y, thickness); if (p["_mask_"]) //侧面 coords.push(p.x, p.y, 0, p.x, p.y, thickness); } let m = inv ? (ext.Grooves.length ? inv.clone() : inv).multiply(cu.OCSNoClone) : cu.OCSNoClone; if (!MatrixIsIdentityCS(m)) { let count = coords.length / 3; for (let i = startIndex; i < count; i++) { let a = i * 3; let b = a + 1; let c = a + 2; tempP.set(coords[a], coords[b], coords[c]); tempP.applyMatrix4(m); coords[a] = tempP.x; coords[b] = tempP.y; coords[c] = tempP.z; } } let ocsInv = inv ? (inv.multiply(ext.OCSInv)) : ext.OCSInv; optArc = ext.Grooves.length < 100; for (let g of ext.Grooves) { FastExtrudeEdgeGeometry(g, color, divCount, optArc, coords, ocsInv.clone().multiply(g.OCSNoClone)); } return coords; } function FastWireframe2(dr, color = 0) { color = color || dr.ColorIndex; let material = ColorMaterial.GetLineMaterial(color); let height = dr.Height; let cu = dr.ContourCurve; let pts = cu.Shape.getPoints(6); let geo = new BufferGeometry(); let coords = []; let edgeCoords = []; for (let p of pts) { coords.push(p.x, p.y, 0); if (p["_mask_"]) edgeCoords.push(p.x, p.y, 0, p.x, p.y, height); } for (let p of pts) coords.push(p.x, p.y, height); let edgeGeo = new BufferGeometry(); edgeGeo.setAttribute('position', new Float32BufferAttribute(edgeCoords, 3)); geo.setAttribute('position', new Float32BufferAttribute(coords, 3)); let line = new Line$1(geo, material); line.applyMatrix4(cu.OCS); let edge = new LineSegments(edgeGeo, material); edge.applyMatrix4(cu.OCS); let result = [line, edge]; return result; } /** * 快速的对点表进行偏移 * @param contour * @param [offsetDist=1] * @param [ignoreSpike=true] 忽略尖角优化(如果不忽略,那么尖角将会变钝(有点奇怪)) * @returns 偏移后的点表 */ function FastOffset(contour, offsetDist = 1, ignoreSpike = true) { let res = []; for (let i = 0; i < contour.length; i++) { let v = AsVector3(getBevelVec(contour[i], contour[FixIndex$1(i - 1, contour)], contour[FixIndex$1(i + 1, contour)])); // TestDraw(new Line(contour[i], contour[i].clone().add(v)), 3); let p = v.multiplyScalar(offsetDist).add(contour[i]); res.push(p); } return res; } //Ref:threejs ExtrudeGeometry源码 function getBevelVec(inPt, inPrev, inNext, ignoreSpike = true) { // computes for inPt the corresponding point inPt' on a new contour // shifted by 1 unit (length of normalized vector) to the left // if we walk along contour clockwise, this new contour is outside the old one // // inPt' is the intersection of the two lines parallel to the two // adjacent edges of inPt at a distance of 1 unit on the left side. let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt // good reading for geometry algorithms (here: line-line intersection) // http://geomalgorithms.com/a05-_intersect-1.html const v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; const v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; const v_prev_lensq = (v_prev_x * v_prev_x + v_prev_y * v_prev_y); // check for collinear edges const collinear0 = (v_prev_x * v_next_y - v_prev_y * v_next_x); if (Math.abs(collinear0) > Number.EPSILON) { // not collinear // length of vectors for normalizing const v_prev_len = Math.sqrt(v_prev_lensq); const v_next_len = Math.sqrt(v_next_x * v_next_x + v_next_y * v_next_y); // shift adjacent points by unit vectors to the left const ptPrevShift_x = (inPrev.x - v_prev_y / v_prev_len); const ptPrevShift_y = (inPrev.y + v_prev_x / v_prev_len); const ptNextShift_x = (inNext.x - v_next_y / v_next_len); const ptNextShift_y = (inNext.y + v_next_x / v_next_len); // scaling factor for v_prev to intersection point const sf = ((ptNextShift_x - ptPrevShift_x) * v_next_y - (ptNextShift_y - ptPrevShift_y) * v_next_x) / (v_prev_x * v_next_y - v_prev_y * v_next_x); // vector from inPt to intersection point v_trans_x = (ptPrevShift_x + v_prev_x * sf - inPt.x); v_trans_y = (ptPrevShift_y + v_prev_y * sf - inPt.y); // Don't normalize!, otherwise sharp corners become ugly // but prevent crazy spikes const v_trans_lensq = (v_trans_x * v_trans_x + v_trans_y * v_trans_y); if (v_trans_lensq <= 2 || ignoreSpike) return new Vector2(v_trans_x, v_trans_y); else shrink_by = Math.sqrt(v_trans_lensq / 2); } else { // handle special case of collinear edges let direction_eq = false; // assumes: opposite if (v_prev_x > Number.EPSILON) { if (v_next_x > Number.EPSILON) direction_eq = true; } else { if (v_prev_x < -Number.EPSILON) { if (v_next_x < -Number.EPSILON) direction_eq = true; } else { if (Math.sign(v_prev_y) === Math.sign(v_next_y)) direction_eq = true; } } if (direction_eq || ignoreSpike) { // console.log("Warning: lines are a straight sequence"); v_trans_x = -v_prev_y; v_trans_y = v_prev_x; shrink_by = Math.sqrt(v_prev_lensq); } else { // console.log("Warning: lines are a straight spike"); v_trans_x = v_prev_x; v_trans_y = v_prev_y; shrink_by = Math.sqrt(v_prev_lensq / 2); } } return new Vector2(v_trans_x / shrink_by, v_trans_y / shrink_by); } var DepthType; (function (DepthType) { DepthType[DepthType["Front"] = 1] = "Front"; DepthType[DepthType["Back"] = 2] = "Back"; DepthType[DepthType["All"] = 3] = "All"; })(DepthType || (DepthType = {})); const ExtrudeBuildConfig = { bevel: false }; /** * 槽的几何数据,包括槽的墙面和槽的盖子 */ class Groove { constructor(contour, holes, depthType, depth, allDepth, box = contour.BoundingBox) { this.depthType = depthType; this.depth = depth; this.allDepth = allDepth; this.box = box; this.holeWalls = []; //槽的网洞的墙 this.contourWall = new ExtudeWall(contour.Curve, depthType, depth, allDepth, DirectionType.Inner); for (let h of holes) this.holeWalls.push(new ExtudeWall(h.Curve, depthType, depth, allDepth, DirectionType.Outer)); this.lid = new CurveTapeShape(contour, holes); } /** * @param groove this - groove * @param [eachOther=true] 相互裁剪 */ ClipTo(groove, eachOther = true) { //相同深度和面不用操作 if (groove.depthType === this.depthType && groove.depth === this.depth) return; if (!IntersectsBox(this.box, groove.box)) return; this.ClipLid(groove); groove.ClipLid(this); //一正一反,不交集 if (this.depthType + groove.depthType === 3 && this.depth + groove.depth < this.allDepth) return; this.contourWall.ClipTo(groove, true); for (let wall of this.holeWalls) wall.ClipTo(groove, true); if (eachOther) { groove.contourWall.ClipTo(this, false); for (let wall of groove.holeWalls) wall.ClipTo(this, false); } } ClipLid(groove) { if (this.depthType === DepthType.All) return; if (groove.depthType === DepthType.All) return; if (this.depthType === groove.depthType) { if (groove.depth > this.depth) this.lid.ClipTo(groove.lid, true); else this.lid.SplitTo(groove.lid); } else { if (this.depth + groove.depth >= this.allDepth) this.lid.ClipTo(groove.lid, true); else this.lid.SplitTo(groove.lid); } } Draw(verticesArray, uvArray, edgeBuild, rotateUv) { this.contourWall.Draw(verticesArray, uvArray, edgeBuild); for (let wall of this.holeWalls) wall.Draw(verticesArray, uvArray, edgeBuild); if (this.depthType === DepthType.All) return; let isFront = this.depthType === DepthType.Front; this.lid.Draw(verticesArray, uvArray, isFront, isFront ? this.allDepth - this.depth : this.depth, rotateUv, this.allDepth); } } function CreateTape(faceType, startParam, endParam, depth, allDepth) { if (faceType === DepthType.Front) return new Tape(startParam, endParam, allDepth - depth, allDepth); else return new Tape(startParam, endParam, 0, depth); } //朝向类型 var DirectionType; (function (DirectionType) { DirectionType[DirectionType["Outer"] = 0] = "Outer"; DirectionType[DirectionType["Inner"] = 1] = "Inner"; //内墙 })(DirectionType || (DirectionType = {})); //轮廓树节点,用于重新确认外墙和网洞的关系 class ContourTreeNode { constructor(contour, children = []) { this.contour = contour; this.children = children; } SetParent(node) { if (this.parent) throw "ContourTreeNode重复设置父对象"; this.parent = node; node.children.push(this); } get Depth() { let depth = 0; let parent = this.parent; while (parent) { depth++; parent = parent.parent; if (depth > 10) throw "ContourTreeNode嵌套超过10层"; } return depth; } get IsHole() { return this.Depth % 2 === 1; } Draw(verticesArray, uvArray, front, z, rotateUv, allDepth) { // TestDraw(this.contour.Curve, depth); let pts = this.contour.Curve.GetStretchPoints(); let isFace; let ptsChoking; if (ExtrudeBuildConfig.bevel) { //进行内缩,使得可以正常倒角 isFace = (z === 0 || z === 18); //是正反面 if (isFace) { ptsChoking = FastOffset(pts, 1, true); [pts, ptsChoking] = [ptsChoking, pts]; } } let vertices = [...pts]; let holes = this.children.map(h => { // TestDraw(h.contour.Curve, depth + 1); let pts = h.contour.Curve.GetStretchPoints(); vertices.push(...pts); return pts; }); let faces = ShapeUtils.triangulateShape(pts, holes); for (let f of faces) { if (front) { AddVertice(vertices[f[0]]); AddVertice(vertices[f[1]]); AddVertice(vertices[f[2]]); } else { AddVertice(vertices[f[0]]); AddVertice(vertices[f[2]]); AddVertice(vertices[f[1]]); } } function AddVertice(v, inz = z) { verticesArray.push(v.x, v.y, inz); if (rotateUv) uvArray.push(v.y * 1e-3, v.x * 1e-3); else uvArray.push(v.x * 1e-3, v.y * 1e-3); } for (let hole of this.children) { for (let h of hole.children) { h.Draw(verticesArray, uvArray, front, z, rotateUv, allDepth); //, depth + 2 } } if (!ExtrudeBuildConfig.bevel || !isFace) return; //如果不倒角 就不执行下面的代码 let z2 = front ? z - 1 : z + 1; //构建倒角边 for (let i = 0; i < pts.length; i++) { let p1 = pts[i]; let nextIndex = FixIndex$1(i + 1, pts); let p2 = pts[nextIndex]; let p3 = ptsChoking[i]; let p4 = ptsChoking[nextIndex]; if (front) { AddVertice(p3, z2); AddVertice(p4, z2); AddVertice(p1); AddVertice(p1); AddVertice(p4, z2); AddVertice(p2); } else { AddVertice(p3, z2); AddVertice(p1); AddVertice(p4, z2); AddVertice(p1); AddVertice(p2); AddVertice(p4, z2); } } } static ParseContourTree(contourNodes) { if (contourNodes.length < 2) return; let fb = new Flatbush(contourNodes.length); for (let node of contourNodes) { node.box = node.contour.BoundingBox; node.area = node.contour.Area; fb.add(node.box.min.x, node.box.min.y, node.box.max.x, node.box.max.y); } fb.finish(); for (let i = 0; i < contourNodes.length; i++) { const node1 = contourNodes[i]; let p = node1.contour.Curve.StartPoint; let ids = fb.search(node1.box.min.x, node1.box.min.y, node1.box.max.x, node1.box.max.y); ids.sort((i1, i2) => contourNodes[i1].area - contourNodes[i2].area); for (let id of ids) { if (id === i) continue; let node2 = contourNodes[id]; if (node2.parent === node1 || node2.area < node1.area) continue; //避免自己的儿子成为自己的父亲 if (node2.contour.Curve.PtInCurve(p)) { node1.SetParent(node2); break; } } } } } class EdgeGeometryBuild { constructor(allDepth) { this.allDepth = allDepth; this.lineVerticesArray = []; this.frontLines = []; this.backLines = []; } AddLidLine(p1, p2, depth) { if (depth === 0) { p1 = p1.clone().setZ(0); p2 = p2.clone().setZ(0); let line = new Line(p1, p2); this.backLines.push(line); } else if (depth === this.allDepth) { p1 = p1.clone().setZ(0); p2 = p2.clone().setZ(0); let line = new Line(p1, p2); this.frontLines.push(line); } } BuildLid(verticesArray, uvArray, rotateUv) { var _a; let arr = [this.backLines, this.frontLines]; for (let index = 0; index < 2; index++) { let lines = arr[index]; let parse = new RegionParse(lines, 2); let contourNodes = []; for (let routes of parse.RegionsOutline) { let cs = routes.map(r => r.curve); let c = (_a = Contour.CreateContour(cs, false)) !== null && _a !== void 0 ? _a : CreateContour2(cs); if (c) contourNodes.push(new ContourTreeNode(c)); else console.error("未能构建盖子"); } ContourTreeNode.ParseContourTree(contourNodes); for (let j = contourNodes.length; j--;) { let node = contourNodes[j]; if (node.parent) continue; node.Draw(verticesArray, uvArray, index === 1, this.allDepth * index, rotateUv, this.allDepth); } } } } /** * 胶带 */ class Tape { constructor(start, end, bottom, top) { this.start = start; this.end = end; this.bottom = bottom; this.top = top; } //用于测试 get Curve() { return new Polyline().RectangleFrom2Pt(new Vector3(this.start, this.bottom), new Vector3(this.end, this.top)); } Clip(t) { let yr = IntersectRange(this.bottom, this.top, t.bottom, t.top, 1e5); if (yr === undefined) return [this]; let xr = IntersectRange(this.start, this.end, t.start, t.end, 1e5); if (xr === undefined) return [this]; let rem = SubtractRange(this.start, this.end, t.start, t.end, 1e5).map(r => { return new Tape(r[0], r[1], this.bottom, this.top); }); let remR = SubtractRange(this.bottom, this.top, t.bottom, t.top, 1e5); for (let hr of remR) { rem.push(new Tape(xr[0], xr[1], hr[0], hr[1])); } return rem; } Split(xlst) { let ret = []; let pre = this.start; for (let x of xlst) { if (x > pre) { if (x >= this.end) x = this.end; if (equaln(pre, x)) continue; ret.push(new Tape(pre, x, this.bottom, this.top)); pre = x; if (x === this.end) break; } } return ret; } } /** * 二维形状,内部用曲线胶带表示(用来计算盖子差集算法) */ class CurveTapeShape { constructor(contour, holes) { this.children = []; this.contour = new CurveTape(contour, DirectionType.Outer); this.holes = holes.map(h => new CurveTape(h, DirectionType.Inner)); } CloneNew() { let s = new CurveTapeShape(this.contour.contour, this.holes.map(h => h.contour)); return s; } /** * 删除包含,同向 */ ClipTo(s, append = false) { for (let c of [this.contour, ...this.holes]) if (c.tapes.length > 0) c.ClipTo(s); if (append) { let sn = s.CloneNew(); sn.ReverseClipTo(this); this.children.push(sn); } } //合理打断(以保证三维网格对齐(否则圆弧点将无法正确的对齐)) SplitTo(s) { for (let c of [this.contour, ...this.holes]) { for (let c2 of [s.contour, ...s.holes]) { let int = GetIntersection(c.contour.Curve, c2.contour.Curve); c.splitParams.push(...int.map(i => i.thisParam)); } } } /** * 只保留被包含部分 */ ReverseClipTo(s) { for (let c of [this.contour, ...this.holes]) if (c.tapes.length > 0) c.ReverseClipTo(s); return this; } ChildrenClip() { for (let i = 0; i < this.children.length; i++) { let s1 = this.children[i]; for (let j = i + 1; j < this.children.length; j++) { let s2 = this.children[j]; s1.ClipTo(s2, false); s2.ClipTo(s1, false); } } } Draw(verticesArray, uvArray, front, z, rotateUv, allDepth) { this.ChildrenClip(); let polylines = this.contour.Curves; for (let h of this.holes) polylines.push(...h.Curves); for (let s of this.children) { polylines.push(...s.contour.Curves); for (let h of s.holes) polylines.push(...h.Curves); } // TestDraw(polylines, z); let groups = curveLinkGroup(polylines); let contourNodes = []; for (let cus of groups) { let c = Contour.CreateContour(cus, false); if (c) contourNodes.push(new ContourTreeNode(c)); else console.error("出错"); } ContourTreeNode.ParseContourTree(contourNodes); for (let j = contourNodes.length; j--;) { let node = contourNodes[j]; // TestDraw(s.contour.Curve.Clone(), z); if (node.parent) continue; node.Draw(verticesArray, uvArray, front, z, rotateUv, allDepth); } } } const SplitLength = 4; const MinSplitCount = 12; const MaxSplitCount = 360; function SplitCurveParams(cu) { let xparams = []; if (cu instanceof Circle) { let splitCount = cu.Radius / SplitLength; splitCount = clamp(Math.floor(splitCount), MinSplitCount, MaxSplitCount); for (let i = 0; i < splitCount; i++) xparams.push(i / splitCount); } else //分段1 for (let i = 0; i < cu.EndParam; i++) { xparams.push(i); if (cu.GetBuilgeAt(i) !== 0) { let arc = cu.GetCurveAtIndex(i); let splitCount = arc.Radius / SplitLength; splitCount = clamp(Math.floor(splitCount), MinSplitCount, MaxSplitCount); if (splitCount === 0) continue; let a = Math.PI * 2 / splitCount; let params = []; for (let j = 0; j < splitCount; j++) { let param = arc.GetParamAtAngle(a * j); if (arc.ParamOnCurve(param)) params.push(param); } arraySortByNumber(params); if (params.length === 0) continue; for (let p of params) { if (p > 1e-5 && p < 9.99999) xparams.push(p + i); } } } xparams.push(cu.EndParam); return xparams; } /** * 曲线胶带(一维) */ class CurveTape { constructor(contour, wallType) { this.contour = contour; this.wallType = wallType; this.splitParams = []; this.tapes = [[0, this.contour.Curve.EndParam]]; } get Curves() { let xparams = SplitCurveParams(this.contour.Curve); if (this.splitParams.length > 0) { xparams.push(...this.splitParams); arraySortByNumber(xparams); arrayRemoveDuplicateBySort(xparams, (p1, p2) => equaln(p1, p2)); } let polylines = []; function TD(p) { return { pt: AsVector2(p), bul: 0 }; } const addPolyline = (t) => { let pts = [TD(this.contour.Curve.GetPointAtParam(t[0]))]; for (let x of xparams) { if (x <= t[0]) continue; if (x >= t[1]) break; pts.push(TD(this.contour.Curve.GetPointAtParam(x))); } pts.push(TD(this.contour.Curve.GetPointAtParam(t[1]))); let pl = new Polyline(pts); polylines.push(pl); }; for (let t of this.tapes) { if (t[0] > t[1]) { addPolyline([0, t[1]]); addPolyline([t[0], this.contour.Curve.EndParam]); } else addPolyline(t); } return polylines; } /** * 分析与另一个形状的包含关系 */ Parse(s) { let [res1] = ParseCurveParamRangeRelation(this.contour.Curve, s.contour.contour.Curve); if (this.wallType === DirectionType.Inner) [res1.syntropy, res1.reverse] = [res1.reverse, res1.syntropy]; if (res1.container.length > 0) { for (let h of s.holes) { let [res2] = ParseCurveParamRangeRelation(this.contour.Curve, h.contour.Curve); if (this.wallType === DirectionType.Outer) [res2.syntropy, res2.reverse] = [res2.reverse, res2.syntropy]; res1.syntropy.push(...res2.syntropy); res1.reverse.push(...res2.reverse); res1.container = SubtractRanges(res1.container, res2.container, this.contour.Curve.EndParam); res1.container = SubtractRanges(res1.container, res2.syntropy, this.contour.Curve.EndParam); res1.container = SubtractRanges(res1.container, res2.reverse, this.contour.Curve.EndParam); } } return res1; } /** * 删除包含,同向面 */ ClipTo(s) { let d = this.Parse(s); this.tapes = SubtractRanges(this.tapes, d.container, this.contour.Curve.EndParam); this.tapes = SubtractRanges(this.tapes, d.syntropy, this.contour.Curve.EndParam); return this; } /** * 保留被包含的部分 */ ReverseClipTo(s) { this.tapes = this.Parse(s).container; return this; } } class ExtudeWall { constructor(curve, depthType, depth, allDepth, wallType) { this.curve = curve; this.depthType = depthType; this.depth = depth; this.allDepth = allDepth; this.wallType = wallType; //一整段 this.Tape = [CreateTape(depthType, 0, this.curve.EndParam, depth, allDepth)]; } /** * 减去在另一个groove内的部分 * @param groove this - groove * @param [clipSyntropy=false] 删除同向的面 */ ClipTo(groove, clipSyntropy = false) { let [res1] = ParseCurveParamRangeRelation(this.curve, groove.contourWall.curve); if (this.wallType !== groove.contourWall.wallType) [res1.syntropy, res1.reverse] = [res1.reverse, res1.syntropy]; if (res1.container.length > 0) { for (let h of groove.holeWalls) { let [resh1] = ParseCurveParamRangeRelation(this.curve, h.curve); //翻转 if (this.wallType !== h.wallType) [resh1.syntropy, resh1.reverse] = [resh1.reverse, resh1.syntropy]; //删除在网洞内的 let subParams; if (clipSyntropy) subParams = resh1.container; //删除共面, else subParams = [...resh1.container, ...resh1.syntropy]; //保留共面部分 for (let i of subParams) { let rems = []; for (let r of res1.container) rems.push(...SubtractRange(r[0], r[1], i[0], i[1], this.curve.EndParam)); res1.container = rems; } } } let params = [...res1.container, ...res1.reverse]; if (clipSyntropy) params.push(...res1.syntropy); for (let c of params) this.ClipFromParam(c[0], c[1], groove.depthType, groove.depth); } ClipReverse(wall) { let [res1] = ParseCurveParamRangeRelation(this.curve, wall.curve); for (let c of res1.syntropy) this.ClipFromParam(c[0], c[1], wall.depthType, wall.depth); } /** * 当起始参数大于终止参数时,裁剪的区域经过终点 * * @param startParam 起始参数 * @param endParam 终止参数 * @param faceType 裁剪面朝向 * @param depth 裁剪面的深度 */ ClipFromParam(startParam, endParam, faceType, depth) { if (equaln(startParam, endParam)) return; if (startParam > endParam) { this.ClipFromParam(startParam, this.curve.EndParam, faceType, depth); this.ClipFromParam(0, endParam, faceType, depth); return this; } let subTape = CreateTape(faceType, startParam, endParam, depth, this.allDepth); let taps = []; for (let t of this.Tape) taps.push(...t.Clip(subTape)); this.Tape = taps; return this; } Draw(verticesArray, uvArray, edgeBuild) { let xparams = SplitCurveParams(this.curve); let isOuter = this.wallType === DirectionType.Outer; let allDepth = this.allDepth; function AddVertice(v) { verticesArray.push(v.x); verticesArray.push(v.y); if (isOuter && ExtrudeBuildConfig.bevel) //如果倒角,则执行下面的代码 { if (v.z === 0) verticesArray.push(1); else if (v.z === allDepth) verticesArray.push(allDepth - 1); else verticesArray.push(v.z); } else verticesArray.push(v.z); } let tapes = []; this.Tape.sort((t1, t2) => t1.start - t2.start); for (let tape of this.Tape) tapes.push(...tape.Split(xparams)); for (let i = 0; i < tapes.length; i++) { let preIndex = FixIndex$1(i - 1, tapes); let nextIndex = FixIndex$1(i + 1, tapes); let tape = tapes[i]; let preTape = tapes[preIndex]; let nextTape = tapes[nextIndex]; let p1 = this.curve.GetPointAtParam(tape.start).setZ(tape.bottom); let p2 = this.curve.GetPointAtParam(tape.end).setZ(tape.bottom); let vs = [p1, p2, p2.clone().setZ(tape.top), p1.clone().setZ(tape.top), p1]; edgeBuild.AddLidLine(p1, p2, tape.bottom); edgeBuild.AddLidLine(p1, p2, tape.top); //#region 构造线框 { let leftRanges; let rightRange; const IsInteger = (n) => equaln(n, Math.round(n), 1e-8); if (!IsInteger(tape.start) && equaln(tape.start, preTape.end)) leftRanges = SubtractRange(tape.bottom, tape.top, preTape.bottom, preTape.top, this.allDepth); else leftRanges = [[tape.bottom, tape.top]]; if (equaln(tape.end, nextTape.start)) rightRange = SubtractRange(tape.bottom, tape.top, nextTape.bottom, nextTape.top, this.allDepth); else rightRange = [[tape.bottom, tape.top]]; //上下两条线 edgeBuild.lineVerticesArray.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p1.x, p1.y, tape.top, p2.x, p2.y, tape.top); //左右线 for (let range of leftRanges) { edgeBuild.lineVerticesArray.push(p1.x, p1.y, range[0], p1.x, p1.y, range[1]); } for (let range of rightRange) { edgeBuild.lineVerticesArray.push(p2.x, p2.y, range[0], p2.x, p2.y, range[1]); } } //#endregion //和X平行平行 let isXPar = equaln(vs[0].x, vs[1].x, 1e-2); function AddUv(p) { if (isXPar) uvArray.push((p.z - 1) * 1e-3, p.y * 1e-3); else uvArray.push((p.z - 1) * 1e-3, p.x * 1e-3); } if (this.wallType === DirectionType.Outer) { AddVertice(vs[0]); AddUv(vs[0]); AddVertice(vs[1]); AddUv(vs[1]); AddVertice(vs[2]); AddUv(vs[2]); AddVertice(vs[0]); AddUv(vs[0]); AddVertice(vs[2]); AddUv(vs[2]); AddVertice(vs[3]); AddUv(vs[3]); } else { AddVertice(vs[0]); AddUv(vs[0]); AddVertice(vs[2]); AddUv(vs[2]); AddVertice(vs[1]); AddUv(vs[1]); AddVertice(vs[0]); AddUv(vs[0]); AddVertice(vs[3]); AddUv(vs[3]); AddVertice(vs[2]); AddUv(vs[2]); } } } } /** * 分析两个曲线关系(包含,分离,同向共线,反向共线)(用参数范围表示) */ function ParseCurveParamRangeRelation(cu1, cu2, reverseParse = false) { let ins = GetIntersection(cu1, cu2); let c1Res = { container: [], syntropy: [], reverse: [], outer: [] }; let c2Res = { container: [], syntropy: [], reverse: [], outer: [] }; if (ins.length === 0) { if (cu1 instanceof Circle && cu2 instanceof Circle && equaln(cu1.Radius, cu2.Radius, 1e-4) && equalv2(cu1.Center, cu2.Center, 1e-4)) { c1Res.syntropy.push([0, 1]); c2Res.syntropy.push([0, 1]); return [c1Res, c2Res]; } if (cu2.PtInCurve(cu1.StartPoint)) c1Res.container.push([0, cu1.EndParam]); else c1Res.outer.push([0, cu1.EndParam]); if (cu1.PtInCurve(cu2.StartPoint)) c2Res.container.push([0, cu2.EndParam]); else c2Res.outer.push([0, cu2.EndParam]); return [c1Res, c2Res]; } //解析出线段列表 let c1Curves = []; let c2Curves = []; ins.sort((a1, a2) => a1.thisParam - a2.thisParam); //点重复->下方ins会sort,导致交点对应不上,导致错误 arrayRemoveDuplicateBySort(ins, (i1, i2) => equalv3(i1.pt, i2.pt, 1e-4)); if (ins.length > 1 && equalv3(ins[0].pt, ins[ins.length - 1].pt, 1e-4)) ins.pop(); for (let i = 0; i < ins.length; i++) { let n1 = ins[i]; let n2 = ins[FixIndex$1(i + 1, ins)]; c1Curves.push({ startParam: n1.thisParam, endParam: n2.thisParam, startPoint: n1.pt, endPoint: n2.pt }); } ins.sort((a1, a2) => a1.argParam - a2.argParam); for (let i = 0; i < ins.length; i++) { let n1 = ins[i]; let n2 = ins[FixIndex$1(i + 1, ins)]; c2Curves.push({ startParam: n1.argParam, endParam: n2.argParam, startPoint: n1.pt, endPoint: n2.pt }); } //分析共边关系和包含关系 for (let c of c1Curves) { let c1MidPoint = CenterPoint(cu1, c.startParam, c.endParam); for (let c2 of c2Curves) { if (c2.used) continue; let c2MidPoint = CenterPoint(cu2, c2.startParam, c2.endParam); if (!equalv3(c1MidPoint, c2MidPoint, 1e-4)) continue; c.used = true; if (c.startPoint === c2.startPoint && c.endPoint === c2.endPoint) { c1Res.syntropy.push([c.startParam, c.endParam]); c2Res.syntropy.push([c2.startParam, c2.endParam]); c2.used = true; break; } else if (c.startPoint === c2.endPoint && c.endPoint === c2.startPoint) { c1Res.reverse.push([c.startParam, c.endParam]); c2Res.reverse.push([c2.startParam, c2.endParam]); c2.used = true; break; } else c.used = false; } if (!c.used) { if (cu2.PtInCurve(c1MidPoint)) c1Res.container.push([c.startParam, c.endParam]); else c1Res.outer.push([c.startParam, c.endParam]); } } //只分析包含关系 if (reverseParse) for (let c of c2Curves) { if (c.used) continue; let p = CenterPoint(cu2, c.startParam, c.endParam); if (cu1.PtInCurve(p)) c2Res.container.push([c.startParam, c.endParam]); else c2Res.outer.push([c.startParam, c.endParam]); } return [c1Res, c2Res]; } function CenterPoint(cu, start, end) { let lenStart = cu.GetDistAtParam(start); let lenEnd = cu.GetDistAtParam(end); if (end > start) return cu.GetPointAtDistance((lenEnd + lenStart) * 0.5); let lenAll = cu.Length; let lenDiv = ((lenAll - lenStart) + lenEnd) * 0.5; if (lenStart + lenDiv >= lenAll) return cu.GetPointAtDistance(lenStart + lenDiv - lenAll); else return cu.GetPointAtDistance(lenStart + lenDiv); } // //0-1 // UnionRange(0.5, 0.3, 0.3, 0.5, 1);//? // //0-1 // UnionRange(0.4, 0.3, 0.3, 0.5, 1);//? // //[ [ 0.8, 0.1 ], [ 0.3, 0.5 ] ] // UnionRange(0.8, 0.1, 0.3, 0.5, 1);//? // //[ 0.3, 0.10000000000000009 ] ]  // UnionRange(0.8, 0.1, 0.3, 0.9, 1);//? function SubtractRange(a, b, c, d, end) { if (a < 0 || b < 0) return []; if (a > b) return [...SubtractRange(a, end, c, d, end), ...SubtractRange(0, b, c, d, end)]; if (c > d) { let arr = SubtractRange(a, b, c, end, end); let rem = []; for (let s of arr) rem.push(...SubtractRange(s[0], s[1], 0, d, end)); return rem; } if (c >= b || d <= a) return [[a, b]]; if (c <= a) // c1 a1 b1 { if (d >= b) return []; return [[d, b]]; } if (d < b) return [[a, c], [d, b]]; return [[a, c]]; } function SubtractRange2(r, sr, end) { return SubtractRange(r[0], r[1], sr[0], sr[1], end); } function SubtractRanges(ranges, subRanges, end) { let rets = ranges; for (let sr of subRanges) { let temps = []; for (let r of rets) temps.push(...SubtractRange2(r, sr, end)); rets = temps; } return rets; } function IntersectRange(a, b, c, d, end) { let b1 = b < a ? b + end : b; let d1 = d < c ? d + end : d; let a1 = a; let c1 = c; if (c < a) [a1, b1, c1, d1] = [c1, d1, a1, b1]; if (c1 > b1) return; return [c1, Math.min(b1, d1)]; } const alMatrix4 = new Matrix4; class ExtrudeGeometryBuilder { constructor(br) { this.br = br; this.verticesArray = []; //用于构建三维网格 this.uvArray = []; //uv this.GenerateMeshData(br); } GenerateMeshData(br, rotateUv) { this.edgeAndLidBuilder = new EdgeGeometryBuild(this.br.Thickness); //计算墙(创建轮廓取出,为了得到正确的轮廓曲线(逆时针之类的)) let outerWall = new ExtudeWall(Contour.CreateContour(br.ContourCurve.Clone()).Curve, DepthType.All, br.Thickness, br.Thickness, DirectionType.Outer); let grooves = this.ParseGrooves(); if (grooves.length < MaxDrawGrooveCount) //只能绘制1000个以下的造型 for (let i = 0; i < grooves.length; i++) { let s1 = grooves[i]; outerWall.ClipTo(s1, false); s1.contourWall.ClipReverse(outerWall); for (let j = i + 1; j < grooves.length; j++) { let s2 = grooves[j]; s1.ClipTo(s2, true); } s1.Draw(this.verticesArray, this.uvArray, this.edgeAndLidBuilder, rotateUv); } outerWall.Draw(this.verticesArray, this.uvArray, this.edgeAndLidBuilder); //这里构建盖子 this.edgeAndLidBuilder.BuildLid(this.verticesArray, this.uvArray, rotateUv); intCache.clear(); } get MeshGeometry() { let geo = new BufferGeometry(); geo.setAttribute('position', new Float32BufferAttribute(this.verticesArray, 3)); geo.setAttribute('uv', new Float32BufferAttribute(this.uvArray, 2)); geo.computeVertexNormals(); return geo; } get EdgeGeometry() { let geo = new BufferGeometry(); geo.setAttribute('position', new Float32BufferAttribute(this.edgeAndLidBuilder.lineVerticesArray, 3)); return geo; } ParseGrooves() { let br = this.br; const brOcsInv = br.OCSInv; let grooves = []; for (let groove of br.Grooves) { //判断槽正反面 let type; if (equaln(groove.Thickness, br.Thickness)) type = DepthType.All; else { if (equaln(groove.Position.applyMatrix4(brOcsInv).z, 0)) type = DepthType.Back; else type = DepthType.Front; } alMatrix4.multiplyMatrices(brOcsInv, groove.OCSNoClone); //槽轮廓 let grooveContourCurve = groove.ContourCurve.Clone(); grooveContourCurve.ApplyMatrix(alMatrix4); grooveContourCurve.Z0(); if (grooveContourCurve instanceof Polyline) grooveContourCurve.UpdateMatrixTo(IdentityMtx4); //不可能改变这个 let grooveContour = Contour.CreateContour(grooveContourCurve); let grooveHoleContours = []; //孤岛 for (let grooveChild of groove.Grooves) { let grooveChildContourCurve = grooveChild.ContourCurve.Clone(); alMatrix4.multiplyMatrices(brOcsInv, grooveChild.OCSNoClone); grooveChildContourCurve.ApplyMatrix(alMatrix4).Z0(); if (grooveChildContourCurve instanceof Polyline) grooveChildContourCurve.UpdateMatrixTo(IdentityMtx4); let grooveChildContour = Contour.CreateContour(grooveChildContourCurve); grooveHoleContours.push(grooveChildContour); } grooves.push(new Groove(grooveContour, grooveHoleContours, type, groove.Thickness, br.Thickness)); } return grooves; } } let intCache = new Map(); function GetIntersection(cu1, cu2) { let m = intCache.get(cu1); if (m) { let r = m.get(cu2); if (r) return r; } else m = new Map(); intCache.set(cu1, m); let r = cu1.IntersectWith2(cu2, IntersectOption.OnBothOperands); let cu1EndParam = cu1.EndParam; let cu2EndParam = cu2.EndParam; for (let d of r) { d.thisParam = MathUtils.clamp(d.thisParam, 0, cu1EndParam); d.argParam = MathUtils.clamp(d.argParam, 0, cu2EndParam); } m.set(cu2, r); let r2 = r.map(r => { return { thisParam: r.argParam, argParam: r.thisParam, pt: r.pt }; }); let m2 = intCache.get(cu2); if (!m2) { m2 = new Map(); intCache.set(cu2, m2); } m2.set(cu1, r2); return r; } var ExtrudeSolid_1; const MaxDrawGrooveCount = 1000; //最大的绘制槽个数(但是还是会绘制线) let ExtrudeSolid = ExtrudeSolid_1 = class ExtrudeSolid extends Entity { constructor() { super(); /* ------------ | | | | | | height | | | | ----width--- */ this.height = 1; //y this.width = 1; //x /** * 拉伸实体的厚度 * 我们允许它是一个负数,但是这个时候这个实体已经是一个无效的拉伸实体了. * 允许负数,用来校验凹槽的合理性. */ this.thickness = 1; this.isRect = true; this.IsKnife = false; /** * 正面和反面的凹槽造型 */ this.grooves = []; this.knifeRadius = 3; this.groovesAddLength = 0; this.groovesAddWidth = 0; this.groovesAddDepth = 0; this.RelevanceKnifs = this.CreateProxyArray((v) => { //可以更新自己,但是不建议,建议手动更新 }); this.RelevanceMeats = this.CreateProxyArray((v) => { //可以更新肉,简单是不建议,建议手动更新 }); } set Material(materialId) { var _a, _b, _c, _d; let oldMaterial = (_a = this.Material) !== null && _a !== void 0 ? _a : (_b = this._db) === null || _b === void 0 ? void 0 : _b.DefaultMaterial.objectId; super.Material = materialId; let isf_old = Boolean((_c = oldMaterial === null || oldMaterial === void 0 ? void 0 : oldMaterial.Object) === null || _c === void 0 ? void 0 : _c.IsFull); let isf_new = Boolean((_d = materialId === null || materialId === void 0 ? void 0 : materialId.Object) === null || _d === void 0 ? void 0 : _d.IsFull); if (isf_old !== isf_new) this.Update(); } get Material() { return super.Material; } get KnifeRadius() { return this.knifeRadius; } set KnifeRadius(v) { if (!equaln(v, this.knifeRadius)) { this.WriteAllObjectRecord(); this.knifeRadius = v; } } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCS); } get BoundingBoxInOCS() { if (this.width > 0 && this.height > 0 && this.thickness > 0) return new Box3Ext(new Vector3, new Vector3(this.width, this.height, this.thickness)); else return new Box3Ext().setFromPoints([new Vector3, new Vector3(this.width, this.height, this.thickness)]); } get OBB() { return new OBB(this.OCS, new Vector3(this.width, this.height, this.thickness).multiplyScalar(0.5)); } get GroovesAddLength() { return this.groovesAddLength; } set GroovesAddLength(v) { if (!equaln(v, this.groovesAddLength)) { this.WriteAllObjectRecord(); this.groovesAddLength = v; //更改它的时候,关联切割被更新,拆单的时候才会正确,否则使用缓存将不正确 this.__UpdateVersion__++; } } get GroovesAddWidth() { return this.groovesAddWidth; } set GroovesAddWidth(v) { if (!equaln(v, this.groovesAddWidth)) { this.WriteAllObjectRecord(); this.groovesAddWidth = v; //更改它的时候,关联切割被更新,拆单的时候才会正确,否则使用缓存将不正确 this.__UpdateVersion__++; } } get GroovesAddDepth() { return this.groovesAddDepth; } set GroovesAddDepth(v) { if (!equaln(v, this.groovesAddDepth)) { this.WriteAllObjectRecord(); this.groovesAddDepth = v; //更改它的时候,关联切割被更新,拆单的时候才会正确,否则使用缓存将不正确 this.__UpdateVersion__++; } } Clone() { let en = super.Clone(); return en; } ApplyMatrix(m) { //暂时关闭更新,避免内部实体还没有更新位置时,先更新了实体的Geometry,导致后续没有进行更新 let updateBak = this.AutoUpdate; this.AutoUpdate = false; super.ApplyMatrix(m); for (let g of this.grooves) g.ApplyMatrix(m); //由于修改矩阵会导致矩阵错误 this.csg = undefined; this.AutoUpdate = updateBak; if (!equaln(m.getMaxScaleOnAxis(), 1)) this.Update(UpdateDraw.Geometry); else if (this.AutoUpdate) this.DeferUpdate(); return this; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let cu = this.ContourCurve; cu.ApplyMatrix(this.OCS); cu.ApplyMatrix(m); cu.ApplyMatrix(this.OCSInv); this.CheckContourCurve(); return this; } ApplyMirrorMatrix(m) { const curve = this.contourCurve; if (curve instanceof Polyline && !equalv3(curve.Position, ZeroVec)) { let pts = curve.LineData; if (equalv2(pts[0].pt, arrayLast(pts).pt)) pts.pop(); let ocs = curve.OCS; for (let p of pts) { Vector2ApplyMatrix4(ocs, p.pt); } curve.OCS = IdentityMtx4; } let nor = this.Normal.applyMatrix4(this.SpaceOCSInv.setPosition(new Vector3)); if (equaln(Math.abs(nor.z), 1)) { reviseMirrorMatrix(this._Matrix, 1); if (this.contourCurve instanceof Circle) { this.contourCurve.ApplyMatrix(new Matrix4().makeRotationX(Math.PI)); } else { let ocs = this.contourCurve.OCS; reviseMirrorMatrix(ocs, 1); this.contourCurve.OCS = ocs; } this.SetContourCurve(this.contourCurve); } else if (equaln(Math.abs(nor.x), 1)) { reviseMirrorMatrix(this._Matrix, 2); this._Matrix.setPosition(this.Position.add(this.Normal.multiplyScalar(-this.Thickness))); } else { reviseMirrorMatrix(this._Matrix, 0); if (this.contourCurve instanceof Circle) { this.contourCurve.ApplyMatrix(new Matrix4().makeRotationY(Math.PI)); } else { let ocs = this.contourCurve.OCS; reviseMirrorMatrix(ocs, 0); this.contourCurve.OCS = ocs; } this.SetContourCurve(this.contourCurve); } return this; } get Position() { return super.Position; } set Position(p) { let v = p.clone().sub(this.Position); if (equalv3(v, ZeroVec)) return; super.Position = p; let m = MoveMatrix(v); for (let g of this.grooves) g.ApplyMatrix(m); //由于修改矩阵会导致bsp错误 this.csg = undefined; } get Width() { return this.width; } get Height() { return this.height; } get Thickness() { return this.thickness; } set Thickness(thickness) { if (!equaln(thickness, this.thickness, 1e-3)) { this.WriteAllObjectRecord(); if (this.grooves.length > 0) { let inv = this.OCSInv; let v = this.Normal.multiplyScalar(thickness - this.thickness); let m = new Matrix4().setPosition(v); for (let g of this.grooves) { let p = g.Position.applyMatrix4(inv); if (equaln(g.thickness, this.thickness)) g.Thickness = thickness; else if (!equaln(p.z, 0)) g.ApplyMatrix(m); } } this.thickness = thickness; this.Update(UpdateDraw.Geometry); } } get Grooves() { return this.grooves; } /** * 返回未拷贝的轮廓曲线 */ get ContourCurve() { if (!this.contourCurve) this.GeneralRectContour(); return this.contourCurve; } set ContourCurve(cu) { this.SetContourCurve(cu); } /** * 生成矩形轮廓(强制) */ GeneralRectContour() { if (!this.contourCurve || !(this.contourCurve instanceof Polyline)) this.contourCurve = new Polyline(); this.contourCurve.Rectangle(this.width, this.height); this.contourCurve.OCS = IdentityMtx4; this.ContourCurve = this.contourCurve; } /** * 转换成矩形拉伸实体 */ ConverToRectSolid(width = this.width, height = this.height, thickness = this.thickness) { this.WriteAllObjectRecord(); this.height = height; this.width = width; this.thickness = thickness; this.isRect = true; this.GeneralRectContour(); return this; } /** * 更新拉伸实体的轮廓 * @param curve 曲线已经存在WCS坐标系 */ SetContourCurve(curve) { if (!curve.IsClose) return; let area = curve.Area; if (!area || equaln(area, 0)) return; if (curve instanceof Spline || curve instanceof Ellipse) curve = curve.Convert2Polyline(); if (curve instanceof Polyline) { curve.CloseMark = true; let pts = curve.LineData; if (equalv2(pts[0].pt, arrayLast(pts).pt)) pts.pop(); //如果曲线被旋转了,那么修正它的旋转矩阵,避免纹路错误 let ocs = curve.OCS; let x = new Vector3().setFromMatrixColumn(ocs, 0); let y = new Vector3().setFromMatrixColumn(ocs, 1); let z = new Vector3().setFromMatrixColumn(ocs, 2); let z1 = x.cross(y); let isMirror = equaln(ocs.elements[10], -1, 1e-4) || !equalv3(z, z1); let isRotate = !equaln(ocs.elements[0], 1); if (isMirror || isRotate) // || ocs.elements[9] || ocs.elements[10] { for (let p of pts) { Vector2ApplyMatrix4(ocs, p.pt); if (isMirror) p.bul *= -1; } curve.OCS = IdentityMtx4; } } else { curve.OCS = new Matrix4().setPosition(curve.Position); } curve.ClearDraw(); this.WriteAllObjectRecord(); this.contourCurve = curve; this.CheckContourCurve(); this.Update(); } /** * 在不改变Normal和实体显示的情况下,修改X轴的指向 * @param xAxis */ SetXAxis(xAxis) { let m = this.OCSInv.setPosition(ZeroVec); let x = xAxis.applyMatrix4(m).setZ(0).normalize(); if (equalv3(ZeroVec, x)) return this; this.WriteAllObjectRecord(); let a = Math.atan2(x.y, x.x); m.copy(this._Matrix).setPosition(ZeroVec); x.applyMatrix4(m); let z = this.Normal; let y = z.cross(x); this._Matrix.elements[0] = x.x; this._Matrix.elements[1] = x.y; this._Matrix.elements[2] = x.z; this._Matrix.elements[4] = y.x; this._Matrix.elements[5] = y.y; this._Matrix.elements[6] = y.z; m.makeRotationZ(-a); this.contourCurve.ApplyMatrix(m); this.CheckContourCurve(); if (this.contourCurve instanceof Polyline) this.contourCurve.UpdateMatrixTo(m.identity()); this.Update(); return this; } /** * 检验轮廓曲线,通常当轮廓曲线被修改时,都需要检验轮廓曲线,并更新实体大小和轮廓位置. * >计算轮廓大小 * >判断是否矩形 * >修正轮廓基点 */ CheckContourCurve() { let box = this.ContourCurve.BoundingBox; let size = box.getSize(new Vector3()); this.width = size.x; this.height = size.y; if (equaln(size.x, 0) || equaln(size.y, 0)) Log(`注意!!该板件尺寸为0!`); this.isRect = equaln(this.width * this.height, this.ContourCurve.Area, 0.1); //修正轮廓基点 if (!equalv3(box.min, ZeroVec)) { this.contourCurve.Position = this.contourCurve.Position.sub(box.min); let v = box.min.applyMatrix4(this.OCS.setPosition(ZeroVec)); this._Matrix.setPosition(this.Position.add(v)); } } get IsRect() { return this.isRect; } /** * 这个拉伸实体的面域形状 */ get Shape() { let contour = Contour.CreateContour(this.ContourCurve.Clone(), false); let holes = []; for (let g of this.grooves) { if (equaln(g.thickness, this.thickness, 1e-3)) holes.push(Contour.CreateContour(g.ContourCurve.Clone().ApplyMatrix(this.OCSInv.multiply(g.OCSNoClone)), false)); } return new Shape(contour, holes); } /** * 实体合并(不会删除target) */ Join(target) { let [n, tn] = [this.Normal, target.Normal]; if (!isParallelTo(n, tn)) return Status.False; let isEqualNorm = equalv3(n, tn); let targetZMin = target.Position.applyMatrix4(this.OCSInv).z; let targetZMax = targetZMin + target.Thickness * (isEqualNorm ? 1 : -1); [targetZMin, targetZMax] = arraySortByNumber([targetZMin, targetZMax]); const MergeRelevance = () => { if (!this.Id || !target.Id) return; for (let kf of target.RelevanceKnifs) { let kfBr = kf.Object; if (!kfBr) continue; if (!kfBr.RelevanceMeats.includes(this.Id)) kfBr.RelevanceMeats.push(this.Id); if (!this.RelevanceKnifs.includes(kf)) this.RelevanceKnifs.push(kf); } for (let meat of target.RelevanceMeats) { let meatBr = meat.Object; if (!meatBr) continue; if (!meatBr.RelevanceKnifs.includes(this.Id)) meatBr.RelevanceKnifs.push(this.Id); if (!this.RelevanceMeats.includes(meat)) this.RelevanceMeats.push(meat); } }; if (equaln(this.thickness, target.thickness) && equaln(0, targetZMin)) { let matrixToLocal = this.OCSInv.multiply(target.OCS); let thisShape = this.Shape; let targetShape = target.Shape.ApplyMatrix(matrixToLocal).Z0(); let unionShapes = thisShape.UnionBoolOperation(targetShape, true); if (unionShapes.length === 1) { this.WriteAllObjectRecord(); // [ + ] 产生网洞. for (let hole of unionShapes[0].Holes) { let g = new ExtrudeSolid_1(); g.thickness = this.thickness; g.ContourCurve = hole.Curve; g.ApplyMatrix(this.OCS); this.AppendGroove(g); } this.ContourCurve = unionShapes[0].Outline.Curve; this.grooves.push(...target.grooves.map(g => g.Clone())); MergeRelevance(); this.GrooveCheckMerge(); this.Update(); return Status.True; } } else { if (!isIntersect(0, this.thickness, targetZMin, targetZMax, 1e-5)) return Status.False; let matrixToLocal = this.OCSInv.multiply(target.OCS); let thisCurve = this.ContourCurve; let targetCurve = target.ContourCurve.Clone().ApplyMatrix(matrixToLocal); targetCurve.Position = targetCurve.Position.setZ(0); if (equalCurve(thisCurve, targetCurve)) { this.WriteAllObjectRecord(); if (targetZMin < 0) this.Position = this.Position.add(n.multiplyScalar(targetZMin)); this.Thickness = Math.max(this.Thickness, targetZMax) - Math.min(0, targetZMin); this.grooves.push(...target.grooves.map(g => g.Clone())); MergeRelevance(); this.GrooveCheckMerge(); this.Update(); return Status.True; } } return Status.False; } get Volume() { let sum = this.ContourCurve.Area * this.thickness; for (let g of this.grooves) sum -= g.Volume; return sum; } /** * 被切割 * @param extrudes 切割刀 * @param [output=undefined] 如果实体被分裂,那么输出分裂的其他实体(如果为空,则尝试入当前实体的容器中) * @param [checkIntersect=true] 检查相交,性能优化 * @returns 切割是否成功 */ Subtract(extrudes, output = undefined, checkIntersect = true) { if (checkIntersect) { let box = this.BoundingBox; extrudes = extrudes.filter(e => box.intersectsBox(e.BoundingBox)); } //清除原先的关联关系 if (this.Id) { let ids = new Set(); for (let e of extrudes) { if (!e.Id) continue; arrayRemoveOnce(e.RelevanceMeats, this.Id); ids.add(e.Id.Index); } arrayRemoveIf(this.RelevanceKnifs, id => ids.has(id.Index)); } let grooves = []; for (let br of extrudes) { let gs = this.ConverToLocalGroove(br); arrayPushArray(grooves, gs); } if (grooves.length === 0) return false; let area1 = this.ContourCurve.Area; let sum1 = this.Volume; this.AppendGrooves(grooves, output); let sum2 = this.Volume; let area2 = this.ContourCurve.Area; if (!equaln(sum1, sum2) || !equaln(area1, area2)) { if (!this.ReadFileIng && this instanceof Board) { if (this.Id) Log(`${this.Name}(${this.Id.Index})被切割成功!`); else if (this.__OriginalId__) Log(`${this.Name}(${this.__OriginalId__.Index})关联切割成功更新槽!`); } return true; } return false; } RelevanceSubtract(knif, check = false) { if (!this.Id || !knif.Id) return; //判断是否已经存在 if (check) { let index = this.RelevanceKnifs.findIndex(id => id.Index === knif.Id.Index); if (index !== -1) return; } this.RelevanceKnifs.push(knif.Id); knif.RelevanceMeats.push(this.Id); } /** * 当实体被分裂后,加入新图纸时,需要修复关联拉槽 */ RepairRelevance() { if (!this.Id) { console.error("不能修复未加入到图纸的板件!"); return; } for (let id of this.RelevanceKnifs) { if (id && !id.IsErase) { let br = id.Object; br.RelevanceMeats.push(this.Id); } } for (let id of this.RelevanceMeats) { if (id && !id.IsErase) { let br = id.Object; br.RelevanceKnifs.push(this.Id); } } } AppendGroove(groove) { this.WriteAllObjectRecord(); this.grooves.push(groove); } /** 添加槽进板件,并且自动分裂. * 通常槽已经校验过准确性,所以不在校验 */ AppendGrooves(grooves, output = undefined) { if (grooves.length === 0) return; this.WriteAllObjectRecord(); this.grooves.push(...grooves); this.GrooveCheckAllAutoSplit(output); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Cen: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: case ObjectSnapMode.Tan: { let contour = this.ContourCurve.Clone(); contour.ApplyMatrix(this.OCSNoClone); let pts = contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); contour.Position = contour.Position.add(this.Normal.multiplyScalar(this.thickness)); pts.push(...contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); let ps = this.contourCurve.GetStretchPoints(); for (let p of ps) { let l = new Line(p, p.clone().setZ(this.thickness)); l.ApplyMatrix(this.OCSNoClone); pts.push(...l.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } if (this.grooves.length < 100) for (let g of this.grooves) pts.push(...g.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); return pts; } } return []; } //#region Stretch GetStrectchPointCountList(dragType) { let counts = [this.ContourCurve.GetDragPointCount(dragType) * 2]; for (let g of this.grooves) { let c = g.ContourCurve.GetDragPointCount(dragType) * 2; for (let g1 of g.grooves) c += g1.contourCurve.GetDragPointCount(dragType) * 2; counts.push(c); } return counts; } GetGripOrStretchPoints(dragType) { let isGrip = dragType === DragPointType.Grip; let pts = isGrip ? this.ContourCurve.GetGripPoints() : this.ContourCurve.GetStretchPoints(); let v = new Vector3(0, 0, this.thickness); pts.push(...pts.map(p => p.clone().add(v))); pts.forEach(p => { p.applyMatrix4(this.OCSNoClone); }); for (let g of this.grooves) { let gpts = g.GetGripOrStretchPoints(dragType); pts.push(...gpts); } return pts; } MoveGripOrStretchPoints(indexList, vec, dragType) { this.WriteAllObjectRecord(); let counts = this.GetStrectchPointCountList(dragType); if (dragType === DragPointType.Stretch && indexList.length === arraySum(counts)) { this.Position = this.Position.add(vec); return; } arraySortByNumber(indexList); let updateBak = this.AutoUpdate; this.AutoUpdate = false; if (this.grooves.length === 0) { this.MoveGripOrStretchPointsOnly(indexList, vec, dragType); } else { let i = 0; let icount = indexList.length; let offset = 0; let grooveIndex = -1; for (let count of counts) { offset += count; let ilist = []; for (; i < icount; i++) { if (indexList[i] < offset) ilist.push(indexList[i] - offset + count); else break; } if (ilist.length > 0) { if (grooveIndex === -1) this.MoveGripOrStretchPointsOnly(ilist, vec, dragType); else this.grooves[grooveIndex].MoveGripOrStretchPoints(ilist, vec, dragType); } grooveIndex++; } } if (this.objectId) { this.CheckContourCurve(); let splitEntitys = []; this.GrooveCheckAll(splitEntitys); if (splitEntitys.length > 0 && this.Owner) { let ms = this.Owner.Object; for (let e of splitEntitys) ms.Append(e); } } this.AutoUpdate = updateBak; this.Update(); } GetGripPoints() { return this.GetGripOrStretchPoints(DragPointType.Grip); } MoveGripPoints(indexList, vec) { this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Grip); } GetStretchPoints() { return this.GetGripOrStretchPoints(DragPointType.Stretch); } MoveStretchPoints(indexList, vec) { this.MoveGripOrStretchPoints(indexList, vec, DragPointType.Stretch); } /** * 只对自身的轮廓和厚度进行拉伸,忽略子实体 */ MoveGripOrStretchPointsOnly(indexList, vec, dragType) { let stretchCount = this.ContourCurve.GetDragPointCount(dragType); if (dragType === DragPointType.Stretch) { //Move if (indexList.length === stretchCount * 2) { this.Position = this.Position.add(vec); return; } //判断是否拉伸厚度 if (this.IsStretchThickness(indexList)) { let isFront = indexList[0] < stretchCount; if (indexList.every(v => v < stretchCount === isFront)) { //Change thickness let lvec = vec.clone().applyMatrix4(this.OCSInv.setPosition(ZeroVec)); if (isFront) { // if (lvec.z >= this.thickness) return; this.thickness -= lvec.z; //移动位置而不改变内部拉槽 let v = this.Normal.multiplyScalar(lvec.z); this._Matrix.elements[12] += v.x; this._Matrix.elements[13] += v.y; this._Matrix.elements[14] += v.z; } else { // if (-lvec.z > this.thickness) return; this.thickness += lvec.z; } return; } } indexList = arrayClone(indexList); } //修正点的索引 for (let i = 0; i < indexList.length; i++) { let index = indexList[i]; if (index >= stretchCount) { index -= stretchCount; indexList[i] = index; } } indexList = [...new Set(indexList)]; let localVec = vec.clone().applyMatrix4(this.OCSInv.setPosition(ZeroVec)); if (dragType === DragPointType.Grip) { if (this.ContourCurve instanceof Polyline && indexList.length === 1 && indexList[0] % 2 === 1) { let param = indexList[0] / 2; if (this.ContourCurve.GetBuilgeAt(Math.floor(param)) === 0) { let der = this.ContourCurve.GetFistDeriv(param).normalize(); [der.x, der.y] = [der.y, -der.x]; let d = localVec.dot(der); localVec.copy(der).multiplyScalar(d); } } this.ContourCurve.MoveGripPoints(indexList, localVec); } else this.ContourCurve.MoveStretchPoints(indexList, localVec); } IsStretchThickness(indexs) { let count = this.ContourCurve.GetStretchPoints().length; if (indexs.length === count) { let isF = indexs[0] < count; return indexs.every(i => isF === (i < count)); } return false; } get CSG() { if (this.csg) return this.csg; this.csg = Geometry2CSG(this.MeshGeometry); return this.csg; } /** * (步骤1.2.) * 将目标拉伸实体转换成在板件内部可用的凹槽实体 * @param target 该对象可能被修改(内部不拷贝该实体) * @param useClone 转换后的实体是目标实体拷贝后修改的 */ ConverToLocalGroove(target) { if (!this.OBB.intersectsOBB(target.OBB)) return []; let n1 = this.Normal; let n2 = target.Normal; //0不平行 1同向,2反向 () 这里保证判断平行和判断方向相反的判断方式是一样的 #I3BUSY const __eqfuzz__ = 1e-3; let parType = equalv3(n1, n2, __eqfuzz__) ? 1 : equalv3(n1, n2.clone().negate(), __eqfuzz__) ? 2 : 0; if (parType > 0) { target = target.Clone().ClearDraw(); if (parType === 2) { let mtx = target._Matrix; matrixSetVector(mtx, 2, n1); let p = n1.setFromMatrixColumn(mtx, 3); p.add(n2.multiplyScalar(target.thickness)); matrixSetVector(mtx, 3, p); } if (this.GrooveCheckPosition(target) !== Status.True) return []; return [target]; } else { //当切割刀是矩形板,并且没有槽的时候,如果轴对齐,我们可以直接用aabb进行求交 if (target.isRect && target.grooves.length === 0) { let diffMtx = target.OCS.premultiply(this.OCSInv); let box = target.BoundingBoxInOCS.applyMatrix4(diffMtx); let size = box.getSize(new Vector3); //轴对齐 if (equaln(size.x * size.y * size.z, target.Width * target.Height * target.Thickness, 1)) { let ibox = this.BoundingBoxInOCS.intersect(box); ibox.getSize(size); if (size.x < 1 || size.y < 1 || size.z < 0.1) return []; //构造新槽(因为我们当前的槽加长是根据槽的长短边进行加长的,所以我们可以这么构建) let g2 = new ExtrudeSolid_1().ConverToRectSolid(size.x, size.y, size.z); g2.Position = ibox.min; g2.ApplyMatrix(this._Matrix); g2.groovesAddDepth = target.groovesAddDepth; g2.groovesAddLength = target.groovesAddLength; g2.groovesAddWidth = target.groovesAddWidth; g2.knifeRadius = target.knifeRadius; return [g2]; } } let grooves = []; let project = ProjectBoard(target, this); if (!project) { let yv = n2; let zv = n1; let xv = yv.clone().cross(zv); yv.copy(zv).cross(xv); //必须修正向量,否则会出错 #I3BUSY xv.normalize(); yv.normalize(); zv.normalize(); let m = new Matrix4().makeBasis(xv, yv, zv).copyPosition(this.OCS); let mi = new Matrix4().getInverse(m).multiply(this.OCS); let interBSP = this.CSG.intersect(target.CSG.transform1(this.OCSInv.multiply(target.OCS))); let topology = new BSPGroupParse(interBSP); let grooves = []; for (let pts of topology.Parse()) { for (let p of pts) p.applyMatrix4(mi); let box = new Box3Ext().setFromPoints(pts); if (!box.isSolid(0.1)) continue; let size = box.getSize(new Vector3()); let ext = new ExtrudeSolid_1(); ext.groovesAddDepth = target.groovesAddDepth; ext.groovesAddLength = target.groovesAddLength; ext.groovesAddWidth = target.groovesAddWidth; ext.knifeRadius = target.knifeRadius; ext.ConverToRectSolid(size.x, size.y, size.z); ext.OCS = m.clone().setPosition(box.min.applyMatrix4(m)); grooves.push(ext); } return grooves; } // project.ApplyMatrix(target.OCSInv); project.Z0(); let c1 = Contour.CreateContour(project); let c2 = Contour.CreateContour(target.ContourCurve); //投影轮廓列表 let contours = c1.IntersectionBoolOperation(c2); let outlines = []; for (let c of contours) { if (c.Curve instanceof Polyline) outlines.push(...PolylineSpliteRect(c.Curve)); else outlines.push(c.Curve); } let xv = n1; let zv = n2; let yv = zv.clone().cross(xv); //把<投影轮廓>对齐到肉的侧面坐标系上 let projection2SideMatrix4 = new Matrix4().makeBasis(xv, yv, zv); for (let c of outlines) { let g = target.Clone().ClearDraw(); let gs = [g]; g.ContourCurve = c; g.GrooveCheckAll(gs); for (let g1 of gs) { //按g1的位置设置 projection2SideMatrix4.setPosition(g1.Position); //投影到肉的侧面坐标系,求槽在肉里面的长度(x)和厚度(z) let alm = new Matrix4().getInverse(projection2SideMatrix4).multiply(g1.OCS); g1.ContourCurve.ApplyMatrix(alm); //破坏它 let box = g1.ContourCurve.BoundingBox; let size = box.getSize(new Vector3); if (equaln(size.x, 0, 1e-2) || equaln(size.y, 0, 1e-2)) continue; //构造新槽 let g2 = new ExtrudeSolid_1().ConverToRectSolid(size.y, g1.Thickness, size.x); g2.groovesAddDepth = target.groovesAddDepth; g2.groovesAddLength = target.groovesAddLength; g2.groovesAddWidth = target.groovesAddWidth; g2.knifeRadius = target.knifeRadius; g2.ApplyMatrix(OverturnMatrix); //翻转到和原先的投影曲线(肉侧面)一样的状态 g2.ApplyMatrix(MoveMatrix(box.min)); //和投影曲线重叠 g2.ApplyMatrix(projection2SideMatrix4); //按照矩形还原回去 grooves.push(g2); } } return grooves; } } /** * (步骤2) * 更新凹槽位置和厚度(校验凹槽的Z轴位置是否存在交集) */ GrooveCheckPosition(target) { if (target.Width < 1e-2 || target.Height < 1e-2 || target.Thickness < 1e-2) return Status.False; let tp = target.Position.applyMatrix4(this.OCSInv); let minZ = tp.z; let maxZ = tp.z + target.thickness; if (minZ <= 1e-2) //背面 { target.Thickness = Math.min(maxZ, this.thickness); if (!(equaln(minZ, 0))) target.ApplyMatrix(MoveMatrix(this.Normal.multiplyScalar(-minZ))); } else if (maxZ >= (this.thickness - 1e-3) && minZ > 0) //正面 target.Thickness = this.thickness - minZ; else return Status.False; if (equaln(target.thickness, this.thickness, 1e-3)) target.thickness = this.thickness; return target.thickness > 1e-3 ? Status.True : Status.False; } /** * (步骤3) * 计算凹槽合并 */ GrooveCheckMerge() { if (this.grooves.length < 2) return; //构建二维空间索引 let ocsInv = this.OCSInv; let mtx = new Matrix4; let fb = new Flatbush(this.grooves.length); for (let i = 0; i < this.grooves.length; i++) { let g = this.grooves[i]; mtx.multiplyMatrices(ocsInv, g.OCSNoClone); let cu = g.ContourCurve.Clone().ApplyMatrix(mtx); let box = cu.BoundingBox; g.TempData = { index: i, used: false, box }; fb.add(box.min.x, box.min.y, box.max.x, box.max.y); } fb.finish(); let retGs = []; //新的槽列表 for (let i = 0; i < this.grooves.length; i++) { let startG = this.grooves[i]; if (startG.TempData === undefined) //已经被使用 continue; retGs.push(startG); let stack = [startG]; for (let j = 0; j < stack.length; j++) { let g = stack[j]; let gd = g.TempData; //能入栈的都是未被使用的 let ids = fb.search(gd.box.min.x - 1e-2, gd.box.min.y - 1e-2, gd.box.max.x + 1e-2, gd.box.max.y + 1e-2, (id => { if (id <= i) return false; //(id比它小(如果能合并 早就合并了)) let gd = this.grooves[id].TempData; return gd && !gd.used; //判断被使用 })); for (let id of ids) { let ng = this.grooves[id]; if (equaln(startG.knifeRadius, ng.knifeRadius, 1e-3) && startG.Join(ng) === Status.True) { ng.TempData.used = true; stack.push(ng); } } g.TempData = undefined; //总是保证被使用的造型这个数据为空 } } if (retGs.length !== this.grooves.length) { this.grooves = retGs; for (let g of this.grooves) g.CheckContourCurve(); } } /** * (步骤4.1) * 计算凹槽轮廓(可能分裂) * @param target 不拷贝修改 * @returns this[] 凹槽在本实体中正确的约束状态.(可能分裂成为多个) */ GrooveCheckContour(target) { let matrixToTarget = target.OCSInv.multiply(this.OCS); matrixToTarget.elements[14] = 0; //z->0 let thisShape = this.Shape.ApplyMatrix(matrixToTarget); let targetShape = new Shape(Contour.CreateContour([target.ContourCurve.Clone()], false)); let inters = thisShape.IntersectionBoolOperation(targetShape); if (inters.length === 1) { target.ContourCurve = inters[0].Outline.Curve; let grooves = [target]; target.GrooveCheckAll(grooves); return grooves; } else { let grooves = []; for (let contour of inters) { let ext = target.Clone().ClearDraw(); ext.ContourCurve = contour.Outline.Curve; ext.GrooveCheckAll(grooves); grooves.push(ext); } return grooves; } } /** * (步骤4.2) * 计算本实体被全身度的凹槽差集后正确的实体轮廓,和有可能的分裂实体 * @param splitEntitys 分裂出来的实体 * @returns [Status] Status : 消失不见 */ ContourCheckSubtract(splitEntitys) { let shapeManager = new ShapeManager(); shapeManager.AppendShapeList(new Shape(Contour.CreateContour(this.ContourCurve.Clone(), false))); let subtractShape = new ShapeManager(); let grooves = []; arrayRemoveIf(this.grooves, groove => { if (equaln(groove.thickness, this.thickness)) { let grooveCurve = groove.ContourCurve.Clone(); let matrixToLocal = this.OCSInv.multiply(groove.OCS); grooveCurve.ApplyMatrix(matrixToLocal); subtractShape.AppendShapeList(new Shape(Contour.CreateContour([grooveCurve], false))); grooves.push(groove); return true; } return false; }); shapeManager.SubstactBoolOperation(subtractShape); let shapes = shapeManager.ShapeList; //不做任何改变 if (shapeManager.ShapeCount === 1 && shapes[0].Holes.length === grooves.length) { this.grooves.push(...grooves); return true; } //分裂 for (let i = 1; i < shapeManager.ShapeCount; i++) { let ext = this.Clone(); let shape = shapes[i]; for (let hole of shape.Holes) { let groove = new ExtrudeSolid_1(); groove.OCS = this.OCS; groove.ContourCurve = hole.Curve; groove.thickness = this.thickness; ext.grooves.push(groove); } ext.ContourCurve = shape.Outline.Curve; ext.GrooveCheckAll(splitEntitys); ext.Update(); splitEntitys.push(ext); } if (shapes.length > 0) { let shape = shapes[0]; for (let hole of shape.Holes) { let groove = new ExtrudeSolid_1(); groove.OCS = this.OCS; groove.ContourCurve = hole.Curve; groove.thickness = this.thickness; this.grooves.push(groove); } if (!equaln(this.contourCurve.Area, shape.Outline.Area)) this.ContourCurve = shape.Outline.Curve; return true; } else return false; } /** * 无法知道修改了轮廓是否为更新到内部凹槽. * 无法知道修改了内部凹槽之后是否会更新到轮廓. * 所以默认全部校验内部的凹槽 */ GrooveCheckAll(splitEntitys) { if (this.IsLazyGrooveCheck) { this.IsNeedGrooveCheck = true; return; } this.IsNeedGrooveCheck = false; this.WriteAllObjectRecord(); //校验Z轴位置 arrayRemoveIf(this.grooves, g => { return this.GrooveCheckPosition(g) === Status.False; }); //清除全深洞的子槽 for (let g of this.grooves) { if (equaln(g.thickness, this.thickness, 1e-3)) { /* 此刻我们直接将它的子槽清空,虽然子槽可能将这个槽分裂成2个, 但是这样的情况只能在造型应用中才会产生 */ g.grooves.length = 0; } else arrayRemoveIf(g.grooves, subg => !equaln(g.thickness, subg.thickness, 1e-3)); } //合并 this.GrooveCheckMerge(); //修改本实体轮廓 if (this.grooves.some(g => equaln(g.thickness, this.thickness, 1e-3))) { if (!this.ContourCheckSubtract(splitEntitys)) { this.Erase(); return; } } //修正凹槽轮廓 let splitGrooves = []; let thisArea = this.contourCurve.Area; for (let i = 0; i < this.grooves.length; i++) { let g = this.grooves[i]; if (equaln(g.thickness, this.thickness, 1e-3)) splitGrooves.push(g); else { let gs = this.GrooveCheckContour(g); if (gs.length === 1) { let gg = gs[0]; if (gg.grooves.length === 0 && equaln(gg.contourCurve.Area, thisArea)) { //判断正反面 let p = gg.Position.applyMatrix4(this.OCSInv); if (equaln(p.z, 0)) { this.thickness -= gg.thickness; let n = this.Normal; n.multiplyScalar(gg.thickness); this._Matrix.elements[12] += n.x; this._Matrix.elements[13] += n.y; this._Matrix.elements[14] += n.z; } else { this.thickness -= gg.thickness; } this.grooves.splice(i, 1); this.GrooveCheckAll(splitEntitys); return; } } splitGrooves.push(...gs); } } this.grooves = splitGrooves; this.Update(); } /** 校验内部槽并且自动分裂 */ GrooveCheckAllAutoSplit(output = undefined) { let splitEntitys = []; this.GrooveCheckAll(splitEntitys); if (output) output.push(...splitEntitys); else if (this._Owner) { let record = this._Owner.Object; for (let e of splitEntitys) { record.Add(e); e.RepairRelevance(); } this.HandleSpliteEntitys(splitEntitys); } } //分裂后重新将排钻实体设置给不同的实体 HandleSpliteEntitys(splitEntitys) { } LazyGrooveCheckAll() { if (this.IsNeedGrooveCheck) this.GrooveCheckAllAutoSplit(); this.IsLazyGrooveCheck = false; } //#endregion //#region Draw GetPrintObject3D() { let geometry = new LineGeometry(); let lineSegments = new Float32Array(this.EdgeGeometry.attributes.position.array); let instanceBuffer = new InstancedInterleavedBuffer(lineSegments, 6, 1); geometry.setAttribute('instanceStart', new InterleavedBufferAttribute(instanceBuffer, 3, 0)); geometry.setAttribute('instanceEnd', new InterleavedBufferAttribute(instanceBuffer, 3, 3)); let line = new Line2(geometry, ColorMaterial.PrintLineMatrial); let mesh = new Mesh(this.MeshGeometry, ColorMaterial.GetPrintConceptualMaterial()); return [line, mesh]; } InitDrawObject(renderType = RenderType.Wireframe) { if (renderType === RenderType.Wireframe) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } else if (renderType === RenderType.Jig) { return new Object3D().add(...FastWireframe(this)); } else if (renderType === RenderType.Print) { return new Object3D().add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Physical2) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return new Object3D().add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } } get UCGenerator() { return boardUVGenerator; } get NeedUpdateRelevanceGroove() { if (!this.__CacheKnifVersion__) return true; for (let k of this.RelevanceKnifs) { if (!k || !k.Object) continue; if (this.__CacheKnifVersion__[k.Index] !== (k.Object).__UpdateVersion__) return true; } return false; } /** * 计算关联拉槽,更新绘制对象(MeshGeometry和EdgeGeometry) */ CalcRelevanceGroove() { var _a, _b, _c, _d, _e, _f; //避免Jig实体更新,导致性能暴跌. if (!this.Id) return; this.__CacheKnifVersion__ = {}; let knifs = []; this.GetRelevanceKnifes(knifs); if (knifs.length > 0) { for (let k of knifs) //复合实体(五金)的子实体没有id this.__CacheKnifVersion__[(_b = (_a = k.Id) === null || _a === void 0 ? void 0 : _a.Index) !== null && _b !== void 0 ? _b : (_c = k.__TempIndexVersion__) === null || _c === void 0 ? void 0 : _c.Index] = (_e = (_d = k.__TempIndexVersion__) === null || _d === void 0 ? void 0 : _d.Version) !== null && _e !== void 0 ? _e : k.__UpdateVersion__; let tempExtrude = this.Clone(); tempExtrude.RelevanceKnifs.length = 0; //避免递归 if (!this.ReadFileIng) tempExtrude.__OriginalId__ = this.Id; //在读取文件时不打印日志 let output = [tempExtrude]; let ok = tempExtrude.Subtract(knifs, output); this.__CacheSplitExtrudes = output; if (ok) { this.__CacheVolume__ = tempExtrude.Volume; let meshs = []; let edges = []; let inv = this.OCSInv; let diff = new Matrix4; for (let e2 of output) { diff.multiplyMatrices(inv, e2._Matrix); meshs.push(e2.MeshGeometry.applyMatrix4(diff)); edges.push(e2.EdgeGeometry.applyMatrix4(diff)); this.__CacheVolume__ += e2.Volume; } if (output.length === 1) { this._MeshGeometry = tempExtrude.MeshGeometry; this._EdgeGeometry = tempExtrude.EdgeGeometry; } else { this._MeshGeometry = BufferGeometryUtils.MergeBufferGeometries(meshs); this._MeshGeometry["IsMesh"] = true; this._EdgeGeometry = BufferGeometryUtils.MergeBufferGeometries(edges); } //我们加入一些拓展信息,以便排钻能够使用(或者其他的,比如发送到效果图?,BBS)(布局视口会直接添加实体到场景,所以我们只在这里设置OriginEntity) for (let i = 0; i < this.__CacheSplitExtrudes.length; i++) { this.__CacheSplitExtrudes[i].objectId = new ObjectId(this.Id.Index * -100 - i); this.__CacheSplitExtrudes[i].__OriginalEnt__ = this; } } else { let id = (_f = this.Id) !== null && _f !== void 0 ? _f : this.__OriginalId__; if (!this.ReadFileIng && id && this instanceof Board && this.__CacheVolume__ !== undefined && !equaln(this.__CacheVolume__, this.Volume)) Log(`${this.Name}(${id.Index})关联槽已逃离!`); this.__CacheVolume__ = undefined; this.__CacheSplitExtrudes = [this]; } } else { if (!this.ReadFileIng && this.Id && this instanceof Board && this.__CacheVolume__ !== undefined && !equaln(this.__CacheVolume__, this.Volume)) Log(`${this.Name}(${this.Id.Index})关联槽已逃离或者被清除!`); this.__CacheSplitExtrudes = [this]; this.__CacheVolume__ = undefined; } } /** * 如果实体被切割,那么将返回分裂的实体数组,否则返回自身 */ get SplitExtrudes() { if (this.NeedUpdateRelevanceGroove) this.Update(UpdateDraw.Geometry); //我们先直接更新绘制 if (this.NeedUpdateRelevanceGroove) //如果更新失败,那么我们更新这个槽(似乎也证明了我们没有绘制实体) this.CalcRelevanceGroove(); //注意,这也将更新绘制的实体(EdgeGeo,MeshGeo)(如果拆单也用这个,可能会带来性能损耗) return this.__CacheSplitExtrudes; } GetRelevanceKnifes(knifs) { var _a; for (let e of this.RelevanceKnifs) { if (!e.IsErase) knifs.push(e.Object); else if (this.__CacheKnifVersion__) this.__CacheKnifVersion__[e.Index] = (_a = e === null || e === void 0 ? void 0 : e.Object) === null || _a === void 0 ? void 0 : _a.__UpdateVersion__; } } ClearRelevance(en) { if (en) { let oldLen = this.RelevanceKnifs.length; arrayRemoveIf(this.RelevanceKnifs, id => !(id === null || id === void 0 ? void 0 : id.Object) || id.Index === en.Id.Index); if (this.RelevanceKnifs.length !== oldLen) arrayRemoveIf(en.RelevanceMeats, id => !(id === null || id === void 0 ? void 0 : id.Object) || id.Index === this.Id.Index); oldLen = this.RelevanceMeats.length; arrayRemoveIf(this.RelevanceMeats, id => !(id === null || id === void 0 ? void 0 : id.Object) || id.Index === en.Id.Index); if (oldLen !== this.RelevanceMeats.length) arrayRemoveIf(en.RelevanceKnifs, id => !(id === null || id === void 0 ? void 0 : id.Object) || id.Index === this.Id.Index); } else { for (let id of this.RelevanceKnifs) { let en = id.Object; if (en) arrayRemoveIf(en.RelevanceMeats, i => !(i === null || i === void 0 ? void 0 : i.Object) || i.Index === this.Id.Index); } for (let id of this.RelevanceMeats) { let en = id.Object; if (en) { arrayRemoveIf(en.RelevanceKnifs, i => !(i === null || i === void 0 ? void 0 : i.Object) || i.Index === this.Id.Index); en.Update(); } } this.RelevanceMeats.length = 0; this.RelevanceKnifs.length = 0; } this.Update(); } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; if (this.thickness <= 0) return new BufferGeometry(); this.CalcRelevanceGroove(); if (this._MeshGeometry) return this._MeshGeometry; let grooves = this.Grooves; if (grooves.every(g => equaln(g.thickness, this.thickness)) || grooves.length === 0) { let contour = this.ContourCurve.Clone(); let holes = []; let ocsInv = this.OCSInv; let alMatrix4 = new Matrix4(); if (grooves.length < 1000) for (let g of grooves) { alMatrix4.multiplyMatrices(ocsInv, g.OCSNoClone); let gContour = g.ContourCurve.Clone(); gContour.ApplyMatrix(alMatrix4); holes.push(Contour.CreateContour(gContour)); } let shape = new Shape(Contour.CreateContour(contour), holes); let extrudeSettings = { steps: 1, bevelEnabled: false, depth: this.Thickness, UVGenerator: this.UCGenerator, }; let geo = new ExtrudeGeometry(shape.Shape, extrudeSettings); geo.applyMatrix4(contour.OCSNoClone); this.UpdateUV(geo, contour.OCSNoClone); let bgeo = new BufferGeometry().fromGeometry(geo); bgeo["IsMesh"] = true; this._MeshGeometry = bgeo; return bgeo; } let builder = new ExtrudeGeometryBuilder(this); this._MeshGeometry = builder.MeshGeometry; if (grooves.length < MaxDrawGrooveCount) this._EdgeGeometry = builder.EdgeGeometry; this.UpdateUV(null, null); return this._MeshGeometry; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this.CalcRelevanceGroove(); if (this._EdgeGeometry) return this._EdgeGeometry; //这里我们超过100就用这个,为了性能 和MaxDrawGrooveCount不一致 if (this.grooves.length > 100 || this.grooves.every(g => equaln(g.thickness, this.thickness)) || this.grooves.length === 0) { let coords = FastExtrudeEdgeGeometry(this, this.ColorIndex, 20, true); let edgeGeo = new BufferGeometry(); edgeGeo.setAttribute('position', new Float32BufferAttribute(coords, 3)); this._EdgeGeometry = edgeGeo; return this._EdgeGeometry; } if (this._MeshGeometry) { this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } this.MeshGeometry; return this._EdgeGeometry; } UpdateUV(geo, ocs, isRev = false) { var _a; let mat; if (this.Material && this.Material.Object) mat = this.Material.Object; else mat = (_a = this.Db) === null || _a === void 0 ? void 0 : _a.DefaultMaterial; if (mat && mat.IsFull) { if (geo) ScaleUV2(geo, ocs, this.width, this.height, isRev); else this.UpdateBufferGeometryUvs(isRev); } else { if (geo) ScaleUV(geo); } } UpdateBufferGeometryUvs(isRev) { let uvs = this._MeshGeometry.attributes.uv; for (let i = 0; i < uvs.count; i++) { let x = uvs.getX(i) * 1e3; let y = uvs.getY(i) * 1e3; if (isRev) uvs.setXY(i, x / this.height, y / this.width); else uvs.setXY(i, x / this.width, y / this.height); } } DeferUpdate() { if (this.NeedUpdateFlag & UpdateDraw.Matrix) { //如果是Jig实体,那么就算它有关联切割,我们也不更新实体(因为似乎没必要?) if (this.Id && this.RelevanceKnifs.some(id => !id.IsErase)) this.NeedUpdateFlag |= UpdateDraw.Geometry; } super.DeferUpdate(); } UpdateDrawGeometry() { this.csg = undefined; if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (renderType === RenderType.Wireframe) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual) { return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.geometry = this.MeshGeometry; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Jig) { obj.add(...FastWireframe(this)); } else if (renderType === RenderType.Print) { return obj.add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Physical2) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return obj.add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } else if (renderType === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else { let mesh = obj; mesh.material = this.MeshMaterial; } } UpdateJigMaterial(color = 8) { } //#endregion //#region -------------------------File------------------------- /** * 简化的文件读取和写入,只写入必要的数据,没有id,没有其他版本号 */ ReadFileLite(file) { this.ReadFileOnly(file); this._Matrix.fromArray(file.Read()); } WriteFileLite(file) { this.WriteFileOnly(file); file.Write(this._Matrix.toArray()); } ReadFileOnly(file) { let ver = file.Read(); this.height = Number(file.Read()); this.width = Number(file.Read()); this.thickness = Number(file.Read()); this.isRect = file.Read(); this.contourCurve = file.ReadObject(); let grooveCount = file.Read(); this.grooves.length = 0; for (let i = 0; i < grooveCount; i++) { if (this.grooves[i] === undefined) this.grooves[i] = new ExtrudeSolid_1(); this.grooves[i].ReadFileLite(file); } this.knifeRadius = file.Read(); this.groovesAddLength = file.Read(); if (ver > 1) { this.groovesAddWidth = file.Read(); this.groovesAddDepth = file.Read(); } if (ver > 2) { this.RelevanceMeats.length = 0; this.RelevanceKnifs.length = 0; let count = file.Read(); for (let index = 0; index < count; index++) { let id = file.ReadSoftObjectId(); if (id) this.RelevanceMeats.push(id); } count = file.Read(); for (let index = 0; index < count; index++) { let id = file.ReadSoftObjectId(); if (id) this.RelevanceKnifs.push(id); } } } WriteFileOnly(file) { file.Write(3); file.Write(this.height); file.Write(this.width); file.Write(this.thickness); file.Write(this.isRect); file.WriteObject(this.ContourCurve); file.Write(this.grooves.length); for (let groove of this.grooves) groove.WriteFileLite(file); file.Write(this.knifeRadius); file.Write(this.groovesAddLength); file.Write(this.groovesAddWidth); file.Write(this.groovesAddDepth); //3 file.Write(this.RelevanceMeats.length); for (let id of this.RelevanceMeats) file.WriteSoftObjectId(id); file.Write(this.RelevanceKnifs.length); for (let id of this.RelevanceKnifs) file.WriteSoftObjectId(id); } //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); this.ReadFileOnly(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); this.WriteFileOnly(file); } }; ExtrudeSolid = ExtrudeSolid_1 = __decorate([ Factory ], ExtrudeSolid); function FastMeshGeometry(width, height, thickness) { let geo = new BoxGeometry(width, height, thickness); geo.translate(width * 0.5, height * 0.5, thickness * 0.5); return geo; } CADFactory.RegisterObjectAlias(ExtrudeSolid, "ExtureSolid"); function ProjectBoard(knifBoard, projectBoard) { let n1 = knifBoard.Normal; let n2 = projectBoard.Normal; if (!isPerpendicularityTo(n1, n2)) return; let p1 = projectBoard.Position; let p2 = n2.clone().multiplyScalar(projectBoard.Thickness).add(p1); let ocsInv = knifBoard.OCSInv; p1.applyMatrix4(ocsInv).setZ(0); p2.applyMatrix4(ocsInv).setZ(0); let dir = new Vector3().crossVectors(n1, n2).applyMatrix4(ocsInv.clone().setPosition(ZeroVec)); let lineLength = projectBoard.Width + projectBoard.Height; //两边之和大于第三边 let pts = [ dir.clone().multiplyScalar(lineLength).add(p1), dir.clone().multiplyScalar(-lineLength).add(p1), dir.clone().multiplyScalar(-lineLength).add(p2), dir.clone().multiplyScalar(lineLength).add(p2), ]; let pl = new Polyline(pts.map(p => { return { pt: AsVector2(p), bul: 0 }; })); pl.CloseMark = true; // pl.ApplyMatrix(knifBoard.OCS); return pl; } //用于翻转绘制出来的槽 const OverturnMatrix = new Matrix4().makeBasis(YAxis, ZAxis, XAxis); export { Circle, ContourTreeNode, CurveTapeShape, DepthType, ExtrudeBuildConfig, ExtrudeGeometryBuilder, ExtrudeSolid, FastMeshGeometry, Groove, MaxDrawGrooveCount, Polyline, TempPolyline }; //# sourceMappingURL=api.esm.js.map