import { Vector3, Matrix4, Box3, Color, FrontSide, MeshPhysicalMaterial, Object3D, MathUtils, Vector2 as Vector2$1, Quaternion, LineDashedMaterial, DoubleSide, MeshBasicMaterial, LineBasicMaterial, ShaderMaterial, BufferGeometry, Shape as Shape$1, ShapeGeometry, BufferAttribute, Line as Line$1, Plane as Plane$1, Line3, EllipseCurve, CatmullRomCurve3, Box2 as Box2$1, Float32BufferAttribute, Path as Path$1, Geometry, Face3, LineSegments, Mesh, CylinderBufferGeometry, InstancedInterleavedBuffer, InterleavedBufferAttribute, ShapeUtils, BoxBufferGeometry, ExtrudeGeometry, Group, BoxGeometry, Triangle, Euler, ShapeBufferGeometry, OrthographicCamera, PerspectiveCamera, AmbientLight as AmbientLight$1, DirectionalLightHelper, SphereBufferGeometry, Matrix3, DirectionalLight as DirectionalLight$1, HemisphereLight as HemisphereLight$1, MirroredRepeatWrapping, Texture, Scene, WebGLRenderTarget, Frustum, MeshNormalMaterial, TextureLoader, PointLight as PointLight$1, SphereGeometry, BackSide, RectAreaLight as RectAreaLight$1, SpotLight as SpotLight$1, ConeGeometry, PointsMaterial, Points, Raycaster } from 'three'; import geom3, { toPolygons, transform as transform$1 } from '@jscad/modeling/src/geometries/geom3'; import mat4 from '@jscad/modeling/src/maths/mat4'; import measureBoundingBox from '@jscad/modeling/src/measurements/measureBoundingBox'; import splitPolygonByPlane from '@jscad/modeling/src/operations/booleans/trees/splitPolygonByPlane'; import retessellate from '@jscad/modeling/src/operations/modifiers/retessellate'; import { transform } from '@jscad/modeling/src/operations/transforms'; import { iaop, end } from 'xaop'; import { Line2 } from 'three/examples/jsm/lines/Line2'; import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'; import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'; import poly3, { create } from '@jscad/modeling/src/geometries/poly3'; import Flatbush from 'flatbush'; import { measureAggregateVolume } from '@jscad/modeling/src/measurements'; import { intersect, scission } from '@jscad/modeling/src/operations/booleans'; import geom2 from '@jscad/modeling/src/geometries/geom2'; import extrudeLinear from '@jscad/modeling/src/operations/extrusions/extrudeLinear'; import extrudeRotate from '@jscad/modeling/src/operations/extrusions/extrudeRotate'; import { retessellate as retessellate$1 } from '@jscad/modeling/src/operations/modifiers'; import * as clipperLib from 'js-angusj-clipper/web'; import { JoinType, EndType } from 'js-angusj-clipper/web'; import polylabel from 'polylabel'; function observable() { }; function toJS() { }; /****************************************************************************** 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; } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * 删除数组中指定的元素,返回数组本身 * @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] 校验对象相等函数,如果相等 则删除e2 * @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; } /**统一板件属性key的命名,修改值会导致无法 .xxx该属性 */ var EBoardKeyList; (function (EBoardKeyList) { EBoardKeyList["Height"] = "height"; EBoardKeyList["Width"] = "width"; EBoardKeyList["Thick"] = "thickness"; EBoardKeyList["RoomName"] = "roomName"; EBoardKeyList["CabinetName"] = "cabinetName"; EBoardKeyList["PaperName"] = "paperName"; EBoardKeyList["BrName"] = "brName"; 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["Remarks"] = "remarks"; EBoardKeyList["ExtraRemarks"] = "extraRemarks"; EBoardKeyList["OpenDir"] = "openDir"; EBoardKeyList["GroovesAddLength"] = "GroovesAddLength"; EBoardKeyList["GroovesAddWidth"] = "GroovesAddWidth"; EBoardKeyList["GroovesAddDepth"] = "GroovesAddDepth"; EBoardKeyList["FrontDrill"] = "frontDrill"; EBoardKeyList["BackDrill"] = "backDrill"; EBoardKeyList["SelectRoomCabinet"] = "selectRoomCabinet"; })(EBoardKeyList || (EBoardKeyList = {})); /** * 坐标系运算. */ 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(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; } } 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["six"] = 6] = "six"; })(FractionDigitsType || (FractionDigitsType = {})); /** * 场景的渲染类型. */ var RenderType; (function (RenderType) { /** * 线框模式 */ RenderType[RenderType["Wireframe"] = 1] = "Wireframe"; /** * 概念 */ RenderType[RenderType["Conceptual"] = 2] = "Conceptual"; /** 概念(透明)*/ RenderType[RenderType["ConceptualTransparent"] = 82] = "ConceptualTransparent"; /** * 物理着色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["BigHoleFace"] = 81] = "BigHoleFace"; RenderType[RenderType["CustomNumber"] = 9] = "CustomNumber"; RenderType[RenderType["ModelGroove"] = 10] = "ModelGroove"; /******************************************** 在视口时的渲染模式 */ /** * 线框模式 */ 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["CustomNumberPrint"] = 109] = "CustomNumberPrint"; })(RenderType || (RenderType = {})); /** 实体的渲染颜色种类 */ const ColorMap = new Map([ ["原始颜色", 0], ["灰度单色", 1], ]); /** 渲染的状态 */ const RenderState = { /** 概念透明下的实体颜色 */ ConceptualColor: ColorMap.get("原始颜色"), }; function IsPhysical(renderType) { return renderType === RenderType.Physical || renderType === RenderType.Physical2 || renderType === RenderType.PhysicalPrint || renderType === RenderType.Physical2Print; } class IHostApplicationServices { get ProxyObject() { return this.__ProxyObject__; } set ProxyObject(obj) { this.__ProxyObject__ = obj; for (let key of IHostApplicationServices.__ProxyKeys__) { let v = this.__ProxyObject__[key]; if (v === undefined) throw "程序内部错误:未能代理变量!"; } } constructor() { this.IsRoomEntityVisible = true; //室内实体可见 /** 当前画笔的颜色索引 */ this.CurrentColorindex = 7; this.isShowLightShadow = true; //灯光阴影 (除太阳光外) this.ShowHistoryLog = true; this.Physical2EdgeColor = 7; //真实视图带线框的线框颜色 默认白色 this.ConceptualEdgeColor = 7; //概念线框的颜色 this.ConceptualOpacity = 1; //概念的透明度 this.ConceptualTransparentOpacity = 0.5; //概念(透明)的透明度 this.DrawWallBottomFace = false; //绘制底面 this.sealReserve = 0; //封边统计留头量 //#region _RenderType 渲染类型 this._renderType = RenderType.Wireframe; //#endregion //#region 排钻数据 this.DrillConfigs = new Map; //#endregion //#region 开启排钻反应器 this.openDrillingReactor = true; //#endregion //#region 封边数据 this.sealingColorMap = []; //#endregion //#region 显示纹路 this.showLines = false; //#endregion //#region 显示开门方向纹路 this.showOpenDirLines = false; //#endregion //#region 开门方向纹路配置 this.doorLinesOption = { physicalShowLines: false, //真实视图显示开门方向纹路 physicalShowLines2: false, //真实视图带线框显示开门方向纹路 reverseOpenDirLines: false, //反转开门方向纹路 }; //#endregion //#region 显示二维刀路路径线条 this.show2DPathLine = false; //#endregion //#region 显示二维刀路 this.show2DPathObject = false; //#endregion //#region 偏心轮过滤 this.forceFilterPxl = false; //#endregion this.chaidanOption = { changXiuBian: 6, duanXiuBian: 6, useDefaultRad: false, radius: 2.5, modeling2HoleRad: 20, //圆造型小于等于该值拆成孔数据 isCheckInterfere: false, noModeingData: "", //非造型遭数据 statTk: false, //统计通孔排钻 statSt: false, //统计双头排钻 drillRemark: false, //拆单获取排钻备注 checkSealType: "1", sealMaxValue: 10, //封边最大值 sealValues: '', //封边值列表 hardwareExpressionFormattingAccuracy: 2, //复合实体表达式值格式化精度 partialSplitValueCanTakesEffect: false, cancelHoleProcessing: false, //填写拆单尺寸板件取消孔槽加工 isCheckCustomBoardNumber: false, //是否开启自动板件编号校验 }; this.viewSize = { minViewHeight: 1e-3, maxViewHeight: 3e6, zoomSpeed: 0.6 }; this.cursorSize = { D2: 1000, D3: 100, SquareSize: 10, }; this.dimTextHeight = 60; this.boardCustomNumberTextHeight = 60; //板编号字体高度 this.lineWidth = 2; //打印线框 this.fractionDigitsType = FractionDigitsType.two; this.throughModelSegmentedDrill = false; //挖穿造型分段排钻 this.autoDeviation = false; //排钻碰撞智能偏移 this.autoDeviationMinDist = 200; //排钻碰撞智能偏移最小排钻面长度 this.smallGrooveThreshold = 900; //全深槽影响阈值 } ; static GetInstance() { if (this._SingleInstance) return this._SingleInstance; this._SingleInstance = new IHostApplicationServices; return this._SingleInstance; } //加载贴图,将在index.ts中设置实现的函数 async LoadDefaultExr() { return; } async LoadMetalEnv() { return; } } IHostApplicationServices.__ProxyKeys__ = []; //代理对象,当代理对象存在时,获取内部的值指向代理对象 __decorate([ ProxyValue ], IHostApplicationServices.prototype, "isShowLightShadow", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "Physical2EdgeColor", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "ConceptualEdgeColor", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "ConceptualOpacity", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "ConceptualTransparentOpacity", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "_renderType", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "DrillConfigs", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "openDrillingReactor", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "sealingColorMap", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "showLines", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "showOpenDirLines", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "doorLinesOption", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "show2DPathLine", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "show2DPathObject", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "uese", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "forceFilterPxl", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "chaidanOption", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "viewSize", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "cursorSize", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "dimTextHeight", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "boardCustomNumberTextHeight", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "lineWidth", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "fractionDigitsType", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "throughModelSegmentedDrill", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "autoDeviation", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "autoDeviationMinDist", void 0); __decorate([ ProxyValue ], IHostApplicationServices.prototype, "smallGrooveThreshold", void 0); let HostApplicationServices = IHostApplicationServices.GetInstance(); //将属性字段指向代理对象 function ProxyValue(target, propertyKey, descriptor) { let privateKey = '__' + propertyKey; IHostApplicationServices.__ProxyKeys__.push(propertyKey); Object.defineProperty(target, propertyKey, { set: function (value) { if (this.ProxyObject) this.ProxyObject[propertyKey] = value; else this[privateKey] = value; }, get: function () { if (this.ProxyObject) return this.ProxyObject[propertyKey]; return this[privateKey]; }, enumerable: true, configurable: 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; } /** 用于表示跟随图层的颜色索引 */ const ByLayerColorIndex = 260; var Status; (function (Status) { Status[Status["False"] = 0] = "False"; Status[Status["True"] = 1] = "True"; Status[Status["Side"] = 2] = "Side"; 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 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 BoxIsSolid(this, 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; } containsBox(box, fuzz = 1e-8) { return this.min.x <= box.min.x + fuzz && box.max.x <= this.max.x + fuzz && this.min.y <= box.min.y + fuzz && box.max.y <= this.max.y + fuzz && this.min.z <= box.min.z + fuzz && box.max.z <= this.max.z + fuzz; } 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; } let size = new Vector3; function BoxIsSolid(box, minSize = 1) { box.getSize(size); return size.x > minSize && size.y > minSize && size.z > minSize; } const ISPROXYKEY = "_isProxy"; /** * 自动对CADObject的属性添加属性记录器,自动调用 `WriteAllObjectRecord` * 如果属性是数组,那么自动添加`Proxy`. * 可以使用`ISPROXYKEY`覆盖这个函数的代理行为(使用CADObject.CreateProxyArray快速覆盖) * * ! 仅在{数组}或者{值}类型上使用,如果是Object,请使用AutoRecordObject * * @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); // 可能的优化,没有启用这个代码 // arr.length = value.length; // for (let i = 0; i < value.length; i++) // arr[i] = value[i]; } } else { let oldv = this[privateKey]; if (oldv !== value) { this.WriteAllObjectRecord(); this[privateKey] = value; } } }, get: function () { return this[privateKey]; }, enumerable: true, configurable: true }); } function AutoRecordObject(target, property, descriptor) { let privateKey = '__' + property; Object.defineProperty(target, property, { set: function (value) { if (value instanceof Object) { 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; return Reflect.get(target, key, receiver); } }); } else { let obj = this[privateKey]; for (let key in value) { if (obj[key] !== value[key]) { this.WriteAllObjectRecord(); obj[key] = value[key]; } } } } else { let oldv = this[privateKey]; if (oldv !== value) { this.WriteAllObjectRecord(); this[privateKey] = value; } } }, get: function () { return this[privateKey]; }, enumerable: true, configurable: true }); } //const UE_REX_DEL = /_.*/g; /** * CAD对象工厂,通过注册 和暴露的创建方法,动态创建对象 */ class CADFactory { constructor() { this.objectNameMap = new Map(); } static RegisterObject(C) { //this.factory.objectNameMap.set(C.name.replace(UE_REX_DEL, ""), 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, _Object) { this._Index = _Index; this._Object = _Object; this._RelevancyType = RelevancyType.General; } get IsErase() { return !this._Object || this._Object.IsErase; } set Object(obj) { this._Object = obj; } get Object() { return this._Object; } 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); } if (!obj) console.error("未注册类名:", className); 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; } WriteBool(v) { this.Write(v ? 1 : 0); } WriteVec3(v3) { this._datas.push(v3.x, v3.y, v3.z); } Write2dArray(arr) { this._datas.push(arr.length, ...arr.flat()); } Read() { return this._datas[this.readIndex++]; } ReadBool() { return Boolean(this.Read()); } ReadArray(count) { let arr = this._datas.slice(this.readIndex, this.readIndex + count); this.readIndex += count; return arr; } Read2dArray() { let count = this.Read(); let arr = this._datas.slice(this.readIndex, this.readIndex + count * 2); this.readIndex += count * 2; let arr2d = []; for (let i = 0; i < arr.length; i = i + 2) arr2d.push([arr[i], arr[i + 1]]); return arr2d; } //------------------------ID序列化------------------------ /* Id关联分为三种情况: 1.普通关联:关联对象未被拷贝时,关联到空对象. 2.软关联 :关联对象未被拷贝时,关联到原先的对象. 3.硬关联 :对象被拷贝时,被关联的对象必须也被拷贝. */ //-------1.普通关联 WriteObjectId(id) { if (id) // id?.Object 为什么没有这么写? 这么写会精简图纸,但是不确定会不会引发新的问题? 其他地方有没有可能依赖这个特性实现一些功能? 比如排钻,如果排钻被删除,我们依然知道排钻的顺序?(曾经拥有?) 暂时不优化似乎也没事? 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 { ReadFile(file) { this.isErase = file.ReadBool(); return this; } WriteFile(file) { file.WriteBool(this.isErase); return this; } constructor(isErase = true) { this.isErase = isErase; } }; 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; if (this.objectId) this.objectId.Object = undefined; //解除绑定(关联bug:绘制关联切割板后删除切割板,在pu时调用了这个,此时obj被删除但是还会被拷贝,导致错误崩溃) } //对象被彻底遗弃 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(); let id = file.ReadObjectId(); if (!this.objectId && id) //避免CopyFrom时错误的修改自身Id { this.objectId = id; id.Object = this; } this._isErase = file.ReadBool(); if (ver > 1) this._Owner = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); file.WriteObjectId(this.objectId); file.WriteBool(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(); f.database = this._db; //这样保证了关联的数据(例如材质) 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 { //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); this.undoData = file.ReadObject(); this.redoData = file.ReadObject(); if (ver === 1) file.ReadObject(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); file.WriteObject(this.undoData); file.WriteObject(this.redoData); } }; HistorycRecord = __decorate([ Factory ], HistorycRecord); const USE_WORLD_UV = "USE_WORLD_UV"; const U_WORLD_REP = "u_w_rep"; const V_WORLD_REP = "v_w_rep"; const U_WORLD_MOVE = "u_w_move"; const V_WORLD_MOVE = "v_w_move"; const U_WORLD_RO = "v_w_ro"; var EMetalsType; (function (EMetalsType) { EMetalsType["Metals"] = "\u4E94\u91D1"; EMetalsType["Comp"] = "\u7EC4\u4EF6"; })(EMetalsType || (EMetalsType = {})); class SymbolTableRecord extends CADObject { constructor() { super(...arguments); this.name = ""; } get Name() { return this.name; } set Name(name) { if (this.name === name) return; let undoData = this.UndoRecord(); if (undoData) { let hr = new HistorycRecord(); hr.undoData = new NameData(this.name); hr.redoData = new NameData(name); undoData.WriteObjectHistoryPath(this, hr); } if (this.Owner) { let symbolTable = this.Owner.Object; if (!symbolTable.ChangeRecordName(this, name)) return; } this.name = name; } Add(obj, isCheckObjectCleanly = true) { return Status.False; } WriteFile(file) { super.WriteFile(file); file.Write(1); file.Write(this.name); } ReadFile(file) { super.ReadFile(file); file.Read(); this.name = file.Read(); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); if (undoData instanceof NameData) { if (this.name === undoData.name) return; if (this.Owner) { let symbolTable = this.Owner.Object; if (!symbolTable.ChangeRecordName(this, undoData.name)) return; } this.name = undoData.name; } } } /** * 记录当前Name的序列化数据 */ let NameData = class NameData { ReadFile(file) { this.name = file.Read(); return this; } WriteFile(file) { file.Write(this.name); return this; } constructor(name) { this.name = name; } }; NameData = __decorate([ Factory ], NameData); let MaterialTableRecord = class MaterialTableRecord extends SymbolTableRecord { }; MaterialTableRecord = __decorate([ Factory ], MaterialTableRecord); var UVType; (function (UVType) { UVType[UVType["LocalUV"] = 0] = "LocalUV"; UVType[UVType["WorldUV"] = 1] = "WorldUV"; })(UVType || (UVType = {})); //有关于pbr材质的属性解释说明: https://knowledge.autodesk.com/zh-hans/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2021/CHS/3DSMax-Lighting-Shading/files/GUID-18087194-B2A6-43EF-9B80-8FD1736FAE52-htm.html let PhysicalMaterialRecord = class PhysicalMaterialRecord extends MaterialTableRecord { get UseWorldUV() { return this.UVType === UVType.WorldUV; } set UseWorldUV(b) { this.UVType = b ? UVType.WorldUV : UVType.LocalUV; } constructor() { super(); this.type = "木纹"; this.ref = ""; //参考材质,当存在这个变量时,使用ue材质 //基础色 this.color = "#ffffff"; //基础色 //#region 基础色附加 默认折叠 this.baseColorLuminance = 0; //基础色亮度 默认0 范围±1 this.baseColorLightColor = new Color(0.5, 0.5, 0.5); //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 this.baseColorDarkColor = new Color(0, 0, 0); //基础色_暗部颜色 默认 0黑色 范围RGB颜色 this.baseColorSaturability = 1; //基础色饱和度 默认1 范围0-10; //#endregion //透明 this.transparent = false; //透明度 0-1 this.opacity = 1; //不透明度. //#region 透明度附加 默认折叠 this.opacityContrast = 1; //不透明度对比 默认1 this.opacityBorder = 1; //不透明度边界 默认1 this.opacityMaximum = 1; //不透明度最大值 默认1 this.opacityMinimum = 0.3; // 不透明度最小值 默认0.3 //#endregion //折射 this.refraction = 1; //玻璃折射 默认1 this.matalness = 0; //金属性 0-1 this.bumpScale = 0; //凹凸比例 UE:法线强度 默认0 范围0-20 this.roughness = 0.2; //粗糙度 0-1 this.specular = 1; //高光 范围0-1 this.selfLuminous = 0; //自发光强度 0-200 this.useMap = true; //#region 法线贴图和粗糙贴图默认折叠 this.useBumpMap = true; this.useRoughnessMap = true; //#endregion this.IsFull = false; //完全平铺板(此时修改板的uv) this.side = FrontSide; //双面 this.UVType = UVType.LocalUV; //#region 菲涅尔 默认折叠(绒毛?) 反射?(皮革 布料中可用) this.fresnelPO = 1; //菲涅尔对比度 默认1 范围-1至10 this.fresnelST = 1; //菲涅尔强度 默认1 范围0至20 this.fresnelLuminance = 1; //菲涅尔亮度 默认1 范围0至20 this.fresnelLightColor = new Color(1, 1, 1); //菲涅尔亮部颜色 默认白色 范围RGB this.fresnelDarkColor = new Color(1, 1, 1); //菲涅尔暗部颜色 默认白色 范围RGB //#endregion this.sharpen = 1; //锐化 默认1 范围0-20 默认折叠 this.UWroldRep = 1; this.VWroldRep = 1; this.UWroldRo = 0; this.UWorldMove = 0; this.VWorldMove = 0; this.depthTest = true; //深度测试(默认true)(弃用(不在UI上显示) this._goodsInfo = { name: "", color: "", material: "", }; // 扩展材质的属性 this._hardwareAttributeInfo = { name: { value: "", checked: false }, roomName: { value: "", checked: false }, cabinetName: { value: "", checked: false }, factory: { value: "", checked: false }, comments: { value: "", checked: false }, actualExpr: { value: "", checked: false }, brand: { value: "", checked: false }, model: { value: "", checked: false }, spec: { value: "", checked: false }, count: { value: "", checked: false }, unit: { value: "", checked: false }, DataList: { value: Array.from({ length: 20 }, () => ["", ""]), checked: false }, type: { value: EMetalsType.Metals, checked: false }, goods: { goodsSn: "", goodsId: "", checked: false, }, }; this._isMaterialLock = false; //材质锁 this.material = new MeshPhysicalMaterial({}); Object.defineProperty(this.material, "ObjectId", { get: () => { return this?.objectId?.Index; } }); } async Update() { await this.PhysicalMaterialUpdate(); } //因为Asset.tsx监听了Update的事件,然后又要去调用这个,导致重复监听,所以分离出这个函数 async PhysicalMaterialUpdate() { this.material[USE_WORLD_UV] = this.UseWorldUV; if (this.material[USE_WORLD_UV]) { this.material[U_WORLD_REP] = this.UWroldRep; this.material[V_WORLD_REP] = this.VWroldRep; this.material[U_WORLD_RO] = this.UWroldRo; this.material[U_WORLD_MOVE] = this.UWorldMove; this.material[V_WORLD_MOVE] = this.VWorldMove; } if (!this.material.color) this.material.color = new Color(this.color); else this.material.color.set(this.color); this.material.transparent = this.transparent; if (this.type === "玻璃") { this.material.metalness = 0.2; this.material.reflectivity = Math.abs(this.refraction); } else this.material.metalness = this.matalness; this.material.side = this.side; this.material.opacity = Math.max(0.1, this.opacity); this.material.depthTest = this.depthTest; this.material.bumpScale = this.bumpScale; this.material.roughness = this.roughness; if (this.material.metalness > 0.9) HostApplicationServices.LoadMetalEnv().then(env => { this.material.envMap = env; this.material.needsUpdate = true; }); else HostApplicationServices.LoadDefaultExr().then(exr => { this.material.envMap = exr; this.material.needsUpdate = true; }); this.material.needsUpdate = true; if (this.useMap && this.map?.Object && !this.map.IsErase) { let map = this.map.Object; let texture = map.GetThreeTexture(); await map.WaitUpdate(); this.material.map = texture; this.material.needsUpdate = true; } else this.material.map = undefined; if (this.type === "自发光") { this.material.emissiveIntensity = this.selfLuminous; this.material.emissive.copy(this.material.color); this.material.emissiveMap = this.material.map; } else { this.material.emissiveIntensity = 1; this.material.emissive.setRGB(0, 0, 0); this.material.emissiveMap = undefined; } if (this.useMap && this.useBumpMap && this.bumpMap?.Object && !this.bumpMap.IsErase) { let map = this.bumpMap.Object; let texture = map.GetThreeTexture(); await map.WaitUpdate(); this.material.bumpMap = texture; this.material.needsUpdate = true; } else this.material.bumpMap = undefined; if (this.useMap && this.roughnessMap?.Object && !this.roughnessMap.IsErase) { let map = this.roughnessMap.Object; let texture = map.GetThreeTexture(); await map.WaitUpdate(); this.material.roughnessMap = texture; this.material.needsUpdate = true; } else this.material.roughnessMap = undefined; this.material.needsUpdate = true; this.AsyncUpdated(); } get Material() { return this.material; } get GoodsInfo() { return this._goodsInfo; } set GoodsInfo(info) { if (info.color === this._goodsInfo.color && info.material === this._goodsInfo.material && info.name === this._goodsInfo.name) return; this.WriteAllObjectRecord(); Object.assign(this._goodsInfo, info); } get HardwareAttributeInfo() { return this._hardwareAttributeInfo; } set HardwareAttributeInfo(info) { this.WriteAllObjectRecord(); this._hardwareAttributeInfo = JSON.parse(JSON.stringify(info)); } get IsMaterialLock() { return !this.IsErase && this._isMaterialLock; } set IsMaterialLock(v) { if (this._isMaterialLock === v) return; this.WriteAllObjectRecord(); this._isMaterialLock = v; } //#region -------------------------File------------------------- ReadFile(file) { super.ReadFile(file); let ver = file.Read(); if (ver === 1) this.name = file.Read(); this.color = file.Read(); this.transparent = file.Read(); this.matalness = file.Read(); this.opacity = file.Read(); this.depthTest = file.Read(); this.map = file.ReadObjectId(); this.bumpMap = file.ReadObjectId(); this.bumpScale = file.Read(); this.roughnessMap = file.ReadObjectId(); this.roughness = file.Read(); this.useMap = file.Read(); this.useBumpMap = file.Read(); this.useRoughnessMap = file.Read(); if (ver <= 2) file.Read(); if (ver > 2) { this._goodsInfo.name = file.Read(); this._goodsInfo.material = file.Read(); this._goodsInfo.color = file.Read(); } if (ver > 3) this.IsFull = file.Read(); if (ver > 4) { this.baseColorLuminance = file.Read(); this.baseColorLightColor.r = file.Read(); this.baseColorLightColor.g = file.Read(); this.baseColorLightColor.b = file.Read(); this.baseColorDarkColor.r = file.Read(); this.baseColorDarkColor.g = file.Read(); this.baseColorDarkColor.b = file.Read(); this.baseColorSaturability = file.Read(); this.opacityContrast = file.Read(); this.opacityBorder = file.Read(); this.opacityMaximum = file.Read(); this.opacityMinimum = file.Read(); this.specular = file.Read(); this.selfLuminous = file.Read(); this.fresnelPO = file.Read(); this.fresnelST = file.Read(); this.fresnelLuminance = file.Read(); this.fresnelLightColor.r = file.Read(); this.fresnelLightColor.g = file.Read(); this.fresnelLightColor.b = file.Read(); this.fresnelDarkColor.r = file.Read(); this.fresnelDarkColor.g = file.Read(); this.fresnelDarkColor.b = file.Read(); this.sharpen = file.Read(); if (ver > 5) this.UVType = file.Read(); if (ver > 6) this.type = file.Read(); if (ver > 7) this.ref = file.Read(); if (ver > 8) { this.UWroldRep = file.Read(); this.VWroldRep = file.Read(); this.UWroldRo = file.Read(); this.UWorldMove = file.Read(); this.VWorldMove = file.Read(); } if (ver > 9) { this.refraction = file.Read(); this.side = file.Read(); } if (ver > 10) { this._isMaterialLock = file.Read(); } if (ver > 11) { this._hardwareAttributeInfo.name.value = file.Read(); this._hardwareAttributeInfo.name.checked = file.Read(); this._hardwareAttributeInfo.roomName.value = file.Read(); this._hardwareAttributeInfo.roomName.checked = file.Read(); this._hardwareAttributeInfo.cabinetName.value = file.Read(); this._hardwareAttributeInfo.cabinetName.checked = file.Read(); this._hardwareAttributeInfo.factory.value = file.Read(); this._hardwareAttributeInfo.factory.checked = file.Read(); this._hardwareAttributeInfo.comments.value = file.Read(); this._hardwareAttributeInfo.comments.checked = file.Read(); this._hardwareAttributeInfo.actualExpr.value = file.Read(); this._hardwareAttributeInfo.actualExpr.checked = file.Read(); this._hardwareAttributeInfo.brand.value = file.Read(); this._hardwareAttributeInfo.brand.checked = file.Read(); this._hardwareAttributeInfo.model.value = file.Read(); this._hardwareAttributeInfo.model.checked = file.Read(); this._hardwareAttributeInfo.spec.value = file.Read(); this._hardwareAttributeInfo.spec.checked = file.Read(); this._hardwareAttributeInfo.count.value = file.Read(); this._hardwareAttributeInfo.count.checked = file.Read(); this._hardwareAttributeInfo.unit.value = file.Read(); this._hardwareAttributeInfo.unit.checked = file.Read(); let DataListCount = file.Read(); for (let i = 0; i < DataListCount; i++) { let d = [ file.Read(), file.Read() ]; this._hardwareAttributeInfo.DataList.value[i] = d; } this._hardwareAttributeInfo.DataList.checked = file.Read(); this._hardwareAttributeInfo.type.value = file.Read(); this._hardwareAttributeInfo.type.checked = file.Read(); this._hardwareAttributeInfo.goods.goodsSn = file.Read(); this._hardwareAttributeInfo.goods.goodsId = file.Read(); this._hardwareAttributeInfo.goods.checked = file.Read(); } } this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(12); file.Write(this.color); file.Write(this.transparent); file.Write(this.matalness); file.Write(this.opacity); file.Write(this.depthTest); file.WriteHardObjectId(this.map); file.WriteHardObjectId(this.bumpMap); file.Write(this.bumpScale); file.WriteHardObjectId(this.roughnessMap); file.Write(this.roughness); file.Write(this.useMap); file.Write(this.useBumpMap); file.Write(this.useRoughnessMap); file.Write(this._goodsInfo.name); file.Write(this._goodsInfo.material); file.Write(this._goodsInfo.color); file.Write(this.IsFull); //ver 5 file.Write(this.baseColorLuminance); file.Write(this.baseColorLightColor.r); file.Write(this.baseColorLightColor.g); file.Write(this.baseColorLightColor.b); file.Write(this.baseColorDarkColor.r); file.Write(this.baseColorDarkColor.g); file.Write(this.baseColorDarkColor.b); file.Write(this.baseColorSaturability); file.Write(this.opacityContrast); file.Write(this.opacityBorder); file.Write(this.opacityMaximum); file.Write(this.opacityMinimum); file.Write(this.specular); file.Write(this.selfLuminous); file.Write(this.fresnelPO); file.Write(this.fresnelST); file.Write(this.fresnelLuminance); file.Write(this.fresnelLightColor.r); file.Write(this.fresnelLightColor.g); file.Write(this.fresnelLightColor.b); file.Write(this.fresnelDarkColor.r); file.Write(this.fresnelDarkColor.g); file.Write(this.fresnelDarkColor.b); file.Write(this.sharpen); //ver 6 file.Write(this.UVType); //ver 7 file.Write(this.type); //ver8 file.Write(this.ref); //ver9 file.Write(this.UWroldRep); file.Write(this.VWroldRep); file.Write(this.UWroldRo); file.Write(this.UWorldMove); file.Write(this.VWorldMove); //ver10 file.Write(this.refraction); file.Write(this.side); //ver11 file.Write(this.IsMaterialLock); // ver12 file.Write(this._hardwareAttributeInfo.name.value); file.Write(this._hardwareAttributeInfo.name.checked); file.Write(this._hardwareAttributeInfo.roomName.value); file.Write(this._hardwareAttributeInfo.roomName.checked); file.Write(this._hardwareAttributeInfo.cabinetName.value); file.Write(this._hardwareAttributeInfo.cabinetName.checked); file.Write(this._hardwareAttributeInfo.factory.value); file.Write(this._hardwareAttributeInfo.factory.checked); file.Write(this._hardwareAttributeInfo.comments.value); file.Write(this._hardwareAttributeInfo.comments.checked); file.Write(this._hardwareAttributeInfo.actualExpr.value); file.Write(this._hardwareAttributeInfo.actualExpr.checked); file.Write(this._hardwareAttributeInfo.brand.value); file.Write(this._hardwareAttributeInfo.brand.checked); file.Write(this._hardwareAttributeInfo.model.value); file.Write(this._hardwareAttributeInfo.model.checked); file.Write(this._hardwareAttributeInfo.spec.value); file.Write(this._hardwareAttributeInfo.spec.checked); file.Write(this._hardwareAttributeInfo.count.value); file.Write(this._hardwareAttributeInfo.count.checked); file.Write(this._hardwareAttributeInfo.unit.value); file.Write(this._hardwareAttributeInfo.unit.checked); const filteredDataList = this._hardwareAttributeInfo.DataList.value.filter(item => item[0] || item[1]); // 过滤掉值为空的项 file.Write(filteredDataList.length); for (let i = 0; i < filteredDataList.length; i++) { file.Write(this._hardwareAttributeInfo.DataList.value[i][0]); file.Write(this._hardwareAttributeInfo.DataList.value[i][1]); } file.Write(this._hardwareAttributeInfo.DataList.checked); file.Write(this._hardwareAttributeInfo.type.value); file.Write(this._hardwareAttributeInfo.type.checked); file.Write(this._hardwareAttributeInfo.goods.goodsSn); file.Write(this._hardwareAttributeInfo.goods.goodsId); file.Write(this._hardwareAttributeInfo.goods.checked); } }; __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "type", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "ref", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "color", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "baseColorLuminance", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "baseColorLightColor", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "baseColorDarkColor", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "baseColorSaturability", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "transparent", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "opacity", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "opacityContrast", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "opacityBorder", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "opacityMaximum", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "opacityMinimum", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "refraction", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "matalness", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "bumpScale", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "roughness", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "specular", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "selfLuminous", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "useMap", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "map", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "useBumpMap", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "bumpMap", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "useRoughnessMap", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "roughnessMap", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "IsFull", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "side", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "UVType", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "fresnelPO", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "fresnelST", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "fresnelLuminance", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "fresnelLightColor", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "fresnelDarkColor", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "sharpen", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "UWroldRep", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "VWroldRep", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "UWroldRo", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "UWorldMove", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "VWorldMove", void 0); __decorate([ AutoRecord ], PhysicalMaterialRecord.prototype, "depthTest", void 0); PhysicalMaterialRecord = __decorate([ Factory ], PhysicalMaterialRecord); var Entity_1; /** * Entity 是所有图元的基类,绘制的实体都集成该类. */ let Entity = Entity_1 = class Entity extends CADObject { constructor() { super(); this.IsEmbedEntity = false; //当这个值为true时,这个实体是复合实体的内嵌实体 /** * 该实体的只有一个渲染类型,任何渲染类型都一个样 */ this.OnlyRenderType = false; this.HasEdgeRenderType = false; //拥有封边检查绘制模式 this.HasPlaceFaceRenderType = false; //拥有排版面绘制模式 this.HasBigHoleFaceRenderType = false; //拥有大孔面绘制模式 this._CacheDrawObject = new Map(); this._Color = HostApplicationServices.CurrentColorindex; //自身坐标系 this._Matrix = new Matrix4(); //模块空间的标系 this._SpaceOCS = new Matrix4(); this._Visible = true; this._VisibleInRender = true; //在渲染器中显示 this._Freeze = false; //冻结(无法被选中) this._LockMaterial = false; // 锁定材质 //加工组 this.ProcessingGroupList = []; /** * 当AutoUpdate为false时,记录需要更新的标志. * 以便延迟更新时找到相应的更新标志. */ this.NeedUpdateFlag = UpdateDraw.None; this.AutoUpdate = true; this._drawObject = undefined; //实体绘制更新版本号 this.__UpdateVersion__ = 0; if (this._drawObject) throw "未知错误 实体内部已经有对象"; this._drawObject = undefined; } get CacheDrawObject() { return this._CacheDrawObject; } get SpaceCSNoClone() { return this._SpaceOCS; } get SpaceOCS() { return this._SpaceOCS.clone(); } set SpaceOCS(m) { this.WriteAllObjectRecord(); this._SpaceOCS.copy(m); } get SpaceOCSInv() { return new Matrix4().getInverse(this._SpaceOCS); } get Freeze() { return this._Freeze; } set Freeze(f) { if (f === this._Freeze) return; this.WriteAllObjectRecord(); this._Freeze = f; this.Update(UpdateDraw.Material); } get VisibleInRender() { return this._VisibleInRender; } set VisibleInRender(v) { if (this._VisibleInRender !== v) { this.WriteAllObjectRecord(); this._VisibleInRender = v; } } get LockMaterial() { return this._LockMaterial; } set LockMaterial(f) { if (f === this._LockMaterial) return; this.WriteAllObjectRecord(); this._LockMaterial = f; } set Material(materialId) { if (this.LockMaterial) return; if (materialId === this._MaterialId) return; if (this._db && materialId?.Object) //我们放宽校验,当图形未加入到图纸时,我们允许它任意设置材质 { if (!(materialId.Object instanceof PhysicalMaterialRecord)) throw "程序内部错误!设置材质错误:该对象不是材质"; if (materialId.Object.Db !== this.Db) throw "程序内部错误!设置材质错误:不同图纸间材质"; } const _obj = this._MaterialId?.Object; if (_obj?.IsMaterialLock) return; this.WriteAllObjectRecord(); this._MaterialId = materialId; for (let [type, obj] of this._CacheDrawObject) this.UpdateDrawObjectMaterial(type, obj); } get Material() { if (this._MaterialId?.IsErase) return; return this._MaterialId; } GetMaterialSlots() { } IsMtlLockAtSlot(slotIndex) { if (this.LockMaterial) return true; return this.Material?.Object?.IsMaterialLock; } SetMaterialAtSlot(mtl, slotIndex) { if (this.IsMtlLockAtSlot()) return; this.WriteAllObjectRecord(); this.Material = mtl; } GetMtlLockedStatus() { const locked = this.Material?.Object?.IsMaterialLock; return { allMtlLocked: locked, partMtlLocked: locked, }; } /** * 获取实体的 PhysicalMaterialRecord * @param {boolean} [containErased=false] 是否包含被删除的材质 * @return {*} {PhysicalMaterialRecord[]} * @memberof Entity */ GetPhyMtlRecords(containErased = false) { if (this._MaterialId) { if (containErased || !this._MaterialId.IsErase) return [this._MaterialId.Object]; } return []; } get HasLayer() { return this._Layer?.Object !== undefined; } get Layer() { return this._Layer?.Object ? this._Layer : HostApplicationServices.Database.LayerTable.Current ?? HostApplicationServices.Database.DefaultLayer.Id; } set Layer(id) { if (!id || id === this._Layer) return; this.WriteAllObjectRecord(); const oldLayerIsOff = this._Layer?.Object?.IsOff; const oldLayerIsLocked = this._Layer?.Object?.IsLocked; const oldLayerColorIndex = this._Layer?.Object?.ColorIndex; this._Layer = id; // 更新隐藏 if (id.Object.IsOff !== oldLayerIsOff) this.UpdateVisible(); // 更新锁定 if (id.Object.IsLocked !== oldLayerIsLocked) { this.Freeze = id.Object.IsLocked; for (let [type, obj] of this._CacheDrawObject) this.UpdateDrawObjectMaterial(type, obj); } // 更新颜色 else if (this._Color === ByLayerColorIndex && id.Object.ColorIndex !== oldLayerColorIndex) { for (let [type, obj] of this._CacheDrawObject) this.UpdateDrawObjectMaterial(type, obj); } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; this.SetMaterialAtSlot(mtl); } 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; } get DrawColorIndex() { return this._Color === ByLayerColorIndex ? this.Layer.Object.ColorIndex : 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 isClearDraw = this._CacheDrawObject.size === 0; let box = this.BoundingBox; this._Matrix = mtxBak; if (isClearDraw) { for (let [, obj] of this._CacheDrawObject) obj.matrix = this._Matrix; //因为使用了备份的矩阵,导致此时这个矩形是错误的,这里还原它 this.Update(UpdateDraw.Matrix); //保证盒子是正确的 } 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); } Move(v) { if (equaln$1(v.x, 0) && equaln$1(v.y, 0) && equaln$1(v.z, 0)) return this; tempMatrix1.identity().setPosition(v.x, v.y, v.z); this.ApplyMatrix(tempMatrix1); return this; } 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]; this.Move({ x: moveX, y: moveY, z: moveZ }); } get Z() { return this._Matrix.elements[14]; } set Z(z) { if (equaln$1(this.Z, z)) return; this.Move({ x: 0, y: 0, z: z - this.Z }); } //Z轴归0 Z0() { if (this._Matrix.elements[14] === 0) return this; this.WriteAllObjectRecord(); this.Move({ x: 0, y: 0, z: -this.Z }); return this; } //坐标系二维化 MatrixPlanarizere() { let z = this._Matrix.elements[10]; if (equaln$1(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.OCSNoClone, 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 CaseShadow() { if (!this.MeshMaterial) return true; if (Array.isArray(this.MeshMaterial)) return true; return !this.MeshMaterial.transparent || this.MeshMaterial.opacity === 1; } get ReceiveShadow() { return this.CaseShadow; } get DrawObject() { if (this.constructor.name === "Entity" || this.constructor.name === "CADObject") throw "出错了"; if (this._drawObject) return this._drawObject; this._drawObject = new Object3D(); if (!this.IsEmbedEntity) this._drawObject.userData.Entity = this; if (this.IsVisible) { this._CurRenderType = HostApplicationServices._renderType; let obj = this.GetDrawObjectFromRenderType(HostApplicationServices._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 (renderType === RenderType.BigHoleFace && !this.HasBigHoleFaceRenderType) 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(); // if (this.__ReadFileIng__) //!警告 // console.error("在读取文件时更新实体的显示!"); } } //三维实体总是一起生成线框实体和网格实体,这个通知更新,然后统一更新就好了 //避免重复更新 UpdateDrawGeometry() { } /** 立即更新 */ DeferUpdate() { let mode = this.NeedUpdateFlag; if (mode === 0) return; if (!this._CacheDrawObject) return; /** * br1 - br2 关联切割(斜的) let temp = br1.Clone() 临时的对象来计算切割后的结果 temp br2 因为求交集,所以要访问temp.csg temp.MeshGeom (drawObjectsize = 0) temp.setContour update失败 */ if (mode & UpdateDraw.Geometry) // && this._CacheDrawObject.size > 0 因为有时提前获取了MeshGeom 如果判断绘制的个数 会导致没有刷新 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() { const mtlId = this._MaterialId; if (!mtlId?.IsErase && mtlId?.Object) return mtlId.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 && !this.Layer.Object.IsOff; } UpdateVisible() { if (this._drawObject) { this._drawObject.visible = this.IsVisible; if (this.IsVisible) this.UpdateRenderType(HostApplicationServices._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(); m.extractBasis(Entity_1._xa, Entity_1._ya, Entity_1._za); if (equaln$1(Entity_1._xa.lengthSq(), 1, 1e-4) && equaln$1(Entity_1._ya.lengthSq(), 1, 1e-4) && equaln$1(Entity_1._za.lengthSq(), 1, 1e-4)) { this._Matrix.multiplyMatrices(m, this._Matrix); this._SpaceOCS.multiplyMatrices(m, this._SpaceOCS); if (!equalv3(Entity_1._xa.cross(Entity_1._ya).normalize(), Entity_1._za)) this.ApplyMirrorMatrix(m); } else this.ApplyScaleMatrix(m); this.Update(UpdateDraw.Matrix); return this; } ApplyScaleMatrix(m) { return this; } ApplyMirrorMatrix(m) { return this; } /** * * @param snapMode 捕捉模式(单一) * @param pickPoint const * @param lastPoint const * @param viewXform const 最近点捕捉需要这个变量 * @returns object snap points */ GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum) { return []; } GetGripPoints() { return []; } MoveGripPoints(indexList, vec) { } GetStretchPoints() { return []; } /** * 拉伸夹点,用于Stretch命令 * @param {Array} indexList 拉伸点索引列表. * @param {Vector3} vec 移动向量 */ MoveStretchPoints(indexList, vec) { } IntersectWith(curve, intType) { return; } //#region -------------------------File------------------------- Clone(cloneDraw = true) { let ent = super.Clone(); ent._CurRenderType = this._CurRenderType; ent.Template = undefined; if (cloneDraw) 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.__ReadFileIng__ = false; this.Update(); } //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this._Color = file.Read(); this._MaterialId = file.ReadHardObjectId(); if (!this._MaterialId) this._MaterialId = file.database?.DefaultMaterial?.Id; 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.ReadBool(); 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); } } if (ver > 8) this._Freeze = file.ReadBool(); else this._Freeze = false; if (ver > 9) this._VisibleInRender = file.ReadBool(); else this._VisibleInRender = true; if (ver > 10) this._Layer = file.ReadObjectId(); else this._Layer = undefined; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(11); 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.WriteBool(this._Visible); file.Write(this._SpaceOCS.toArray()); file.Write(this.ProcessingGroupList.length); for (let id of this.ProcessingGroupList) file.WriteHardObjectId(id); file.WriteBool(this._Freeze); file.WriteBool(this._VisibleInRender); file.WriteHardObjectId(this._Layer); } //局部撤销 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; } }; Entity._xa = new Vector3; Entity._ya = new Vector3; Entity._za = new Vector3; __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); var StoreageKeys; (function (StoreageKeys) { StoreageKeys["IsLogin"] = "isLogin"; StoreageKeys["PlatSession"] = "platSession"; StoreageKeys["PlatToken"] = "platToken"; StoreageKeys["UserName"] = "userName"; StoreageKeys["UserPhone"] = "userPhone"; StoreageKeys["ShopName"] = "shopName"; StoreageKeys["ShopProps"] = "ShopProps"; StoreageKeys["AgentShopName"] = "AgentShopName"; StoreageKeys["AgentPhone"] = "AgentPhone"; StoreageKeys["VersionName"] = "VersionName"; StoreageKeys["RegisterTime"] = "RegisterTime"; 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["HistoryWs"] = "HistoryWs"; })(StoreageKeys || (StoreageKeys = {})); let OPERATORS = new Set(["+", "-", "*", "/"]); /** * eval2("+10", { L: 100 }, "L") * @param expr * @param [params] * @param [defaultParam] 当输入 +10 这样的表达式时,设置默认的操作变量 * @returns 计算结果 */ function eval2(expr, params, defaultParam) { MathUtils.radToDeg; MathUtils.degToRad; MathUtils.RAD2DEG; MathUtils.DEG2RAD; let abs = Math.abs; let acos = Math.acos; let acosh = Math.acosh; let asin = Math.asin; let asinh = Math.asinh; let atan = Math.atan; let atanh = Math.atanh; let atan2 = Math.atan2; let ceil = Math.ceil; let cbrt = Math.cbrt; let expm1 = Math.expm1; let clz32 = Math.clz32; let cos = Math.cos; let cosh = Math.cosh; let exp = Math.exp; let floor = Math.floor; let fround = Math.fround; let hypot = Math.hypot; let imul = Math.imul; let log = Math.log; let log1p = Math.log1p; let log2 = Math.log2; let log10 = Math.log10; let max = Math.max; let min = Math.min; let pow = Math.pow; let random = Math.random; let round = Math.round; let sign = Math.sign; let sin = Math.sin; let sinh = Math.sinh; let sqrt = Math.sqrt; let tan = Math.tan; let tanh = Math.tanh; let trunc = Math.trunc; let E = Math.E; let LN10 = Math.LN10; let LN2 = Math.LN2; let LOG10E = Math.LOG10E; let LOG2E = Math.LOG2E; let PI = Math.PI; let SQRT1_2 = Math.SQRT1_2; let SQRT2 = Math.SQRT2; let clamp = CLAMP; let eq = equaln; let r2d = MathUtils.radToDeg; let d2r = MathUtils.degToRad; let R2D = MathUtils.RAD2DEG; let D2R = MathUtils.DEG2RAD; let fix = FixedNotZero; let code = ""; //模板材质变量的value默认是"",会导致eval报错,params[name]为""时换成0 if (params) for (let name in params) code += `let ${name} = ${params[name] != "" ? params[name] : "0"};`; 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") result = result(); if (typeof result === "bigint") result = Number(result); //防止bigint乱入 return result; } function safeEval(expr, params, defaultParam) { try { return eval2(expr, params, defaultParam); } catch (error) { return NaN; } } const Reg_Expr = /\{[^\}]+\}/g; /**解析大括号内的表达式 */ function ParseExpr(expr, fractionDigits = 0, params) { let strs = expr.match(Reg_Expr); if (!strs) return expr; for (let str of strs) { let value = safeEval(str.slice(1, -1), params); let strValue; if (typeof value === "number") strValue = FixedNotZero(value, fractionDigits, expr); else strValue = value; expr = expr.replace(str, strValue); } 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; } //判断v在v1和v2之间.(v1>v2 or v2>v1) function isBetweenNums(v1, v2, v, fuzz = 1e-8) { if (v1 > v2) [v1, v2] = [v2, v1]; return equaln$1(v, clamp(v, v1, v2), fuzz); } //使用定点表示法来格式化一个数,小数点后面不尾随0. 如 FixedNotZero(1.1 , 3) => 1.1 function FixedNotZero(v, fractionDigits = 0, expr = undefined) { if (typeof v === "string") { if (expr?.includes("fix")) return v; //如果表达式中有fix,则尊重表达式的格式化,不再二次格式化 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] */ function ToFixed(v, fractionDigits = 5) { if (equaln$1(v, 0, Math.pow(0.1, fractionDigits))) return "0"; return v.toFixed(fractionDigits); } function GetEntity(obj) { while (obj) { if (obj.userData.Entity) return obj.userData.Entity; obj = obj.parent; } } function IsEntity(obj) { return GetEntity(obj) instanceof Entity; } function IsFreeze(obj) { return obj["freeze"]; } const IdentityMtx4 = new Matrix4(); const ZeroVec = new Vector3(); const XAxis = new Vector3(1, 0, 0); const XAxisN = new Vector3(-1, 0, 0); const YAxis = new Vector3(0, 1, 0); const YAxisN = new Vector3(0, -1, 0); const ZAxis = new Vector3(0, 0, 1); const ZAxisN = new Vector3(0, 0, -1); function AsVector2(p) { return new Vector2$1(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; } //范围交集 [a1,a2] 与 [b1,b2] 的交集 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$1(v1, v2, fuzz = 1e-5) { return Math.abs(v1 - v2) <= fuzz; } function equalv3(v1, v2, fuzz = 1e-8) { return equaln$1(v1.x, v2.x, fuzz) && equaln$1(v1.y, v2.y, fuzz) && equaln$1(v1.z, v2.z, fuzz); } function equalv2(v1, v2, fuzz = 1e-8) { return equaln$1(v1.x, v2.x, fuzz) && equaln$1(v1.y, v2.y, fuzz); } /** * 按照极坐标的方式移动一个点 * @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$1(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 */ 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$1(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$1(v1.dot(v2), 0, fuzz); } function midPoint(v1, v2) { return v1.clone().add(v2).multiplyScalar(0.5); } 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); } function angleAndX(v) { return v.x ? Math.atan(v.y / v.x) : Math.PI / 2; } /** * 将角度调整为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 ComparePointFnGenerate(sortKey, fuzz = 1e-8) { let cacheKey = `${sortKey}${fuzz}`; if (comparePointCache.has(cacheKey)) return comparePointCache.get(cacheKey); 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$1(vv1, vv2, fuzz)) 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} mtx 矩阵 * @param {number} col 列索引,0x 1y 2z 3org * @param {Vector3} v 向量或点 */ function SetMtxVector(mtx, col, v) { let index = col * 4; mtx.elements[index] = v.x; mtx.elements[index + 1] = v.y; mtx.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$1(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(); return cs.getMatrix4(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 MakeMirrorMtx(planeNormal, pos) { let mirrorMtx = new Matrix4(); let xAxis = new Vector3(1 - 2 * planeNormal.x ** 2, -2 * planeNormal.x * planeNormal.y, -2 * planeNormal.x * planeNormal.z); let yAxis = new Vector3(-2 * planeNormal.x * planeNormal.y, 1 - 2 * planeNormal.y ** 2, -2 * planeNormal.y * planeNormal.z); let zAxis = new Vector3(-2 * planeNormal.x * planeNormal.z, -2 * planeNormal.y * planeNormal.z, 1 - 2 * planeNormal.z ** 2); mirrorMtx.makeBasis(xAxis, yAxis, zAxis); if (pos) mirrorMtx.setPosition(pos.clone().applyMatrix4(mirrorMtx).sub(pos).negate()); return mirrorMtx; } /** * 对向量进行方向变换 (如果是pos 请使用pos.applyMatrix4) * @param vec 向量 * @param m 矩阵 * @returns vec */ function TransformVector(vec, m) { let { x, y, z } = vec; let e = m.elements; vec.x = e[0] * x + e[4] * y + e[8] * z; vec.y = e[1] * x + e[5] * y + e[9] * z; vec.z = e[2] * x + e[6] * y + e[10] * z; return vec; } /** * 把变换矩阵展平成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; } new Vector3; new Vector3; new Quaternion; const tempMatrix1 = new Matrix4; const ZMirrorMatrix = MakeMirrorMtx(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 = {})); function GetGoodShaderSimple(color = new Vector3, side = FrontSide, logBuf = false, opacity = 1) { return { uniforms: { "SurfaceColor": { value: color }, "opacity": { value: opacity }, }, side, polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 }; } const ColorPalette = [ [0, 0, 0, 0], //----- 0 - lets make it red for an example //[255, 255, 255, 255],//----- 0 - ByBlock - White [255, 0, 0, 255], //----- 1 - Red // [255, 0, 0, 255], //----- 1 - Red [255, 255, 0, 255], //----- 2 - Yellow [0, 255, 0, 255], //----- 3 - Green [0, 255, 255, 255], //----- 4 - Cyan [0, 0, 255, 255], //----- 5 - Blue [255, 0, 255, 255], //----- 6 - Magenta // [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], //----- 7 - White [128, 128, 128, 255], //----- 8 [192, 192, 192, 255], //----- 9 [255, 0, 0, 255], //----- 10 [255, 127, 127, 255], //----- 11 [165, 0, 0, 255], //----- 12 [165, 82, 82, 255], //----- 13 [127, 0, 0, 255], //----- 14 [127, 63, 63, 255], //----- 15 [76, 0, 0, 255], //----- 16 [76, 38, 38, 255], //----- 17 [38, 0, 0, 255], //----- 18 [38, 19, 19, 255], //----- 19 [255, 63, 0, 255], //----- 20 [255, 159, 127, 255], //----- 21 [165, 41, 0, 255], //----- 22 [165, 103, 82, 255], //----- 23 [127, 31, 0, 255], //----- 24 [127, 79, 63, 255], //----- 25 [76, 19, 0, 255], //----- 26 [76, 47, 38, 255], //----- 27 [38, 9, 0, 255], //----- 28 [38, 23, 19, 255], //----- 29 [255, 127, 0, 255], //----- 30 [255, 191, 127, 255], //----- 31 [165, 82, 0, 255], //----- 32 [165, 124, 82, 255], //----- 33 [127, 63, 0, 255], //----- 34 [127, 95, 63, 255], //----- 35 [76, 38, 0, 255], //----- 36 [76, 57, 38, 255], //----- 37 [38, 19, 0, 255], //----- 38 [38, 28, 19, 255], //----- 39 [255, 191, 0, 255], //----- 40 [255, 223, 127, 255], //----- 41 [165, 124, 0, 255], //----- 42 [165, 145, 82, 255], //----- 43 [127, 95, 0, 255], //----- 44 [127, 111, 63, 255], //----- 45 [76, 57, 0, 255], //----- 46 [76, 66, 38, 255], //----- 47 [38, 28, 0, 255], //----- 48 [38, 33, 19, 255], //----- 49 [255, 255, 0, 255], //----- 50 [255, 255, 127, 255], //----- 51 [165, 165, 0, 255], //----- 52 [165, 165, 82, 255], //----- 53 [127, 127, 0, 255], //----- 54 [127, 127, 63, 255], //----- 55 [76, 76, 0, 255], //----- 56 [76, 76, 38, 255], //----- 57 [38, 38, 0, 255], //----- 58 [38, 38, 19, 255], //----- 59 [191, 255, 0, 255], //----- 60 [223, 255, 127, 255], //----- 61 [124, 165, 0, 255], //----- 62 [145, 165, 82, 255], //----- 63 [95, 127, 0, 255], //----- 64 [111, 127, 63, 255], //----- 65 [57, 76, 0, 255], //----- 66 [66, 76, 38, 255], //----- 67 [28, 38, 0, 255], //----- 68 [33, 38, 19, 255], //----- 69 [127, 255, 0, 255], //----- 70 [191, 255, 127, 255], //----- 71 [82, 165, 0, 255], //----- 72 [124, 165, 82, 255], //----- 73 [63, 127, 0, 255], //----- 74 [95, 127, 63, 255], //----- 75 [38, 76, 0, 255], //----- 76 [57, 76, 38, 255], //----- 77 [19, 38, 0, 255], //----- 78 [28, 38, 19, 255], //----- 79 [63, 255, 0, 255], //----- 80 [159, 255, 127, 255], //----- 81 [41, 165, 0, 255], //----- 82 [103, 165, 82, 255], //----- 83 [31, 127, 0, 255], //----- 84 [79, 127, 63, 255], //----- 85 [19, 76, 0, 255], //----- 86 [47, 76, 38, 255], //----- 87 [9, 38, 0, 255], //----- 88 [23, 38, 19, 255], //----- 89 [0, 255, 0, 255], //----- 90 [127, 255, 127, 255], //----- 91 [0, 165, 0, 255], //----- 92 [82, 165, 82, 255], //----- 93 [0, 127, 0, 255], //----- 94 [63, 127, 63, 255], //----- 95 [0, 76, 0, 255], //----- 96 [38, 76, 38, 255], //----- 97 [0, 38, 0, 255], //----- 98 [19, 38, 19, 255], //----- 99 [0, 255, 63, 255], //----- 100 [127, 255, 159, 255], //----- 101 [0, 165, 41, 255], //----- 102 [82, 165, 103, 255], //----- 103 [0, 127, 31, 255], //----- 104 [63, 127, 79, 255], //----- 105 [0, 76, 19, 255], //----- 106 [38, 76, 47, 255], //----- 107 [0, 38, 9, 255], //----- 108 [19, 38, 23, 255], //----- 109 [0, 255, 127, 255], //----- 110 [127, 255, 191, 255], //----- 111 [0, 165, 82, 255], //----- 112 [82, 165, 124, 255], //----- 113 [0, 127, 63, 255], //----- 114 [63, 127, 95, 255], //----- 115 [0, 76, 38, 255], //----- 116 [38, 76, 57, 255], //----- 117 [0, 38, 19, 255], //----- 118 [19, 38, 28, 255], //----- 119 [0, 255, 191, 255], //----- 120 [127, 255, 223, 255], //----- 121 [0, 165, 124, 255], //----- 122 [82, 165, 145, 255], //----- 123 [0, 127, 95, 255], //----- 124 [63, 127, 111, 255], //----- 125 [0, 76, 57, 255], //----- 126 [38, 76, 66, 255], //----- 127 [0, 38, 28, 255], //----- 128 [19, 38, 33, 255], //----- 129 [0, 255, 255, 255], //----- 130 [127, 255, 255, 255], //----- 131 [0, 165, 165, 255], //----- 132 [82, 165, 165, 255], //----- 133 [0, 127, 127, 255], //----- 134 [63, 127, 127, 255], //----- 135 [0, 76, 76, 255], //----- 136 [38, 76, 76, 255], //----- 137 [0, 38, 38, 255], //----- 138 [19, 38, 38, 255], //----- 139 [0, 191, 255, 255], //----- 140 [127, 223, 255, 255], //----- 141 [0, 124, 165, 255], //----- 142 [82, 145, 165, 255], //----- 143 [0, 95, 127, 255], //----- 144 [63, 111, 127, 255], //----- 145 [0, 57, 76, 255], //----- 146 [38, 66, 76, 255], //----- 147 [0, 28, 38, 255], //----- 148 [19, 33, 38, 255], //----- 149 [0, 127, 255, 255], //----- 150 [127, 191, 255, 255], //----- 151 [0, 82, 165, 255], //----- 152 [82, 124, 165, 255], //----- 153 [0, 63, 127, 255], //----- 154 [63, 95, 127, 255], //----- 155 [0, 38, 76, 255], //----- 156 [38, 57, 76, 255], //----- 157 [0, 19, 38, 255], //----- 158 [19, 28, 38, 255], //----- 159 [0, 63, 255, 255], //----- 160 [127, 159, 255, 255], //----- 161 [0, 41, 165, 255], //----- 162 [82, 103, 165, 255], //----- 163 [0, 31, 127, 255], //----- 164 [63, 79, 127, 255], //----- 165 [0, 19, 76, 255], //----- 166 [38, 47, 76, 255], //----- 167 [0, 9, 38, 255], //----- 168 [19, 23, 38, 255], //----- 169 [0, 0, 255, 255], //----- 170 [127, 127, 255, 255], //----- 171 [0, 0, 165, 255], //----- 172 [82, 82, 165, 255], //----- 173 [0, 0, 127, 255], //----- 174 [63, 63, 127, 255], //----- 175 [0, 0, 76, 255], //----- 176 [38, 38, 76, 255], //----- 177 [0, 0, 38, 255], //----- 178 [19, 19, 38, 255], //----- 179 [63, 0, 255, 255], //----- 180 [159, 127, 255, 255], //----- 181 [41, 0, 165, 255], //----- 182 [103, 82, 165, 255], //----- 183 [31, 0, 127, 255], //----- 184 [79, 63, 127, 255], //----- 185 [19, 0, 76, 255], //----- 186 [47, 38, 76, 255], //----- 187 [9, 0, 38, 255], //----- 188 [23, 19, 38, 255], //----- 189 [127, 0, 255, 255], //----- 190 [191, 127, 255, 255], //----- 191 [82, 0, 165, 255], //----- 192 [124, 82, 165, 255], //----- 193 [63, 0, 127, 255], //----- 194 [95, 63, 127, 255], //----- 195 [38, 0, 76, 255], //----- 196 [57, 38, 76, 255], //----- 197 [19, 0, 38, 255], //----- 198 [28, 19, 38, 255], //----- 199 [191, 0, 255, 255], //----- 200 [223, 127, 255, 255], //----- 201 [124, 0, 165, 255], //----- 202 [145, 82, 165, 255], //----- 203 [95, 0, 127, 255], //----- 204 [111, 63, 127, 255], //----- 205 [57, 0, 76, 255], //----- 206 [66, 38, 76, 255], //----- 207 [28, 0, 38, 255], //----- 208 [33, 19, 38, 255], //----- 209 [255, 0, 255, 255], //----- 210 [255, 127, 255, 255], //----- 211 [165, 0, 165, 255], //----- 212 [165, 82, 165, 255], //----- 213 [127, 0, 127, 255], //----- 214 [127, 63, 127, 255], //----- 215 [76, 0, 76, 255], //----- 216 [76, 38, 76, 255], //----- 217 [38, 0, 38, 255], //----- 218 [38, 19, 38, 255], //----- 219 [255, 0, 191, 255], //----- 220 [255, 127, 223, 255], //----- 221 [165, 0, 124, 255], //----- 222 [165, 82, 145, 255], //----- 223 [127, 0, 95, 255], //----- 224 [127, 63, 111, 255], //----- 225 [76, 0, 57, 255], //----- 226 [76, 38, 66, 255], //----- 227 [38, 0, 28, 255], //----- 228 [38, 19, 33, 255], //----- 229 [255, 0, 127, 255], //----- 230 [255, 127, 191, 255], //----- 231 [165, 0, 82, 255], //----- 232 [165, 82, 124, 255], //----- 233 [127, 0, 63, 255], //----- 234 [127, 63, 95, 255], //----- 235 [76, 0, 38, 255], //----- 236 [76, 38, 57, 255], //----- 237 [38, 0, 19, 255], //----- 238 [38, 19, 28, 255], //----- 239 [255, 0, 63, 255], //----- 240 [255, 127, 159, 255], //----- 241 [165, 0, 41, 255], //----- 242 [165, 82, 103, 255], //----- 243 [127, 0, 31, 255], //----- 244 [127, 63, 79, 255], //----- 245 [76, 0, 19, 255], //----- 246 [76, 38, 47, 255], //----- 247 [38, 0, 9, 255], //----- 248 [38, 19, 23, 255], //----- 249 [84, 84, 84, 255], //----- 250 [118, 118, 118, 255], //----- 251 [152, 152, 152, 255], //----- 252 [186, 186, 186, 255], //----- 253 [220, 220, 220, 255], //----- 254 [255, 255, 255, 255], //----- 255 [0, 0, 0, 0], //----- ByLayer - White ----256 [135, 206, 235, 255], //-----257 ]; const LINE_WIDTH = 2; //颜色材质,对于二维图像来说可能有用,应该不对三维对象使用该材质 class ColorMaterial { constructor() { } static GetLineMaterial(color, freeze = false) { if (freeze) color = 257; 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 GetWallLineMtl(color) { if (this._WallLineMtlMap.has(color)) return this._WallLineMtlMap.get(color); let mtl = new LineDashedMaterial({ color: this.GetColor(color), dashSize: 10, gapSize: 10, }); this._WallLineMtlMap.set(color, mtl); return mtl; } 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, enableTransparent = false) { let key = `${color},${side},${enableTransparent ? 1 : 0}`; if (this._ConceptualMaterial.has(key)) return this._ConceptualMaterial.get(key); let shaderParams = GetGoodShaderSimple(new Vector3().fromArray(this.GetColor(color).toArray()), side, ColorMaterial.UseLogBuf); let mtl = new ShaderMaterial(shaderParams); if (enableTransparent) { Object.defineProperty(mtl, "transparent", { get: () => mtl.uniforms.opacity.value !== 1 }); Object.defineProperty(mtl.uniforms.opacity, "value", { get: () => HostApplicationServices.ConceptualOpacity }); } this._ConceptualMaterial.set(key, mtl); return mtl; } static GetConceptualTransparentMaterial(color, side = FrontSide) { let key = `${color},${side}`; if (this._ConceptualTransparentMaterial.has(key)) return this._ConceptualTransparentMaterial.get(key); let shaderParams = GetGoodShaderSimple(new Vector3().fromArray(this.GetColor(color).toArray()), side, ColorMaterial.UseLogBuf); let mtl = new ShaderMaterial(shaderParams); Object.defineProperty(mtl, "transparent", { get: () => mtl.uniforms.opacity.value !== 1 }); Object.defineProperty(mtl.uniforms.opacity, "value", { get: () => HostApplicationServices.ConceptualTransparentOpacity }); this._ConceptualTransparentMaterial.set(key, mtl); return mtl; } static UpdateConceptualMaterial(useLogBuf) { } static GetPrintConceptualMaterial() { if (!this._printConceptualMaterial) { this._printConceptualMaterial = new ShaderMaterial({ uniforms: { "SurfaceColor": { value: [1.0, 1.0, 1.0] } }, 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, color: this.GetColor(color) }); this._BasicTransparentMaterialMap.set(key, mat); return mat; } static GetBasicMaterialTransparent2(color, opacity) { let key = `${color},${opacity}`; let mtl = this._BasicTransparentMaterialMap2.get(key); if (mtl) return mtl; mtl = new MeshBasicMaterial({ transparent: true, opacity: opacity, color: this.GetColor(color) }); this._BasicTransparentMaterialMap2.set(key, mtl); return mtl; } static GetColor(color) { let rgb = ColorPalette[color]; if (rgb) return new Color(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); //避免无法获得到颜色而产生的错误 return new Color(); } static GetConceptualEdgeMaterial() { if (!this._ConceptualEdgeMaterial) this._ConceptualEdgeMaterial = new LineBasicMaterial({ color: this.GetColor(HostApplicationServices.ConceptualEdgeColor), side: DoubleSide }); return this._ConceptualEdgeMaterial; } static UpdateConceptualEdgeMaterial() { if (this._ConceptualEdgeMaterial) this._ConceptualEdgeMaterial.color.copy(this.GetColor(HostApplicationServices.ConceptualEdgeColor)); } static GetPhysical2EdgeMaterial() { if (!this._Physical2EdgeMaterial) this._Physical2EdgeMaterial = new LineBasicMaterial({ color: this.GetColor(HostApplicationServices.Physical2EdgeColor), side: DoubleSide }); return this._Physical2EdgeMaterial; } static UpdatePhysical2EdgeMaterial() { if (this._Physical2EdgeMaterial) this._Physical2EdgeMaterial.color.copy(this.GetColor(HostApplicationServices.Physical2EdgeColor)); } } ColorMaterial._LineMaterialMap = new Map(); ColorMaterial._BasicMaterialMap = new Map(); ColorMaterial.UseLogBuf = false; ColorMaterial._WallLineMtlMap = new Map(); ColorMaterial._BasicDoubleSideMaterialMap = new Map(); ColorMaterial._ConceptualMaterial = new Map(); ColorMaterial._ConceptualTransparentMaterial = 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$1(1000, 1000), side: DoubleSide, }); //灰色半透明(板件为不拆单时候的显示样式) ColorMaterial.GrayTransparentMeshMaterial = new MeshBasicMaterial({ color: 0xcccccc, transparent: true, opacity: 0.3, }); ColorMaterial.TransparentLineMaterial = new MeshBasicMaterial({ transparent: true, opacity: 0, }); 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 //此时这个Geometry.Dispose后在进行重新生成会比较后,否则属性没有被回收导致内存泄漏 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; let materialOffset = 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; if (geometry.groups.length === 0) { mergedGeometry.addGroup(offset, count, materialOffset); materialOffset++; } else { let maxMaterialIndex = 0; for (let g of geometry.groups) { maxMaterialIndex = Math.max(maxMaterialIndex, g.materialIndex ?? 0); mergedGeometry.addGroup(offset + g.start, g.count, materialOffset + g.materialIndex); } materialOffset += (maxMaterialIndex + 1); } 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 = {})); /** * 轨道控制的数学类,观察向量和角度的互相转换 * 当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$1(dir.x, 0) && equaln$1(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; } } /** * 相交延伸选项. * * @export * @enum {number} */ var IntersectOption; (function (IntersectOption) { /** * 两者都不延伸 */ IntersectOption[IntersectOption["ExtendNone"] = 0] = "ExtendNone"; /** * 延伸自身 */ 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, tolerance = 1e-4) { if (!cu1.IsCoplaneTo(cu2)) return []; let c1OcsInv = cu1.OCSInv; let c1Ocs = cu1.OCSNoClone; 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$1(dist, 0, tolerance)) 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, tolerance)) //防止点重复 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, tolerance); 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, tolerance = 1e-6) { let lineOrg = line.StartPoint; let lineDirection = line.EndPoint.sub(lineOrg); let dirLen = lineDirection.length(); if (equaln$1(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$1(discr, 0, tolerance)) { 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, tolerance); return CheckPointOnCurve(ptArr, line, circle, extType | IntersectOption.ExtendArg); } //直线和圆弧 function IntersectLineAndArc(line, arc, extType, tolerance = 1e-6) { let ptArr = IntersectLineAndCircleOrArc(line, arc, tolerance); return CheckPointOnCurve(ptArr, line, arc, extType, tolerance); } function IntersectLAndLFor2D2(p1, p2, p3, p4) { //v1 p1->p2 //v2 p3->p4 //v3 p2->p4 let dx1 = p1.x - p2.x; //v1 let dx2 = p3.x - p4.x; //v2 let dx3 = p4.x - p2.x; //v3 let dy1 = p1.y - p2.y; //v1 let dy2 = p3.y - p4.y; //v2 let dy3 = p4.y - p2.y; //v3 let det = (dx2 * dy1) - (dy2 * dx1); //v1.cross(v2) 叉积 几何意义是平行四边形的面积 let v2Length = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (equaln$1(det / v2Length, 0.0, 1e-5)) //平行 1e-5是平行四边形的高 { let det2 = (dx2 * dy3) - (dy2 * dx3); if (equaln$1(det2 / v2Length, 0.0, 1e-5)) //共线 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$1(pt1.z, 0, fuzz) && equaln$1(pt2.z, 0, fuzz) && equaln$1(pt3.z, 0, fuzz) && equaln$1(pt4.z, 0, fuzz)) { ipts = IntersectLAndLFor2D2(pt1, pt2, pt3, pt4); ipts.sort(ComparePointFnGenerate("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) { //暂时没有必要 // let cu1EndPoint = cu1.EndPoint; // for (let i = 0; i < ptPars.length; i++) // if (equalv3(ptPars[i].pt, cu1EndPoint, tolerance)) // ptPars[i].thisParam = 1;//如果通过容差测试,我们认为它在终点上 ptPars = ptPars.filter(res => res.thisParam <= 1); } else if (isEnd) { //暂时没有必要 // let cu1StartPoint = cu1.StartPoint; // for (let i = 0; i < ptPars.length; i++) // if (equalv3(ptPars[i].pt, cu1StartPoint, tolerance)) // ptPars[i].thisParam = 0;//如果通过容差测试,我们认为它在起点上 ptPars = ptPars.filter(res => res.thisParam >= 0); } } else //当曲线不延伸时,它通过了容差测验,这时我们认为曲线在线上 for (let i = 0; i < ptPars.length; i++) ptPars[i].thisParam = clamp(ptPars[i].thisParam, 0, 1); 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); } } else //当曲线不延伸时,它通过了容差测验,这时我们认为曲线在线上 for (let i = 0; i < ptPars.length; i++) ptPars[i].argParam = clamp(ptPars[i].argParam, 0, 1); intRes.push(...ptPars.map(r => { return { pt: r.pt, thisParam: i + r.thisParam, argParam: j + r.argParam, }; })); } } let fn = ComparePointFnGenerate("xyz", tolerance); intRes.sort((p1, p2) => fn(p1.pt, p2.pt)); arrayRemoveDuplicateBySort(intRes, (p1, p2) => equalv3(p1.pt, p2.pt, tolerance)); return intRes; } function IntersectLineAndEllipseFor2D(l, el) { let elInv = new Matrix4().makeRotationZ(-el.Rotation).multiply(el.OCSInv); let matInv = new Matrix4().getInverse(elInv); let a = el.RadX; let b = el.RadY; let sp = l.StartPoint.applyMatrix4(elInv); let ep = l.EndPoint.applyMatrix4(elInv); if (!(equaln$1(sp.z, 1e-6) && equaln$1(ep.z, 1e-6))) { if (equalv2(sp, ep, 1e-6)) //如果与之垂直 { let p = sp.setZ(0).applyMatrix4(matInv); if (el.PtOnCurve(p)) return [ { pt: p, thisParam: l.GetParamAtPoint(p), argParam: el.GetParamAtPoint(p) } ]; } //todo:求交面 return []; } let pts = []; if (equaln$1(sp.x, ep.x)) { let c = sp.x; let j = (b ** 2) * (1 - (c ** 2) / (a ** 2)); if (equaln$1(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$1(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) ]; } } 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$1(j, 0) || equaln$1(j, r ** 2)) { if (equaln$1(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.ExtendNone; 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$1(el1.RadX, el2.RadX) && equaln$1(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 ExtendType$1; (function (ExtendType) { /** * 前后都不延伸 */ ExtendType[ExtendType["None"] = 0] = "None"; /** * 只允许延伸前面 */ ExtendType[ExtendType["Front"] = 1] = "Front"; /** * 只允许延伸后面 */ ExtendType[ExtendType["Back"] = 2] = "Back"; /** * 前后延伸 */ ExtendType[ExtendType["Both"] = 3] = "Both"; })(ExtendType$1 || (ExtendType$1 = {})); /** * 曲线的基类,子类请实现以下方法. */ let Curve = class Curve extends Entity { constructor() { super(); this._RoomName = ""; this._CabinetName = ""; //------------------绘制相关------------------ //重载 this.OnlyRenderType = true; } get RoomName() { return this._RoomName; } set RoomName(value) { if (value === this._RoomName) return; this.WriteAllObjectRecord(); this._RoomName = value; } get CabinetName() { return this._CabinetName; } set CabinetName(value) { if (value === this._CabinetName) return; this.WriteAllObjectRecord(); this._CabinetName = value; } get Is2D() { return equaln$1(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; } get Shape() { throw "未实现"; } GetPointAtParam(param) { return; } GetPointAtDistance(distance) { return; } GetDistAtParam(param) { return; } GetDistAtPoint(pt) { return; } GetParamAtPoint(pt, fuzz = 1e-6) { return; } //直接提供点在线上的参数,不管点有没有在线上 GetParamAtPoint2(pt, fuzz = 1e-6) { return this.GetParamAtPoint(pt, fuzz); } GetParamAtDist(d) { return; } /** * 返回曲线在指定位置的一阶导数(在wcs内) * @param {(number | Vector3)} param */ GetFirstDeriv(param) { return; } GetFirstDerivAngle(param) { let d = this.GetFirstDeriv(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$1(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-5) { return equalv3(this.StartPoint, pt, fuzz) || equalv3(this.EndPoint, pt, fuzz) || this.ParamOnCurve(this.GetParamAtPoint(pt, fuzz)); } //点在曲线中,不在起点或者终点. 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; } /** * 偏移曲线 * @param offsetDist 左边负数 右边正数 * @returns 返回偏移后的曲线 可能返回多条曲线 */ 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 (pts.length === 0) pts.push(new Vector3); 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.DrawColorIndex)); } /** * 重载:更新绘制的实体 * @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.DrawColorIndex); } } UpdateJigMaterial(color = 8) { for (let [type, obj] of this._CacheDrawObject) { this.UpdateDrawObjectMaterial(type, obj, ColorMaterial.GetLineMaterial(color)); } } ReadRoomAndCabinetName(file) { this._RoomName = file.Read(); this._CabinetName = file.Read(); } WriteRoomAndCabinetName(file) { file.Write(this._RoomName); file.Write(this._CabinetName); } }; Curve = __decorate([ Factory ], Curve); const DbCurve = Curve; 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; return this; } 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; } } 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.parseConstantFromPoint(constant); } parseConstantFromPoint(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); } } const ROTATE_MTX2 = new Matrix2().set(0, -1, 1, 0); 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$1(this._StartPoint.z, 0) && equaln$1(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.DrawColorIndex)); } /** * 重载:更新绘制的实体 * @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.GetFirstDeriv(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); } } GetFirstDeriv(param) { return this.EndPoint.sub(this.StartPoint); } /** * 需要注意的是,平行线和共线无交点 * @param curve * @param intType * @param [tolerance=1e-4] * @returns */ IntersectWith2(curve, intType, tolerance = 1e-4) { if (curve instanceof Line_1 || curve.constructor.name === "RoomWallLine") { return IntersectLineAndLine(this, curve, intType, tolerance); } if (curve instanceof Arc || curve.constructor.name === "RoomWallArc") { 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.GetFirstDeriv(0).multiplyScalar(param)); } GetParamAtPoint(pt, fuzz = 1e-5) { let { closestPt, param } = this.GetClosestAtPoint(pt, true); if (!equalv3(closestPt, pt, fuzz)) 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.SetStartEndPoint(pts[i], 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.GetFirstDeriv(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 || cu.constructor.name === "RoomWallLine") { //平行 if (!isParallelTo(this.GetFirstDeriv(0).normalize(), cu.GetFirstDeriv(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 offset = this._EndPoint.clone().sub(this._StartPoint).normalize().multiplyScalar(offsetDist); ROTATE_MTX2.applyVector(offset); let newLine = this.Clone(); newLine.ClearDraw(); newLine._StartPoint.add(offset); newLine._EndPoint.add(offset); 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); let ver = file.Read(); //1 this._StartPoint.fromArray(file.Read()); this._EndPoint.fromArray(file.Read()); if (ver > 1) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.Write(this._StartPoint.toArray()); file.Write(this._EndPoint.toArray()); this.WriteRoomAndCabinetName(file); } //#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 StartPointInOcs() { return this._StartPoint.clone(); } get EndPointInOcs() { return this._EndPoint.clone(); } get EndPoint() { return this._EndPoint.clone().applyMatrix4(this.OCSNoClone); } set EndPoint(p) { this.WriteAllObjectRecord(); this._EndPoint.copy(p).applyMatrix4(this.OCSInv); this.Update(); } SetStartEndPoint(s, e) { this.WriteAllObjectRecord(); let inv = this.OCSInv; this._StartPoint.copy(s).applyMatrix4(inv); this._EndPoint.copy(e).applyMatrix4(inv); 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$1(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._endAngle; } 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; } ApplyScaleMatrix(m) { //或许我们应该在缩放一下轴尺寸 但是先不做了 let p = this.Position; p.applyMatrix4(m); this.Position = p; return this; } Extend(newParam) { this.WriteAllObjectRecord(); if (newParam < 0) this._startAngle = this.GetAngleAtParam(newParam); else if (newParam > 1) this._endAngle = this.GetAngleAtParam(newParam); this.Update(); } 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) { return this.PtOnEllipse(pt) && this.ParamOnCurve(this.GetParamAtPoint(pt)); } PtOnEllipse(pt) { let p = rotatePoint(pt.clone().applyMatrix4(this.OCSInv), -this.Rotation); return equaln$1(p.x ** 2 / this.RadX ** 2 + p.y ** 2 / this.RadY ** 2, 1, 1e-3); } GetPointAtParam(param) { let an = this.TotalAngle * param + this._startAngle; let a = this.RadX; let b = this.RadY; let pt = new Vector3(a * Math.cos(an), b * Math.sin(an), 0); let mtx = new Matrix2().setRotate(this._rotate); mtx.applyVector(pt); return pt.applyMatrix4(this.OCSNoClone); } GetParamAtPoint(pt) { if (!this.PtOnEllipse(pt)) return NaN; let an = this.GetCircleAngleAtPoint(pt); let allAngle = this.TotalAngle; let param = an / allAngle; if (this.IsClose) return param; else { if (an >= this._startAngle) param = (an - this._startAngle) / allAngle; else param = ((Math.PI * 2) - (this._startAngle - an)) / allAngle; //剩余的参数 let remParam = Math.PI * 2 / allAngle - 1; if (param > (remParam * 0.5 + 1)) //一半 param = (param - 1) - remParam; //返回负数 return param; } } 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(param) { return this._startAngle + param * this.TotalAngle; } GetCircleAngleAtPoint(pt) { pt = pt.clone().applyMatrix4(this.OCSInv); let romtx = new Matrix2().setRotate(-this._rotate); romtx.applyVector(pt); //https://www.petercollingridge.co.uk/tutorials/computational-geometry/finding-angle-around-ellipse/ let an = Math.atan(this.RadX * pt.y / (this.RadY * pt.x)); if (pt.x < 0) an += Math.PI; else if (an < 0) an += Math.PI * 2; return an; } GetFirstDeriv(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); } rotatePoint(vec, this._rotate); return vec.applyMatrix4(this.OCS.setPosition(0, 0, 0)); } 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.OCSNoClone); 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$1(sa, ea, 1e-6)) { el.StartAngle = ea; el.EndAngle = equaln$1(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$1(this._endAngle, this._startAngle)) { this.EndAngle = this._endAngle; status = Status.True; } else if (equaln$1(this._startAngle, el._endAngle)) { this.StartAngle = el._startAngle; status = Status.True; } if (status === Status.True && !this.IsClose && equaln$1(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$1(lastPoint.distanceToSquared(this.Center), 0, 1e-10)) return []; return [this.GetClosestPointTo(lastPoint, false)]; } case ObjectSnapMode.Tan: { //TODO:过某点获取椭圆全部切点 if (lastPoint) { return getTanPtsOnEllipse(); } return []; } 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) ]; for (let p of pts) p.applyMatrix4(tmpMat4).applyMatrix4(this.OCSNoClone); if (!equaln$1(0, this._startAngle)) pts.push(this.StartPoint); if (!equaln$1(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(); //忽略空多段线怎么样? if (pts.length < 2) { let pl = new Polyline; return pl; } 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); let ver = file.Read(); this._radX = file.Read(); this._radY = file.Read(); this._rotate = file.Read(); this._startAngle = file.Read(); this._endAngle = file.Read(); if (ver > 1) this.ReadRoomAndCabinetName(file); this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this.RadX); file.Write(this.RadY); file.Write(this.Rotation); file.Write(this._startAngle); file.Write(this._endAngle); this.WriteRoomAndCabinetName(file); } }; Ellipse = Ellipse_1 = __decorate([ Factory ], Ellipse); var Arc_1; /** * 圆弧实体类 * 与ACAD不同,这个类加入了时针变量,并且默认构造的圆弧为顺时针圆弧. * * 关于时针圆弧: * 起始圆弧到终止圆弧总是在0-2PI之间.(一个完整的圆). * 圆弧的绘制从起始圆弧绘制到终止圆弧. 按照时针绘制. * 参考计算圆弧的完整角度方法查看该计算方式. */ let Arc = Arc_1 = class Arc extends Curve { constructor(center = ZeroVec, radius = 0.1, startAngle = 0.1, endAngle = 0, clockwise = true) { super(); this._DisplayAccuracy = 0; /** * 曲线为顺时针 */ 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); if (this._DisplayAccuracy !== 0) { sp.DisplayAccuracy = this._DisplayAccuracy; } 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(); SetMtxVector(this._Matrix, 2, v); this.Update(); } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = 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 BoundingBoxPtsInOCS() { 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 pts; } get BoundingBox() { Arc_1._Z.setFromMatrixColumn(this._Matrix, 2); Orbit.ComputUpDirection(Arc_1._Z, Arc_1._Y, Arc_1._X); Arc_1._Mtx.makeBasis(Arc_1._X, Arc_1._Y, Arc_1._Z).setPosition(this._Matrix.elements[12], this._Matrix.elements[13], this._Matrix.elements[14]); let pts = [ polar(new Vector3(), this._StartAngle, this._Radius), polar(new Vector3(), this._EndAngle, this._Radius), ]; let ocsInv = this.OCSInv; for (let p of [new Vector3(this._Radius), new Vector3(0, this._Radius), new Vector3(-this._Radius), new Vector3(0, -this._Radius)]) { p.applyMatrix4(Arc_1._Mtx).applyMatrix4(ocsInv); if (this.ParamOnCurve(this.GetParamAtAngle(angle(p)))) pts.push(p); } for (let p of pts) p.applyMatrix4(this.OCSNoClone); return new Box3Ext().setFromPoints(pts); } /** * 返回对象在自身坐标系下的Box */ get BoundingBoxInOCS() { return new Box3Ext().setFromPoints(this.BoundingBoxPtsInOCS); } 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) { // if (equaln(v, this._StartAngle)) return;//优化导致测试用例失败 this.WriteAllObjectRecord(); this._StartAngle = v; this.Update(); } get EndAngle() { return this._EndAngle; } set EndAngle(v) { // if (equaln(v, this._EndAngle)) return;//优化导致测试用例失败 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.OCSNoClone); } 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, fuzz = 1e-6) { if (this._Radius == 0 || this.AllAngle == 0 || !equaln$1(pt.distanceTo(this.Center), this._Radius, fuzz)) 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) { return angle(Arc_1.__PointTemp__.copy(pt).applyMatrix4(this.OCSInv)); } 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$1(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$1(sa, this._StartAngle)) //this起点对起点 { if (eaAllan > allAn) this.EndAngle = ea; return Status.True; } else if (equaln$1(sa, this._EndAngle)) //this终点对起点 { if (eaAllan < allAn || equaln$1(ea, this._StartAngle)) return Status.ConverToCircle; else this.EndAngle = ea; return Status.True; } else if (equaln$1(ea, this.StartAngle)) //this起点对终点 { if (saAllan < allAn) return Status.ConverToCircle; else this.StartAngle = sa; return Status.True; } else if (equaln$1(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 || curve.constructor.name === "RoomWallArc") { return IntersectArcAndArc(this, curve, intType, tolerance); } if (curve instanceof Line || curve.constructor.name === "RoomWallLine") { 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$1(this.AllAngle, Math.PI * 2)) return 1; return Math.tan(this.AllAngle * 0.25) * (this.IsClockWise ? -1 : 1); } /** * 计算所包含的角度 * @param {number} endAngle 结束的角度 */ 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$1) p1 = AsVector3(p1); if (p2 instanceof Vector2$1) 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 this; 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); if (!center) { this.ParseFromBul(pt1.applyMatrix4(this.OCSNoClone), pt3.applyMatrix4(this.OCSNoClone), 1e-3); //faker line return this; } 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$1(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 index = indexList[0]; if (index > 2) this.Center = this.Center.add(vec); else { let p1 = polar(new Vector3, this._StartAngle, this._Radius); let p2 = polar(new Vector3, this.GetAngleAtParam(0.5), this._Radius); let p3 = polar(new Vector3, this._EndAngle, this._Radius); vec = TransformVector(vec.clone(), this.OCSInv).setZ(0); [p1, p2, p3][index].add(vec); let center = getCircleCenter(p1, p2, p3); if (!center) //三点共线 使用faker arc { this.ParseFromBul(p1.applyMatrix4(this.OCSNoClone), p3.applyMatrix4(this.OCSNoClone), 1e-3); this.Update(); return; } //起始角度 端点角度 this._StartAngle = angle(p1.clone().sub(center)); this._EndAngle = angle(p3.clone().sub(center)); if (equaln$1(this._StartAngle, this._EndAngle, 1e-5)) //差不多也是三点共线,只不过逃逸了 { this.ParseFromBul(p1.applyMatrix4(this.OCSNoClone), p3.applyMatrix4(this.OCSNoClone), 1e-3); this.Update(); return; } //用圆心和其中一个点求距离得到半径: this._Radius = center.distanceTo(p1); this.Center = center.clone().applyMatrix4(this.OCS); //求出向量p1->p2,p1->p3 let v1 = p2.clone().sub(p1); let v2 = p3.clone().sub(p1); this._Clockwise = v1.cross(v2).z < 0; 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; } GetFirstDeriv(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.ReadBool(); if (ver > 2) { this._DisplayAccuracy = file.Read(); } if (ver > 3) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(4); file.Write(this._Radius); file.Write(this._StartAngle); file.Write(this._EndAngle); file.WriteBool(this._Clockwise); file.Write(this._DisplayAccuracy); //ver4 this.WriteRoomAndCabinetName(file); } }; Arc._X = new Vector3; Arc._Y = new Vector3; Arc._Z = new Vector3; Arc._Mtx = new Matrix4; Arc.__PointTemp__ = new Vector3; Arc = Arc_1 = __decorate([ Factory ], Arc); var DragPointType; (function (DragPointType) { DragPointType[DragPointType["Grip"] = 0] = "Grip"; DragPointType[DragPointType["Stretch"] = 1] = "Stretch"; DragPointType[DragPointType["End"] = 2] = "End"; })(DragPointType || (DragPointType = {})); 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(); this._DisplayAccuracy = 0; center && this._Matrix.setPosition(center); this._Radius = radius; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } get Shape() { let sp = new Shape2(); sp.ellipse(0, 0, this._Radius, this._Radius, 0, 2 * Math.PI, false, 0); if (this._DisplayAccuracy !== 0) { sp.DisplayAccuracy = this._DisplayAccuracy; } 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$1(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, fuzz = 1e-5) { return equaln$1(pt.distanceToSquared(this.Center), this._Radius * this._Radius, fuzz); } GetOffsetCurves(offsetDist) { if ((offsetDist + this._Radius) > 0) { let circle = this.Clone(); circle.Radius = this._Radius + offsetDist; return [circle]; } return []; } IntersectWith2(curve, intType, tolerance = 1e-5) { if (curve instanceof Arc) { return IntersectCircleAndArc(this, curve, intType, tolerance); } if (curve instanceof Line) { return SwapParam(IntersectLineAndCircle(curve, this, reverseIntersectOption(intType), tolerance)); } if (curve instanceof Circle_1) { return IntersectCircleAndCircle(this, curve, tolerance); } if (curve instanceof Ellipse) { return SwapParam(IntersectEllipseAndCircleOrArc(curve, this, intType)); } if (curve instanceof Polyline) return SwapParam(IntersectPolylineAndCurve(curve, this, reverseIntersectOption(intType), tolerance)); return []; } //******************** Curve function end*****************// get BoundingBoxInOCS() { return new Box3Ext(new Vector3(-this.Radius, -this.Radius), new Vector3(this.Radius, this.Radius)); } get BoundingBox() { let z = this.Normal; let x = new Vector3; let y = new Vector3; Orbit.ComputUpDirection(z, y, x); let m = new Matrix4().makeBasis(x, y, z).setPosition(this.Center); //使用任意轴坐标系 以便我们正确的对齐世界坐标系 return this.BoundingBoxInOCS.applyMatrix4(m); } 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.DrawColorIndex)); 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.DrawColorIndex); 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$1(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; break; 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); } } GetFirstDeriv(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); let ver = file.Read(); this._Radius = file.Read(); if (ver > 1) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._Radius); this.WriteRoomAndCabinetName(file); } }; Circle = Circle_1 = __decorate([ Factory ], Circle); const ARC_DRAW_CONFIG = { ARC_SplitLength: 0.4, //圆的分段长度 ARC_RADIUS_MIN: 2.5, //大于半径25的自动优化成36等分圆 保证绘制光滑 Arc_MinSplitCount: 8, //圆的最小分段个数 ARC_MaxSplitCount: 90, //圆的最大分段个数 }; function GetArcDrawCount(arc) { let radius = typeof arc === "number" ? arc : arc.Radius; let splitCount = radius / ARC_DRAW_CONFIG.ARC_SplitLength; //保证是偶数(避免奇数和Shape2计算方式一致导致的干涉) splitCount = clamp(Math.floor(splitCount * 0.5) * 2, ARC_DRAW_CONFIG.Arc_MinSplitCount, ARC_DRAW_CONFIG.ARC_MaxSplitCount); if (radius > ARC_DRAW_CONFIG.ARC_RADIUS_MIN) splitCount = Math.max(36, splitCount); return splitCount; } /** * * @param cu */ function SplitCurveParams(cu) { let xparams = []; if (cu instanceof Circle) { let splitCount = GetArcDrawCount(cu); if (cu.DisplayAccuracy > 0) splitCount = cu.DisplayAccuracy; 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 (!equaln$1(cu.GetBulgeAt(i), 0, BUL_IS_LINE_FUZZ)) // is arc { let arc = cu.GetCurveAtIndex(i); let splitCount = GetArcDrawCount(arc); 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 < 0.99999) xparams.push(p + i); } } } xparams.push(cu.EndParam); return xparams; } function SplitCurvePoints(cu) { let pts = []; if (cu instanceof Circle) { let splitCount = GetArcDrawCount(cu); for (let i = 0; i < splitCount; i++) pts.push(cu.GetPointAtParam(i / splitCount)); } else //分段1 for (let i = 0; i < cu.EndParam; i++) { pts.push(AsVector3(cu.GetPoint2dAt(i)).applyMatrix4(cu.OCSNoClone)); if (!equaln$1(cu.GetBulgeAt(i), 0, BUL_IS_LINE_FUZZ)) // is arc { let arc = cu.GetCurveAtIndex(i); let splitCount = GetArcDrawCount(arc); if (splitCount === 0) continue; let divParam = 1 / splitCount; for (let j = 1; j < splitCount - 1; j++) { let p = arc.GetPointAtParam(divParam * j); pts.push(p); } } } pts.push(cu.EndPoint); return pts; } function SplitArcParams(arc) { let splitCount = GetArcDrawCount(arc); if (splitCount === 0) return []; 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) return []; return params.filter(p => p > 1e-5 && p < 9.99999); } let tempArc; class Shape2 extends Shape$1 { constructor() { super(...arguments); this._DisplayAccuracy = 0; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) this._DisplayAccuracy = v; } 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; else tempArc.ClearDraw(); tempArc.IsClockWise = curve.aClockwise; tempArc.StartAngle = curve.aStartAngle; tempArc.EndAngle = curve.aEndAngle; tempArc.Radius = Math.abs(curve.xRadius); //根据圆的半径来确定绘制个数(与SplitCurveParams一致) let splitCount = GetArcDrawCount(tempArc); if (this._DisplayAccuracy > 0) { splitCount = this._DisplayAccuracy; } resolution = Math.max(1, Math.ceil(Math.abs((tempArc.AllAngle * 0.5) / Math.PI) * splitCount * 0.5)) * 2; } 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 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; } } //创建轮廓 通过点表和凸度 function CreatePolylinePath(pts, buls, displayAccuracy = 0) { let shape = new Shape2(); shape.DisplayAccuracy = displayAccuracy; 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$1(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; } /** * 曲线连接图 * 所有的顶点和边的关系 */ 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 = ''; for (let hashpart of hashparts) { key += hashpart[hashmaskShifted & 1]; hashmaskShifted >>= 1; } this._LookupTable[key] = p; } return p; } } class CurveTreeNode { Create(curve, box) { return new this.constructor(curve, box); } 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.IntersectWith2(contour.Curve, IntersectOption.ExtendNone) .map(p => p.thisParam); //#region 交点参数优化,避免出现无意义长度的曲线被移除 CurveIsFine let length = this.curve.Length; if (!(this.curve instanceof Circle)) iParams.push(0, 1); iParams.sort((e1, e2) => e1 - e2); if (this.curve instanceof Arc || this.curve instanceof Circle) { let allAngle = this.curve instanceof Circle ? Math.PI * 2 : this.curve.AllAngle; arrayRemoveDuplicateBySort(iParams, (p1, p2) => { return allAngle * (p2 - p1) < ARC_MIN_ALLANGLE || length * (p2 - p1) < CURVE_MIN_LENGTH; }); } else { arrayRemoveDuplicateBySort(iParams, (p1, p2) => { return length * (p2 - p1) < CURVE_MIN_LENGTH; }); } if (!(this.curve instanceof Circle)) { iParams.shift(); //remove 0 iParams.pop(); //remove 1 or 0.9999 } //#endregion 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(this.Create(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; } } } var PolylineJoinType; (function (PolylineJoinType) { PolylineJoinType[PolylineJoinType["Square"] = 0] = "Square"; PolylineJoinType[PolylineJoinType["Round"] = 1] = "Round"; })(PolylineJoinType || (PolylineJoinType = {})); class OffsetPolyline { /** * * @param _Polyline * @param _OffsetDist * @param [_ToolPath=false] 走刀模式(在这个模式下,我们会进行圆弧过渡(或者直线过渡)避免尖角过大) * @param [_OffsetDistSq=(_OffsetDist ** 2) * 2.1] 允许的最大尖角长度 默认值差不多是矩形的尖角大一点 * @param [_JoinType=PolylineJoinType.Round] 尖角的处理方式,默认是圆弧过渡,可以切换成直线过渡 */ constructor(_Polyline, _OffsetDist, _ToolPath = false, _OffsetDistSq = (_OffsetDist ** 2) * 2.1, //对直角走刀不进行圆弧过度 _JoinType = PolylineJoinType.Round //仅在走刀路径时生效 ) { this._Polyline = _Polyline; this._OffsetDist = _OffsetDist; this._ToolPath = _ToolPath; this._OffsetDistSq = _OffsetDistSq; this._JoinType = _JoinType; this._IsTopoOffset = false; //局部偏移,允许特殊延伸,参考测试用例 this._AllowConverToCircle = true; } 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(); // for (let c of this._SubOffsetedCurves) // { // TestDraw(c.curve.Clone(), 1); // if (c.paddingCurve) // for (let cc of c.paddingCurve) // TestDraw(cc.Clone(), 2); // } // for (let c of this._TrimPolylineContours) // { // TestDraw(c.Curve.Clone(), 3); // } 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); //TODO: 简化这个 并且生成圆 顶点 圆 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 iPtsP = curveNow.IntersectWith2(curveNext, IntersectOption.ExtendBoth, 1e-6); let iPts = iPtsP.map(p => p.pt); let tPts = iPtsP.filter(p => curveNow.ParamOnCurve(p.thisParam) && curveNext.ParamOnCurve(p.argParam)).map(p => p.pt); let code = EntityEncode2(curveNow, curveNext); let tp; if (code === 1) { if (this._IsTopoOffset && tPts.length === 0 && curveResNow.dist !== curveResNext.dist) tPts = iPts; if (tPts.length > 0) //不走刀或者有真交点 this._ToolPath === false || tp = iPts[0]; else { if (iPts.length > 0 && iPtsP[0].thisParam > 1) { let refP = this._Vertexs[curveResNext.index]; let distSq = iPts[0].distanceToSquared(refP); if (this._ToolPath && distSq > this._OffsetDistSq) { if (this._JoinType === PolylineJoinType.Round) curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)]; else curveResNow.paddingCurve = [this.CreateSquare(refP, curveResNow, curveResNext, code)]; //补直线 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 (this._IsTopoOffset && tPts.length === 0 && curveResNow.dist !== curveResNext.dist) { if (iPts.length) tPts = iPts; else { curveResNow.paddingCurve = [new Line(sp, ep)]; //补直线 continue; } } if (tPts.length > 0) //ipts = 1 or ipts = 2 tp = SelectNearP(tPts, refP); else //补圆弧 或者尝试连接 { if (iPts.length > 0 && !this._ToolPath && this.IsSharpCorner(curveResNow, curveResNext)) //非加工刀路 并且尖角化(谷)时 { //设置新的连接点,并且备份旧点 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 = [this.CreateArc(refP, sp, ep)]; //补圆弧 } else { if (this._JoinType === PolylineJoinType.Round) curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)]; //补圆弧 else { curveResNow.paddingCurve = [this.CreateSquare(refP, curveResNow, curveResNext, code)]; //补直线 } } let circle = this._Circles[curveResNext.index]; if (circle) this._TrimCircleContours.push(circle); //因为局部偏移可能未提供圆 } } if (tp) { curveResNow.ep = tp; curveResNext.sp = tp; curveResNow.nextCurve = curveNext; curveResNext.preCurve = curveNow; } } else { let padCirs = []; for (let s = FixIndex$1(curveResNow.index + 1, this._Circles);; s = FixIndex$1(s + 1, this._Circles)) { let circle = this._Circles[s]; if (circle) //因为局部偏移可能未提供圆 { this._TrimCircleContours.push(circle); padCirs.push(circle); } if (s === curveResNext.index) break; } curveResNow.paddingCurve = padCirs; } } } /** * 判断两曲线是否尖角化 * 在 * 1.直线和圆弧,圆弧和圆弧 * 2.有交点,无真交点时 * 必须在正确的方向做出延伸动作,所以必须在尖角的情况下延伸,偏移的结果就像一个谷字去掉一个口的结果,上面的人就是偏移后的结果. * 如果是谷,则允许连接 #I7WKKG */ IsSharpCorner(curveResNow, curveResNext) { let v1 = this._SubCurves[curveResNow.index].GetFirstDeriv(1); let v2 = this._SubCurves[curveResNext.index].GetFirstDeriv(0); 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$1(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.ExtendNone, 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.ExtendNone); let ipts2 = cu2.IntersectWith(l2, IntersectOption.ExtendNone); 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.preCurve && d.preCurve instanceof Arc) { let a = d.preCurve; if (Math.sign(a.Bul) !== this._OffsetDistSign && a.AllAngle > 1e-6) { let ipts = a.IntersectWith(l1, IntersectOption.ExtendNone); if (ipts.length === 2) { let sp = SelectNearP(ipts, p1); l1.EndPoint = sp; l1PadArc = a.Clone(); l1PadArc.StartPoint = sp; } } } if (l2Intact && d.nextCurve && d.nextCurve instanceof Arc) { let a = d.nextCurve; if (Math.sign(a.Bul) !== this._OffsetDistSign && a.AllAngle > 1e-6) { let ipts = a.IntersectWith(l2, IntersectOption.ExtendNone); 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 && this._AllowConverToCircle) { 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, isJoinEnd = true) => { 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; minDist = 0; break; } } if (minR && Math.sqrt(minDist) < 0.085) { used.add(minR.curve); preP = minR.e; // let status = pl.Join(minR.curve, false, 8e-2); // if (status !== Status.True) // console.warn("连接失败"); //#region fast join (为了避免Polyline.Join 导致的顺序错误) let cu = minR.curve; //bul let bul = 0; if (cu instanceof Arc) { bul = cu.Bul; if (minR.isReverse) bul *= -1; if (!isJoinEnd) bul *= -1; } if (pl.LineData.length === 0) { pl.LineData.push({ pt: AsVector2(minR.s), bul: bul }); } if (isJoinEnd) { pl.LineData[pl.LineData.length - 1].bul = bul; pl.LineData.push({ pt: AsVector2(minR.e), bul: 0 }); } else { pl.LineData.unshift({ pt: AsVector2(minR.e), bul }); } //#endregion 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, true); ss = s; preP = pl.StartPoint; while (ss && !pl.IsClose) ss = searchNext(ss, pl, false); 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$1(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$1(param, 0) && ((minIndex === 0) ? this._IsClose : true)) { let preIndex = FixIndex$1(minIndex - 1, this._SubCurves); let preCurve = this._SubCurves[preIndex]; if (!equalv3(c.GetFirstDeriv(0).normalize(), preCurve.GetFirstDeriv(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$1(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.GetFirstDeriv(1).normalize(), nextCurve.GetFirstDeriv(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.GetFirstDeriv(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; } CreateSquare(center, curveNow, curveNext, entTypeCode) { const arc = this.CreateArc(center, curveNow.curve.EndPoint, curveNext.curve.StartPoint); const centerPoint = arc.GetPointAtParam(0.5); const tangentLine = new Line(centerPoint, arc.GetFirstDeriv(0.5).add(centerPoint)); //切线 let ep, sp; if (entTypeCode === 1) { ep = tangentLine.IntersectWith(curveNow.curve, IntersectOption.ExtendBoth)[0]; //第一条线新的终点坐标 sp = centerPoint.multiplyScalar(2).sub(ep); } else // if (entTypeCode === 0)//全圆弧 直线和圆弧 { ep = SelectNearP(tangentLine.IntersectWith(curveNow.curve, IntersectOption.ExtendBoth), center); //第一条线新的终点坐标 sp = SelectNearP(tangentLine.IntersectWith(curveNext.curve, IntersectOption.ExtendBoth), center); } curveNow.ep = ep; curveNext.sp = sp; return new Line(ep, sp); } } function EntityEncode(c) { if (c instanceof Line) return 1; else return 2; } function EntityEncode2(c1, c2) { return EntityEncode(c1) & EntityEncode(c2); } const CURVE_MIN_LENGTH = 5e-5; const ARC_MIN_ALLANGLE = 2e-6; //表示这个是一个正常的曲线,不是0长度的线,也不是0长度的圆弧 function CurveIsFine(curve) { if (curve instanceof Arc && curve.AllAngle < ARC_MIN_ALLANGLE) return false; return curve.Length > CURVE_MIN_LENGTH; } /** * 判断点在多段线内外(如果点在线上 则返回false) * @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$1(pl.GetBulgeAt(i), 0, BUL_IS_LINE_FUZZ)) //直线 { let sp = pl.GetPointAtParam(i); let ep = pl.GetPointAtParam(i + 1); if (equalv2(sp, pt, 1e-5) || equalv2(ep, pt, 1e-5)) //在起点或者终点 return false; //点位于线上面 if (pt.y > Math.max(sp.y, ep.y)) continue; //线垂直Y轴 let derX = ep.x - sp.x; if (equaln$1(derX, 0, 5e-6)) { if (equaln$1(pt.x, ep.x, 1e-5) && (pt.y > Math.min(sp.y, ep.y) - 1e-5 && pt.y < Math.max(sp.y, ep.y) + 1e-5)) return false; //点在线上 continue; } //起点 if (equaln$1(sp.x, pt.x, 5e-6)) { if (sp.y > pt.y && derX < 0) crossings++; continue; } //终点 if (equaln$1(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; let iptY = (pt.x - sp.x) * k + sp.y; if (equaln$1(iptY, pt.y, 1e-5)) //点在线上 返回false return false; if (iptY > pt.y) crossings++; } } else //圆弧 { let arc = pl.GetCurveAtIndex(i); let sp = arc.StartPoint; let ep = arc.EndPoint; if (equalv2(sp, pt, 1e-5) || equalv2(ep, pt, 1e-5)) //在起点或者终点 return false; let center = arc.Center; //如果相切 if (equaln$1(Math.abs(pt.x - center.x), arc.Radius)) { //点在线上 if (equaln$1(pt.y, center.y) && arc.ParamOnCurve(arc.GetParamAtPoint2(pt))) return false; //当点和起点或者终点和点相切时 if (equaln$1(sp.x, pt.x) && sp.y > pt.y) { if (ep.x - sp.x < -1e-5) crossings++; } else if (equaln$1(ep.x, pt.x) && ep.y > pt.y) { if (ep.x - sp.x > 1e-5) crossings++; } continue; } if (equaln$1(sp.x, pt.x) && sp.y > pt.y) { let der = arc.GetFirstDeriv(0).normalize(); if (der.x < -1e-5) crossings++; } if (equaln$1(ep.x, pt.x) && ep.y > pt.y) { let der = arc.GetFirstDeriv(1).normalize(); if (der.x > 1e-5) crossings++; } for (let pti of arc.IntersectWith(insLine, IntersectOption.ExtendArg)) { if (equalv2(pti, pt, 1e-5)) //点在线上 返回false return false; //交点在点下 交点在起点? 交点在终点? if (pti.y + 1e-5 < pt.y || equalv2(sp, pti, 1e-5) || equalv2(ep, pti, 1e-5)) continue; crossings++; } } } return (crossings % 2) === 1; } var Polyline_1; const BUL_IS_LINE_FUZZ = 1e-5; let Polyline = Polyline_1 = class Polyline extends Curve { constructor(_LineData = []) { super(); this._LineData = _LineData; this._DisplayAccuracy = 0; this._ClosedMark = false; } UpdateOCSTo(m) { this.WriteAllObjectRecord(); let p = new Vector3().setFromMatrixPosition(m); p.applyMatrix4(this.OCSInv); if (equaln$1(p.z, 0)) { let dir = Math.sign(this.Area2); let tm = matrixAlignCoordSys(this.OCSNoClone, 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; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } set LineData(data) { this.WriteAllObjectRecord(); this._LineData = data; this.Update(); } get LineData() { return this._LineData; } get NumberOfVertices() { return this._LineData.length; } /** * 在指定位置插入点. * 例如: * pl.AddVertexAt(pl.NumberOfVertices,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(); let inv = this.OCSInv; for (let i = 0; i <= this.EndParam; i++) { let p = this.GetPointAtParam(i); p.applyMatrix4(m).applyMatrix4(inv); this.SetPointAt(i, AsVector2(p)); } return this; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let oldPts = this.GetStretchPoints(); reviseMirrorMatrix(this._Matrix); let inv = this.OCSInv; for (let i = 0; i < oldPts.length; i++) { let newP = oldPts[i].applyMatrix4(inv); let newBul = -this.GetBulgeAt(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; } GetBulgeAt(index) { return this._LineData[index].bul; } Rectangle(length, height) { this.LineData = [ { pt: new Vector2$1(), bul: 0 }, { pt: new Vector2$1(length), bul: 0 }, { pt: new Vector2$1(length, height), bul: 0 }, { pt: new Vector2$1(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$1(px3.x, px1.y); let px4 = new Vector2$1(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; } FromPoints2d(pts) { this.WriteAllObjectRecord(); this._LineData.length = 0; for (let p of pts) this._LineData.push({ pt: AsVector2(p), bul: 0 }); this.Update(); 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.GetBulgeAt(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.GetBulgeAt(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 }); } } /** * 删除重复点 * @param [fuzz=0.1] 容差=0.1 */ RemoveRepeatPos(fuzz = 0.1) { let index = 0; let pre = 0; let writed = false; for (let next = 1; next <= this._LineData.length; next++) { if ((!this._ClosedMark && next === this._LineData.length) || //如果曲线没闭合 则总是保留最后一个点 !equalv2(this._LineData[pre].pt, this._LineData[FixIndex$1(next, this._LineData)].pt, fuzz)) { if (!writed) { this.WriteAllObjectRecord(); writed = true; } this._LineData[index] = this._LineData[pre]; index++; } pre++; } if (equalv2(this._LineData[0].pt, this._LineData[index - 1].pt, fuzz)) this._LineData[index - 1].pt.copy(this._LineData[0].pt); this._LineData.length = index; this.Update(); } get Length() { return this.Explode().reduce((l, cu) => l + cu.Length, 0); } /** * 获得指定参数所在的点. * 当曲线存在闭合标志时,参数必须在曲线内部. * 当曲线不存在闭合标志时,参数允许延伸出曲线. * * @param {number} param 参数 * @returns {Vector3} 三维点,可为空 */ GetPointAtParam(param) { if (param === Math.floor(param) && this.ParamOnCurve(param)) return AsVector3(this.GetPoint2dAt(FixIndex$1(param, this.NumberOfVertices))).applyMatrix4(this.OCSNoClone); 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 cuCount = paramFloor > this.EndParam ? this.EndParam : paramFloor; let dist = 0; //首先计算完整曲线的长度 for (let i = 0; i < cuCount; i++) { dist += this.GetCurveAtIndex(i).Length; } //参数已经大于索引,证明参数在线外. if (paramFloor !== cuCount) { dist += this.GetCurveAtParam(param).GetDistAtParam(param - cuCount); } 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, fuzz = 1e-5) { 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, fuzz); if (cu.ParamOnCurve(param, fuzz)) return i + param; //返回点在曲线内部的参数 } //当曲线闭合时,不需要延伸首尾去判断参数 if (this._ClosedMark) return NaN; //起点终点参数集合 let seParams = []; //点在第一条曲线上的参数 let startParam = cus[0].GetParamAtPoint(pt, fuzz); if (!isNaN(startParam) && startParam < 0) seParams.push(startParam); //点在最后一条线上的参数 let endParam = cus[cus.length - 1].GetParamAtPoint(pt, fuzz); 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) { if (equaln$1(dist, 0)) return 0; let cus = []; for (let i = 0; i < this.EndParam; i++) { let cu = this.GetCurveAtIndex(i); let len = cu.Length; if (len < 1e-6) continue; cus.push(cu); if (dist <= len) return i + cu.GetParamAtDist(dist); else if (equaln$1(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 pt 需要保证传入的点路径上 * @returns */ GetDistAtPoint2(pt) { let param = this.GetParamAtPoint(pt); if (param < 0) { let c1 = this.GetCurveAtIndex(0); return c1.Length * param; } else if (param > this.EndParam) { let ce = this.GetCurveAtIndex(this.EndParam - 1); return this.Length + ce.Length * (param - this.EndParam); } return this.GetDistAtParam(param); } /** * 返回曲线的一阶导数. * 当曲线闭合(标志)且点不在曲线上. * 或者曲线不闭合(标志) 且点不在曲线上也不在延伸上 * * @param {(number | Vector3)} param * @returns {Vector3} * @memberof Polyline */ GetFirstDeriv(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.GetFirstDeriv(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$1(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$1(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$1(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$1(pa, pafloor, 1e-8)) //如果pa在点上 { plData.push({ pt: pts[0].clone(), bul: buls[0] }); } else //在曲线上 { let bul = buls[0]; if (!equaln$1(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$1(sfloor, startParam, 1e-8)) startParam = sfloor; else sfloor = Math.floor(startParam); let efloor = Math.floor(endParam + 0.5); if (equaln$1(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$1(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$1(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$1(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 + Math.abs(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 }; } /** * 将多段线的OCS清除(请保证这个实体是在WCSZ0坐标系下,否则结果不正确!) */ MatrixIdentity() { this.WriteAllObjectRecord(); const { pts: pathPts, buls } = this.MatrixAlignTo2(new Matrix4); this.OCSNoClone.identity(); for (let i = 0; i < this.LineData.length; i++) { this.LineData[i].pt.copy(pathPts[i]); this.LineData[i].bul = buls[i]; } } 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) { let x = new Vector3().setFromMatrixColumn(cu.OCSNoClone, 0); let y = new Vector3().setFromMatrixColumn(cu.OCSNoClone, 1); let cuZ = x.cross(y); let dir = equalv3(this.Normal, cuZ, 1e-4) ? 1 : -1; let bul = cu.Bul * dir; this._LineData.push({ pt: cuSp2, bul: 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 (epspDisSq < minDis) { linkType = LinkType.EpSp; minDis = epspDisSq; } if (epepDisSq < minDis) { linkType = LinkType.EpEp; minDis = epepDisSq; } if (spspDisSq < minDis) { linkType = LinkType.SpSp; minDis = spspDisSq; } if (spepDisSq < minDis) { linkType = LinkType.SpEp; minDis = spepDisSq; } 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 x = new Vector3().setFromMatrixColumn(cu.OCSNoClone, 0); let y = new Vector3().setFromMatrixColumn(cu.OCSNoClone, 1); let cuZ = x.cross(y); let dir = equalv3(this.Normal, cuZ, 1e-4) ? 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, 0.1); // if (status === Status.False) // console.log(); } 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, computOCS = true) { if (!curves || curves.length === 0) return; let pl = new Polyline_1; if (computOCS) 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, fuzz = 1e-6) { for (let i = 0; i < this.EndParam; i++) { let c = this.GetCurveAtIndex(i); if (c.PtOnCurve(pt, fuzz)) 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$1.Both : ExtendType$1.None); } GetClosestPointTo2(pt, extType) { //当曲线空时,返回空 if (this.EndParam < 1) return undefined; //当有闭合标志时,曲线在任何位置都不延伸 if (this._ClosedMark) extType = ExtendType$1.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$1.Front) > 0) { let ptCFirst = cu.GetClosestPointTo(pt, true); if (cu.GetParamAtPoint(ptCFirst) <= 1) { ptC = ptCFirst; ptCDist = ptC.distanceToSquared(pt); } if (extType === ExtendType$1.Front) continue; } let ptCloseNew; //新的最近点 //后延伸 (此处与前延伸分开if 如果线只有一段,那么前后延伸都能同时触发) if (i === (this.EndParam - 1) && (extType & ExtendType$1.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$1(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, joinType = PolylineJoinType.Round) { if (equaln$1(offsetDist, 0)) return []; let polyOffestUtil = new OffsetPolyline(this, offsetDist, true, offsetDistSq, joinType); 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$1(d1.bul, 0, BUL_IS_LINE_FUZZ)) curve = new Line(AsVector3(d1.pt), AsVector3(d2.pt)).ApplyMatrix(this.OCSNoClone); else curve = new Arc().ParseFromBul(d1.pt, d2.pt, d1.bul).ApplyMatrix(this.OCSNoClone); 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.ExtendNone); 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$1(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.GetFirstDeriv(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.GetFirstDeriv(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$1(r.thisParam, 0, 1e-3) || equaln$1(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; } SetPtsBuls(pts, buls) { this.WriteAllObjectRecord(); this._LineData.length = 0; for (let i = 0; i < pts.length; i++) { let pt = pts[i]; let bul = buls[i]; this._LineData.push({ pt, bul }); } this.Update(); return this; } /** * 得到曲线有用的点表和凸度(闭合曲线首尾重复) */ 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; return CreatePolylinePath(pts, buls, this._DisplayAccuracy); } get SVG() { let sp = this.StartPoint; let str = `M${sp.x} ${sp.y} `; for (let i = 1; i <= this.EndParam; i++) { let bul = this.GetBulgeAt(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$1(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 ptCount = this._LineData.length; let frontIndex = cuIndex - 1; if (this._ClosedMark) frontIndex = FixIndex$1(frontIndex, ptCount); if (frontIndex >= 0 && this.GetBulgeAt(frontIndex)) { let arc = this.GetCurveAtIndex(frontIndex); arc.MoveGripPoints([2], moveVec); this._LineData[frontIndex].bul = arc.Bul; } if ((cuIndex !== ptCount - 1) && this.GetBulgeAt(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 ptCount = this._LineData.length; for (let index of indexList) { if (index >= ptCount) throw "在拉伸多段线顶点时,尝试拉伸不存在的顶点!(通常是因为模块中的板轮廓被破坏,导致的顶点丢失!)"; let frontIndex = index - 1; let nextIndex = index + 1; if (this._ClosedMark) { frontIndex = FixIndex$1(frontIndex, ptCount); nextIndex = FixIndex$1(nextIndex, ptCount); } /** * 根据新的拉伸点修改凸度. * * @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 count = file.Read(); for (let i = 0; i < count; i++) { let v = new Vector2$1().fromArray(file.Read()); let bul = file.Read(); this._LineData.push({ pt: v, bul: bul }); } if (ver > 1) this._ClosedMark = file.Read(); // 某些时候会画出这样的多段线 尚未知道是为什么画出的 // if (this._LineData.length === 0) // this.Erase(); if (ver > 2) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(3); file.Write(this._LineData.length); for (let l of this._LineData) { file.Write(l.pt.toArray()); file.Write(l.bul); } file.Write(this._ClosedMark); this.WriteRoomAndCabinetName(file); } }; Polyline = Polyline_1 = __decorate([ Factory ], Polyline); const TempPolyline$1 = new Polyline(); const _overlap_ = "_overlap_"; /** 面域分析,基于最小循环图重新实现的版本,拓展了实现求最大轮廓。 当最大轮廓=最小轮廓时,只绘制最大轮廓(独立轮廓无分裂)。 算法只实现去重模式,业务场景应该没有非去重模式。 如果需要非去重模式,那么应该获取到多个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 = []; //外轮廓和内轮廓重叠时,只有外轮廓有数据,可以使用RegionParse.RegionsIsOverlap来取得 //区域列表 通常是内轮廓 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?.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) { w.curve.TempData = 2; this.RemoveEdge(w); this.RemoveFilamentAt(w.from, vertices); this.RemoveFilamentAt(w.to, vertices); } maxWalk[_overlap_] = true; 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); } } } } } //大小圈重叠 static RegionsIsOverlap(Route) { return Boolean(Route[_overlap_]); } 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 > 1e-4 && param < 0.9999) 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]; } /** * 线性切割多线段 * @param {Polyline} meatPl 被切割的曲线 * @param {Polyline[]} knifePls 刀曲线 * @param {boolean} [isSweep = false] 是否为圆弧板(被切割的曲线是否为圆弧板的轮廓) * @return {Polyline[]} 切割后的多线段 */ function SplitPolyline(meatPl, knifePls, isSweep = false) { let allSplitPls = []; let allIntersePts = []; for (let pl of knifePls) { let ipts = pl.IntersectWith(meatPl, IntersectOption.ExtendThis); allIntersePts.push(...ipts); if (pl.LineData.length === 2 && ipts.length > 1) //直线切割,且有2个已经的顶点 { if (ipts.length === 2) { pl.StartPoint = ipts[0]; pl.EndPoint = ipts[1]; } else { ipts.sort(ComparePointFnGenerate("xy")); pl.StartPoint = ipts[0]; pl.EndPoint = arrayLast(ipts); } } else { let iptsNotExt = ipts.filter(p => pl.PtOnCurve(p)); if (iptsNotExt.length < 2 && ipts.length === 2) { let params = ipts.map(p => pl.GetParamAtPoint(p)); pl.Extend(params[0]); pl.Extend(params[1]); } } let splitPls = pl.GetSplitCurvesByPts(ipts); allSplitPls.push(...splitPls); } let brSplitCus = meatPl.GetSplitCurvesByPts(allIntersePts); allSplitPls = allSplitPls.filter(pl => { return meatPl.PtInCurve(pl.GetPointAtParam(pl.EndParam / 2)); //切割线必须在板内才有用 }); let regionParse = new RegionParse(brSplitCus.concat(allSplitPls)); let cus = regionParse.RegionsInternal.map(r => { let pl = new Polyline(); for (let route of r) pl.Join(route.curve); if (pl.Area2 < 0) pl.Reverse(); // 针对圆弧板特殊处理(去除多余的控制点) if (isSweep) { pl = pl.GetOffsetCurves(10)[0]; pl = pl.GetOffsetCurves(-10)[0]; } return pl; }); return cus; } 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$1(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].clone().applyMatrix4(this.OCSNoClone); } get EndPoint() { return arrayLast(this._PointList).clone().applyMatrix4(this.OCSNoClone); } get StartParam() { return 0; } get EndParam() { return this._ClosedMark ? this._PointList.length : this._PointList.length - 1; } GetPointAtParam(param) { const endParam = this.EndParam; if (param < 0) { const p1 = this.StartPoint; const derv = this.GetFirstDeriv(0.1); return p1.add(derv.multiplyScalar(param / endParam * this.Length)); } if (param > endParam) { const p2 = this.EndPoint; const derv = this.GetFirstDeriv(endParam); return p2.add(derv.multiplyScalar((param - endParam) / endParam * this.Length)); } return this.Shape.getPoint(param / endParam).applyMatrix4(this.OCSNoClone); } GetFirstDeriv(param) { if (param instanceof Vector3) param = this.GetParamAtPoint(param); return this.GetPointAtParam(param).sub(this.GetPointAtParam(param - 0.1)).normalize(); } 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 = TransformVector(vec.clone(), this.OCSInv).setZ(0); if (equalv3(vec, ZeroVec)) return; this.WriteAllObjectRecord(); for (let index of indexList) this._PointList[index].add(vec); this.Update(); } MoveStretchPoints(indexList, vec) { vec = TransformVector(vec.clone(), this.OCSInv).setZ(0); if (equalv3(vec, ZeroVec)) return; 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(); if (ver > 2) this.ReadRoomAndCabinetName(file); } WriteFile(file) { super.WriteFile(file); file.Write(3); //ver file.Write(this._PointList.length); this._PointList.forEach(p => file.Write(p.toArray())); file.Write(this._ClosedMark); this.WriteRoomAndCabinetName(file); } }; Spline = Spline_1 = __decorate([ Factory ], Spline); /** * 一个简单的计数器实现,本质是使用一个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); } } //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 (equaln$1(temp, 0, 1e-5)) { return; } 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, numdimensions = 4) { //返回的曲线组 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; const fuzz = 5 * Math.pow(0.1, numdimensions); //曲线节点图 let cuMap = new CurveMap(numdimensions); 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, fuzz)) cu.Reverse(); cus.push(cu); } else { //保证曲线总是从起点连接到终点 if (!equalv3(cu.EndPoint, stand.position, fuzz)) 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$1(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$1(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$1) && 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$1(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$1(cu1.Radius, cu2.Radius, 1e-6) && equaln$1(cu1.StartAngle, cu2.StartAngle) && equaln$1(cu1.EndAngle, cu2.EndAngle); } else if (cu1 instanceof Ellipse && cu2 instanceof Ellipse) { return equalv3(cu1.Center, cu2.Center) && equaln$1(cu1.RadX, cu2.RadX) && equaln$1(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 deriv = cu.GetFirstDeriv(cu.GetParamAtPoint(cp)); let vec2 = pt.clone().sub(cp); let cross = deriv.cross(vec2).applyMatrix4(cu.OCSInv.setPosition(0, 0, 0)); 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.OCSNoClone; 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.OCSNoClone); 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 []; } /** * 判断多段线是不是矩形 * 因为用户画的垃圾图,所以我们会给容差 * 1.简化点表成4个点 * -得到x向量,构建二维旋转矩阵 * -所有的点旋转 * 2.构建box * 3.4个点都在盒子里,面积是矩形 * @param cu */ function IsRect(cu) { if (cu instanceof Polyline) { //如果不封闭(就不是矩形) if (!cu.IsClose) return; //如果点个数小于4(就不是矩形) if (cu.LineData.length < 4) return; //如果有圆弧(就不是矩形) for (let i = 0; i < cu.LineData.length; i++) { let d = cu.LineData[i]; if (equaln$1(d.bul, 0, BUL_IS_LINE_FUZZ)) continue; let next = FixIndex$1(i + 1, cu.LineData); if (equalv2(d.pt, cu.LineData[next].pt)) continue; return; } let pts2d = cu.LineData.map(d => d.pt); //去除重复点 arrayRemoveDuplicateBySort(pts2d, (p1, p2) => equalv2(p1, p2)); if (equalv2(pts2d[0], pts2d[pts2d.length - 1])) pts2d.pop(); //这里我们判断它是不是有4个90度的角,并且有4个点 let preV = pts2d[0].clone().sub(pts2d[pts2d.length - 1]).negate(); //preVector let preL = preV.length(); //preLength let nowV = new Vector2$1; //nowVector let crossV = 0; //永远相同方向的90度,如果不是(就不是矩形) let pts4 = []; //简化成4个点 for (let i = 0; i < pts2d.length; i++) { nowV.subVectors(pts2d[FixIndex$1(i + 1, pts2d.length)], pts2d[i]); let cross = preV.cross(nowV) / preL; let nowL = nowV.length(); //nowLength if (equaln$1(cross, 0, 0.01)) //平行 此时的cross = 三角形的高(其中preL是三角形的底边) 我们认为它移动了0.01是可以接受的 continue; //TODOX:我们可能要合并这条线? 把preV preL更新一下? cross /= nowL; //此时的cross = sin@ //如果不等于90度(就不是矩形) if (!equaln$1(Math.abs(cross), 1, 1e-5)) return; cross = Math.sign(cross); if (!crossV) crossV = cross; else if (crossV !== cross) //如果方向不一致(没有绕着90度)(就不是矩形) return; pts4.push(pts2d[i]); if (pts4.length > 4) //如果超过4个点(就不是矩形) return; preL = nowL; preV.copy(nowV).negate(); //翻转它 以便下一次计算 } if (pts4.length !== 4 || !crossV) //没有90度 (就不是矩形) return; let rectOCS; preV.subVectors(pts4[1], pts4[0]); let box = new Box2$1; if (equaln$1(preV.x, 0, 1e-3) || equaln$1(preV.y, 0, 1e-3)) //判断是不是与X轴平行或者与Y轴平行,精度容差在0.001 看起来没问题 { rectOCS = cu.OCS; box.setFromPoints(pts4); } else //如果矩形不与X轴平行,我们旋转这个点表,然后变换它 { let a = Math.atan2(preV.y, preV.x); let r = new Matrix2().setRotate(-a); let p0 = pts4[0]; pts4 = pts4.map(p => { p = p.clone().sub(p0); r.applyVector(p); return p; }); box.setFromPoints(pts4); nowV.set(-preV.y, preV.x); //旋转90度 rectOCS = new Matrix4().makeBasis(AsVector3(preV.normalize()), AsVector3(nowV.normalize()), ZAxis).setPosition(p0.x, p0.y, 0); rectOCS.multiplyMatrices(cu.OCSNoClone, rectOCS); } //4个点都在角上 if (!pts4.every(p => { return (equaln$1(p.x, box.min.x, 0.01) || equaln$1(p.x, box.max.x, 0.01)) && (equaln$1(p.y, box.min.y, 0.01) || equaln$1(p.y, box.max.y, 0.01)); })) return; let size = box.getSize(new Vector2$1); return { size: AsVector3(size), box: new Box3(AsVector3(box.min), AsVector3(box.max)), OCS: rectOCS, }; } } function MergeCurvelist(cus) { arrayRemoveIf(cus, c => c.Length < LINK_FUZZ); let cir; arrayRemoveDuplicateBySort(cus, (c1, c2) => { if (cir) return true; let status = c1.Join(c2, false, LINK_FUZZ); if (status === Status.ConverToCircle) { let arc = c1; cir = new Circle(arc.Center, arc.Radius); return true; } return status === Status.True; }); if (cir) { cus.length = 0; cus.push(cir); } 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.GetFirstDeriv(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.GetFirstDeriv(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$1(v1.angleTo(v2), 0)) arc = new Line(p1, p3); else arc = new Arc().FromThreePoint(p1, p2, p3); pl.Join(arc); } return pl; } /** * 将封闭的多段线,通过和y轴平行的线,分割成多个矩形 * @param outline 这个多边形是横平竖直的,否则返回自身 * @param polylineParalleXYFuzz 平行x或y的容差 * @returns 裁剪后的矩形集 */ function PolylineSpliteRect(outline, polylineParalleXYFuzz = 1e-3) { if (!outline.IsClose || IsRect(outline)) return [outline]; let cus = outline.Explode(); let xSet = new Set(); for (let c of cus) { if (c instanceof Arc) return [outline]; let derv = c.GetFirstDeriv(0).normalize(); if (isParallelTo(derv, YAxis, polylineParalleXYFuzz)) xSet.add(c.StartPoint.x); else if (!isParallelTo(derv, XAxis, polylineParalleXYFuzz)) return [outline]; } //轮廓的每条线段都平行于X或Y轴 可直接按端点切割 const knifePls = []; const xList = Array.from(xSet).sort((a, b) => a - b); for (let x of xList) knifePls.push(new Polyline([{ pt: AsVector2({ x, y: 0 }), bul: 0 }, { pt: AsVector2({ x, y: 1 }), bul: 0 }])); //裁剪结果 let rects = SplitPolyline(outline, knifePls).filter(pl => pl.IsClose); return rects; } /** * 简化优化版本的曲线求交, 优化版本可以参考(算法导论33.2 确定任意一对线段是否相交 p599) */ class CurveIntersection { /** * @param {Curve[]} cus 请注意数组的顺序会被更改,如果你在意数组的顺序,请拷贝数组后传进来 * @memberof CurveIntersection */ constructor(cus, parseIntersectionParam = false, intType = IntersectOption.ExtendNone, fuzz = 1e-6, parseRecord = false) { this.fuzz = fuzz; //用来缓存的曲线包围盒 this.boxMap = new Map(); /** * 交点数据集,key 为曲线 value 为和它相交的(曲线和交点的Map) */ this.intersect = new Map(); //交点参数集 this.intersect2 = new Map(); this.intersect3 = []; 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, fuzz); if (ints.length > 0) { let pts = ints.map(i => i.pt); c1d.set(c2, pts); this.GetIntersect(c2).set(c1, pts); if (parseRecord) this.intersect3.push([c1, c2, pts]); if (parseIntersectionParam) { this.AppendIntersectionParams(c1, ints.map(i => [i.thisParam, i.pt])); this.AppendIntersectionParams(c2, ints.map(i => [i.argParam, i.pt])); } } } } } IntersectWith2(c1, c2, intType, fuzz) { return c1.IntersectWith2(c2, intType, fuzz); } AppendIntersectionParams(curve, params) { let arr = this.intersect2.get(curve); if (!arr) { arr = []; this.intersect2.set(curve, arr); } arrayPushArray(arr, 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) || (p.thisParam < 0 ? c1.StartPoint.distanceTo(p.pt) < this.fuzz : c1.EndPoint.distanceTo(p.pt) < this.fuzz); if (!inC1) return false; let inC2 = c2.ParamOnCurve(p.argParam) || (p.argParam < 0 ? c2.StartPoint.distanceTo(p.pt) < this.fuzz : c2.EndPoint.distanceTo(p.pt) < this.fuzz); return inC2; }); } } /** * 某些时候我们不能创建轮廓,此时我们使用类似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, Math.ceil(-Math.log10(fuzz))); for (let rs of parse.RegionsOutline) { const polyline = new Polyline; polyline.CloseMark = true; //获取曲线所在平面矩阵 const OCS = ComputerCurvesNormalOCS(rs.map(r => r.curve)); const OCSInv = new Matrix4().getInverse(OCS); for (let r of rs) { let bul = 0; if (r.curve instanceof Arc) { bul = r.curve.Bul; if (equalv3(TransformVector(r.curve.Normal, OCSInv), ZAxisN, 1e-3)) bul *= -1; if (r.isReverse) bul *= -1; } polyline.LineData.push({ pt: AsVector2(r.s.applyMatrix4(OCSInv)), bul }); } polyline.ApplyMatrix(OCS); let contour = Contour.CreateContour(polyline, 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(bigCurve, smallCurve) { //#fix fileid 2087494 使用二维的box来计算看起来没有问题 if (!Box2$1.prototype.containsBox.apply(bigCurve.BoundingBox.expandByVector(fuzzV3), [smallCurve.BoundingBox])) return false; let cus = []; if (smallCurve instanceof Polyline) cus = smallCurve.Explode(); else cus = [smallCurve]; return cus.every(c => { let pts = getIntPtContextPts(bigCurve, c); if (pts.length <= 1) pts.push(c.StartPoint, c.EndPoint); return IsPtsAllInOrOnReg(bigCurve, pts); }); } //获取交点处上下距0.01par的点 function getIntPtContextPts(sourceCur, cu, pts = []) { let interPts = cu.IntersectWith(sourceCur, IntersectOption.ExtendNone); 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.ExtendNone, COMBINE_FUZZ); let sourceContainerTarget; let targetContainerSource; if (sourceOutline.Area > targetOutline.Area) { sourceContainerTarget = CurveContainerCurve(sourceOutline, targetOutline, interPts); targetContainerSource = false; } else { sourceContainerTarget = false; targetContainerSource = CurveContainerCurve(targetOutline, sourceOutline, interPts); } //包含.相交.分离(三种状态) 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, COMBINE_FUZZ); if (hasEqualCus) { //方向相同 if (equalv3(cu.GetFirstDeriv(cu.MidParam).normalize(), pl.GetFirstDeriv(pl.MidParam).normalize(), 1e-2) === 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.ExtendNone, COMBINE_FUZZ); if (interPts.length <= 1) { let areaState = sourceOutline.Area > targetOutline.Area; //反包含 if ((!areaState && fastCurveInCurve2(targetOutline, sourceOutline, interPts[0]?.pt)) || equalCurve(targetOutline, sourceOutline)) return []; //包含 if ((areaState && fastCurveInCurve2(sourceOutline, targetOutline, interPts[0]?.pt))) 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.GetFirstDeriv(plMidParam).normalize(); let index = targetCus.findIndex(cu => fastEqualCurve(cu, pl)); if (index !== -1) { let cu = targetCus[index]; let cuMidParam = cu.MidParam; let cuDir = cu.GetFirstDeriv(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) => { for (let routes of routeSet) { let cs = routes.map(r => r.curve); let c = Contour.CreateContour(cs, false) ?? 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 curveIntParamsMap = 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.ExtendNone, COMBINE_FUZZ); if (pts.length <= 1) { let areaState = sourceOutline.Area > targetOutline.Area; //反包含 if ((!areaState && fastCurveInCurve2(targetOutline, sourceOutline, pts[0]?.pt)) || equalCurve(targetOutline, sourceOutline)) return { holes, subtractList }; //包含 if (areaState && fastCurveInCurve2(sourceOutline, targetOutline, pts[0]?.pt)) holes.push(targetOutline); } else { intPars.push(...pts.map(r => r.thisParam)); curveIntParamsMap.set(targetOutline, pts.map(r => r.argParam)); } } let sourceSplitCurves = sourceOutline.GetSplitCurves(intPars); let targetSplitCurves = []; let targetSplitCurve_CurvesMap = new WeakMap(); //分裂后->原始曲线 映射 for (let [curve, intParams] of curveIntParamsMap) { let splitCurves = curve.GetSplitCurves(intParams); for (let splitCurve of splitCurves) { targetSplitCurve_CurvesMap.set(splitCurve, curve); targetSplitCurves.push(splitCurve); } } for (let sourceSplitcu of sourceSplitCurves) { let index = targetSplitCurves.findIndex(cu => fastEqualCurve(cu, sourceSplitcu, 0.05)); if (index !== -1) { let targetSplitcu = targetSplitCurves[index]; let isEqualNormal = equalv3(sourceOutline.Normal, targetSplitCurve_CurvesMap.get(targetSplitcu).Normal, 1e-3); if (isEqualNormal === !equalv3(sourceSplitcu.StartPoint, targetSplitcu.StartPoint, 0.05)) //不同向 { subtractList.push(sourceSplitcu); // TestDraw(sourceSplitcu.Clone(), 1); // TestDraw(targetSplitcu.Clone(), 1); } // else // TestDraw(sourceSplitcu.Clone(), 2); targetSplitCurves.splice(index, 1); continue; } if (targets.every(t => !fastCurveInCurve(t.Curve, sourceSplitcu))) subtractList.push(sourceSplitcu); } //源对象没有被破坏 let sourceNotBreak = subtractList.length === sourceSplitCurves.length; for (let pl of targetSplitCurves) if (fastCurveInCurve(sourceOutline, pl)) subtractList.push(pl); if (sourceNotBreak && subtractList.length === sourceSplitCurves.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, -Math.log10(COMBINE_FUZZ)); let contours = []; for (let g of cuGroups) contours.push(Contour.CreateContour(g, false)); return contours.filter(c => c !== undefined && !equaln$1(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, -Math.log10(tolerance)) : [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; } /** * 判断是否完全包含曲线 * @param smallCurve 传入的这个曲线不能比本轮廓还大(这个需要自己优化?) * @returns */ ContainerCurve(smallCurve, isAreaCheckd = false, ipts = undefined) { if (!isAreaCheckd && this.Area < smallCurve.Area) return false; return CurveContainerCurve(this._Curve, smallCurve, ipts); } Equal(tar) { return equalCurve(this._Curve, tar._Curve); } } /** * 对于轮廓切割后的曲线判断相同,使用这个函数进行快速判断 */ function fastEqualCurve(c1, c2, tolerance = 5e-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); } /** * 对于双多段线互相切割后的结果(或者交点个数为0),快速判断曲线是否在另一条曲线内部 * @param bigCurve * @param smallCurve * @returns */ function fastCurveInCurve(bigCurve, smallCurve) { return bigCurve.PtInCurve(smallCurve.Midpoint); } //当交点小于等于1时 function fastCurveInCurve2(bigCurve, smallCurve, iPt) { if (iPt) //提高准确性,避免使用交点去判断 导致的错误 { let sp = smallCurve.StartPoint; if (equalv3(sp, iPt, 1e-3)) return bigCurve.PtInCurve(smallCurve.Midpoint); else return bigCurve.PtInCurve(smallCurve.StartPoint); } else return bigCurve.PtInCurve(smallCurve.Midpoint) || bigCurve.PtInCurve(smallCurve.StartPoint); } //大曲线是否完全包含小曲线(或者重合) function CurveContainerCurve(bigCurve, smallCurve, ipts = undefined, fuzz = COMBINE_FUZZ) { if (!ipts) ipts = bigCurve.IntersectWith2(smallCurve, IntersectOption.ExtendNone, fuzz); if (ipts.length === 0) return fastCurveInCurve(bigCurve, smallCurve); else if (ipts.length === 1) return fastCurveInCurve2(bigCurve, smallCurve, ipts[0].pt); else return isTargetCurInOrOnSourceCur(bigCurve, smallCurve); } var BoardType; (function (BoardType) { BoardType[BoardType["Layer"] = 0] = "Layer"; BoardType[BoardType["Vertical"] = 1] = "Vertical"; BoardType[BoardType["Behind"] = 2] = "Behind"; //背板 })(BoardType || (BoardType = {})); //纹路类型 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 = {})); 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 = {})); 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 AddRemarkType; (function (AddRemarkType) { AddRemarkType[AddRemarkType["Cover"] = 1] = "Cover"; AddRemarkType[AddRemarkType["NoCover"] = 0] = "NoCover"; })(AddRemarkType || (AddRemarkType = {})); const DIRS = [XAxis, YAxis, ZAxis, XAxisN, YAxisN, ZAxisN]; function GetFaceDir(direction) { let absx = Math.abs(direction.x); let absy = Math.abs(direction.y); let absz = Math.abs(direction.z); let face = -1.0; if (absx > absz) { if (absx > absy) face = direction.x > 0 ? 0 : 3; else face = direction.y > 0 ? 1 : 4; } else { if (absz > absy) face = direction.z > 0 ? 2 : 5; else face = direction.y > 0 ? 1 : 4; } return DIRS[face]; } class GenUVForWorld { constructor() { this.InvMtxMap = new Map(); this._Z = new Vector3; this._X = new Vector3; this._Y = new Vector3; this._Box = new Box3; this._Box2 = new Box3; } GetMtxInv(normalX, normalY, normalZ) { this._Z.set(normalX, normalY, normalZ); let n = GetFaceDir(this._Z); let mtx = this.InvMtxMap.get(n); if (mtx) return mtx; this._Z.copy(n); Orbit.ComputUpDirection(this._Z, this._Y, this._X); mtx = new Matrix4().makeBasis(this._X, this._Y, this._Z); mtx.getInverse(mtx); this.InvMtxMap.set(n, mtx); return mtx; } GenUV(mesh) { if (Array.isArray(mesh.material)) { let geo = mesh.geometry; if (!geo.boundingBox) geo.computeBoundingBox(); let normals = geo.getAttribute("normal"); let pos = geo.getAttribute("position"); let uvs = geo.getAttribute("uv"); for (let i = 0; i < mesh.material.length; i++) { let mtl = mesh.material[i]; if (mtl[USE_WORLD_UV]) { this._Box.makeEmpty(); let g = mesh.geometry.groups[i]; for (let y = 0; y < g.count; y++) { let index = (y + g.start) * 3; this._X.set(pos.array[index], pos.array[index + 1], pos.array[index + 2]); this._Box.expandByPoint(this._X); } for (let y = 0; y < g.count; y++) { let index = (y + g.start) * 3; let mtx = this.GetMtxInv(normals.array[index], normals.array[index + 1], normals.array[index + 2]); this._X.set(pos.array[index], pos.array[index + 1], pos.array[index + 2]); this._X.applyMatrix4(mtx); this._Box2.copy(this._Box).applyMatrix4(mtx); //@ts-ignore uvs.array[(y + g.start) * 2] = (((this._X.x - (this._Box2.min.x + this._Box2.max.x) * 0.5)) * 1e-2 + mtl[U_WORLD_MOVE]) * mtl[U_WORLD_REP] + 0.5; //@ts-ignore uvs.array[(y + g.start) * 2 + 1] = (((this._X.y - (this._Box2.min.y + this._Box2.max.y) * 0.5)) * 1e-2 - mtl[V_WORLD_MOVE]) * mtl[V_WORLD_REP] + 0.5; } uvs.needsUpdate = true; } } } else { let mtl = mesh.material; if (mtl[USE_WORLD_UV]) this.GenGeoUV(mesh.geometry, mtl, 1e-2); } } GenGeoUV(bGeo, mtl, scale = 1e-3) { if (!bGeo.boundingBox) bGeo.computeBoundingBox(); let normals = bGeo.getAttribute("normal"); let pos = bGeo.getAttribute("position"); let uvs = bGeo.getAttribute("uv"); if (!pos || !uvs || pos.count === 0) return; for (let y = 0; y < pos.count; y++) { let index = y * 3; let mtx = this.GetMtxInv(normals.array[index], normals.array[index + 1], normals.array[index + 2]); this._X.set(pos.array[index], pos.array[index + 1], pos.array[index + 2]); this._X.applyMatrix4(mtx); this._Box.copy(bGeo.boundingBox); this._Box.applyMatrix4(mtx); //@ts-ignore uvs.array[y * 2] = (((this._X.x - (this._Box.min.x + this._Box.max.x) * 0.5)) * scale + mtl[U_WORLD_MOVE]) * mtl[U_WORLD_REP] + 0.5; //@ts-ignore uvs.array[y * 2 + 1] = (((this._X.y - (this._Box.min.y + this._Box.max.y) * 0.5)) * scale + mtl[V_WORLD_MOVE]) * mtl[V_WORLD_REP] + 0.5; } uvs.needsUpdate = true; } } //板件的UV计算 class GenLocalUv { constructor() { this.InvMtxMap = new Map(); this._Z = new Vector3; this._X = new Vector3; this._Y = new Vector3; } GetMtxInv({ normalX, normalY, normalZ }, IsReverse) { this._Z.set(normalX, normalY, normalZ); let n = GetFaceDir(this._Z); let mtx = this.InvMtxMap.get(n); if (mtx) return mtx; this._Z.copy(n); if (n.z != 0 && !IsReverse) //正面和背面不旋转 Orbit.ComputUpDirection(this._Z, this._Y, this._X); else Orbit.ComputUpDirection(this._Z, this._X, this._Y); mtx = new Matrix4().makeBasis(this._X, this._Y, this._Z); mtx.getInverse(mtx); this.InvMtxMap.set(n, mtx); return mtx; } //计算uv值 CalculateUv(geo, IsReverse, scale = 1e-3) { if (!geo.boundingBox) geo.computeBoundingBox(); let normals = geo.getAttribute("normal"); let pos = geo.getAttribute("position"); const uvs = []; for (let y = 0; y < pos.count; y++) { let index = y * 3; const normalX = normals.array[index]; const normalY = normals.array[index + 1]; const normalZ = normals.array[index + 2]; let mtx = this.GetMtxInv({ normalX, normalY, normalZ }, IsReverse); this._X.set(pos.array[index], pos.array[index + 1], pos.array[index + 2]); this._X.applyMatrix4(mtx); uvs.push(this._X.x * scale); uvs.push(this._X.y * scale); } geo.setAttribute("uv", new Float32BufferAttribute(uvs, 2)); } } class Shape { constructor(_Outline = new Contour, _Holes = []) { this._Outline = _Outline; this._Holes = _Holes; this._DisplayAccuracy = 0; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) this._DisplayAccuracy = v; } 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(con) { this._Outline = con; } set Holes(holes) { this._Holes = holes; } get Shape() { let shape = this.Outline.Shape; for (let h of this._Holes) { if (h.Curve instanceof Polyline) h.Curve.UpdateOCSTo(this.Outline.Curve.OCS); if (h.Curve instanceof Circle) { let sp = new Path$1(); 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); shape.holes.push(sp); } else shape.holes.push(h.Shape); } shape.DisplayAccuracy = this._DisplayAccuracy; return 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) { // TestDraw(this._Outline.Curve.Clone()); //测试代码 // TestDraw(targetShape._Outline.Curve.Clone()); 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].ContainerCurve(cu.Curve, true)); //洞是否被最大的洞包含,是,则把被包含的洞都提取出来加入形状数组 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$1(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.ContainerCurve(con.Curve, true); 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("未知情况"); //qiannianzhou_lvzhijia.test.ts触发这个 本质是轮廓+轮廓会产生新的洞! //TODO: 这是个BUG // let f = new CADFiler; // f.Write(3); // c.Curve.ColorIndex = 1; // f.WriteObject(c.Curve); // f.WriteObject(ic.Curve); // ic.Curve.ColorIndex = 2; // f.WriteObject(unions.holes[0].Curve); // unions.holes[0].Curve.ColorIndex = 3; // copyTextToClipboard(f.ToString()); } if (unions.contours.length === 1) //并集成功 { if (!canSidewipe) { if (equaln$1(c.Area + ic.Area, unions.contours[0].Area, 0.1)) return true; if (equaln$1(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.ContainerCurve(h.Curve, true)); } return holes; } //读写文件 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)); } } /** 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++; // ////////////////////////////// // tolerance: The maximum difference for each parameter allowed to be considered a match class FuzzyFactory { constructor(numdimensions = 3, tolerance = EPS) { 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; } } } let x$1 = new Vector3(); let y$1 = new Vector3(); let z = new Vector3(); function IsMirror(mtx) { mtx.extractBasis(x$1, y$1, 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$1.cross(y$1).dot(z); const ismirror = (mirrorvalue < 0); return ismirror; } /** 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$1 { // 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$1(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 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 CSG2Geometry(csg) { let geo = new Geometry; let uvs = geo.faceVertexUvs[0]; for (let poly of csg.polygons) { let normal = AsVector3(poly.plane.normal); if (equalv3(normal, ZeroVec)) continue; for (let v of poly.vertices) { v.tag = geo.vertices.length; geo.vertices.push(AsVector3(v.pos)); } let firstVertex = poly.vertices[0]; for (let i = poly.vertices.length - 3; i >= 0; i--) { let [a, b, c] = [ firstVertex.tag, poly.vertices[i + 1].tag, poly.vertices[i + 2].tag ]; let f = new Face3(a, b, c, normal); geo.faces.push(f); uvs.push([ AsVector2(firstVertex.uv), AsVector2(poly.vertices[i + 1].uv), AsVector2(poly.vertices[i + 2].uv) ]); } } return geo; } function Geometry2CSG2(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 vertices = []; if (face instanceof Face3) { vertices.push(geometry.vertices[face.a].toArray()); vertices.push(geometry.vertices[face.b].toArray()); vertices.push(geometry.vertices[face.c].toArray()); } polygons.push(create(vertices)); } return retessellate(geom3.create(polygons)); } function CSG2Geometry2(csg) { let geo = new Geometry; for (let poly of csg.polygons) { let normal; //面的法线 //@ts-ignore if (poly.plane) //@ts-ignore normal = new Vector3().fromArray(poly.plane); let startIndex = geo.vertices.length; for (let v of poly.vertices) geo.vertices.push(new Vector3().fromArray(v)); if (!normal && poly.vertices.length > startIndex + 2) //如果没有法线,则自己计算 normal = new Plane$1().setFromCoplanarPoints(geo.vertices[startIndex], geo.vertices[startIndex + 1], geo.vertices[startIndex + 2]).normal; for (let i = poly.vertices.length - 3; i >= 0; i--) { let f = new Face3(startIndex, startIndex + i + 1, startIndex + i + 2, normal); geo.faces.push(f); } } return geo; } function Vector3ToVector3D(v) { return new Vector3D(v.x, v.y, v.z); } /** *勿随意更改属性名,若更改,需更改对应UI模态框的属性和检验方法的key * */ //排钻类型 var DrillType; (function (DrillType) { DrillType["Yes"] = "\u6392"; DrillType["None"] = "\u4E0D\u6392"; DrillType["More"] = "**\u591A\u79CD**"; DrillType["Invail"] = "\u65E0\u6548\u914D\u7F6E"; })(DrillType || (DrillType = {})); //偏心轮类型 // 左右侧板 Font朝向柜内 Back朝向柜外 // 顶底板 Font朝向柜外 Back两面朝下 Inside朝向柜内 var FaceDirection; (function (FaceDirection) { FaceDirection[FaceDirection["Front"] = 0] = "Front"; FaceDirection[FaceDirection["Back"] = 1] = "Back"; FaceDirection[FaceDirection["Inside"] = 2] = "Inside"; })(FaceDirection || (FaceDirection = {})); class ArcBoardBuild { //弃用 // private _SweepParamRanges: [number, number][];//Path 每段的起点参数和终点参数 基于SweepCurves1 constructor(_board, //放样路径 _SweepPath = _board.GetSweepPath(), //路径基于板旋转 _SweepAngle = _board.SweepAngle, _FaceDir = _board.SweepVisibleFace) { this._board = _board; this._SweepPath = _SweepPath; this._SweepAngle = _SweepAngle; this._FaceDir = _FaceDir; // //稳健折弯 // private ParseSweepLengths2() // { // let sweepCurves = this.SweepCurves; // let sweepCurves2 = this.SweepCurves2; // let lengths: number[] = []; // this._SweepParamRanges = []; // for (let i = 0; i < sweepCurves.length; i++) // { // let c1 = sweepCurves[i]; // let c2 = sweepCurves2[i]; // if (c1 instanceof Line) // { // let param = new GetLineParam(c1); // //我们这里得到了折弯处的参数 或许可以有用? // let sp = param.GetParamAtPoint(c2.StartPoint); // let ep = param.GetParamAtPoint(c2.EndPoint); // this._SweepParamRanges.push([sp, ep]); // sp = Math.min(sp, 0); // ep = Math.max(ep, 1); // lengths.push(param.Length * (ep - sp)); // } // else // { // let sa = c1.GetAngleAtPoint(c2.StartPoint); // let ea = c1.GetAngleAtPoint(c2.EndPoint); // let sp = c1.GetParamAtAngle(sa); // let ep = c1.GetParamAtAngle(ea); // this._SweepParamRanges.push([sp, ep]); // sp = Math.min(sp, 0); // ep = Math.max(ep, 1); // //背面为c1 正面为c2 // lengths.push((this._FaceDir === FaceDirection.Front ? c2.Length : c1.Length) * (ep - sp)); // } // } // this._SweepLengths = lengths; // } //用于缓存X位置->映射点和方向 this._CacheFuzzXFactory = new FuzzyFactory(1, 1e-3); //唯一映射x this._Cache_X_PosDirMap = new Map; this.ParseContourLength(); } get SweepPath1() { return this._SweepPath1; } get SweepPath2() { return this._SweepPath2; } ParseContourLength() { if (this._SweepAngle !== 0 && !this._Rotate2OCSMtx) this.ParseRotateMtx(); } //针对本算法特殊定制的偏移算法 避免偏移后起点丢失(主要是圆) static OffsetPolyline(path, dist) { let offset = new OffsetPolyline(path, dist); offset._AllowConverToCircle = false; return offset.Do()[0]; } get OCS2RotateMtx() { if (!this._OCS2RotateMtx) this.ParseRotateMtx(); return this._OCS2RotateMtx; } get Rotate2OCSMtx() { if (!this._OCS2RotateMtx) this.ParseRotateMtx(); return this._Rotate2OCSMtx; } ParseRotateMtx() { if (this._SweepAngle === 0) this._OCS2RotateMtx = new Matrix4; let con = this._board.ContourCurve.Clone(); let mtx = new Matrix4().makeRotationZ(-this._SweepAngle); con.ApplyMatrix(mtx); let box = con.BoundingBox; mtx.setPosition(box.min.negate()); this._OCS2RotateMtx = mtx; this._Rotate2OCSMtx = new Matrix4().getInverse(mtx); } get SweepCurves1() { return this._SweepCurves1; } get SweepCurves2() { return this._SweepCurves2; } ParseSweepCurves() { let path = ArcBoardBuild.OffsetPolyline(this._SweepPath, -this._board.Thickness); if (this._FaceDir === FaceDirection.Back) { this._SweepPath1 = ArcBoardBuild.OffsetPolyline(path, this._board.Thickness); this._SweepPath2 = path; this._SweepCurves1 = this._SweepPath1.Explode(); this._SweepCurves2 = this._SweepCurves1.map(c => { let offsetC = c.GetOffsetCurves(-this._board.Thickness)[0]; if (!offsetC) offsetC = new Line(c.StartPoint, c.StartPoint); return offsetC; }); } else { this._SweepPath1 = path; this._SweepPath2 = ArcBoardBuild.OffsetPolyline(this._SweepPath1, this._board.Thickness); this._SweepCurves1 = this._SweepPath1.Explode(); this._SweepCurves2 = this._SweepCurves1.map(c => { let offsetC = c.GetOffsetCurves(this._board.Thickness)[0]; if (!offsetC) offsetC = new Line(c.StartPoint, c.StartPoint); return offsetC; }); } this._SweepLengths = this._SweepCurves1.map(c => c.Length); return this; } get SweepLengths() { if (!this._SweepLengths) this.ParseSweepCurves(); return this._SweepLengths; } get SweepLength() { let length = 0; for (let l of this.SweepLengths) length += l; return length; } get SweepEndDists() { if (this._SweepEndDists) return this._SweepEndDists; this._SweepEndDists = []; let sumLength = 0; for (let i = 0; i < this.SweepLengths.length; i++) { let length = this.SweepLengths[i]; sumLength += length; this._SweepEndDists.push(sumLength); } return this._SweepEndDists; } get CSGBoard() { if (!this._csgBoard) this.ParseCSGBoard(); return this._csgBoard; } get SplitXList() { if (!this._splitXList) this.ParseSplitXlist(); return this._splitXList; } /** 解析切割的位置 */ ParseSplitXlist() { //每段线段的结束距离 let dists = []; let split_xs = []; //分割位置 //解析每段长度 解析分割位置 let sumLength = 0; for (let i = 0; i < this.SweepLengths.length; i++) { let length = this.SweepLengths[i]; if (this._SweepCurves1[i] instanceof Arc) //在圆弧时分段切割 { let splitCount = GetArcDrawCount(this._SweepCurves1[i]); let divDist = length / splitCount; for (let j = 1; j < splitCount; j++) split_xs.push(sumLength + (divDist * j)); } sumLength += length; dists.push(sumLength); split_xs.push(sumLength); } split_xs.pop(); //最后一段不在切割 this._SweepEndDists = dists; this._splitXList = split_xs; } /** 解析临时板 */ ParseCSGBoard() { //特性坍塌 我们记录所有转角处的x位置 以便我们遇到这个位置的坐标时,求正确的dir. this.ParseCorner(); //拿CSG过来分割 // let br = new Board; // br.Thickness = this._board.Thickness; // br.ContourCurve = this._board.ContourCurve; let br = this._board.Clone(); br.Name = "临时板"; br.SetSweepPath(undefined, 0); let arcBoardOptions = this._board.ArcBoardOptions; //构建转角处的槽 let mmm = []; for (let i = 0; i < this.SweepEndDists.length - 1; i++) { const arcBoardOption = arcBoardOptions.get(-1); let c1 = this._SweepCurves1[i]; let c2 = this._SweepCurves1[i + 1]; let co1 = this._SweepCurves2[i]; let co2 = this._SweepCurves2[i + 1]; //避免多余槽 if (co1.Length < 0.1 || co2.Length < 0.1 || equalv2(co1.EndPoint, co2.StartPoint, 0.1)) continue; let p = co1.IntersectWith(co2, IntersectOption.ExtendNone, 0.1); if (p.length === 1) { let cp1 = c1.GetClosestPointTo(p[0], false); let cp2 = c2.GetClosestPointTo(p[0], false); let dist1 = c1.GetDistAtPoint(cp1); let dist2 = c2.GetDistAtPoint(cp2); dist1 = this.SweepLengths[i] - dist1; if (dist1 + dist2 < 0.1) continue; let d = this.SweepEndDists[i]; let zValue = arcBoardOption.retainedThickness; let thickness = br.Thickness - zValue; if (this._FaceDir === FaceDirection.Front) zValue = 0; let pl = new Polyline().Rectangle(dist1 + dist2, 10000).Move(new Vector3(d - dist1, -100, 0)); if (this._SweepAngle !== 0) pl.ApplyMatrix(this.Rotate2OCSMtx); let data = { shape: new Shape(Contour.CreateContour(pl)), thickness: thickness, dir: 1 - this._FaceDir, //见光面和开槽面相反 这里翻转它 knifeRadius: 3, addLen: 0, }; mmm.push(data); } else if (p.length === 0) { let d = this.SweepEndDists[i]; let zValue = arcBoardOption.retainedThickness; let thickness = br.Thickness - zValue; let pl = new Polyline().Rectangle(1, 10000).Move(new Vector3(d - 0.5, -100, 0)); if (this._SweepAngle !== 0) pl.ApplyMatrix(this.Rotate2OCSMtx); let data = { shape: new Shape(Contour.CreateContour(pl)), thickness: thickness, dir: 1 - this._FaceDir, //见光面和开槽面相反 这里翻转它 knifeRadius: 3, addLen: 0, }; mmm.push(data); } } // mmm = []; br.BoardModeling = this._board.BoardModeling.concat(mmm); // br.Modeling2D = this._board.Modeling2D; // br.Modeling3D = this._board.Modeling3D; //TestDraw(br.Clone()); this._csgBoard = br; } /** 生成圆弧板的Geometry */ BuildMeshEdgeGeom(csg) { let br = this.CSGBoard; if (!csg) csg = this.CSGBoard.CSG; //旋转 r+m 逆矩阵(m+r) 将板轮廓旋转后 移动到WCS0(包围盒) let min, max; if (this._SweepAngle !== 0) { //根据csg旋转 let roMtx = mat4.create(); mat4.rotateZ(roMtx, roMtx, -this._SweepAngle); csg = transform(roMtx, csg); [min, max] = measureBoundingBox(csg); let moveMtx = mat4.create(); mat4.translate(moveMtx, moveMtx, min.map(v => -v)); csg = transform(moveMtx, csg); if (max[0] > this.SweepLength) console.log("出错"); // let geom = CSG2Geometry2(geom3.create(toPolygons(csg))); // geom.computeVertexNormals(); // TestDraw(new Mesh(geom, new MeshNormalMaterial)); this._OCS2RotateMtx = new Matrix4().makeRotationZ(-this._SweepAngle).setPosition(-min[0], -min[1], -min[2]); //rotate + move this._Rotate2OCSMtx = new Matrix4().makeRotationZ(this._SweepAngle).multiply(new Matrix4().setPosition(min[0], min[1], min[2])); //move + rotate } let remPolys = toPolygons(csg); let plane = [1, 0, 0, 0]; let splitedPolys = []; for (let x of this.SplitXList) { plane[3] = x; let leftPolys = []; let rightPolys = []; for (let poly of remPolys) { // Returns object: // .type: // 0: coplanar-front // 1: coplanar-back // 2: front // 3: back // 4: spanning // In case the polygon is spanning, returns: // .front: a Polygon3 of the front part // .back: a Polygon3 of the back part if (!poly) // 先临时处理为null的情况,以免Error continue; let res = splitPolygonByPlane(plane, poly); if (res.type === 0) leftPolys.push(poly); else if (res.type === 1) leftPolys.push(poly); else if (res.type === 2) rightPolys.push(poly); else if (res.type === 3) leftPolys.push(poly); else if (res.type === 4) { if (res.back) leftPolys.push(res.back); if (res.front) rightPolys.push(res.front); } } splitedPolys.push(...toPolygons(retessellate(geom3.create(leftPolys)))); remPolys = rightPolys; } splitedPolys.push(...toPolygons(retessellate(geom3.create(remPolys)))); //分割结束 //生成UV let geom = CSG2Geometry2(geom3.create(splitedPolys)); const bufferGeometry = new BufferGeometry().fromGeometry(geom); //旋转板先变换到原始状态计算uv if (this._SweepAngle !== 0) bufferGeometry.applyMatrix4(this._Rotate2OCSMtx); const gen = new GenLocalUv(); gen.CalculateUv(bufferGeometry, this._board.BoardProcessOption.lines === LinesType.Reverse); //变换回来映射 if (this._SweepAngle !== 0) bufferGeometry.applyMatrix4(this._OCS2RotateMtx); //收集所有的x let pos = bufferGeometry.getAttribute("position"); let xs = []; for (let y = 0; y < pos.count; y++) xs.push(pos.array[y * 3]); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs, equaln$1); //映射所有的x this.ParseAllX_Map(xs); //变换所有的点 let tempP = new Vector3; for (let i = 0; i < pos.count; i++) { tempP.fromArray(pos.array, i * 3); this.PosMap2ArcPos(tempP); pos.setXYZ(i, tempP.x, tempP.y, tempP.z); } //#region 构建线框 let edgeGeom = br.EdgeGeometry; pos = edgeGeom.getAttribute("position"); let plane2 = new Plane$1(new Vector3(-1, 0, 0), 0); let remLiens = []; for (let y = 0; y < pos.count; y += 2) { let p1 = new Vector3().fromArray(pos.array, y * 3); let p2 = new Vector3().fromArray(pos.array, (y + 1) * 3); if (this._OCS2RotateMtx) { p1.applyMatrix4(this._OCS2RotateMtx); p2.applyMatrix4(this._OCS2RotateMtx); } let line3 = new Line3(p1, p2); remLiens.push(line3); } //所有的线被切割分组 let lines = []; for (let x of this.SplitXList) { plane2.constant = x; let rightLines = []; for (let line of remLiens) { let ip = plane2.intersectLine(line, tempP); if (ip) { if (line.start.x < line.end.x) { lines.push(new Line3(line.start, ip.clone())); rightLines.push(new Line3(ip.clone(), line.end)); } else { lines.push(new Line3(ip.clone(), line.end)); rightLines.push(new Line3(line.start, ip.clone())); } } else if (line.start.x > x) rightLines.push(line); else lines.push(line); } remLiens = rightLines; } lines = lines.concat(remLiens); //转换为点表 let pts = []; for (let l of lines) pts.push(l.start, l.end); xs = pts.map(p => p.x); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs, equaln$1); this.ParseAllX_Map(xs); //变换所有的点 for (let p of pts) this.PosMap2ArcPos(p); edgeGeom = BufferGeometryUtils.CreateFromPts(pts); //#endregion 结束线框构建 // TestDraw(new Mesh(bufferGeometry.clone(), new MeshNormalMaterial)); if (this._SweepAngle !== 0) //将板从旋转状态转回原始状态 { edgeGeom.applyMatrix4(this._Rotate2OCSMtx); bufferGeometry.applyMatrix4(this._Rotate2OCSMtx); } bufferGeometry.computeBoundingBox(); bufferGeometry.computeVertexNormals(); // TestDraw(new Mesh(bufferGeometry, new MeshNormalMaterial)); return [bufferGeometry, edgeGeom]; } /** 生成大孔面 */ BuildBigHoleFace() { let split_xs = []; //分割位置 //解析每段长度 解析分割位置 let sumLength = 0; for (let i = 0; i < this.SweepLengths.length; i++) { let length = this.SweepLengths[i]; if (this._SweepCurves1[i] instanceof Arc) //在圆弧时分段切割 { let splitCount = GetArcDrawCount(this._SweepCurves1[i]); let divDist = length / splitCount; for (let j = 1; j < splitCount; j++) split_xs.push(sumLength + (divDist * j)); } sumLength += length; split_xs.push(sumLength); } split_xs.pop(); //最后一段不在切割 //拿CSG过来分割 let br = new Board; br.Thickness = this._board.Thickness; br.ContourCurve = this._board.ContourCurve; br.BoardProcessOption[EBoardKeyList.BigHole] = this._board.BoardProcessOption[EBoardKeyList.BigHole]; let csg = Geometry2CSG2(br.BigHoleFaceGeo); //旋转 r+m 逆矩阵(m+r) 将板轮廓旋转后 移动到WCS0(包围盒) let min, max; let Rotate2OCSMtx = new Matrix4(); if (this._SweepAngle !== 0) { //根据csg旋转 let roMtx = mat4.create(); mat4.rotateZ(roMtx, roMtx, -this._SweepAngle); csg = transform(roMtx, csg); [min, max] = measureBoundingBox(csg); let moveMtx = mat4.create(); mat4.translate(moveMtx, moveMtx, [-min[0], -min[1], 0]); csg = transform(moveMtx, csg); if (max[0] > this.SweepLength) console.log("出错"); Rotate2OCSMtx = new Matrix4().makeRotationZ(this._SweepAngle).multiply(new Matrix4().setPosition(min[0], min[1], 0)); //move + rotate } let remPolys = toPolygons(csg); let plane = [1, 0, 0, 0]; let splitedPolys = []; for (let x of split_xs) { plane[3] = x; let leftPolys = []; let rightPolys = []; for (let poly of remPolys) { if (!poly) // 先临时处理为null的情况,以免Error continue; let res = splitPolygonByPlane(plane, poly); if (res.type === 0) leftPolys.push(poly); else if (res.type === 1) leftPolys.push(poly); else if (res.type === 2) rightPolys.push(poly); else if (res.type === 3) leftPolys.push(poly); else if (res.type === 4) { leftPolys.push(res.back); rightPolys.push(res.front); } } splitedPolys.push(...toPolygons(retessellate(geom3.create(leftPolys)))); remPolys = rightPolys; } splitedPolys.push(...toPolygons(retessellate(geom3.create(remPolys)))); //分割结束 //生成UV let geom = CSG2Geometry2(geom3.create(splitedPolys)); const bufferGeometry = new BufferGeometry().fromGeometry(geom); //收集所有的x let pos = bufferGeometry.getAttribute("position"); let xs = []; for (let y = 0; y < pos.count; y++) xs.push(pos.array[y * 3]); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs, equaln$1); //映射所有的x // 为了不影响this._CacheFuzzXFactory和this._Cache_X_PosDirMap,这里重新定义(否则可能导致错误,但目前没测到) const [cacheFuzzXFactory, cache_X_PosDirMap] = this.ParseAllX_Map_BigHole(xs); //变换所有的点 let tempP = new Vector3; for (let i = 0; i < pos.count; i++) { tempP.fromArray(pos.array, i * 3); this.PosMap2ArcPos(tempP, cacheFuzzXFactory, cache_X_PosDirMap); pos.setXYZ(i, tempP.x, tempP.y, tempP.z); } if (this._SweepAngle !== 0) //将板从旋转状态转回原始状态 { bufferGeometry.applyMatrix4(Rotate2OCSMtx); } return bufferGeometry; } // ParseCorner() { this._CornerSet = new Set(); this._CornerFuzzFactory = new FuzzyFactory(3, 1e-3); for (let d of this.SweepEndDists) { d = this._CornerFuzzFactory.lookupOrCreate([d], d); this._CornerSet.add(d); } } ParseAllX_Map(xs) { if (!this._CornerSet) this.ParseCorner(); let dists = this.SweepEndDists; let fuzzX = this._CacheFuzzXFactory; let curveIndex = 0; let curSum = dists[0]; let preSum = 0; let rotateAngle = this._FaceDir === FaceDirection.Back ? Math.PI / 2 : -Math.PI / 2; for (let x of xs) { while (x > curSum && curveIndex < dists.length - 1) { curveIndex++; preSum = curSum; curSum = dists[curveIndex]; } let c = this._SweepCurves1[curveIndex]; let param = ((x - preSum) / this._SweepLengths[curveIndex]); let onlyX = fuzzX.lookupOrCreate([x], x); let derv = c.GetFirstDeriv(param); // if (c instanceof Line) // derv.divideScalar(this._SweepLengths[curveIndex]); // else // derv.divideScalar(c.Radius); derv.normalize(); rotatePoint(derv, rotateAngle); let onlyD = this._CornerFuzzFactory.lookupOrCreate([x], x); if (this._CornerSet.has(onlyD)) //坍塌 { // console.log("坍塌"); let p2 = this._SweepPath2.GetPointAtParam(curveIndex + Math.round(param)); derv = p2.sub(c.GetPointAtParam(param)).divideScalar(this._board.Thickness); } this._Cache_X_PosDirMap.set(onlyX, [c.GetPointAtParam(param), derv]); } } ParseAllX_Map_BigHole(xs) { const cacheFuzzXFactory = new FuzzyFactory(1, 1e-3); const cache_X_PosDirMap = new Map; let dists = this.SweepEndDists; let curveIndex = 0; let curSum = dists[0]; let preSum = 0; let rotateAngle = this._FaceDir === FaceDirection.Back ? Math.PI / 2 : -Math.PI / 2; for (let x of xs) { while (x > curSum && curveIndex < dists.length - 1) { curveIndex++; preSum = curSum; curSum = dists[curveIndex]; } let c = this._SweepCurves1[curveIndex]; let param = ((x - preSum) / this._SweepLengths[curveIndex]); let onlyX = cacheFuzzXFactory.lookupOrCreate([x], x); let derv = c.GetFirstDeriv(param); derv.normalize(); rotatePoint(derv, rotateAngle); let onlyD = this._CornerFuzzFactory.lookupOrCreate([x], x); if (this._CornerSet.has(onlyD)) //坍塌 { let p2 = this._SweepPath2.GetPointAtParam(curveIndex + Math.round(param)); derv = p2.sub(c.GetPointAtParam(param)).divideScalar(this._board.Thickness); } cache_X_PosDirMap.set(onlyX, [c.GetPointAtParam(param), derv]); } return [cacheFuzzXFactory, cache_X_PosDirMap]; } PosMap2ArcPos(p, cacheFuzzXFactory = this._CacheFuzzXFactory, cache_X_PosDirMap = this._Cache_X_PosDirMap) { let [x, y, z] = p.toArray(); let onlyX = cacheFuzzXFactory.lookupOrCreate([x], x); let arr = cache_X_PosDirMap.get(onlyX); if (!arr) { console.error("未知情况?"); return; } let [pox, v] = arr; if (this._FaceDir === FaceDirection.Front) z = this._board.Thickness - z; p.copy(v).multiplyScalar(z).add(pox); [p.y, p.z] = [y, p.y]; } } //圆弧默认的槽配置 const defultArcBoardOption = { arcLength: 0, grooveSpacing: 6, grooveWidth: 6, retainedThickness: 2, knifeRadius: 3, grooveAddLength: 0, grooveAddWidth: 0, grooveAddDepth: 0, arcExtension: 0, }; /** * 解析圆弧板需要的走刀数据 * @param br * @param path 圆弧放样路径 * @param angle 角度 * @param dir 圆弧板见光面 见光面正面走刀颜色黄色,背面颜色红色 * @param [onlyVert=false] 仅解析交点位置 (默认解析所有的槽) * @returns 返回需要增加的槽的数据 */ function ParseBoardArcFeed(br, path, angle, dir, arcBoardOptions, onlyVert = false) { const arcBoardBuild = new ArcBoardBuild(br); arcBoardBuild.ParseSweepCurves(); //每段线段的起始位置 const dists = []; //解析每段长度 let sumLength = 0; dists.push(sumLength); let SweepLengths = arcBoardBuild.SweepLengths; for (let i = 0; i < SweepLengths.length; i++) { let length = SweepLengths[i]; sumLength += length; dists.push(sumLength); } const board = new Board(); board.Thickness = br.Thickness; board.ContourCurve = br.ContourCurve; //获取板形状 const c1 = br.Shape; if (angle !== 0) c1.ApplyMatrix(arcBoardBuild.OCS2RotateMtx); //记录每个转角槽信息 const grooveInfo = new Map(); for (let i = 0; i < dists.length - 2; i++) { const arcBoardOption = arcBoardOptions.get(-1); let c1 = arcBoardBuild.SweepCurves1[i]; let c2 = arcBoardBuild.SweepCurves1[i + 1]; let co1 = arcBoardBuild.SweepCurves2[i]; let co2 = arcBoardBuild.SweepCurves2[i + 1]; //避免多余槽 if (equalv2(co1.EndPoint, co2.StartPoint, 0.1)) continue; let p = co1.IntersectWith(co2, IntersectOption.ExtendNone, 0.1); if (p.length === 1) { let cp1 = c1.GetClosestPointTo(p[0], false); let cp2 = c2.GetClosestPointTo(p[0], false); let dist1 = c1.GetDistAtPoint(cp1); let dist2 = c2.GetDistAtPoint(cp2); dist1 = arcBoardBuild.SweepLengths[i] - dist1; if (dist1 + dist2 < 0.1) continue; grooveInfo.set(i, { l: dist1, r: dist2 }); } else if (p.length === 0) { grooveInfo.set(i, { l: arcBoardOption.grooveWidth / 2, r: arcBoardOption.grooveWidth / 2 }); } } const appendModel = (grooveWidth, x, arcBoardOption) => { const pl = new Polyline().Rectangle(grooveWidth, 10000).Move(new Vector3(x, 0, 0)); const c2 = new Shape(Contour.CreateContour(pl)); for (const contour of c1.IntersectionBoolOperation(c2)) { if (angle !== 0) contour.ApplyMatrix(arcBoardBuild.Rotate2OCSMtx); mmm.push({ shape: contour, thickness: br.Thickness - arcBoardOption.retainedThickness, dir: dir === 0 ? FaceDirection.Back : FaceDirection.Front, knifeRadius: arcBoardOption.knifeRadius, addLen: arcBoardOption.grooveAddLength, addWidth: arcBoardOption.grooveAddWidth, addDepth: arcBoardOption.grooveAddDepth, }); } }; //判断是否可以等分 const checkIsDivide = (remainLength, grooveWidth, grooveSpacing, firstGrooveLength, lastGrooveLength) => { //如果前面有默认槽,则减去一个槽宽度 if (!firstGrooveLength) remainLength -= grooveWidth; //如果后面有默认槽,则减去一个槽宽度 if (!lastGrooveLength) remainLength -= grooveWidth; const pericycleWidth = grooveSpacing + grooveWidth; //周期宽度 const length = remainLength + grooveWidth; //加一个槽宽,让槽宽和槽间距个数相等 const count = Math.ceil(length / pericycleWidth); //计算挖槽个数 const newSpacing = (length / count) - grooveWidth; //计算新的槽间距值 return newSpacing > 0; //如果新的槽间距大于零,可等分,如果小等于零槽就会重叠 }; //构建槽 const mmm = []; for (let i = 0; i < dists.length - 1; i++) { const cu = arcBoardBuild.SweepCurves1[i]; const startGroove = grooveInfo.get(i - 1); if (startGroove) { const arcBoardOption = arcBoardOptions.get(-1); const grooveWidth = startGroove.l + startGroove.r; const x = dists[i] - startGroove.l; appendModel(grooveWidth, x, arcBoardOption); } if (!onlyVert && (cu instanceof Arc)) { const arcBoardOption = arcBoardOptions.get(i); const { grooveWidth, grooveSpacing, arcExtension } = arcBoardOption; const firstGrooveLength = grooveInfo.get(i - 1)?.r ?? 0; //头通用挖槽长度 const lastGrooveLength = grooveInfo.get(i)?.l ?? 0; //尾通用挖槽长度 //挖槽的规律是先挖最左右两个槽,再根据最大槽间距、槽宽和剩余长度计算出比例挖槽 let remainLength = cu.Length - firstGrooveLength - lastGrooveLength; //剩余长度 //更新起始距离 let startDist = dists[i] + firstGrooveLength; //先计算是否可以添加槽加长 const cu1 = arcBoardBuild.SweepCurves1[i - 1]; const cu2 = arcBoardBuild.SweepCurves1[i + 1]; if (!firstGrooveLength && arcExtension && cu1 && cu1 instanceof Line) { remainLength += arcExtension; startDist -= arcExtension; } if (!lastGrooveLength && arcExtension && cu2 && cu2 instanceof Line) remainLength += arcExtension; if (remainLength > 0) { //只能画一个槽的情况 if (remainLength <= grooveWidth) { if (firstGrooveLength) { startDist += grooveSpacing; remainLength -= grooveSpacing; } if (lastGrooveLength) { remainLength -= grooveSpacing; } if (remainLength > 0) appendModel(remainLength, startDist, arcBoardOption); continue; } //这里只够分配前后两个槽,这两个槽的大小可能一大一小 if (remainLength <= grooveWidth * 2 + grooveSpacing) { if (firstGrooveLength) { startDist += grooveSpacing; remainLength -= grooveSpacing; } if (lastGrooveLength) { remainLength -= grooveSpacing; } appendModel(grooveWidth, startDist, arcBoardOption); let x2 = startDist + grooveSpacing + grooveWidth; let grooveLength2 = remainLength - grooveWidth - grooveSpacing; // 第二个槽太小不够用了,向第一个槽靠拢 if (grooveLength2 < 0) { x2 -= grooveSpacing; grooveLength2 = grooveSpacing + grooveLength2; } appendModel(grooveLength2, x2, arcBoardOption); continue; } //绘制多个槽 if (checkIsDivide(remainLength, grooveWidth, grooveSpacing, firstGrooveLength, lastGrooveLength)) { if (!firstGrooveLength) { appendModel(grooveWidth, startDist, arcBoardOption); startDist += grooveWidth; remainLength -= grooveWidth; } if (!lastGrooveLength) { const cu1 = arcBoardBuild.SweepCurves1[i + 1]; remainLength -= grooveWidth; //判断是否满足尾部延伸 const x = dists[i + 1] - grooveWidth + ((arcExtension && cu1 && cu1 instanceof Line) ? arcExtension : 0); appendModel(grooveWidth, x, arcBoardOption); } const pericycleWidth = grooveSpacing + grooveWidth; //周期宽度 const length = remainLength + grooveWidth; const count = Math.ceil(length / pericycleWidth); // 计算新的槽位间距 const newSpacing = (length / count) - grooveWidth; const newPericycleWidth = newSpacing + grooveWidth; for (let j = 0; j < count - 1; j++) { const x = startDist + j * newPericycleWidth + newSpacing; appendModel(grooveWidth, x, arcBoardOption); } } else { //不能等分,根据指定槽大小绘制,最后补一个小槽 const pericycleWidth = grooveSpacing + grooveWidth; //周期宽度 const count = Math.ceil(remainLength / pericycleWidth); for (let j = 0; j < count; j++) { let x = startDist + j * pericycleWidth + grooveSpacing; if (remainLength >= pericycleWidth) { appendModel(grooveWidth, x, arcBoardOption); remainLength -= pericycleWidth; } else { remainLength -= grooveSpacing * 2; // 最后个槽太小不够用了,向前一个槽靠拢 if (remainLength < 0) { x -= grooveSpacing; remainLength = grooveSpacing + remainLength; } appendModel(remainLength, x, arcBoardOption); } } } } } } return mmm; } function BoxLine(box) { if (box.isEmpty()) return []; let pts = [ new Vector3(box.min.x, box.min.y, box.min.z), new Vector3(box.min.x, box.min.y, box.max.z), new Vector3(box.min.x, box.max.y, box.min.z), new Vector3(box.min.x, box.max.y, box.max.z), new Vector3(box.max.x, box.min.y, box.min.z), new Vector3(box.max.x, box.min.y, box.max.z), new Vector3(box.max.x, box.max.y, box.min.z), new Vector3(box.max.x, box.max.y, box.max.z), ]; let lines = []; for (let line of [ [0, 1], [2, 3], [0, 2], [1, 3], [4, 5], [6, 7], [4, 6], [5, 7], [0, 4], [2, 6], [1, 5], [3, 7], ]) { let p1 = pts[line[0]]; let p2 = pts[line[1]]; if (!equalv3(p1, p2)) lines.push(new Line(p1, p2)); } return lines; } /** 视图类型 */ var ViewType; (function (ViewType) { ViewType["Normal"] = "\u6B63\u89C6\u56FE"; ViewType["Front"] = "\u524D\u89C6\u56FE"; ViewType["Left"] = "\u5DE6\u89C6\u56FE"; ViewType["Right"] = "\u53F3\u89C6\u56FE"; ViewType["Down"] = "\u4FEF\u89C6\u56FE"; ViewType["Unknown"] = "\u672A\u77E5\u89C6\u56FE"; })(ViewType || (ViewType = {})); /** 线段管理器 */ class CurveManager { /* 多段线展开直线(类型依旧是多段线) */ static CreateExpandPl(pl) { const cus = pl.Explode(); let len = 0; const ps = []; for (const cu of cus) { ps.push(new Vector2$1(len, 0)); len += cu.Length; } ps.push(new Vector2$1(len, 0)); return this.CreatePolyline(ps).ApplyMatrix(pl.OCS); } ; /** 绘制矩阵所表示的坐标系 */ static CreateAxis(m) { const len = 100; const xArrow = new Line(new Vector3(0, 0, 0), new Vector3(len, 0, 0)); const yArrow = new Line(new Vector3(0, 0, 0), new Vector3(0, len, 0)); const zArrow = new Line(new Vector3(0, 0, 0), new Vector3(0, 0, len)); xArrow.ColorIndex = 1; yArrow.ColorIndex = 3; zArrow.ColorIndex = 5; xArrow.ApplyMatrix(m); yArrow.ApplyMatrix(m); zArrow.ApplyMatrix(m); return [xArrow, yArrow, zArrow]; } ; /** 点集转线段簇 */ static PtsToLines(pts) { const lines = []; for (let i = 1; i < pts.length; i++) { const p1 = pts[i - 1]; const p2 = pts[i]; const line = new Line(p1, p2); lines.push(line); } return lines; } ; /** 线段簇转多段线 */ static LinesToPl(lines) { const pl = new Polyline(); // 确保坐标系不要沿着直线方向 const lines2 = []; if (lines.length === 1) { lines2.push(lines[0]); const p1 = lines[0].EndPoint; const p2 = p1.clone().add(p1.clone().normalize().multiplyScalar(1)); const line = new Line(p1, p2); lines2.push(line); pl.OCS = ComputerCurvesNormalOCS(lines2); } else { pl.OCS = ComputerCurvesNormalOCS(lines); } // 转成多段线 pl.ColorIndex = lines[0].ColorIndex; for (const splitLine of lines) { pl.Join(splitLine, false, 0.01); } return pl; } ; /** 获取pA-pB之间的曲线 */ static Get_Pl_InPtAtoPtB(pl, pA, pB) { // 确保pA在pB前面 if (pl.GetParamAtPoint(pA) > pl.GetParamAtPoint(pB)) { const temp = pA; pA = pB; pB = temp; } const paramA = pl.GetParamAtPoint(pA); const pls = pl.GetSplitCurves(paramA); pl = pls[1] || pls[0]; const paramB = pl.GetParamAtPoint(pB); return pl.GetSplitCurves(paramB)[0]; } ; } /** X轴的平行线 */ CurveManager.CreateXLine = (p) => new Line(new Vector3(p.x - 1000, p.y, p.z), new Vector3(p.x + 1000, p.y, p.z)); /** Y轴的平行线 */ CurveManager.CreateYLine = (p) => new Line(new Vector3(p.x, p.y - 1000, p.z), new Vector3(p.x, p.y + 1000, p.z)); /** Z轴的平行线 */ CurveManager.CreateZLine = (p) => new Line(new Vector3(p.x, p.y, p.z - 1000), new Vector3(p.x, p.y, p.z + 1000)); /** 通过点集生成多段线 */ CurveManager.CreatePolyline = (ps) => new Polyline(ps.map(p => { return { pt: p, bul: 0 }; })); var LogType; (function (LogType) { LogType["Error"] = "ERROR"; LogType["Warning"] = "WARNING"; LogType["Info"] = "INFO"; LogType["Command"] = "COMMAND"; LogType["All"] = "ALL"; })(LogType || (LogType = {})); const _LogInjectFunctions = []; //Log(`板:${br.Name}没有提供排钻信息!`, LogType.Warning, [br]); function Log(message, ...optionalParams) { for (let f of _LogInjectFunctions) f(message, ...optionalParams); } const _LogInjectInteractionFunctions = []; //InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, cyHole] }, { msg: "侧孔与板无交点,无法加工该侧孔!" }], LogType.Warning); function InteractionLog(message, ...optionalParams) { for (let f of _LogInjectInteractionFunctions) f(message, ...optionalParams); } //为了避免Core对UI库的依赖,导致测试用例失败,导致外部项目引用失败,我们分离了这个函数 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); } const ToasterShowEntityMsgInjectFunctions = []; function ToasterShowEntityMsg(option) { for (let f of ToasterShowEntityMsgInjectFunctions) f(option); let logMsgs = [{ msg: option.msg }]; if (option.ent) logMsgs.push({ msg: "点击查看", entity: Array.isArray(option.ent) ? option.ent : [option.ent] }); InteractionLog(logMsgs, LogType.Warning); } function equaln(v1, v2, fuzz = 1e-5) { return Math.abs(v1 - v2) <= fuzz; } 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; } /** * @param compart true => t2 , false => t1 * @returns 索引 */ function Max(arr, compart) { let best = arr[0]; let bestIndex = 0; for (let i = 1; i < arr.length; i++) { let t1 = arr[i]; if (compart(best, t1)) { best = t1; bestIndex = i; } } return bestIndex; } let Hole = class Hole extends Entity { constructor() { super(...arguments); this.allowHoleAtBoardEdge = false; //允许大孔面在板边缘 this.type = GangDrillType.Pxl; } get Height() { return this._Height; } set Height(v) { if (this._Height !== v) { this.WriteAllObjectRecord(); this._Height = v; this.Update(); } } get Type() { return this.type; } set Type(t) { if (this.type !== t) { this.WriteAllObjectRecord(); this.type = t; } } get AllowPxl() { return this.allowHoleAtBoardEdge; } set AllowPxl(v) { if (this.allowHoleAtBoardEdge !== v) { this.WriteAllObjectRecord(); this.allowHoleAtBoardEdge = v; } } 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 > 5) { this.OtherHalfTongKong = file.ReadSoftObjectId(); } if (ver > 6) { this.allowHoleAtBoardEdge = file.ReadBool(); } else { this.allowHoleAtBoardEdge = false; } if (ver > 8) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } WriteFile(file) { super.WriteFile(file); file.Write(9); file.Write(this._Height); file.WriteSoftObjectId(this.FId); file.WriteSoftObjectId(this.MId); file.WriteSoftObjectId(this.OtherHalfTongKong); file.WriteBool(this.allowHoleAtBoardEdge); // ver9 file.WriteBool(this._LockMaterial); } }; __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._SameSideHole = false; this._GoodsId = ""; this._GoodsSn = ""; this._Color = 1; } static CreateCylHole(radius, height, type) { let drill = new CylinderHole_1(); drill.Height = height; drill._Radius = radius; drill.type = type; return drill; } 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 SameSideHole() { return this._SameSideHole; } set SameSideHole(value) { if (this._SameSideHole !== value) { this.WriteAllObjectRecord(); this._SameSideHole = value; } } get Radius() { return this._Radius; } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this._Matrix); } get GoodsId() { return this._GoodsId; } set GoodsId(value) { if (this._GoodsId !== value) { this.WriteAllObjectRecord(); this._GoodsId = value; } } get GoodsSn() { return this._GoodsSn; } set GoodsSn(value) { if (this._GoodsSn !== value) { this.WriteAllObjectRecord(); this._GoodsSn = value; } } /** * 返回对象在自身坐标系下的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 if (renderType === RenderType.CustomNumber || renderType === RenderType.ModelGroove) return; //不绘制了 // return new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex, FrontSide, true)); 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; if (type === RenderType.CustomNumber || type === RenderType.ModelGroove) mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex, FrontSide, true); else mesh.material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); } } ClearDraw() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; return super.ClearDraw(); } _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(); } if (ver > 5) this.SameSideHole = file.Read(); if (ver > 6) { this.GoodsId = file.Read(); this.GoodsSn = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(7); //ver file.Write(this._Radius); file.Write(this.type); file.Write(this.SameSideHole); file.Write(this.GoodsId); file.Write(this.GoodsSn); } }; 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"); const SCALAR = 0.1; function CyHoleInBoard(cys, br, ocs, checkAll = false) { if (!checkAll && cys.length === 1 && cys[0].Type === GangDrillType.Ymj) return true; const outline = br.ContourCurve; let box = new Box3Ext(); 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, 1e-5)) 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, 1e-4).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 (HostApplicationServices.forceFilterPxl) { if (pxl.AllowPxl) return outline.IntersectWith(cir, 0, 1e-4).length <= 1 && !outline.PtInCurve(center); else return outline.IntersectWith(cir, 0, 1e-4).length > 0 || !outline.PtInCurve(center); } else return outline.IntersectWith(cir, 0, 1e-4).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, 1e-4).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, 1e-4).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, 1e-4).length !== 1 || l2.IntersectWith(outline, 0, 1e-4).length !== 1) return false; } } return true; } /**分析上下左右排钻 */ function ParseBoardRectHoleType(br, outBrRectHoleType = {}) { 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.GetFirstDeriv(0).multiplyScalar(dir); if (Math.abs(derv.x) > Math.abs(derv.y)) { if (derv.x > 0) outBrRectHoleType.down = hightDrill[i]; else outBrRectHoleType.up = hightDrill[i]; } else { if (derv.y > 0) outBrRectHoleType.right = hightDrill[i]; else outBrRectHoleType.left = hightDrill[i]; } } return outBrRectHoleType; } 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 ?? br.OCSInv); } else { return ExtureHoleInBoard(holes, br, ocs ?? br.OCSInv); } } /**上下左右排钻写入到板件的高级排钻中 */ function SetBrHighHoleTypeFromRectHoleType(br, brRectHoleType) { 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.GetFirstDeriv(0).multiplyScalar(dir); if (Math.abs(derv.x) > Math.abs(derv.y)) { if (derv.x > 0) highDrill.push(brRectHoleType.down); else highDrill.push(brRectHoleType.up); } else { if (derv.y > 0) highDrill.push(brRectHoleType.right); else highDrill.push(brRectHoleType.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; } var FontType; (function (FontType) { FontType["YaHei"] = "yahei"; FontType["SongTi"] = "songti"; FontType["KaiTi"] = "KaiTi"; FontType["FangSong"] = "FangSong"; FontType["LiShu"] = "LiShu"; FontType["HeiTi"] = "HeiTi"; FontType["HuaWenLiShu"] = "HuaWenLiShu"; FontType["HuaWenXingKai"] = "HuaWenXingKai"; })(FontType || (FontType = {})); [ { label: "宋体", value: FontType.SongTi }, { label: "雅黑", value: FontType.YaHei }, { label: "楷体", value: FontType.KaiTi }, { label: "仿宋", value: FontType.FangSong }, { label: "隶书", value: FontType.LiShu }, { label: "黑体", value: FontType.HeiTi }, { label: "华文隶书", value: FontType.HuaWenLiShu }, { label: "华文行楷", value: FontType.HuaWenXingKai } ]; var FindModifyStyle; (function (FindModifyStyle) { FindModifyStyle[FindModifyStyle["FuzzyQuery"] = 0] = "FuzzyQuery"; FindModifyStyle[FindModifyStyle["PreciseQuery"] = 1] = "PreciseQuery"; })(FindModifyStyle || (FindModifyStyle = {})); [EBoardKeyList.UpSealed, EBoardKeyList.DownSealed, EBoardKeyList.RightSealed, EBoardKeyList.LeftSealed]; 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["GetHardWareOption"] = 10] = "GetHardWareOption"; EFindType[EFindType["RemoveSplitSize"] = 11] = "RemoveSplitSize"; })(EFindType || (EFindType = {})); var ECompareType; (function (ECompareType) { ECompareType["Equal"] = "="; ECompareType["UnEqual"] = "!="; ECompareType["Greater"] = ">="; ECompareType["Less"] = "<="; ECompareType["Include"] = "//"; ECompareType["Prefix"] = "\u524D\u7F00"; ECompareType["Suffix"] = "\u540E\u7F00"; })(ECompareType || (ECompareType = {})); //门板位置类型 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 ELatticeArrayType; (function (ELatticeArrayType) { ELatticeArrayType[ELatticeArrayType["ByWidth"] = 0] = "ByWidth"; ELatticeArrayType[ELatticeArrayType["ByCount"] = 1] = "ByCount"; })(ELatticeArrayType || (ELatticeArrayType = {})); var ForBoardNameType; (function (ForBoardNameType) { ForBoardNameType["Same"] = "same"; ForBoardNameType["NoSame"] = "nosame"; ForBoardNameType["Include"] = "include"; ForBoardNameType["NoInclude"] = "noinclude"; })(ForBoardNameType || (ForBoardNameType = {})); var CurtailType; (function (CurtailType) { CurtailType["PerBr"] = "0"; CurtailType["Total"] = "1"; CurtailType["OCS"] = "2"; })(CurtailType || (CurtailType = {})); var StripType; (function (StripType) { StripType["H"] = "h"; StripType["V"] = "v"; })(StripType || (StripType = {})); /** *背板靠上还是靠下 * * @export * @enum {number} */ var BehindHeightPositon; (function (BehindHeightPositon) { BehindHeightPositon["ForTop"] = "top"; BehindHeightPositon["ForBottom"] = "bottom"; BehindHeightPositon["AllHeight"] = "all"; //总高 })(BehindHeightPositon || (BehindHeightPositon = {})); /** *板件相对位置 * * @export * @enum {number} */ var ViewDirection; (function (ViewDirection) { ViewDirection[ViewDirection["Left"] = 1] = "Left"; ViewDirection[ViewDirection["Right"] = 2] = "Right"; ViewDirection[ViewDirection["Up"] = 3] = "Up"; ViewDirection[ViewDirection["Front"] = 4] = "Front"; ViewDirection[ViewDirection["Bottom"] = 5] = "Bottom"; ViewDirection[ViewDirection["Back"] = 6] = "Back"; ViewDirection[ViewDirection["Southwest"] = 7] = "Southwest"; })(ViewDirection || (ViewDirection = {})); var ViewportPosition; (function (ViewportPosition) { ViewportPosition["Vertical"] = "vertical"; ViewportPosition["Horizontal"] = "horizontal"; ViewportPosition["Left"] = "left"; ViewportPosition["Right"] = "right"; ViewportPosition["Bottom"] = "bottom"; ViewportPosition["Top"] = "top"; })(ViewportPosition || (ViewportPosition = {})); var RadioType; (function (RadioType) { RadioType["lefttop"] = "1"; RadioType["leftbottom"] = "2"; RadioType["righttop"] = "3"; RadioType["rightbottom"] = "4"; })(RadioType || (RadioType = {})); var EWineRackType; (function (EWineRackType) { EWineRackType[EWineRackType["Oblique"] = 0] = "Oblique"; EWineRackType[EWineRackType["Upright"] = 1] = "Upright"; })(EWineRackType || (EWineRackType = {})); /**酒格样式 */ var EWineRackStyle; (function (EWineRackStyle) { EWineRackStyle[EWineRackStyle["WholeLattice"] = 0] = "WholeLattice"; EWineRackStyle[EWineRackStyle["Semilattice"] = 1] = "Semilattice"; })(EWineRackStyle || (EWineRackStyle = {})); 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 EOrderType; (function (EOrderType) { EOrderType["ByCreate"] = "create_date desc"; EOrderType["ByCreate2"] = "create_date"; EOrderType["ByUpdate"] = "update_date desc"; EOrderType["ByUpdate2"] = "update_date"; EOrderType["ByName"] = "name"; EOrderType["ByName2"] = "name desc"; })(EOrderType || (EOrderType = {})); const DefaultLayerBoardConfig = { version: 4, 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", exprCount: "1", exprThickness: "18" }; Object.freeze(DefaultLayerBoardConfig); const DefaultVerticalBoardConfig = { version: 4, 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", exprCount: "1", exprThickness: "18" }; Object.freeze(DefaultVerticalBoardConfig); const DefaultBehindBoardConfig = { version: 3, 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", exprCount: "1", exprThickness: "18" }; Object.freeze(DefaultBehindBoardConfig); const DefaultWineRackConfig = { version: 6, type: EWineRackType.Oblique, wineRackStyle: EWineRackStyle.WholeLattice, 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, isExtendsBH2: false, followNarrow: false, useBoardProcessOption: true, boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 }; 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: 3, 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, bigHoleDir: FaceDirection.Front, }; 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 DefaultViewportConfigOption = { view: ViewDirection.Up, renderType: RenderType.Print, revertRotation: false }; Object.freeze(DefaultViewportConfigOption); const DefaultViewport2ConfigOption = { viewportPosition: ViewportPosition.Vertical, renderType: [RenderType.Print, RenderType.Print], view1: ViewDirection.Left, view2: ViewDirection.Up, revertRotation: false }; Object.freeze(DefaultViewport2ConfigOption); const DefaultViewport3ConfigOption = { viewportPosition: ViewportPosition.Vertical, renderType: [RenderType.Print, RenderType.Print, RenderType.Print], view: [ViewDirection.Front, ViewDirection.Up, ViewDirection.Southwest], revertRotation: false }; Object.freeze(DefaultViewport3ConfigOption); const DefaultViewport4ConfigOption = { view: [ViewDirection.Front, ViewDirection.Left, ViewDirection.Up, ViewDirection.Southwest], renderType: [RenderType.Print, RenderType.Print, RenderType.Print, RenderType.Print], revertRotation: false }; Object.freeze(DefaultViewport4ConfigOption); const DefaultModifyTextsOption = { changeTexts: Array.from({ length: 5 }, () => ["", ""]), }; Object.freeze(DefaultModifyTextsOption); const DefaultPointLightOption = { version: 1, lightColor: "#FFFFFF", temperature: 6500, Intensity: 100, IndirectLightingIntensity: 3, SpecularScale: 1, SourceRadius: 10, SoftSourceRadius: 0, SourceLength: 0, CaseShadow: true, }; Object.freeze(DefaultPointLightOption); const DefaultSpotLightOption = { version: 1, lightColor: "#FFFFFF", temperature: 6500, Intensity: 100, IndirectLightingIntensity: 3, SpecularScale: 1, SourceRadius: 10, SoftSourceRadius: 30, SourceLength: 0, Angle: 45, InnerConeAngle: 30, AttenuationRadius: 300, CaseShadow: true, ShowHelper: true, }; Object.freeze(DefaultSpotLightOption); const DefaultRectAreaLightOption = { version: 1, lightColor: "#FFFFFF", temperature: 6500, Intensity: 100, IndirectLightingIntensity: 3, SpecularScale: 1, AttenuationRadius: 300, Width: 150, Height: 150, BarnDoorAngle: 90, BarnDoorLength: 20, CaseShadow: true, ShowHelper: true, }; Object.freeze(DefaultRectAreaLightOption); const DefaultRightPlaneLightOption = { version: 3, ShowHemiLight: true, ShowSunLight: true, SkyLightColor: "#FFFFFF", SkyLightIntensity: 1, SkyLightIndirectLightingIntensity: 3, SunLightIntensity: 50, SunLightIndirectLightingIntensity: 3, SunLightColor: "#FFFFFF", SunLightTemperature: 6500, SunLightElevationDeg: 60, SunLightRotateDeg: 300, SunLightSourceAngle: 0.5, ShowExposure: true, AutoExposure: false, ExposureCompensation: 0, SunTime: "默认", }; Object.freeze(DefaultRightPlaneLightOption); const DefaultArcBoardOption = { version: 1, name: "圆弧板", type: BoardType.Layer, height: 1200, thickness: 18, }; Object.freeze(DefaultArcBoardOption); 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: 6, type: BoardType.Vertical, name: "收口条", //辅助条占用 striptype: StripType.H, boardRelative: BrRelativePos.Left, width: 54, thickness: 18, frontShrink: 0, isDrawFuZhu: true, fzWidth: 80, fzThickness: 18, addSKTCabinetName: true, brName: "左收口条", FuZhuType: StripType.V, fuZhuBrName: '', fuZhuMaterial: '', fuZhuColor: '', }; Object.freeze(DefaultClosingStripOption); const DefaultBoardFindOption = { version: 14, condition: { // layer: false, //这个KEY看起来没用了 height: false, width: false, thickness: false, useWood: false, useDrill: false, useNail: false, useDoor: false, useDim: false, useSpecial: false, useModeling: false, roomName: false, hardwareName: false, cabinetName: false, brName: false, material: false, lines: false, bigHoleDir: false, drillType: false, useKeyWord: false, addRemarks: false, composingFace: false, openDir: false, sealedUp: false, sealedDown: false, sealedLeft: false, sealedRight: false, upDrill: false, downDrill: false, leftDrill: false, rightDrill: false, useZhengFanDrill: false, useChaidan: false, [EBoardKeyList.KnifeRad]: false, edgeRemarkUp: false, edgeRemarkDown: false, edgeRemarkLeft: false, edgeRemarkRight: false, [EBoardKeyList.SpliteHeight]: false, [EBoardKeyList.SpliteWidth]: false, [EBoardKeyList.SpliteThickness]: false, useCurve: false, noUseBr: false, noUseHwd: false, useText: false, }, compareType: { height: ECompareType.Equal, width: ECompareType.Equal, thickness: ECompareType.Equal, roomName: ECompareType.Equal, cabinetName: ECompareType.Equal, brName: ECompareType.Equal, hardwareName: 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, openDir: ECompareType.Equal, [EBoardKeyList.KnifeRad]: ECompareType.Equal, edgeRemarkUp: ECompareType.Equal, edgeRemarkDown: ECompareType.Equal, edgeRemarkLeft: ECompareType.Equal, edgeRemarkRight: ECompareType.Equal, [EBoardKeyList.SpliteHeight]: ECompareType.Equal, [EBoardKeyList.SpliteWidth]: ECompareType.Equal, [EBoardKeyList.SpliteThickness]: ECompareType.Equal, }, tolerance: { height: "", width: "", thickness: "", [EBoardKeyList.KnifeRad]: "", [EBoardKeyList.SpliteHeight]: "", [EBoardKeyList.SpliteWidth]: "", [EBoardKeyList.SpliteThickness]: "", }, // layer: "0", 删除无用的key height: "", width: "", thickness: "", roomName: "", cabinetName: "", brName: "", hardwareName: "", [EBoardKeyList.BrMat]: "", material: "", color: "", lines: LinesType.Positive, bigHoleDir: FaceDirection.Front, drillType: "", composingFace: ComposingType.Positive, openDir: BoardOpenDir.None, hardwareDoorName: "", sealedUp: "", sealedDown: "", sealedLeft: "", sealedRight: "", highDrill: [], upDownDrill: [true, true], isClose: false, remarks: Array.from({ length: 10 }, () => ["", ""]), extraRemarks: Array.from({ length: 10 }, () => ["", ""]), isChaidan: false, [EBoardKeyList.KnifeRad]: "", edgeRemarkUp: "", edgeRemarkDown: "", edgeRemarkLeft: "", edgeRemarkRight: "", query: FindModifyStyle.FuzzyQuery, [EBoardKeyList.SpliteHeight]: "", [EBoardKeyList.SpliteWidth]: "", [EBoardKeyList.SpliteThickness]: "", appendRemark: Array.from({ length: 10 }, () => ["", "", AddRemarkType.Cover]), }; Object.freeze(DefaultBoardFindOption); const DefaultLatticOption = { version: 4, 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, linesType: LinesType.Reverse, useBoardProcessOption: true, boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 }; Object.freeze(DefaultLatticOption); const DefaultDoorOption = { version: 13, 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, cbHightDrillOption: { up: "", down: "", left: "", right: "" }, 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, frontAndBackDrill: false, verticalBoardName: "立板", layerBoardName: "层板", lbSealedUp: 1, lbSealedDown: 1, lbSealedLeft: 1, lbSealedRight: 1, lbHightDrillOption: { up: "", down: "", left: "", right: "" }, useBoardProcessOption: true, isModifyHardwareMaterial: false, deviation: 100, //偏移量 boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 layerEdgeRemarkUp: '', //层板板边备注上下左右 layerEdgeRemarkDown: '', layerEdgeRemarkLeft: '', layerEdgeRemarkRight: '', verticalEdgeRemarkUp: '', //立板板边备注上下左右 verticalEdgeRemarkDown: '', verticalEdgeRemarkLeft: '', verticalEdgeRemarkRight: '', parseHinge: false, sealColorUp: "", //门板封边颜色 sealColorDown: "", sealColorLeft: "", sealColorRight: "", reservedEdgeUp: "0", //门板预留边 reservedEdgeDown: "0", reservedEdgeLeft: "0", reservedEdgeRight: "0", layerSealColorUp: "", //层板封边颜色 layerSealColorDown: "", layerSealColorLeft: "", layerSealColorRight: "", layerReservedEdgeUp: "0", //层板预留边 layerReservedEdgeDown: "0", layerReservedEdgeLeft: "0", layerReservedEdgeRight: "0", verticalSealColorUp: "", //立板封边颜色 verticalSealColorDown: "", verticalSealColorLeft: "", verticalSealColorRight: "", verticalReservedEdgeUp: "0", //层板预留边 verticalReservedEdgeDown: "0", verticalReservedEdgeLeft: "0", verticalReservedEdgeRight: "0", }; Object.freeze(DefaultDoorOption); const DefaultHingeOption = { version: 1, hingeCount: 0, hindeTopDist: 0, hindeBottomDist: 0, useRule: false, deviation: 100, parseHinge: false, }; Object.freeze(DefaultHingeOption); const DefaultDrawerOption = { version: 10, 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, isFloor50: 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", verticalBoardName: "立板", lbSealedUp: 1, lbSealedDown: 1, lbSealedLeft: 1, lbSealedRight: 1, lbHightDrillOption: { up: "", down: "", left: "", right: "" }, useBoardProcessOption: true, isModifyHardwareMaterial: false, boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 sealColorUp: "", //抽屉立板封边颜色 sealColorDown: "", sealColorLeft: "", sealColorRight: "", reservedEdgeUp: "0", //抽屉立板预留边 reservedEdgeDown: "0", reservedEdgeLeft: "0", reservedEdgeRight: "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 DefaultBatchModifyPanelOption = { version: 1, length: "L", width: "W", thick: "H", position: RadioType.lefttop, }; Object.freeze(DefaultBatchModifyPanelOption); const DefaultLatticeConfig = { version: 1, 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, linesType: LinesType.Reverse, useBoardProcessOption: true, boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 }; 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: 3, 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, goodsId: "", goodsSn: "" }; Object.freeze(DefaultCompositeMetalsOption); const DefaultToplineMetalsOption = { version: 3, name: "顶线", unit: "毫米", roomName: "", cabinetName: "", costExpr: "", actualExpr: "", model: "", factory: "", brand: "", spec: "", comments: "", addLen: "0", isHole: false, goodsId: "", goodsSn: "" }; Object.freeze(DefaultToplineMetalsOption); const DefaultBoardProcessOption = { version: 5, 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, edgeRemarkUp: "", edgeRemarkDown: "", edgeRemarkLeft: "", edgeRemarkRight: "", highBoardEdgeRemark: [], reservedEdgeUp: "0", reservedEdgeDown: "0", reservedEdgeLeft: "0", reservedEdgeRight: "0", highReservedEdge: [], sealColorUp: "", sealColorDown: "", sealColorLeft: "", sealColorRight: "", sealColorType: "", }; 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: 3, [EBoardKeyList.BrName]: "", [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, edgeRemarkUp: "", edgeRemarkDown: "", edgeRemarkLeft: "", edgeRemarkRight: "", condition: { [EBoardKeyList.BrName]: false, [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, autoCutOption: { isAutoCut: false, isRelevance: false }, edgeRemarkUp: false, edgeRemarkDown: false, edgeRemarkLeft: false, edgeRemarkRight: false, } }; Object.freeze(DefaultUpdateInfoOption); const DefaultKuGanOption = { insertMode: "0", minSpacing: 50, count: 1, isHor: false, depth: 0, isDefault: true, leftDist: 0, rightDist: 0, upDist: 0, downDist: 0, }; Object.freeze(DefaultKuGanOption); const DefaultParseBoardNameOPtion = { version: 3, verticalBrShrink: 0, layerBrShrink: 0, topBrShrink: 0, bottomBrShrink: 0, groundLineBrShrink: 0, farLeftVerticalBrName: "左侧板", farRightVerticalBrName: "右侧板", middleVerticalBrName: "立板", topMostLayerBrName: "顶板", middleLayerBrName: "层板", bottomMostLayerBrName: "底板", bottomMostBackBrName: "地脚线", stripeBrName: "收口条", cabinetName: "", isfarLeftVerticalBrName: true, //最左侧立板名称 isModifyMiddleVerticalBrName: false, //是否修改中间立板名称 isfarRightVerticalBrName: true, istopMostLayerBrName: true, isModifyMiddleLayerBrName: false, //是否修改中间层板名称 isbottomMostLayerBrName: true, isbottomMostBackBrName: true, isstripeBrName: true, iscabinetName: false, //修改柜名 isModifyRoomName: true, //修改房名 isMultiBackBr: false, isBack: true, backName: "背板", isAloneStripName: true, //收口条名字独立 }; Object.freeze(DefaultParseBoardNameOPtion); const DefaultR2bOption = { version: 8, 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, backBrTemplateId: "", remarks: Array.from({ length: 12 }, () => ["", ""]), maxThickness: 20, useBrName: true, configName: "", backBrName: "背板", behindIsRelative: false, footerThickness: 18, closeStripThickness: 18, useSktTemplate: false, sktTemplate: null, sktTemplateId: "", rightSktTemplateId: "", topSktTemplateId: "", }; Object.freeze(DefaultR2bOption); const DefaultR2b2Option = { version: 2, depthExpr: "W", drillType: "", sealedDown: "1", sealedLeft: "1", sealedRight: "1", sealedUp: "1", remarks: Array.from({ length: 12 }, () => ["", ""]), maxThickness: 20, layerShrink: 0, vertialShrink: 0, useBoardProcessOption: true, boardMatName: "", //板材名 material: "", //材料 color: "", //颜色 roomName: "", //房名 cabinetName: "", //柜名 }; Object.freeze(DefaultR2b2Option); ({ version: 7, isAll: true, isHide: true, isDelete: false, behind: false, layer: false, vertial: false, footer: false, tbBoard: false, specialShape: false, hole: false, door: false, drawer: false, closingStrip: false, noChaiDan: false, winerack: false, wood: false, nails: false, topline: false, handle: false, hinge: false, hwComposity: false, lattice: false, dim: false, visual: false, curve: false, line: false, polyline: false, circle: false, arc: false, custom: false, spotlight: false, pointLight: false, rectAreaLight: false, customBoardName: "", matchType: ECompareType.Equal, text: false, }); const DefaultCommonPanelOption = { version: 1, orderMap: {}, orderType: EOrderType.ByUpdate, }; Object.freeze(DefaultCommonPanelOption); const DefaultDatalistOption = { resizeUI: {} }; Object.freeze(DefaultDatalistOption); const DefaultAutoDimBrsOption = { version: 2, total: true, out: true, inW: false, inH: false, noRepeat: false, noSmSize: false, noAppointSize: false, noInSize: false, noShowMinSize: 20, noShowMinInSize: 300, noShowAppointSizes: "", useParseGroups: "0", forBoardName: false, forBoardNameStr: "", forBoardNameType: ForBoardNameType.Same, insideOffset: false, insideOffsetDist: 0 }; Object.freeze(DefaultAutoDimBrsOption); const DefaultWindowPanelOption = { Length: 1100, Height: 1500, Thick: 280, WindowOffGround: 900, IsBayWindow: false, BayLeftIsWall: false, BayRightIsWall: false, BayDist: 500, BayLeftDist: 600, BayMiddleDist: 600, BayRightDist: 600, HasWindowStone: false, StoneThick: 50, StoneBulge: 50, StoneLeftRightBulge: 50, }; Object.freeze(DefaultWindowPanelOption); const DefaultDimStyleOption = { dimFXLON: true, dimFXL: 100, dimALTD: 2, dimASZ: 10, dimGAP: 10, dimEXE: 20, dimTXT: 60, dimTAD: 2, dimADEC: 2, }; Object.freeze(DefaultDimStyleOption); const DefaultChangeColorByBoardMaterialOption = { accordThickness: false, accordMaterialColor: true, accordMaterial: true, accordMaterialName: true }; Object.freeze(DefaultChangeColorByBoardMaterialOption); const DefaultShareBoardInfConfigurationOption = { version: 2, Physical2EdgeColor: 7, VisualStyle: RenderType.Conceptual, Viewport: ViewDirection.Southwest, IsExportBoard: true, IsExportHardware: true, showBom: true, expireDays: "", IsExportCurve: true, IsExportDimension: true, IsExportText: true }; Object.freeze(DefaultShareBoardInfConfigurationOption); const DefaultBulkheadCeilingOption = { Item: [], Height: 300 }; Object.freeze(DefaultBulkheadCeilingOption); const DefaultChangeColorByRoomOrCabinetOption = { accordCabinetName: true, accordRoomName: true }; Object.freeze(DefaultChangeColorByRoomOrCabinetOption); const DefaultDoorRelatesInfoOption = { version: 2, hingeOption: [], hingeDecimal: 1, }; Object.freeze(DefaultDoorRelatesInfoOption); const DefaultPartsAnalysisOption = { version: 1, PartsOption: [ ["name", true], ["factory", false], ["brand", false], ["model", false], ["spec", true], ["material", false], ["color", false], ["unit", false], ["count", true], ["comments", false], ["process", false], ["actualExpr", false], ] }; Object.freeze(DefaultPartsAnalysisOption); const DefaultFastDimOption = { filterSmallSize: false, filterSmallSizeValue: 0, filterAppointSize: false, filterAppointSizeValues: "", filterAppointForBoardName: false, filterAppointForBoardNameValues: "", conditionType: ForBoardNameType.Same, }; Object.freeze(DefaultFastDimOption); const DefaultOneClickInspectionOption = { version: 2, InspectionOption: { isInterfere: true, isMaxSizeBoard: true, isMinSizeBoard: true, isModel: true, isDrill: true, isSpecialBoardContour: true, isSplitBoard: true, isDrawHole: true, }, excludeDrawHoleOption: { boardName: false, boardNameValue: "", processingGroupName: false, processingGroupNameValue: "", boardThickness: false, boardThicknessValue: 18, IsSplitBoard: false, boardNameCompareType: ECompareType.Include, processingGroupNameCompareType: ECompareType.Include, boardThicknessCompareType: ECompareType.Less, }, excludeInterfereOption: { boardName: false, boardNameValue: "", processingGroupName: false, processingGroupNameValue: "", hwComposityName: false, hwComposityNameValue: "", noChaiDanBoard: false, boardNameCompareType: ECompareType.Include, processingGroupNameCompareType: ECompareType.Include, hwComposityNameCompareType: ECompareType.Include, } }; Object.freeze(DefaultOneClickInspectionOption); const DefaultArcBoardGrooveOption = { version: 2, isDrawArcGroove: true, retainedThickness: "2", knifeRadius: "3", grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "0", grooveSpacing2: "6", grooveWidth2: "6", arcExtension2: "0", retainedThickness2: "2", knifeRadius2: "3", grooveAddLength2: "0", grooveAddWidth2: "0", grooveAddDepth2: "0", }; Object.freeze(DefaultArcBoardGrooveOption); const DefaultSpaceParseOption = { autoParseDepth: false, }; Object.freeze(DefaultSpaceParseOption); const DefaultEditViewOption = { version: 1, hight: 60, renderType: false, renderTypeValue: "概念", viewDir: false, viewDirValue: "左视图", roomName: false, cabinetName: false, boardMaterialName: false, material: false, color: false, page: false, date: false, designer: false, sheetName: false, hideDoor: false, hideDrawer: false, hideDim: false, hideCurve: false, hideModel: false, hideLayer: false, hideLayerValue: "", showLayer: false, showLayerValue: "" }; Object.freeze(DefaultEditViewOption); const DefaultFontStyleOption = { height: 60, fontType: FontType.YaHei, widthFactor: 1, }; Object.freeze(DefaultFontStyleOption); /** * 使用轮廓和扫描路径构建扫描几何体,实现衣柜中的顶线或者地脚线之类的实体. * 该几何体需要轮廓和路径的起始截面垂直,否则构造的实体将会错误. */ class SweepGeometry extends Geometry { constructor(contour, path, ShapeMaterialSlotData) { super(); this.edgePts = []; this.polygonIndexes = []; this.ShapeMaterialSlotData = ShapeMaterialSlotData; if (Array.isArray(path)) this.AddShape2(contour, path); else this.AddShape(contour, path); this.computeVertexNormals(); this.computeFaceNormals(); } get LineGeom() { let lineGeo = new LineGeometry(); let lineSegments = new Float32Array(this.edgePts); var instanceBuffer = new InstancedInterleavedBuffer(lineSegments, 6, 1); lineGeo.setAttribute('instanceStart', new InterleavedBufferAttribute(instanceBuffer, 3, 0)); lineGeo.setAttribute('instanceEnd', new InterleavedBufferAttribute(instanceBuffer, 3, 3)); return lineGeo; } get EdgeGeom() { return new BufferGeometry().setAttribute('position', new Float32BufferAttribute(this.edgePts, 3)); } 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.OCSNoClone); 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.OCSNoClone); let isClosePath = path.IsClose; let verts = []; //所有路径上的轮廓点 let pathNormal = path.Normal; //计算所有需要的几何点,本质是不断的投影 if (!isClosePath) verts.push(ProjectionToPlane(shapePts3d, pathNormal, pathPts[0], undefined, pathPts[1])); //遍历所有的路径节点进行顶点投射 for (let i = 1; i < pathPts.length; i++) { if (i === pathPts.length - 1) { if (isClosePath) verts.push(ProjectionToPlane(shapePts3d, pathNormal, pathPts[i], pathPts[i - 1], pathPts[1])); else verts.push(ProjectionToPlane(shapePts3d, pathNormal, pathPts[i], pathPts[i - 1])); } else { verts.push(ProjectionToPlane(shapePts3d, pathNormal, pathPts[i], pathPts[i - 1], pathPts[i + 1])); } } if (isClosePath) verts.unshift(verts[verts.length - 1]); this.BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts); if (!isClosePath) this.BuildLid(shapePts2d, verts); } AddShape2(contour, paths) { let pathPts = []; let pathNormals = []; //路径点表 for (let path of paths) { let pathPts2d = path.Shape.getPoints(4); arrayRemoveDuplicateBySort(pathPts2d, (p1, p2) => { if (equalv2(p1, p2)) { p2["_mask_"] = p1["_mask_"]; return true; } return false; }); if (path !== paths[0]) pathPts2d.shift(); let pNormal = path.Normal; for (let p of pathPts2d) { let p3 = AsVector3(p).applyMatrix4(path.OCSNoClone); p3["_mask_"] = p["_mask_"]; pathPts.push(p3); pathNormals.push(pNormal); } } 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.OCSNoClone); let isClosePath = equalv3(pathPts[0], pathPts[pathPts.length - 1], 1e-3); let verts = []; //所有路径上的轮廓点 //计算所有需要的几何点,本质是不断的投影 if (!isClosePath) verts.push(ProjectionToPlane(shapePts3d, pathNormals[0], pathPts[0], undefined, pathPts[1])); //遍历所有的路径节点进行顶点投射 for (let i = 1; i < pathPts.length; i++) { if (i === pathPts.length - 1) { if (isClosePath) verts.push(ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1], pathPts[1])); else verts.push(ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1])); } else { verts.push(ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1], pathPts[i + 1])); } } if (isClosePath) verts.unshift(verts[verts.length - 1]); this.BuildSideFaces(shapePts2d, pathPts, pathPts, verts); if (!isClosePath) this.BuildLid(shapePts2d, verts); } /** * 使用4点构建面 * @param a 左下 * @param b 右下 * @param c 左上 * @param d 右上 * @param uvs * @param [materialIndex] */ BuildFace4(a, b, c, d, uvs, materialIndex) { let f1 = new Face3(a, b, c, undefined, undefined, materialIndex); let f2 = new Face3(b, d, c, undefined, undefined, materialIndex); 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()]); } /** * 构造边缘面开始标记 * @param dir 前进方向(单位向量) */ SideStartMark(dir) { } BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts) { let addCount = 0; //补充个数 shapePts2d[0]["_mask_"] = true; for (let p of shapePts2d) if (p["_mask_"]) { addCount++; if (this.ShapeMaterialSlotData) p["_material_index_"] = this.ShapeMaterialSlotData[addCount - 1]; } let sumCount = addCount + shapePts2d.length; //实际个数 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(); this.SideStartMark(p1Dir); let tempStartX = 0; let lastMaterialIndex = undefined; for (let contourIndex = 0; contourIndex < shapePts2d.length; contourIndex++) { let p1 = pts[contourIndex]; let p2 = pts2[contourIndex]; let p2d = shapePts2d[contourIndex]; if (p2d["_mask_"]) lastMaterialIndex = p2d["_material_index_"] ?? lastMaterialIndex; 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$1(v1, x1), new Vector2$1(v2, x2), new Vector2$1(v1, x3), new Vector2$1(v2, x4), ]; this.BuildFace4(curIndex, nextIndex, curIndex2, nextIndex2, uvs, lastMaterialIndex); } 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 mtx = ContourTransfromToPath(curP, normal, nextP.clone().sub(curP)); pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); } else if (!nextP && preP) { let mtx = ContourTransfromToPath(curP, normal, curP.clone().sub(preP)); pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); } 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); if (equalv2(v, ZeroVec)) norm.copy(dir); //角平分线的平面 let plane = new PlaneExt(norm, curP); let mtx = ContourTransfromToPath(preP, normal, dir); pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); 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; } class SweepGeometrySimple extends SweepGeometry { computeVertexNormals() { } computeFaceNormals() { } BuildFace4(a, b, c, d, uvs, materialIndex) { let polygon = [a, b, d, c]; polygon["dir"] = this._curDir; this._curSidePolygons.push(polygon); } SideStartMark(dir) { this._curDir = dir; if (this._curSidePolygons?.length) this.SidePolygons.push(this._curSidePolygons); this._curSidePolygons = []; } BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts) { this.shapeVerts = verts; this.shapePts2d = shapePts2d; pathPts2d[0]["_mask_"] = true; if (!this.TriFaces) this.TriFaces = ShapeUtils.triangulateShape(shapePts2d, []); if (!this.SidePolygons) this.SidePolygons = []; super.BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts); if (this._curSidePolygons?.length) this.SidePolygons.push(this._curSidePolygons); } BuildLid(shapePts2d, verts) { } } // 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 ?? this.ocs).extractBasis(xAxisA, yAxisA, zAxisA); (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 ?? obb.center, 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._DisplayAccuracy = 0; this._Contour = contour; this._PathCurve = pathCurve; if (this._Contour && this._Contour.Id) this._Contour = this._Contour.Clone(); if (this._Contour && this._PathCurve) { this.TransfromPathToWCS(); //将OCS变换成第一个路径的OCS(合理一点) let paths = this.Paths; let path = paths[0]; this.OCS = path.OCSNoClone; this._SpaceOCS.copy(path.OCSNoClone); let ocsInv = this.OCSInv; for (let p of paths) p.ApplyMatrix(ocsInv); } } Explode() { if (Array.isArray(this._PathCurve)) { const explode = [this._Contour.Clone().ApplyMatrix(this._Matrix)]; for (let path of this._PathCurve) { explode.push(path.Clone().ApplyMatrix(this._Matrix)); } return explode; } return [this._Contour.Clone().ApplyMatrix(this._Matrix), this._PathCurve.Clone().ApplyMatrix(this._Matrix)]; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } get Contour() { return this._Contour; } get Path() { return this._PathCurve; } //单纯的返回数组 get Paths() { return Array.isArray(this._PathCurve) ? this._PathCurve : [this._PathCurve]; } Reverse() { this.WriteAllObjectRecord(); if (Array.isArray(this._PathCurve)) { for (let path of this._PathCurve) path.Reverse(); this._PathCurve.reverse(); } else this._PathCurve.Reverse(); this.Update(); } /** * 将轮廓变换到wcs空间,当用户选定某个与扫描线起点相切的轮廓时. */ TransfromPathToWCS() { if (Array.isArray(this._PathCurve)) return; if (equalv3(this._Contour.Normal, ZAxis)) return; let fDir = this._PathCurve.GetFirstDeriv(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.GetFirstDeriv(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("错误:提供的轮廓没有和路径垂直!", LogType.Error); } 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); } if (this._PathCurve instanceof Arc || this._PathCurve instanceof Circle || this._PathCurve instanceof Polyline) { this._PathCurve.DisplayAccuracy = this._DisplayAccuracy; } this._Contour.DisplayAccuracy = this._DisplayAccuracy; this._MeshGeometry = new SweepGeometry(contour, this._PathCurve); this._EdgeGeometry = this._MeshGeometry.EdgeGeom; this._LineGeom = this._MeshGeometry.LineGeom; this._MeshGeometry.edgePts = undefined; return this._MeshGeometry; } catch (error) { return new BoxBufferGeometry(1000, 1000, 1000); } } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this.MeshGeometry; return this._EdgeGeometry; } InitDrawObject(renderType) { if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { let line = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); // for (let p of this.Paths) // { // p.IsEmbedEntity = true; // let lineObj = p.GetDrawObjectFromRenderType(RenderType.Wireframe) as TLine; // lineObj.material = ColorMaterial.GetWallLineMtl(1); // lineObj.computeLineDistances(); // lineObj.matrix.copy(p.OCSNoClone); // line.add(lineObj); // } return line; } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } 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._LineGeom, ColorMaterial.PrintLineMatrial); return new Object3D().add(line, mesh); } else if (renderType === RenderType.Jig) { if (Array.isArray(this._PathCurve)) { const object3d = new Object3D(); for (let path of this._PathCurve) object3d.add(path.DrawObject); return object3d; } 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.GetPhysical2EdgeMaterial())); } } UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); if (renderType === RenderType.Wireframe || renderType === RenderType.Edge) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex); // Object3DRemoveAll(l); // for (let p of this.Paths) // { // p.IsEmbedEntity = true; // let lineObj = p.GetDrawObjectFromRenderType(RenderType.Wireframe) as TLine; // lineObj.material = ColorMaterial.GetWallLineMtl(1); // lineObj.computeLineDistances(); // l.add(lineObj); // } } else if (renderType === RenderType.Conceptual) { Object3DRemoveAll(obj); return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { Object3DRemoveAll(obj); let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.geometry = this.MeshGeometry; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Jig) { Object3DRemoveAll(obj); if (Array.isArray(this._PathCurve)) for (let path of this._PathCurve) obj.add(path.DrawObject); else 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.GetPhysical2EdgeMaterial())); } } /** * 当实体需要被更新时,更新实体材质 */ UpdateDrawObjectMaterial(type, obj) { if (type === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex); } else if (type === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } // 概念透明 else if (type === RenderType.ConceptualTransparent) { let mesh = obj.children[0]; let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; mesh.material = ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide); } else if (type === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else { let mesh = obj; mesh.material = this.MeshMaterial; } } get BoundingBox() { let geom = this.MeshGeometry; if (!geom) { console.error("SweepSolid无法建模"); return new Box3; } if (!geom.boundingBox) geom.computeBoundingBox(); return geom.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, frustum) { 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 pts = []; //拷贝来自圆弧板最近点捕捉 if (snapMode === ObjectSnapMode.Nea) { //这里实现对线框的快速捕捉 let edgeGeom = this.EdgeGeometry; let pos = edgeGeom.getAttribute("position"); let p1 = new Vector3; let p2 = new Vector3; let line = new Line(p1, p2); line.ApplyMatrix(this.OCSNoClone); let sel = frustum["_select_"]; for (let y = 0; y < pos.count; y += 2) { p1.fromArray(pos.array, y * 3); p2.fromArray(pos.array, (y + 1) * 3); p1.applyMatrix4(this._Matrix); p2.applyMatrix4(this._Matrix); sel.WorldToScreenPoint(p1); sel.WorldToScreenPoint(p2); if (sel.IntersectLine(p1, p2)) { p1.fromArray(pos.array, y * 3); p2.fromArray(pos.array, (y + 1) * 3); arrayPushArray(pts, line.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } } return pts; } let pathArr = Array.isArray(this._PathCurve) ? this._PathCurve : [this._PathCurve]; for (let path of pathArr) { let contour = path.Clone(); contour.ApplyMatrix(this.OCSNoClone); arrayPushArray(pts, contour.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); if (snapMode === ObjectSnapMode.Mid) arrayPushArray(pts, this.GetMidPoints()); } return pts; } } return []; } /** 获取夹点与子实体的索引 */ GetGripSubIndexMap() { if (this._GripSubIndexMap) return this._GripSubIndexMap; this.GetGripPoints(); return this._GripSubIndexMap; } GetGripPoints() { this._GripSubIndexMap = undefined; if (Array.isArray(this._PathCurve)) { this._GripSubIndexMap = new Map; const points = []; for (let path of this._PathCurve) { let pts = path.GetGripPoints(); for (let p of pts) { this._GripSubIndexMap.set(points.length, path); p.applyMatrix4(this._Matrix); points.push(p); } } return points; } else { let pts = this._PathCurve.GetGripPoints(); for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } } GetStretchPoints() { if (Array.isArray(this._PathCurve)) { const points = []; for (let path of this._PathCurve) { let pts = path.GetStretchPoints(); for (let p of pts) { p.applyMatrix4(this._Matrix); points.push(p); } } return points; } else { let pts = this._PathCurve.GetStretchPoints(); for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } } //端点捕捉时提供端点 GetEndPoint() { let pathPts = []; let pathNormals = []; //路径点表 if (Array.isArray(this._PathCurve)) { for (let path of this._PathCurve) { pathPts = path.GetStretchPoints(); arrayRemoveDuplicateBySort(pathPts, (p1, p2) => { if (equalv3(p1, p2)) { p2["_mask_"] = p1["_mask_"]; return true; } return false; }); if (path !== this._PathCurve[0]) pathPts.shift(); let pNormal = path.Normal; for (let p of pathPts) pathNormals.push(pNormal); } } else { const path = this._PathCurve; //路径点表 pathPts = path.GetStretchPoints(); if (path.IsClose && !equalv3(pathPts[0], pathPts[pathPts.length - 1], 1e-3)) pathPts.push(pathPts[0]); arrayRemoveDuplicateBySort(pathPts, equalv3); let pNormal = path.Normal; for (let p of pathPts) pathNormals.push(pNormal); } let shapePts2d = this.Contour.GetStretchPoints(); //轮廓点表 let shapePts3d = shapePts2d.map(AsVector3); let isClosePath = equalv3(pathPts[0], pathPts[pathPts.length - 1], 1e-3); let pts = []; //端点 if (!isClosePath) pts.push(...ProjectionToPlane(shapePts3d, pathNormals[0], pathPts[0], undefined, pathPts[1])); //遍历所有的路径节点进行顶点投射 for (let i = 1; i < pathPts.length; i++) { if (i === pathPts.length - 1) { if (isClosePath) pts.push(...ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1], pathPts[1])); else pts.push(...ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1])); } else { pts.push(...ProjectionToPlane(shapePts3d, pathNormals[i], pathPts[i], pathPts[i - 1], pathPts[i + 1])); } } for (let pt of pts) pt.applyMatrix4(this.OCSNoClone); return pts; } GetMidPoints() { let conPts = this._Contour.GetStretchPoints(); const pts = []; const UpdateEndMtx = (path) => { for (let i = 0.5; i < path.EndParam; i++) { let pos = path.GetPointAtParam(i); let dir = path.GetFirstDeriv(i).normalize(); let y = path.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)); pts.push(...conPts.map(p => p.clone().applyMatrix4(tempMatrix1))); } }; if (Array.isArray(this._PathCurve)) for (let path of this._PathCurve) UpdateEndMtx(path); else UpdateEndMtx(this._PathCurve); return pts; } MoveGripPoints(indexList, vec) { if (equalv3(vec, ZeroVec)) return; this.WriteAllObjectRecord(); vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); if (Array.isArray(this._PathCurve)) { let ptsLengths = 0; for (let i = 0; i < this._PathCurve.length; i += 1) { const path = this._PathCurve[i]; const pathGripPts = path.GetGripPoints(); const idxList = indexList.filter(v => v >= ptsLengths && v < (ptsLengths + pathGripPts.length)).map((v) => v - ptsLengths); ptsLengths += pathGripPts.length; if (idxList.length === 0) continue; let isMoveLine = (path instanceof Line && idxList.length === 1 && idxList[0] === 1); let isMovePolylineStart = (path instanceof Polyline) && idxList.length === 1 && idxList.includes(1); let isMovePolylineEnd = (path instanceof Polyline) && idxList.length === 1 && idxList.includes(pathGripPts.length - 2); //如果不是整体移动,那么vec被限制在CURVE OCS Z0 let isMove = isMoveLine || idxList.length === pathGripPts.length; if (!isMove) { vec = vec.clone() .applyMatrix4(path.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(path.OCS.setPosition(0, 0, 0)); } //我们校验它的前后曲线支不支持它移动这个点 let prePath = this._PathCurve[FixIndex$1(i - 1, this._PathCurve)]; let nextPath = this._PathCurve[FixIndex$1(i + 1, this._PathCurve)]; if ((isMoveLine || idxList.includes(0) || isMovePolylineStart) //(move line ) or (move start) or(move pl start) && (i || equalv3(prePath.EndPoint, path.StartPoint, 1e-3))) //连接到下一段 { //vec限制在上一段的坐标系内 无z vec = vec.clone() .applyMatrix4(prePath.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(prePath.OCS.setPosition(0, 0, 0)); if (isMoveLine || isMovePolylineStart) //顺带移动上一段 prePath.MoveGripPoints([prePath.GetGripPoints().length - 1], vec); } if ((isMoveLine || idxList.includes(pathGripPts.length - 1) || isMovePolylineEnd) //(move line ) or (move end) or(move pl end) && (i < this._PathCurve.length - 2 || equalv3(path.EndPoint, nextPath.StartPoint, 1e-3))) //连接到上一段 { //vec限制在下一段的坐标系内 无z vec = vec.clone() .applyMatrix4(nextPath.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(nextPath.OCS.setPosition(0, 0, 0)); if (isMoveLine || isMovePolylineEnd) //顺带移动下一段 nextPath.MoveGripPoints([0], vec); } if (isMove) path.Move(vec); else path.MoveGripPoints(idxList, vec); } } else { this._PathCurve.MoveGripPoints(indexList, vec); } this.Update(); } MoveStretchPoints(indexList, vec) { if (equalv3(vec, ZeroVec)) return; this.WriteAllObjectRecord(); vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); if (Array.isArray(this._PathCurve)) { let ptsLengths = 0; let pathIndexMap = new Map(); //1.parse for (let i = 0; i < this._PathCurve.length; i++) { let path = this._PathCurve[i]; const pts = path.GetStretchPoints(); const idxList = indexList.filter(v => v >= ptsLengths && v < (ptsLengths + pts.length)).map((v) => v - ptsLengths); ptsLengths += pts.length; pathIndexMap.set(path, { idx: idxList, count: pts.length, isMove: pts.length === idxList.length, }); } //2.change vec for (let i = 0; i < this._PathCurve.length; i++) { let path = this._PathCurve[i]; let { idx: idxList, count: ptsCount, isMove } = pathIndexMap.get(path); if (idxList.length === 0) continue; let isMoveStart = idxList.includes(0); let isMoveEnd = idxList.includes(ptsCount - 1); if (!isMove) //如果不是移动 限制在本OCS内 NO Z { vec.applyMatrix4(path.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(path.OCS.setPosition(0, 0, 0)); } //我们校验它的前后曲线支不支持它移动这个点 let prePath = this._PathCurve[FixIndex$1(i - 1, this._PathCurve)]; let nextPath = this._PathCurve[FixIndex$1(i + 1, this._PathCurve)]; //如果pre是move 则不需要过滤z if ((isMove || isMoveStart) //(move line ) or (move start) or(move pl start) && (i || equalv3(prePath.EndPoint, path.StartPoint, 1e-3)) //连接到下一段 && (!pathIndexMap.get(prePath).isMove) //非移动 ) { //vec限制在上一段的坐标系内 无z vec.applyMatrix4(prePath.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(prePath.OCS.setPosition(0, 0, 0)); } if (isMove || isMoveEnd && (i < this._PathCurve.length - 2 || equalv3(path.EndPoint, nextPath.StartPoint, 1e-3)) //连接到上一段 && (!pathIndexMap.get(nextPath).isMove) //非移动 ) { //vec限制在下一段的坐标系内 无z vec.applyMatrix4(nextPath.OCSInv.setPosition(0, 0, 0)) .setZ(0) .applyMatrix4(nextPath.OCS.setPosition(0, 0, 0)); } } //3 move for (let i = 0; i < this._PathCurve.length; i++) { let path = this._PathCurve[i]; let { idx: idxList, isMove } = pathIndexMap.get(path); if (isMove) path.Move(vec); else path.MoveStretchPoints(idxList, vec); } } else { this._PathCurve.MoveStretchPoints(indexList, vec); } this.Update(); } ApplyMatrix(m) { this.WriteAllObjectRecord(); if (equaln$1(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; if (Array.isArray(this._PathCurve)) { for (let p of this._PathCurve) p.ApplyMatrix(this.OCSNoClone).ApplyMatrix(m).ApplyMatrix(ocsInv); let group = curveLinkGroup(this._PathCurve); this._PathCurve = group[0]; } else this._PathCurve.ApplyMatrix(this.OCSNoClone).ApplyMatrix(m).ApplyMatrix(ocsInv); this.Update(UpdateDraw.Geometry); return this; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //ver this._Contour = file.ReadObject(); if (ver === 1) { this._PathCurve = file.ReadObject(); if (this._Contour instanceof Spline || this._PathCurve instanceof Spline) { this._isErase = true; Log("放样实体是样条线生成的,自动删除它!", LogType.Info); } } else if (ver > 1) { const pathCurveCount = file.Read(); if (pathCurveCount === 1) this._PathCurve = file.ReadObject(); else { this._PathCurve = []; for (let i = 0; i < pathCurveCount; i++) { this._PathCurve.push(file.ReadObject()); } } } if (ver > 2) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; if (ver > 3) this._DisplayAccuracy = file.Read(); } WriteFile(file) { super.WriteFile(file); file.Write(4); //ver file.WriteObject(this._Contour); if (Array.isArray(this._PathCurve)) { file.Write(this._PathCurve.length); for (let c of this._PathCurve) file.WriteObject(c); } else { file.Write(1); file.WriteObject(this._PathCurve); } // ver3 file.WriteBool(this._LockMaterial); // ver4 file.Write(this._DisplayAccuracy); } }; 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 Segmentations() { //轮廓点表 let contourPts = this._Contour.Shape.getPoints(4).map(AsVector3); for (let p of contourPts) p.applyMatrix4(this._Contour.OCSNoClone); //收集所有的点 let pathCurves = []; if (Array.isArray(this._PathCurve)) { for (let p of this._PathCurve) { if (p instanceof Polyline) pathCurves.push(...p.Explode()); else pathCurves.push(p.Clone()); } } else if (this._PathCurve instanceof Polyline) pathCurves.push(...this._PathCurve.Explode()); else pathCurves.push(this._PathCurve.Clone()); let isClosePath = equalv3(pathCurves[0].StartPoint, pathCurves[pathCurves.length - 1].EndPoint, 1e-3); let radiusMap = new Map(); function ExtendsCurve(path, pts) { let params = pts.map(p => path.GetParamAtPoint2(path.GetClosestPointTo(p, true))); let min = Math.min(0, params[Max(params, (p1, p2) => p1 > p2)]); let max = Math.max(1, params[Max(params, (p1, p2) => p1 < p2)]); let sp = path.GetPointAtParam(min); let ep = path.GetPointAtParam(max); path.StartPoint = sp; path.EndPoint = ep; } //遍历所有的路径节点进行顶点投射 for (let i = 0; i < pathCurves.length; i++) { let path = pathCurves[i]; if (isClosePath || i !== pathCurves.length - 1) //与下一段 { let ep = path.EndPoint; let nextPath = pathCurves[FixIndex$1(i + 1, pathCurves)]; let preP; if (path instanceof Line) preP = ep.clone().sub(path.GetFirstDeriv(1).normalize()); else { let pts = path.Shape.getPoints(4); preP = AsVector3(pts[pts.length - 2]).applyMatrix4(path.OCSNoClone); } let nextP; if (nextPath instanceof Line) nextP = ep.clone().add(nextPath.GetFirstDeriv(0).normalize()); else { let pts = nextPath.Shape.getPoints(4); nextP = AsVector3(pts[1]).applyMatrix4(nextPath.OCSNoClone); } //投射的点表 let pts = ProjectionToPlane(contourPts, path.Normal, ep, preP, nextP); // for (let j = 0; j < pts.length - 1; j++) // TestDraw(new Line(pts[j].clone(), pts[j + 1].clone()).ApplyMatrix(this.OCSNoClone), i + 1); //针对圆弧 修改它的半径 if (path instanceof Arc) { let mp = path.GetPointAtParam(0.5); let arcPts = ProjectionToPlane(contourPts, path.Normal, mp, mp.clone().sub(path.GetFirstDeriv(0.5).normalize())); let r = radiusMap.get(path); let ocsInv = path.OCSInv; let radius = arcPts.map(p => p.applyMatrix4(ocsInv).setZ(0).distanceTo(ZeroVec)); if (r) radius.push(r); let maxRadius = radius[Max(radius, (r1, r2) => r1 < r2)]; radiusMap.set(path, maxRadius); } ExtendsCurve(path, pts); ExtendsCurve(nextPath, pts); } } for (let [arc, rad] of radiusMap) arc.Radius = rad; return pathCurves; } get MaxLength() { return this.Segmentations.reduce((len, c) => len + c.Length, 0); } set ContourRotation(ro) { if (ro === this._contourRotation) return; this.WriteAllObjectRecord(); 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); let ver = 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); } if (ver > 1) { this.HardwareOption.goodsId = file.Read(); this.HardwareOption.goodsSn = file.Read(); } if (ver > 2) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } WriteFile(file) { super.WriteFile(file); file.Write(3); //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]); } file.Write(this.HardwareOption.goodsId); file.Write(this.HardwareOption.goodsSn); // ver3 file.WriteBool(this._LockMaterial); } }; __decorate([ AutoRecordObject ], 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(); this.ParseProcessingGroupTags = (entity) => { return [...entity.ProcessingGroupList].map((g) => { let obj = g?.Object; if (obj) return obj.Name; }); }; } GetCount(brs, options = null, IsBbsCountChaidan) { let drillCount = []; let sealCount = []; let hardwareCount = []; let areaCount = []; this.drillTypeMap.clear(); this.sealMap.clear(); this.Update(brs, options, IsBbsCountChaidan); if (this.drillTypeMap.size > 0) for (let [k, v] of this.drillTypeMap) { if (v[0] instanceof Hole) if (k === "木销") { for (let drill of v) { let goodsSn = drill.GoodsSn; let drillGroup = drillCount.find(d => d.name === k && d.goodsSn === goodsSn); //区分排钻商品ID if (drillGroup) drillGroup.count += 1; else drillCount.push({ name: k, count: 1, goodsSn, goodsId: drill.GoodsId }); } } else if (k === "层板钉") drillCount.push({ name: k, count: v.length }); else { for (let drill of v) { let goodsSn = drill.GoodsSn; let drillGroup = drillCount.find(d => d.name === k && d.goodsSn === goodsSn); //区分排钻商品ID if (drillGroup) drillGroup.count += 1; else drillCount.push({ name: k, count: 1, goodsSn, goodsId: drill.GoodsId }); } } 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) { const chaiDan = IsBbsCountChaidan ? bs : bs.filter(b => b.IsChaiDan); areaCount.push({ entity: bs[0], count: chaiDan.length, count2: this.GetBoardsArea(chaiDan) }); } return { drillCount, hardwareCount, sealCount, areaCount }; } ; Update(ens, options = null, IsBbsCountChaidan) { //计算排钻个数 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, goodsSn } = h.HardwareOption; let tags = this.ParseProcessingGroupTags(h); // :254行 代码对关联复合实体又进行一遍分析 需同步修改 addDrillToMap(`${name},${unit},${this.ParseSpec(h, factory)},${this.ParseSpec(h, spec)},${this.ParseSpec(h, model)},${this.ParseSpec(h, brand)},${tags.join(",")},${goodsSn}`, h); } this.UpdateBoardMap(brsProps); //统计 排钻 封边 关联的五金(排钻?) for (let br of brsProps) { if (!IsBbsCountChaidan && !br.IsChaiDan) //非拆单板我们不统计 continue; //排钻 层板钉 let dlist = br.DrillList; if (equaln$1(br.ContourCurve.Area, 0)) { ToasterShowEntityMsg({ msg: `${br.BoardProcessOption.roomName} ${br.BoardProcessOption.cabinetName} ${br.Name}轮廓有有问题,请检查`, timeout: 5000, intent: Intent.DANGER, ent: br }); continue; } for (let [id, idList] of dlist) { for (let ids of idList) { let holes = ids.map(id => id.Object).filter(h => h); if (!holes[0] || !HoleInBoard(holes, br)) 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 = gd.GroupId?.Object; if (!group) { Toaster({ message: `柜名:${br.BoardProcessOption.cabinetName} 房间名:${br.BoardProcessOption.roomName} 板名:${br.Name} 的排钻的编组丢失,统计排钻个数时会丢失该个数!`, timeout: 5000, intent: Intent.DANGER }); Log(`柜名:${br.BoardProcessOption.cabinetName} 房间名:${br.BoardProcessOption.roomName} 板名:${br.Name} 的排钻的编组丢失,统计排钻个数时会丢失该个数!`, LogType.Error, [br, gd]); 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; } options?.getHoles && options?.getHoles(spliteName || group.Name || "未命名", gd); } else { if (gd.isThrough) isTk = true; } if (!spliteName) spliteName = group.Name || "未命名"; if (!hole) hole = gd; } if (spliteName && hole) { if (isTk && HostApplicationServices.chaidanOption.statTk) { addDrillToMap("通孔" + spliteName, hole); } else if (pxlCount === 2 && HostApplicationServices.chaidanOption.statSt) { addDrillToMap("双头" + spliteName, hole); } else { addDrillToMap(spliteName, hole); } } } } // 被复制的层板钉暂未加入LayerNails数组 等做好关联后解除注释 if (br.LayerNails.length > 0) for (let objId of br.LayerNails) { if (!objId?.IsErase) addDrillToMap("层板钉", objId.Object); } //分析五金 for (const mId of br.RelativeHardware) { let metal = mId?.Object; if (metal && !metal.IsErase && metal.HardwareOption) { let { name, unit, factory, spec, model, brand, goodsSn } = metal.HardwareOption; let tags = this.ParseProcessingGroupTags(metal); // :135行 代码对关联复合实体又进行一遍分析 需同步修改 addDrillToMap(`${name},${unit},${this.ParseSpec(metal, factory)},${this.ParseSpec(metal, spec)},${this.ParseSpec(metal, model)},${this.ParseSpec(metal, brand)},${tags.join(",")},${goodsSn}`, metal); } } //封边 let sealdData = GetSealedBoardContour(br); if (!sealdData) { ToasterShowEntityMsg({ intent: Intent.DANGER, msg: "板件扣封边失败,请检查板件轮廓!", timeout: 10000, ent: br }); throw "错误:板扣除封边失败!"; } let { seals: sealData, reservedEdges } = Production.ParseSealData(sealdData, br.BoardProcessOption.color); //封边留头量 let sealReserve = HostApplicationServices.sealReserve * 2; let thickness = this.GetBoardThickness(br); for (let data of sealData) { if (equaln$1(0, data.size)) continue; data.length += sealReserve; let color = data.sealColor; let k = `${data.size}-${FixedNotZero(thickness, 2)}-${color}`; if (options && options.sealGruopKey) { options.sealGruopKey(k, br, thickness, data); } 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); const accuracy = HostApplicationServices.chaidanOption.hardwareExpressionFormattingAccuracy; return ParseExpr(spec, accuracy, { L: len ?? size.x, W: size.y, H: size.z }) || "[ 无 ]"; } ParseHardwareCount(k, v, hardwareCount) { const accuracy = HostApplicationServices.chaidanOption.hardwareExpressionFormattingAccuracy; if (v.length > 0) { if (!(v[0] instanceof HardwareTopline)) { let count2 = v.reduce((v, d) => { let size = d.BoundingBoxInOCS.getSize(new Vector3); let c = safeEval(d.HardwareOption.count, { L: size.x, W: size.y, H: size.z }) ?? 0; return v + c; }, 0); hardwareCount.push({ name: k.split(",")[0], count: v.length, entity: v[0], count2: FixedNotZero(count2, accuracy) }); } 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), accuracy)); 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 BoardSideModelCSGBuilder(board) { const sideModeingCsgs = []; if (!(board.SideModelingMap.size || board.RelevanceSideModelMap.size)) return sideModeingCsgs; let con = GetBoardContour(board); if (!con) return sideModeingCsgs; let inverseZ = con.Area2 < 0; let cus = con.Explode(); const mirrorMtxZ = MakeMirrorMtx(ZAxis); for (let [index, soilds] of board.AllSideModelGrooveMap) { let cu = cus[index]; if (!cu) continue; let mt4 = GetSideCuFaceMtx(cus[index], inverseZ); for (let soild of soilds) { let s = soild.Clone(); let geom3 = Geometry2CSG2(s.MeshGeometry); geom3 = transform$1(mirrorMtxZ.elements, geom3); geom3 = transform$1(mt4.clone().multiply(soild.OCS).elements, geom3); sideModeingCsgs.push(geom3); } } return sideModeingCsgs; } function GetSideCuFaceMtx(cu, inverseZ = false) { let x = cu.GetFirstDeriv(0).normalize(); let y = ZAxis; let z = x.clone().cross(y); if (inverseZ) z.negate(); let basePt = cu.StartPoint; return new Matrix4().makeBasis(x, y, z).setPosition(basePt); } //使用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); } } if (br instanceof Board && br.HasSideModel) { let con = GetBoardContour(br); if (con) { let inverseZ = con.Area2 < 0; let cus = con.Explode(); const mirrorMtxZ = MakeMirrorMtx(ZAxis); for (let [index, soilds] of br.SideModelingMap) { let cu = cus[index]; if (!cu) continue; let mt4 = GetSideCuFaceMtx(cus[index], inverseZ); for (let soild of soilds) { let lines = FastWireframe(soild, color, 3, false); for (let line of lines) { line.applyMatrix4(mirrorMtxZ); line.applyMatrix4(soild.OCSNoClone); line.applyMatrix4(mt4); result.push(line); } } } } } return result; } function FastExtrudeEdgeGeometryOfShape(shape, z0, z1, divCount = 6, optArc = true, coords = []) { let ptss = [shape.getPoints(divCount, optArc)]; for (let hole of shape.holes) ptss.push(hole.getPoints(divCount, optArc)); for (let pts of ptss) 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, z0, nextp.x, nextp.y, z0); //顶面 coords.push(p.x, p.y, z1, nextp.x, nextp.y, z1); if (p["_mask_"]) //侧面 coords.push(p.x, p.y, z0, p.x, p.y, z1); } return coords; } let tempP = new Vector3; //这个代码天生不和Mesh对齐,因为独立坐标系的原因,槽的坐标系可能和主题的坐标系不一致导致的 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; } //使用轮廓点表快速拉伸 function GenerateExtrudeEdgeGeometry(contourPoints, height) { let pts = []; for (let cs of contourPoints) arrayPushArray(pts, GenerateExtrudeEdgeGeometryPoints(cs, height)); let geo = new BufferGeometry().setFromPoints(pts); return geo; } //拉伸点表成为Geom 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; } //创建一个盒子几何体 function GenerateBoxEdgeGeometry(length, width, height) { let pts = [new Vector3(), new Vector3(length), new Vector3(length, width), new Vector3(0, width)]; return GenerateExtrudeEdgeGeometry([pts], height); } 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; } } } } } //https://github.com/mrdoob/three.js/blob/master/src/geometries/ExtrudeGeometry.js#L727 class WorldUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { const a_x = vertices[indexA * 3]; const a_y = vertices[indexA * 3 + 1]; const b_x = vertices[indexB * 3]; const b_y = vertices[indexB * 3 + 1]; const c_x = vertices[indexC * 3]; const c_y = vertices[indexC * 3 + 1]; return [ new Vector2$1(a_x, a_y), new Vector2$1(b_x, b_y), new Vector2$1(c_x, c_y) ]; } generateSideWallUV(geometry, vertices, indexA, indexB, indexC, indexD) { const a_x = vertices[indexA * 3]; const a_y = vertices[indexA * 3 + 1]; const a_z = vertices[indexA * 3 + 2]; const b_x = vertices[indexB * 3]; const b_y = vertices[indexB * 3 + 1]; const b_z = vertices[indexB * 3 + 2]; const c_x = vertices[indexC * 3]; const c_y = vertices[indexC * 3 + 1]; const c_z = vertices[indexC * 3 + 2]; const d_x = vertices[indexD * 3]; const d_y = vertices[indexD * 3 + 1]; const d_z = vertices[indexD * 3 + 2]; if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) { return [ new Vector2$1(a_x, 1 - a_z), new Vector2$1(b_x, 1 - b_z), new Vector2$1(c_x, 1 - c_z), new Vector2$1(d_x, 1 - d_z) ]; } else { return [ new Vector2$1(a_y, 1 - a_z), new Vector2$1(b_y, 1 - b_z), new Vector2$1(c_y, 1 - c_z), new Vector2$1(d_y, 1 - d_z) ]; } } } const worldUVGenerator = new WorldUVGenerator(); function CSGIntersect(csg1, csg2, csg2tranfrom) { //因为内部使用geom3进行box cache 所以我们新建了一个geom3 避免被cache导致错误 let csg1Clone = geom3.create(csg1.polygons.concat()); let csg2Clone = geom3.create(csg2.polygons.concat()); csg2Clone.transforms = csg2tranfrom.elements; return intersect(csg1Clone, csg2Clone); } /** * 解决 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)); 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().fromArray(v3); }); 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.map(n => ToFixed(n, this.fractionDigits)).join(","); this.vecMap.set(str, v); return str; } } /** * @Finish 目前已实现 * 1. 圆弧板垂直切普通板 * 2. 普通板穿透切圆弧板 * @TODO 当前问题记录 * 1. 普通板切圆弧板,凹槽的长度与厚度如何确定?(目前写死厚度切穿, 长度切断) * 2. 圆弧板切普通版, 若二者不垂直,存在夹角,怎么办?(目前不考虑) * 3. 圆弧板切圆弧板,如何处理?(目前不考虑) * 4. 平行情况,如何处理?(目前不考虑) * @Log 2024/06/20 * 处理问题1:采用CSG的包围盒与路径求交方式,来确定凹槽的长度 * @Log 2024/06/21 * 发现问题并处理: 若圆弧板为异形板,垂直切普通板,生成的凹槽还是整段路径 * 发现问题并处理: 由于精度问题,导致切割后在切割,依旧有交集,故这里加了体积过滤 * @Log 2024/06/25 * 优化: 优化体积过滤,改用CSG真实的体积计算 * 重构: 重构了代码结构,修复了普通板切圆弧板的bug(采用点集投影,而非之前的包围盒) * @Log 2024/06/26 * 处理问题2:采用对CSG上下面进行投影,然后转为面域做并集,从而生成凹槽 * @Log 2024/06/27 * 优化问题2:面域的布尔运算很慢,容易造成肉眼可见的卡顿(约1-2秒),现在改用project * @Log 2024/07/02 * 发现问题并处理: project存在精度问题,导致凹槽的长度不准确,故做了修正(若快切断则为切断) * @Log 2024/07/10 * 优化: 当普通版斜切圆弧板时,采用逆映射生成凹槽轮廓,而非原有的矩形轮廓 * @Log 2024/07/11 * 优化: 改用C2R生成凹槽的轮廓线,而非之前的project(因为它不准) * 处理问题3:通过逆映射实现圆弧板切圆弧板 */ /** 针对圆弧板的板件切割*/ class BoardCuttingForSweep { /** 生成圆弧板的切割器*/ constructor(meat, knife) { this.meat = meat; this.knife = knife; /** 生成基础的凹槽 */ this.CreateBaseGroove = (contourCurve) => { const knife = this.knife; /** 凹槽 */ const groove = new ExtrudeSolid(); // @ts-ignore 直接修改即可(省略OCS转换) groove.contourCurve = contourCurve; // 将刀的切割信息带给凹槽 groove.GroovesAddDepth = knife.GroovesAddDepth; groove.GroovesAddLength = knife.GroovesAddLength; groove.GroovesAddWidth = knife.GroovesAddWidth; groove.KnifeRadius = knife.KnifeRadius; return groove; }; this.meat = meat; this.knife = knife; } /** 生成凹槽 */ ConverToLocalGroove() { const meat = this.meat; const knife = this.knife; // 确保圆弧板构建成功 meat.MeshGeometry; knife.MeshGeometry; // 二者都是圆弧板 if (knife.IsArcBoard && meat.IsArcBoard) return this.ArcCutArc(); // 刀是圆弧板 if (knife.IsArcBoard) return this.ArcCutBoard(); // 肉是圆弧板 if (meat.IsArcBoard) return this.BoardCutArc(); return []; } /** 圆弧板切普通板 */ ArcCutBoard() { const meat = this.meat; const knife = this.knife; /** 生成轮廓-垂直切 */ const CreateContour = () => { const contours = []; // 路径信息 const path1 = knife.GetSweepPath1InWCS().ApplyMatrix(meat.OCSInv); const path2 = knife.GetSweepPath2InWCS().ApplyMatrix(meat.OCSInv); path1.Z0(); path2.Z0(); // 不垂直 if (!equalv3(path1.Normal, new Vector3(0, 0, 1)) && !equalv3(path1.Normal, new Vector3(0, 0, -1))) return CreateContourByOblique(); // CSG const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); const interCSGs = scission(interCSG); for (const interCSG of interCSGs) { // 过滤掉小于肉的1/10000体积的凹槽 const interVolume = measureAggregateVolume(interCSG); const meatVolume = measureAggregateVolume(meat.CSG); if (interVolume < meatVolume / 10000) continue; // 投影取点 const pts = this.ProjectCSGToPts(interCSG, "Z"); const range1 = this.GetParamRange(pts, path1); const range2 = this.GetParamRange(pts, path2); // 获取局部路径 const sp1 = path1.GetPointAtParam(range1.sParam); const ep1 = path1.GetPointAtParam(range1.eParam); const sp2 = path2.GetPointAtParam(range2.sParam); const ep2 = path2.GetPointAtParam(range2.eParam); const localPath1 = this.Get_Pl_InAtoB(path1, sp1, ep1); const localPath2 = this.Get_Pl_InAtoB(path2, sp2, ep2); // 围成轮廓 const line1 = new Line(localPath1.StartPoint, localPath2.StartPoint); const line2 = new Line(localPath1.EndPoint, localPath2.EndPoint); const contour = localPath1.Clone(); contour.Join(line1); contour.Join(line2); contour.Join(localPath2); // 修正轮廓 this.AdjustPolyline(contour); contours.push(contour); } return contours; }; /** 生成轮廓-斜切(任意角度) */ const CreateContourByOblique = () => { const contours = []; // CSG const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); const interCSGs = scission(interCSG); for (const interCSG of interCSGs) { // 过滤掉小于肉的1/10000体积的凹槽 const interVolume = measureAggregateVolume(interCSG); const meatVolume = measureAggregateVolume(meat.CSG); if (interVolume < meatVolume / 10000) continue; const contour = this.CSGToPolyline(interCSG); // 修正轮廓 this.AdjustPolyline(contour); contours.push(contour); } return contours; }; /** 生成凹槽 */ const CreateGroove = (contourCurve) => { /** 凹槽 */ const groove = this.CreateBaseGroove(contourCurve); // 获取凹槽的厚度 const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); const topology = new BSPGroupParse(interCSG); const interBox = new Box3Ext(); for (let pts of topology.Parse()) interBox.setFromPoints(pts); const interBoxSize = interBox.getSize(new Vector3()); groove.Thickness = interBoxSize.z; // 修正厚度 if (equaln$1(groove.Thickness, meat.Thickness, 0.1)) groove.Thickness = meat.Thickness; // 修正位置 groove.OCS = meat.OCS; if (interBox.min.z > 0) groove.ApplyMatrix(MoveMatrix(meat.Normal.multiplyScalar(meat.Thickness - groove.Thickness))); return groove; }; const grooves = []; // 1.生成轮廓 const grooveContours = CreateContour(); // 2.生成凹槽 for (const grooveContour of grooveContours) { const groove = CreateGroove(grooveContour); grooves.push(groove); } return grooves; } /** 普通板切圆弧板 */ BoardCutArc() { const meat = this.meat; const knife = this.knife; /** 生成轮廓-垂直切 */ const CreateContour = () => { const contours = []; // 路径信息 const path = meat.GetSweepPathInWCS().ApplyMatrix(meat.OCSInv); const tempPath = meat.GetSweepPathInWCS().ApplyMatrix(knife.OCSInv); // 不垂直(或有放样角度,这里偷懒了,懒得去算了,直接按不垂直处理) if (!equalv3(tempPath.Normal, new Vector3(0, 0, 1)) && !equalv3(tempPath.Normal, new Vector3(0, 0, -1)) || meat.SweepAngle !== 0) return CreateContourByOblique(); // CSG const csg1Clone = geom3.create(meat.CSG.polygons.concat()); const csg2Clone = geom3.create(knife.CSG.polygons.concat()); csg1Clone.transforms = meat.OCS.elements; csg2Clone.transforms = knife.OCS.elements; const interCSG = intersect(csg1Clone, csg2Clone); interCSG.transforms = meat.OCSInv.elements; intersect(interCSG, interCSG); const interCSGs = scission(interCSG); for (const interCSG of interCSGs) { // 过滤掉小于肉的100体积的凹槽(这里甩固定值) const interVolume = measureAggregateVolume(interCSG); if (interVolume < 100) continue; const topology = new BSPGroupParse(interCSG); /** 交集部分的包围盒 */ const box = new Box3Ext(new Vector3(Infinity, Infinity, Infinity), new Vector3(-Infinity, -Infinity, -Infinity)); const ptsList = topology.Parse(); for (let pts of ptsList) { box.min.x = Math.min(box.min.x, ...pts.map(p => p.x)); box.min.y = Math.min(box.min.y, ...pts.map(p => p.y)); box.min.z = Math.min(box.min.z, ...pts.map(p => p.z)); box.max.x = Math.max(box.max.x, ...pts.map(p => p.x)); box.max.y = Math.max(box.max.y, ...pts.map(p => p.y)); box.max.z = Math.max(box.max.z, ...pts.map(p => p.z)); } // 投影取点 const pts = this.ProjectCSGToPts(interCSG, "Y"); // 计算槽的长度 const range = this.GetParamRange(pts, path); // 获取局部路径 const sDist1 = path.GetDistAtParam(range.sParam); const eDist1 = path.GetDistAtParam(range.eParam); /** 凹槽轮廓-多段线 */ const contour = new Polyline(); contour.LineData.push({ pt: new Vector2$1(sDist1, box.min.y), bul: 0 }); contour.LineData.push({ pt: new Vector2$1(eDist1, box.min.y), bul: 0 }); contour.LineData.push({ pt: new Vector2$1(eDist1, box.max.y), bul: 0 }); contour.LineData.push({ pt: new Vector2$1(sDist1, box.max.y), bul: 0 }); contour.LineData.push({ pt: new Vector2$1(sDist1, box.min.y), bul: 0 }); // 修正轮廓 this.AdjustPolyline(contour); contours.push(contour); } return contours; }; /** 生成轮廓-斜切(任意角度) */ const CreateContourByOblique = () => { const contours = []; // CSG const CSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); CSG.transforms = meat.OCS.elements; const interCSGs = scission(CSG); for (const interCSG of interCSGs) { // 过滤掉小于肉的1/10000体积的凹槽 const interVolume = measureAggregateVolume(interCSG); const meatVolume = measureAggregateVolume(meat.CSG); if (interVolume < meatVolume / 10000) continue; // 对CSG进行逆映射 this.InverseCSG(interCSG); const contour = this.CSGToPolyline(interCSG); // 修正轮廓 this.AdjustPolyline(contour); contours.push(contour); } return contours; }; /** 生成凹槽 */ const CreateGroove = (contourCurve) => { /** 凹槽 */ const groove = this.CreateBaseGroove(contourCurve); // 更新凹槽的厚度 this.UpdateGrooveThickness(groove); return groove; }; const grooves = []; const grooveContours = CreateContour(); for (const grooveContour of grooveContours) { /** 凹槽 */ const groove = CreateGroove(grooveContour); grooves.push(groove); } return grooves; } /** 圆弧板切圆弧板 */ ArcCutArc() { const meat = this.meat; const knife = this.knife; /** 生成轮廓 */ const CreateContour = () => { const contours = []; // CSG const CSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); CSG.transforms = meat.OCS.elements; const interCSGs = scission(CSG); for (const interCSG of interCSGs) { // 过滤掉小于肉的1/10000体积的凹槽 const interVolume = measureAggregateVolume(interCSG); const meatVolume = measureAggregateVolume(meat.CSG); if (interVolume < meatVolume / 10000) continue; // 对CSG进行逆映射 this.InverseCSG(interCSG); const contour = this.CSGToPolyline(interCSG); // 修正轮廓 this.AdjustPolyline(contour); contours.push(contour); } return contours; }; /** 生成凹槽 */ const CreateGroove = (contourCurve) => { /** 凹槽 */ const groove = this.CreateBaseGroove(contourCurve); // 更新凹槽的厚度 this.UpdateGrooveThickness(groove); return groove; }; const grooves = []; const grooveContours = CreateContour(); for (const grooveContour of grooveContours) { /** 凹槽 */ const groove = CreateGroove(grooveContour); grooves.push(groove); } return grooves; } /** 更新凹槽厚度 */ UpdateGrooveThickness(groove) { const meat = this.meat; const knife = this.knife; let grooveThickness = meat.Thickness; let dir = false; const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); /** 通过包围盒获取凹槽厚度 */ const GetThinknessByBox = () => { const topology = new BSPGroupParse(interCSG); const interBox = new Box3Ext(); for (let pts of topology.Parse()) interBox.setFromPoints(pts); const interBoxSize = interBox.getSize(new Vector3()); const grooveThickness = interBoxSize.z; const dir = interBox.min.z > 0; return [grooveThickness, dir]; }; /** 通过路径获取凹槽厚度 */ const GetThinknessByPath = () => { const path1 = meat.GetSweepPath1InWCS().ApplyMatrix(meat.OCSInv); const path2 = meat.GetSweepPath2InWCS().ApplyMatrix(meat.OCSInv); const pts = this.ProjectCSGToPts(interCSG, "Y"); const isInPath1 = pts.some(pt => path1.PtOnCurve(pt, 0.5)); const grooveThickness = this.GetDistToPl(pts, isInPath1 ? path1 : path2); const dir = !isInPath1; return [grooveThickness, dir]; }; if (meat.IsArcBoard) { [grooveThickness, dir] = GetThinknessByPath(); // 如果很小,说明是CSG的有平面,导致只有一侧的顶点,故此时可以改用包围盒 if (grooveThickness < 0.1) [grooveThickness] = GetThinknessByBox(); } else [grooveThickness, dir] = GetThinknessByBox(); groove.Thickness = grooveThickness; // 修正厚度 if (equaln$1(groove.Thickness, meat.Thickness, 0.1)) groove.Thickness = meat.Thickness; // 修正位置 groove.OCS = meat.OCS; if (dir) groove.ApplyMatrix(MoveMatrix(meat.Normal.multiplyScalar(meat.Thickness - groove.Thickness))); } /** 计算点集到多段线的距离 */ GetDistToPl(pts, pl) { let dist = 0; for (const pt of pts) { const closestPt = pl.GetClosestPointTo(pt, false); dist = Math.max(closestPt.distanceTo(pt), dist); } return dist; } /** 对CSG进行逆映射(根据放样路径) */ InverseCSG(interCSG) { const meat = this.meat; const pathWCS = meat.GetSweepPathInWCS(); const path = pathWCS.Clone().ApplyMatrix(pathWCS.OCSInv); for (const polygon of interCSG.polygons) { for (const vertice of polygon.vertices) { const p = new Vector3(vertice[0], vertice[1], vertice[2]); p.applyMatrix4(pathWCS.OCSInv); const cp = path.GetClosestPointTo(p, false); const param = Math.max(path.GetParamAtPoint(cp), 0); const x = path.GetDistAtParam(param); const y = -p.z; p.y = y; p.x = x; vertice[0] = p.x; vertice[1] = p.y; } } } /** 对CSG进行投影,返回点集 */ ProjectCSGToPts(CSG, dir = "Z") { // 转为点集 const topology = new BSPGroupParse(CSG); const ps = []; for (let pts of topology.Parse()) { for (const pt of pts) { const fuzz = 0.1; // 对CSG进行投影 if (dir === "Z") { if (!ps.find(p => equaln$1(p.x, pt.x, fuzz) && equaln$1(p.y, pt.y, fuzz))) { pt.z = 0; ps.push(pt); } } else if (dir === "Y") { if (!ps.find(p => equaln$1(p.x, pt.x, fuzz) && equaln$1(p.z, pt.z, fuzz))) { pt.y = 0; ps.push(pt); } } } } return ps; } ; /** 对CSG进行投影,返回多段线 */ CSGToPolyline(CSG) { const polygons = toPolygons(CSG); const pls = []; for (const polygon of polygons) { const pl = new Polyline(); for (const v of polygon.vertices) { const pt = new Vector2$1(v[0], v[1]); pl.LineData.push({ pt, bul: 0 }); pl.CloseMark = true; // 强制闭合 } pls.push(pl); } // 若报错,可能是c2r的精度不足导致的 return CreateContour2(pls).Curve; } ; /** 获取CSG在路径上的param范围 */ GetParamRange(pts, path) { // 与路径匹配 const range = { sParam: Infinity, eParam: -Infinity }; for (const pt of pts) { const cpt = path.GetClosestPointTo(pt, false); const param = path.GetParamAtPoint(cpt); if (param || param === 0) { range.sParam = Math.min(range.sParam, param); range.eParam = Math.max(range.eParam, param); } } // 避免应精度出现小于0的情况 range.sParam = Math.max(0, range.sParam); return range; } /** 获取pA-pB之间的曲线 */ Get_Pl_InAtoB(pl, pA, pB) { const paramA = pl.GetParamAtPoint(pA); const pls = pl.GetSplitCurves(paramA); pl = pls[1] || pls[0]; const paramB = pl.GetParamAtPoint(pB); const pls2 = pl.GetSplitCurves(paramB); return pls2[0]; } ; /** 修正轮廓线(避免造型的布尔运算错误) */ AdjustPolyline(pl) { const meat = this.meat; // 修正轮廓 const newData = []; for (const LineData of pl.LineData) { // 针对放样角度做出修正 const pt = AsVector3(LineData.pt).applyMatrix4(pl.OCS).applyMatrix4(meat.ArcBuild.Rotate2OCSMtx); // 针对边缘噪点做出修正 this.AdjustPtClosePl(pt, meat.ContourCurve); pt.applyMatrix4(pl.OCSInv); newData.push({ pt: AsVector2(pt), bul: LineData.bul }); } pl.LineData = newData; } /** 调整点到线的最近点 */ AdjustPtClosePl(pt, pl) { const closestPt = pl.GetClosestPointTo(pt, false); const dist = pt.distanceTo(closestPt); if (dist < 1e-1) pt.copy(closestPt); } ; } class BoardUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { const a_x = vertices[indexA * 3]; const a_y = vertices[indexA * 3 + 1]; const b_x = vertices[indexB * 3]; const b_y = vertices[indexB * 3 + 1]; const c_x = vertices[indexC * 3]; const c_y = vertices[indexC * 3 + 1]; return [ new Vector2$1(a_x, a_y), new Vector2$1(b_x, b_y), new Vector2$1(c_x, c_y) ]; } generateSideWallUV(geometry, vertices, indexA, indexB, indexC, indexD) { const a_x = vertices[indexA * 3]; const a_y = vertices[indexA * 3 + 1]; const a_z = vertices[indexA * 3 + 2]; const b_x = vertices[indexB * 3]; const b_y = vertices[indexB * 3 + 1]; const b_z = vertices[indexB * 3 + 2]; const c_x = vertices[indexC * 3]; const c_y = vertices[indexC * 3 + 1]; const c_z = vertices[indexC * 3 + 2]; const d_x = vertices[indexD * 3]; const d_y = vertices[indexD * 3 + 1]; const d_z = vertices[indexD * 3 + 2]; if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) { return [ new Vector2$1(a_z - 1, a_x), new Vector2$1(b_z - 1, b_x), new Vector2$1(c_z - 1, c_x), new Vector2$1(d_z - 1, d_x) ]; } else { return [ new Vector2$1(a_z - 1, a_y), new Vector2$1(b_z - 1, b_y), new Vector2$1(c_z - 1, c_y), new Vector2$1(d_z - 1, d_y) ]; } } } class BoardUVGenerator2 extends BoardUVGenerator { generateTopUV(geometry, vertices, indexA, indexB, indexC) { const a_x = vertices[indexA * 3]; const a_y = vertices[indexA * 3 + 1]; const b_x = vertices[indexB * 3]; const b_y = vertices[indexB * 3 + 1]; const c_x = vertices[indexC * 3]; const c_y = vertices[indexC * 3 + 1]; return [ new Vector2$1(a_y, a_x), new Vector2$1(b_y, b_x), new Vector2$1(c_y, c_x) ]; } } let boardUVGenerator = new BoardUVGenerator(); let boardUVGenerator2 = new BoardUVGenerator2(); class ShapeManager { constructor(_ShapeList = []) { this._ShapeList = _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 { 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; } } constructor(_ShapeManager = new ShapeManager()) { super(); this._ShapeManager = _ShapeManager; } //如果需要修改获取到的属性,需要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() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } InitDrawObject(renderType = RenderType.Wireframe) { if (renderType === RenderType.Wireframe) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)), new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex))); } 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.DrawColorIndex)), mesh); } } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (renderType === RenderType.Wireframe) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex); } else if (renderType === RenderType.Conceptual) { return obj.add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)), new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex))); } 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.DrawColorIndex)), 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.DrawColorIndex); } 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.DrawColorIndex); } else { let mesh = obj.children[i]; mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } } } else if (type === RenderType.Physical) { let mesh = obj; mesh.material = this.MeshMaterial; } } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 this._ShapeManager.Clear(); this._ShapeManager.ReadFile(file); if (ver > 1) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } WriteFile(file) { super.WriteFile(file); file.Write(2); //ver this._ShapeManager.WriteFile(file); // ver2 file.WriteBool(this._LockMaterial); } }; Region = Region_1 = __decorate([ Factory ], Region); const CanDrawHoleFuzz = 0.1; const FUZZ = 1e-5; //Segment1d列表中 合并相交的区间 function Segment1dJoin(segment) { if (segment.length < 2) return segment; segment.sort((a, b) => a[0] - b[0]); const NewSegment = []; for (let i = 0; i < segment.length; i++) { let segment1 = segment[i]; if (i === segment.length - 1) { NewSegment.push(segment1); break; } for (let j = i + 1; j < segment.length; j++) { let segment2 = segment[j]; if (segment2[0] < segment1[1] + FUZZ) segment1 = [segment1[0], Math.max(segment1[1], segment2[1])]; else { NewSegment.push(segment1); break; } if (j === segment.length - 1) { NewSegment.push(segment1); i = segment.length - 1; } } } return NewSegment; } //获取多个区间列表相交的部分 function IntersectSegment1ds(segment1, segment2) { const IntersectSegments = []; for (let s1 of segment1) { for (let s2 of segment2) { //[1,10] + [5,15] = [5,10] if ((s2[0] > s1[0] - FUZZ && s2[0] < s1[1]) || (s1[0] > s2[0] - FUZZ && s1[0] < s2[1])) { IntersectSegments.push([Math.max(s1[0], s2[0]), Math.min(s1[1], s2[1])]); } } } return IntersectSegments; } class Face { constructor(parameters) { this.isEqualType = false; this.OCS = new Matrix4(); this.IsRect = true; this.PolyLineOCS = new Matrix4(); if (parameters) { this.type = parameters.type; this._Region = parameters.region; this.LocalBoard = parameters.localBoard; this.OCS = parameters.matrix4; this.Length = parameters.length; this.Width = parameters.width; if (parameters.isRect !== undefined) this.IsRect = parameters.isRect; if (parameters.drillType) this.DrillType = parameters.drillType; else this.DrillType = this.LocalBoard.BoardProcessOption.drillType; if (parameters.polyLineOCS) this.PolyLineOCS = parameters.polyLineOCS; if (parameters.CurveBoardFaceRegionPolyline) this.CurveBoardFaceRegionPolyline = parameters.CurveBoardFaceRegionPolyline; } } get Region() { if (!this._Region) { let curve = this.CurveBoardFaceRegionPolyline ?? new Polyline().Rectangle(this.Length, this.Width); this._Region = Region.CreateFromCurves([curve]); } return this._Region; } get OCSInv() { return new Matrix4().getInverse(this.OCS); } get Normal() { return new Vector3().setFromMatrixColumn(this.OCS, 2); } Intersect(f) { //获得侧面和非侧面 let [sideFace, noSideFace] = this.type === BoardFaceType.Side ? [this, f] : [f, this]; //同侧面排钻时 厚度小的那块当做第一块 if (sideFace.type === noSideFace.type) [sideFace, noSideFace] = this.Width > f.Width ? [f, this] : [this, f]; //布尔面和被布尔面得差异矩阵 let diffMtx = sideFace.OCSInv.multiply(noSideFace.OCS); MatrixPlanarizere(diffMtx); let isSuccess = false; let x = new Vector3().setFromMatrixColumn(diffMtx, 0); let ang = x.angleTo(XAxis); //盒子旋转0,90,180度不会被破坏 let canUseBoxCalc = equaln$1(ang, 0) || equaln$1(ang, Math.PI / 2) || equaln$1(ang, Math.PI); let retBoxs = []; let sizes = []; //如果不是矩形,用布尔运算,如果 if (!noSideFace.IsRect || !canUseBoxCalc) { let sideReg = sideFace.Region?.Clone(); if (!sideReg || !noSideFace.Region) return []; let toReg = noSideFace.Region.Clone().ApplyMatrix(diffMtx); //注意: 排钻因为布尔运算失败的重灾区 // TestDraw(sideReg.Clone(), 1); // TestDraw(toReg.Clone(), 2); isSuccess = sideReg.BooleanOper(toReg, BoolOpeartionType.Intersection); //挖穿造型分段排钻 const throughModelSegmentedDrill = HostApplicationServices.throughModelSegmentedDrill; if (throughModelSegmentedDrill) { let boardModeling = noSideFace.LocalBoard.BoardModeling; if (boardModeling?.length) { let boardThickness = noSideFace.LocalBoard.Thickness; for (let modal of boardModeling) { if (boardThickness - modal.thickness > 1e-5) continue; let modalReg = Region.CreateFromCurves([modal.shape.Outline.Curve]).ApplyMatrix(diffMtx); isSuccess = sideReg.BooleanOper(modalReg, BoolOpeartionType.Subtract); } } } for (let s of sideReg.ShapeManager.ShapeList) { if (!throughModelSegmentedDrill) { let box = s.BoundingBox; retBoxs.push(box); sizes.push(box.getSize(new Vector3())); } else { //求以X轴和Y=Thickness轴上的点 相互切割形成的矩形面 const XLists = []; const TLists = []; const Thickness = s.BoundingBox.getSize(new Vector3).y; for (let cu of s.Outline.Shape.curves) { let pt1 = cu.getPoint(0); let pt2 = cu.getPoint(1); let x1 = Math.abs(pt1.x); let x2 = Math.abs(pt2.x); if (equaln$1(pt1.y, 0) && equaln$1(pt2.y, 0)) XLists.push([Math.min(x1, x2), Math.max(x1, x2)]); if (equaln$1(Math.abs(pt1.y), Thickness) && equaln$1(Math.abs(pt2.y), Thickness)) TLists.push([Math.min(x1, x2), Math.max(x1, x2)]); } //合并可以相连的区间 如[0,100] + [100,200] = [0,200] const XJoinLists = Segment1dJoin(XLists); const TJoinLists = Segment1dJoin(TLists); //X轴和厚度轴相交的区间 const IntersectSegments = IntersectSegment1ds(XJoinLists, TJoinLists); //造型切割出来会有Position点 其他都是(0,0,0) let startPt = new Vector3(s.Position.x); for (let segment of IntersectSegments) { let minV; let maxV; //圆弧板面宽度具有方向性 if (sideFace.Width > 0) { minV = new Vector3(segment[0]); maxV = new Vector3(segment[1], Thickness); } else { minV = new Vector3(segment[0], sideFace.Width); maxV = new Vector3(segment[1]); } let box = new Box3Ext(minV.add(startPt), maxV.add(startPt)); retBoxs.push(box); sizes.push(box.getSize(new Vector3)); } } } if (throughModelSegmentedDrill && !retBoxs.length) isSuccess = false; } else { let minV; let maxV; //圆弧板面宽度具有方向性 if (sideFace.Width > 0) { minV = new Vector3(); maxV = new Vector3(sideFace.Length, sideFace.Width); } else { minV = new Vector3(0, sideFace.Width); maxV = new Vector3(sideFace.Length, 0); } let retBox = new Box3Ext(minV, maxV); let p1 = new Vector3().setFromMatrixPosition(diffMtx); let p2 = new Vector3(noSideFace.Length, noSideFace.Width).applyMatrix4(diffMtx); let box3 = new Box3Ext().setFromPoints([p1, p2]); if (retBox.intersectsBox(box3)) { retBox.intersect(box3); let size = retBox.getSize(new Vector3()); isSuccess = !equaln$1(size.x * size.y, 0); retBoxs = [retBox]; sizes = [size]; } } let newFaces = []; if (isSuccess) { for (let i = 0; i < sizes.length; i++) { let newFace = new Face(); //提供侧面的板件作为相交面 newFace.LocalBoard = noSideFace.LocalBoard; newFace.InterBoard = sideFace.LocalBoard; newFace.Length = sizes[i].x; newFace.Width = sizes[i].y; let min = retBoxs[i].min; min.applyMatrix4(sideFace.OCS); //构建碰撞面坐标系 newFace.OCS = sideFace.OCS.clone().setPosition(min); newFace.DrillType = sideFace.DrillType; //都是侧面 if (this.type === f.type) newFace.isEqualType = true; newFaces.push(newFace); } } return newFaces; } IsIntersect(f, fuzz = 1e-6, currentCoverBoxes = []) { //获得侧面和非侧面 let [sideFace, noSideFace] = this.type === BoardFaceType.Side ? [this, f] : [f, this]; //布尔面和被布尔面得差异矩阵 let diffMtx = sideFace.OCSInv.multiply(noSideFace.OCS); MatrixPlanarizere(diffMtx); let x = new Vector3().setFromMatrixColumn(diffMtx, 0); let ang = x.angleTo(XAxis); //盒子旋转0,90,180度不会被破坏 let canUseBoxCalc = equaln$1(ang, 0) || equaln$1(ang, Math.PI / 2) || equaln$1(ang, Math.PI); //如果不是矩形,用布尔运算,如果 if (!noSideFace.IsRect || !canUseBoxCalc) { let c1 = new Polyline().Rectangle(sideFace.Length, sideFace.Width); let c2 = noSideFace.LocalBoard.ContourCurve.Clone().ApplyMatrix(diffMtx); let box = c1.BoundingBox.intersect(c2.BoundingBox); let size = box.getSize(new Vector3); if (equaln$1(size.x * size.y, 0)) return { isInt: false, coverBoxesList: currentCoverBoxes }; let con1 = Contour.CreateContour(c1); let con2 = Contour.CreateContour(c2); let cs = con1.IntersectionBoolOperation(con2); let width = 0; let boxList = []; //当前碰撞区域如果遮光直接退出 for (let c of cs) { let b = c.BoundingBox; b.getSize(size); width += size.x; if (width / sideFace.Length > fuzz) return { isInt: true }; boxList.push(b); } if (currentCoverBoxes.length === 0) { return { isInt: false, coverBoxesList: boxList }; } //与旧盒子合并后测试是否遮光 width = 0; while (currentCoverBoxes.length > 0) { let b = currentCoverBoxes.pop(); let isInt = false; for (let box of boxList) { if (box.intersectsBox(b)) { isInt = true; box.union(b); break; } } if (!isInt) boxList.push(b); } for (let b of boxList) { b.getSize(size); width += size.x; if (width / sideFace.Length > fuzz) return { isInt: true }; } return { isInt: false, coverBoxesList: boxList }; } else { let minV; let maxV; if (sideFace.Width > 0) { minV = new Vector3(); maxV = new Vector3(sideFace.Length, sideFace.Width); } else { minV = new Vector3(0, sideFace.Width); maxV = new Vector3(sideFace.Length, 0); } let retBox = new Box3Ext(minV, maxV); let p1 = new Vector3().setFromMatrixPosition(diffMtx); let p2 = new Vector3(noSideFace.Length, noSideFace.Width).applyMatrix4(diffMtx); let box3 = new Box3Ext().setFromPoints([p1, p2]); if (retBox.intersectsBox(box3)) { retBox.intersect(box3); let size = retBox.getSize(new Vector3()); if (equaln$1(size.x * size.y, 0) || Math.abs(size.y / sideFace.Width) <= fuzz) return { isInt: false, coverBoxesList: currentCoverBoxes }; if (size.x / sideFace.Length > fuzz) return { isInt: true }; if (currentCoverBoxes.length === 0) { return { isInt: false, coverBoxesList: [retBox] }; } for (let b of currentCoverBoxes) { if (b.intersectsBox(retBox)) { b.union(retBox); retBox = null; break; } } if (retBox) currentCoverBoxes.push(retBox); let width = 0; for (let b of currentCoverBoxes) { b.getSize(size); width += size.x; if (width / sideFace.Length > fuzz) return { isInt: true }; } return { isInt: false, coverBoxesList: currentCoverBoxes }; } return { isInt: false, coverBoxesList: currentCoverBoxes }; } } } var BoardFaceType; (function (BoardFaceType) { BoardFaceType[BoardFaceType["Side"] = 0] = "Side"; BoardFaceType[BoardFaceType["NoSide"] = 1] = "NoSide"; })(BoardFaceType || (BoardFaceType = {})); class BoardGetFace { constructor(Board) { this.Board = Board; this.Faces = []; this.ParseFaces(); } ParseFaces() { if (this.Board.IsArcBoard) { this.GetArcBoardFaces(); return; } //正反面 this.GetTopAndBottomFace(); //侧面 this.GetSideFaces(); } GetTopAndBottomFace(isEdgeFace = false) { let curve = this.Board.ContourCurve; let reg; if (this.Board.IsSpecialShape) reg = Region.CreateFromCurves([curve]); let thickness = this.Board.Thickness; let ocs = this.Board.OCS; const opt = this.Board.BoardProcessOption; //挖穿造型分段排钻 let throughModelSegmentedDrill = HostApplicationServices.throughModelSegmentedDrill; let isRect = throughModelSegmentedDrill ? (this.Board.IsRect && !this.Board.BoardModeling.length) : this.Board.IsRect; //正反面 if (opt.frontDrill || isEdgeFace) this.Faces.push(new Face({ type: BoardFaceType.NoSide, region: reg, isRect, localBoard: this.Board, matrix4: ocs.clone().multiply(new Matrix4().setPosition(new Vector3(0, 0, thickness))), length: this.Board.Width, width: this.Board.Height })); if (opt.backDrill || isEdgeFace) { let mtx = MakeMirrorMtx(ZAxis); this.Faces.push(new Face({ type: BoardFaceType.NoSide, localBoard: this.Board, isRect, region: reg ? reg.Clone() : undefined, matrix4: new Matrix4().multiplyMatrices(ocs, mtx), length: this.Board.Width, width: this.Board.Height })); } } GetSideFaces() { let con = this.Board.ContourCurve.Clone(); let inverseZ = con.Area2 < 0; let cus = con.Explode(); const highDrill = this.Board.BoardProcessOption.highDrill.slice(); for (let i = 0; i < cus.length; i++) { let cu = cus[i]; let length = cu.Length; if ((highDrill.length > 0 && highDrill[i] === DrillType.None) || equaln$1(length, 0) || cu instanceof Arc) continue; let mtx = GetSideFaceMtx(cu, inverseZ); this.Faces.push(new Face({ type: BoardFaceType.Side, localBoard: this.Board, matrix4: new Matrix4().multiplyMatrices(this.Board.OCS.clone(), mtx), length, width: this.Board.Thickness, drillType: highDrill.length > 0 && highDrill[i] })); } } //获取曲线板的正反 侧面 GetArcBoardFaces(faceSealingDataMap, highSealingData, sealCu) { let br = this.Board; const opt = br.BoardProcessOption; //排钻反应器 实体渲染赋值可能延迟 手动构造ArcBoardBuild let sweepArcBoardBuild = new ArcBoardBuild(br); sweepArcBoardBuild.ParseSweepCurves(); let conCus = sweepArcBoardBuild.SweepCurves1.map(c => c.Clone()); //曲线路径相对曲线板 const RX = new Matrix4().makeRotationX(Math.PI / 2); const PathOCS = new Matrix4().multiplyMatrices(br.OCS, RX); // //弧形板旋转角度 const AMtx = new Matrix4().makeRotationY(br.SweepAngle); PathOCS.multiply(AMtx); let basePt = new Vector3().applyMatrix4(new Matrix4().multiplyMatrices(RX, sweepArcBoardBuild.OCS2RotateMtx)); PathOCS.multiply(new Matrix4().setPosition(basePt)); const PathOCSInv = new Matrix4().getInverse(PathOCS); for (let cu of conCus) cu.ApplyMatrix(PathOCS); const ZNormal = new Vector3().setFromMatrixColumn(PathOCS, 2); let currentLength = 0; const FaceDir = br.SweepVisibleFace === FaceDirection.Front ? -1 : 1; const ContourCurve = br.ContourCurve.Clone(); //弧形板旋转角度 ContourCurve.ApplyMatrix(sweepArcBoardBuild.OCS2RotateMtx); if (highSealingData) { let cus = br.ContourCurve.Clone().Explode(); highSealingData.push(...structuredClone(GetBoardHighSeal(br, cus))); sealCu.push(...cus); } let contourLength = br.ParseBoardLengthInArcSweep(); for (let i = 0; i < conCus.length; i++) { if (currentLength > contourLength) continue; //按分段曲线 对板轮廓裁剪 let conCu = conCus[i]; let length = currentLength + conCu.Length; //跳过圆弧 if (conCu instanceof Arc) { currentLength = length; continue; } let starKnifePls = new Polyline([{ pt: AsVector2({ x: currentLength, y: -1 }), bul: 0 }, { pt: AsVector2({ x: currentLength, y: 10000 }), bul: 0 }]); let endKnifePls = new Polyline([{ pt: AsVector2({ x: length, y: -1 }), bul: 0 }, { pt: AsVector2({ x: length, y: 10000 }), bul: 0 }]); //裁剪结果 let faceRegions = SplitPolyline(ContourCurve, [starKnifePls, endKnifePls]); faceRegions = faceRegions.filter((faceRegion) => { let x = faceRegion.BoundingBox.getCenter(new Vector3).x; return x > currentLength && x < length; }); if (faceRegions.length) { let c = conCu.Clone().ApplyMatrix(PathOCSInv); let lineToward = c.EndPoint.clone().sub(c.StartPoint); let ro = angle(lineToward); let ocs = PathOCS.setPosition(0, 0, 0); ocs = new Matrix4().multiplyMatrices((new Matrix4().makeRotationAxis(ZNormal, ro)), ocs).setPosition(conCu.StartPoint); ocs.multiply(new Matrix4().getInverse(RX)); for (let faceRegion of faceRegions) { //添加正反面 for (let data of faceRegion.LineData) data.pt.add(new Vector2$1(-currentLength)); let frontMat; let backMat; let mtx = MakeMirrorMtx(ZAxis); if (br.SweepVisibleFace === FaceDirection.Front) { frontMat = new Matrix4().multiplyMatrices(ocs, mtx.setPosition(new Vector3(0, faceRegion.BoundingBox.min.y, br.Thickness * FaceDir))); backMat = new Matrix4().multiplyMatrices(ocs, new Matrix4().setPosition(new Vector3(0, faceRegion.BoundingBox.min.y))); } else { frontMat = new Matrix4().multiplyMatrices(ocs, new Matrix4().setPosition(new Vector3(0, faceRegion.BoundingBox.min.y, br.Thickness * FaceDir))); backMat = new Matrix4().multiplyMatrices(ocs, mtx.setPosition(new Vector3(0, faceRegion.BoundingBox.min.y))); } if (opt.frontDrill) this.Faces.push(new Face({ type: BoardFaceType.NoSide, localBoard: br, matrix4: frontMat, length: conCu.Length, width: faceRegion.BoundingBox.max.y, CurveBoardFaceRegionPolyline: faceRegion })); if (opt.backDrill) { this.Faces.push(new Face({ type: BoardFaceType.NoSide, localBoard: br, matrix4: backMat, length: conCu.Length, width: faceRegion.BoundingBox.max.y, CurveBoardFaceRegionPolyline: faceRegion })); } //侧面 let cus = faceRegion.Explode(); //应用新轮廓 计算排钻顺序 let cloneBr = br.Clone(); cloneBr.ContourCurve = faceRegion; const HighDrill = cloneBr.BoardProcessOption.highDrill.slice(); for (let j = 0; j < cus.length; j++) { if (HighDrill.length > 0 && HighDrill[j] === DrillType.None) continue; let cu = cus[j]; let mtx = GetSideFaceMtx(cu, faceRegion.IsClockWise); let ocs = PathOCS.clone().setPosition(0, 0, 0); ocs = new Matrix4().multiplyMatrices((new Matrix4().makeRotationAxis(ZNormal, ro)), ocs).setPosition(conCu.StartPoint); ocs.multiply(new Matrix4().getInverse(RX)).multiply(mtx); let f = new Face({ type: BoardFaceType.Side, localBoard: br, matrix4: ocs, length: cu.Length, width: br.Thickness * FaceDir, drillType: HighDrill.length > 0 && HighDrill[j] }); if (faceSealingDataMap) { let pt = cu.GetPointAtParam(0.5).add(new Vector3(currentLength)); let index = Math.floor(ContourCurve.GetParamAtPoint(pt)); if (!isNaN(index)) faceSealingDataMap.set(f, index); } this.Faces.push(f); } } } currentLength = length; } // 测试代码 // for (let f of this.Faces) // { // let r = f.Region.Clone().ApplyMatrix(f.OCS); // TestDraw(r); // } } IntersectFace(br, bInsEqual = false) { let collisionFaces = []; for (let f1 of this.Faces) { for (let f2 of br.Faces) { //都是正面,或者不允许侧面同侧面并且2板件类型不一样就跳过 if (f1.type === f2.type && (f1.type === BoardFaceType.NoSide || !bInsEqual || br.Board.BoardType !== this.Board.BoardType)) continue; //不共面 if (!MatrixIsCoplane2(f1.OCS, f2.OCS, CanDrawHoleFuzz)) continue; collisionFaces.push(...f1.Intersect(f2)); } } return collisionFaces; } static GetAllSidesFaces(br, isMergeFace = false) { let faces = []; let con = br.ContourCurve; let inverseZ = con.Area2 < 0; let cus = con.Explode(); if (isMergeFace) MergeCurvelist(cus); for (let i = 0; i < cus.length; i++) { let cu = cus[i]; let length = cu.Length; let mtx = GetSideFaceMtx(cu, inverseZ); faces.push(new Face({ type: BoardFaceType.Side, localBoard: br, matrix4: new Matrix4().multiplyMatrices(br.OCS.clone(), mtx), length, width: br.Thickness, })); } return faces; } } //坐标系共面且法线相反 function MatrixIsCoplane2(matrixFrom, matrixTo, zFuzz) { let nor1 = new Vector3().setFromMatrixColumn(matrixFrom, 2); let nor2 = new Vector3().setFromMatrixColumn(matrixTo, 2); //法线共面 if (!equalv3(nor1, nor2.negate(), 1e-4)) return false; //高共面 let pt = new Vector3().setFromMatrixPosition(matrixTo); //变换到自身对象坐标系. pt.applyMatrix4(new Matrix4().getInverse(matrixFrom)); return equaln$1(pt.z, 0, zFuzz); } function GetSideFaceMtx(cu, inverseZ = false) { let x = cu.GetFirstDeriv(0).normalize(); let y = ZAxis; let z = x.clone().cross(y); if (inverseZ) z.negate(); let basePt; if ((equaln$1(x.x, 0, 1e-5) && x.y > 0) || x.x < -1e-5) { x.negate(); basePt = cu.EndPoint; } else basePt = cu.StartPoint; //构建面矩阵 return new Matrix4() .makeBasis(x, y, z) .setPosition(basePt); } class ParseBoardSideFace extends BoardGetFace { constructor(Board) { super(Board); this.Board = Board; } ParseFaces() { this.GetSideFaces(); } GetSideFaces() { let con = GetBoardContour(this.Board); if (!con) return; let inverseZ = con.Area2 < 0; let cus = con.Explode(); for (let cu of cus) { let type = BoardFaceType.Side; let length = cu.Length; if (equaln$1(length, 0) || cu instanceof Arc) type = BoardFaceType.NoSide; let mtx = GetSideCuFaceMtx(cu, inverseZ); let face = new Face({ type, localBoard: this.Board, matrix4: new Matrix4().multiplyMatrices(this.Board.OCS.clone(), mtx), length, width: this.Board.Thickness, }); this.Faces.push(face); } } } /** * 快速的对点表进行偏移 * @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$1(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$1(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, displayAccuracy = 0) { this.contourWall.Draw(verticesArray, uvArray, edgeBuild, displayAccuracy); 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.concat(); let holes = this.children.map(h => { // TestDraw(h.contour.Curve, depth + 1); let pts = h.contour.Curve.GetStretchPoints(); arrayPushArray(vertices, 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, ignoreInCurve = false, checkIntersect = false) { 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 (checkIntersect && node1.contour.Curve.IntersectWith(node2.contour.Curve, IntersectOption.ExtendNone)?.length) continue; if (node2.contour.Curve.PtInCurve(p) || ignoreInCurve) { 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) { 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 = []; //对于未被面域分析出来的线,我们进行join操作 let unusedLines = []; for (let l of lines) if (!parse.GetCueveUsed(l)) unusedLines.push(l); if (unusedLines.length > 2) { let groups = curveLinkGroup(unusedLines, 1); for (let g of groups) { if (g.length < 2) continue; let pl = new Polyline(); pl.ColorIndex = g[0].ColorIndex; pl.OCS = ComputerCurvesNormalOCS(g); for (let cu of g) { if (pl.Join(cu, false, 0.1) === Status.True) cu.Erase(); } pl.CloseMark = true; let c = Contour.CreateContour(pl, false) ?? CreateContour2(pl.Explode(), 0.1); if (c && c.Area > 0.1) contourNodes.push(new ContourTreeNode(c)); else console.error("错误:构建板件的MeshGeometry的盖子生成轮廓失败了!"); } } for (let routes of parse.RegionsOutline) { let cs = routes.map(r => r.curve); let c = Contour.CreateContour(cs, false) ?? CreateContour2(cs) ?? CreateContour2(cs, 0.1); if (c) contourNodes.push(new ContourTreeNode(c)); else console.error("错误:构建板件的MeshGeometry的盖子生成轮廓失败了!"); } 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$1(pre, x)) continue; ret.push(new Tape(pre, x, this.bottom, this.top)); pre = x; if (x === this.end) break; } } if (pre < this.end) //避免最后一个切割元素小于终点时没有补上最后一个 ret.push(new Tape(pre, this.end, this.bottom, this.top)); 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); } } } /** * 曲线胶带(一维) */ 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$1(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$1(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, displayAccuracy = 0) { this.curve.DisplayAccuracy = displayAccuracy; 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$1(n, Math.round(n), 1e-8); if (!IsInteger(tape.start) && equaln$1(tape.start, preTape.end)) leftRanges = SubtractRange(tape.bottom, tape.top, preTape.bottom, preTape.top, this.allDepth); else leftRanges = [[tape.bottom, tape.top]]; if (equaln$1(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$1(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); 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(); let c1Res = { container: [], syntropy: [], reverse: [], outer: [] }; let c2Res = { container: [], syntropy: [], reverse: [], outer: [] }; if (ins.length === 0) { if (cu1 instanceof Circle && cu2 instanceof Circle && equaln$1(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]; } let a1 = cu1.Area, a2 = cu2.Area; if (a2 > a1 && cu2.PtInCurve(cu1.StartPoint)) //cu2包含cu1 c1Res.container.push([0, cu1.EndParam]); else c1Res.outer.push([0, cu1.EndParam]); if (a1 > a2 && cu1.PtInCurve(cu2.StartPoint)) //cu1包含cu2 c2Res.container.push([0, cu2.EndParam]); else c2Res.outer.push([0, cu2.EndParam]); return [c1Res, c2Res]; } if (ins.length === 1) { let a1 = cu1.Area, a2 = cu2.Area; if (a2 > a1 && fastCurveInCurve2(cu2, cu1)) //cu2包含cu1 c1Res.container.push([0, cu1.EndParam]); else c1Res.outer.push([0, cu1.EndParam]); if (a1 > a2 && fastCurveInCurve2(cu1, cu2)) //cu1包含cu2 c2Res.container.push([0, cu2.EndParam]); else c2Res.outer.push([0, cu2.EndParam]); return [c1Res, c2Res]; } //解析出线段列表 let c1Curves = []; let c2Curves = []; 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); } /** * * @param orgStart 被裁剪范围的起点(如果起点大于终点,那么表示 s->end + 0->e) * @param orgEnd * @param clipStart * @param clipEnd * @param end * @returns */ function SubtractRange(orgStart, orgEnd, clipStart, clipEnd, end) { if (orgStart < 0 || orgEnd < 0 || orgEnd > end || orgStart > end || clipStart < 0 || clipEnd < 0 || clipStart > end || clipEnd > end) return []; if (orgStart > orgEnd) return SubtractRange(orgStart, end, clipStart, clipEnd, end).concat(SubtractRange(0, orgEnd, clipStart, clipEnd, end)); if (clipStart > clipEnd) { let arr = SubtractRange(orgStart, orgEnd, clipStart, end, end); let rem = []; for (let s of arr) arrayPushArray(rem, SubtractRange(s[0], s[1], 0, clipEnd, end)); return rem; } if (clipStart >= orgEnd || clipEnd <= orgStart) return [[orgStart, orgEnd]]; if (clipStart <= orgStart) // c1 a1 b1 { if (clipEnd >= orgEnd) return []; return [[clipEnd, orgEnd]]; } if (clipEnd < orgEnd) return [[orgStart, clipStart], [clipEnd, orgEnd]]; return [[orgStart, clipStart]]; } 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) arrayPushArray(temps, 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, rotateUv = false) { this.br = br; this.verticesArray = []; //用于构建三维网格 this.uvArray = []; //uv this.GenerateMeshData(br, rotateUv); } GenerateMeshData(br, rotateUv) { this.edgeAndLidBuilder = new EdgeGeometryBuild(this.br.Thickness); rotateUv = rotateUv || (br instanceof Board && br.BoardProcessOption.lines === LinesType.Reverse); //计算墙(创建轮廓取出,为了得到正确的轮廓曲线(逆时针之类的)) 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, br.DisplayAccuracy); } 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 = []; //备份原始的槽,下面的代码为了合并槽,会将板的槽先清空,后续我们在还原它 (未来我们可能不强制合并板的槽 而是在这里合并? 保证板的槽的纯粹?例如矩形) let bakGrooves; { //合并深度相同 但是半径不相同的槽 let thicknessKnifsMap = new Map(); //深度->刀半径Set let thicknessGroovesMap = new Map(); //深度->刀半径Set let fuzz = new FuzzyFactory(2, 1e-2); for (let g of br.Grooves) //准备数据 map { let thick = fuzz.lookupOrCreate([g.Thickness], g.Thickness); let knifsSet = thicknessKnifsMap.get(thick); let grooveArr = thicknessGroovesMap.get(thick); if (!knifsSet) { knifsSet = new Set(); grooveArr = []; thicknessKnifsMap.set(thick, knifsSet); thicknessGroovesMap.set(thick, grooveArr); } grooveArr.push(g); knifsSet.add(g.KnifeRadius); } let mergeGrooves = []; for (let [thick, set] of thicknessKnifsMap) { if (set.size > 1) //如果深度相同的大于1 { if (!bakGrooves) bakGrooves = br.Grooves.concat(); //复制一个 let arr = thicknessGroovesMap.get(thick).map(g => { let newg = g.Clone(); newg.KnifeRadius = 0; return newg; }); br.Grooves.length = 0; br.Grooves.push(...arr); br.GrooveCheckMerge(false); mergeGrooves.push(...br.Grooves); } else { mergeGrooves.push(...thicknessGroovesMap.get(thick)); } } if (bakGrooves) { br.Grooves.length = 0; br.Grooves.push(...mergeGrooves); } } for (let groove of br.Grooves) { //判断槽正反面 let type; if (equaln$1(groove.Thickness, br.Thickness)) type = DepthType.All; else { if (equaln$1(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.UpdateOCSTo(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.UpdateOCSTo(IdentityMtx4); let grooveChildContour = Contour.CreateContour(grooveChildContourCurve); grooveHoleContours.push(grooveChildContour); } grooves.push(new Groove(grooveContour, grooveHoleContours, type, groove.Thickness, br.Thickness)); } if (bakGrooves) { br.Grooves.length = 0; br.Grooves.push(...bakGrooves); } 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.ExtendNone, 1e-4); 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(); this._DisplayAccuracy = 0; /* y----------- ^ | | ↑ | | | | height | ↓ | | | 0---width->x */ this.height = 1; //y this.width = 1; //x /** * 拉伸实体的厚度 * 我们允许它是一个负数,但是这个时候这个实体已经是一个无效的拉伸实体了. * 允许负数,用来校验凹槽的合理性. */ this.thickness = 1; this.isRect = true; this.bevelEnabled = false; 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) { let oldMaterial = this.Material ?? this._db?.DefaultMaterial?.objectId; super.Material = materialId; let isf_old = Boolean(oldMaterial?.Object?.IsFull); let isf_new = Boolean(materialId?.Object?.IsFull); if (isf_old !== isf_new) this.Update(); else if (materialId?.Object?.UseWorldUV) { if (this._MeshGeometry) this.GenWorldUV(this._MeshGeometry); } } get Material() { return super.Material; } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } get KnifeRadius() { return this.knifeRadius; } set KnifeRadius(v) { if (!equaln$1(v, this.knifeRadius)) { this.WriteAllObjectRecord(); this.knifeRadius = v; //在双击板修改的时候,我们希望它能修改绘制版本,这样它的关联实体也会被刷新 this.__UpdateVersion__++; } } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } 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$1(v, this.groovesAddLength)) { this.WriteAllObjectRecord(); this.groovesAddLength = v; //更改它的时候,关联切割被更新,拆单的时候才会正确,否则使用缓存将不正确 this.__UpdateVersion__++; } } get GroovesAddWidth() { return this.groovesAddWidth; } set GroovesAddWidth(v) { if (!equaln$1(v, this.groovesAddWidth)) { this.WriteAllObjectRecord(); this.groovesAddWidth = v; //更改它的时候,关联切割被更新,拆单的时候才会正确,否则使用缓存将不正确 this.__UpdateVersion__++; } } get GroovesAddDepth() { return this.groovesAddDepth; } set GroovesAddDepth(v) { if (!equaln$1(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); let normal = this.Normal.negate(); for (let g of this.grooves) { g._SpaceOCS.copy(this._SpaceOCS); //因为在镜像(Extrude.ApplyMirrorMatrix)的时候 没有设置这个会导致错误(参考该函数的代码,似乎是为了实现柜子镜像) 所以拷贝一下 g.objectId = new ObjectId; g.ApplyMatrix(m); //如果是镜像,如果槽的方向相反了,那么需要修正 let gNormal = g.Normal; if (equalv3(normal, gNormal, 1e-3)) { let p = g.Position.add(gNormal.multiplyScalar(g.thickness * 0.5)); g.ApplyMatrix(MakeMirrorMtx(normal, p)); } g.objectId = undefined; } //del_exp2_start //由于修改矩阵会导致矩阵错误 this.csg = undefined; //del_exp2_end this.AutoUpdate = updateBak; let te = m.elements; let scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2]; let scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6]; let scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10]; if (!equaln$1(scaleXSq, 1, 1e-4) || !equaln$1(scaleYSq, 1, 1e-4) || !equaln$1(scaleZSq, 1, 1e-4)) this.Update(UpdateDraw.Geometry); else if (this.AutoUpdate) this.DeferUpdate(); return this; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let cu = this.ContourCurve; cu.ApplyMatrix(this.OCSNoClone); cu.ApplyMatrix(m); cu.ApplyMatrix(this.OCSInv); this.CheckContourCurve(); return this; } //实现了这个函数后 实现了柜子的镜像 ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); const curve = this.ContourCurve; if (curve instanceof Polyline && !equalv3(curve.Position, ZeroVec)) //移除多段线的OCS(目前只判断了基点) { let pts = curve.LineData; if (equalv2(pts[0].pt, arrayLast(pts).pt)) pts.pop(); let ocs = curve.OCSNoClone; for (let p of pts) Vector2ApplyMatrix4(ocs, p.pt); curve.OCS = IdentityMtx4; } let nor = this.Normal.applyMatrix4(this.SpaceOCSInv.setPosition(ZeroVec)); //法向量在柜子坐标系中的表示 if (equaln$1(Math.abs(nor.z), 1)) //在柜子中是一个层板 { reviseMirrorMatrix(this._Matrix, 1); if (curve instanceof Circle) curve.ApplyMatrix(new Matrix4().makeRotationX(Math.PI)); else reviseMirrorMatrix(curve.OCSNoClone, 1); this.SetContourCurve(curve); } else if (equaln$1(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 (curve instanceof Circle) curve.ApplyMatrix(new Matrix4().makeRotationY(Math.PI)); else reviseMirrorMatrix(curve.OCSNoClone, 0); this.SetContourCurve(curve); } if (this.grooves.length) this.Update(UpdateDraw.Geometry); return this; } get Width() { return this.width; } get Height() { return this.height; } get Thickness() { return this.thickness; } set Thickness(thickness) { if (!equaln$1(thickness, this.thickness, 1e-4)) //避免18.0009 无法改成 18 { 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$1(g.thickness, this.thickness)) g.Thickness = thickness; else if (!equaln$1(p.z, 0)) g.ApplyMatrix(m); } } this.thickness = thickness; this.Update(UpdateDraw.Geometry); } } //修复#I7CBHO的过程中 直接修改了这个属性 get Grooves() { return this.grooves; } //侧面造型 get SideModelingMap() { return undefined; } /** * 返回未拷贝的轮廓曲线 */ get ContourCurve() { if (!this.contourCurve) this.GeneralRectContour(); // 默认轮廓都是白的 this.contourCurve.ColorIndex = 7; 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坐标系0点 */ SetContourCurve(curve) { if (!curve.IsClose) return; let area = curve.Area; if (!area || equaln$1(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$1(ocs.elements[10], -1, 1e-4) || !equalv3(z, z1); let isRotate = !equaln$1(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 ocsInv = this.OCSInv; let x = TransformVector(xAxis.clone(), ocsInv).setZ(0).normalize(); if (equalv3(ZeroVec, x, 1e-5)) return this; this.WriteAllObjectRecord(); let a = Math.atan2(x.y, x.x); x.transformDirection(this._Matrix); 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; this.ContourCurve.ApplyMatrix(tempMatrix1.makeRotationZ(-a)); //复用了这个矩阵 this.CheckContourCurve(); if (this.contourCurve instanceof Polyline) this.contourCurve.UpdateOCSTo(IdentityMtx4); 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$1(size.x, 0) || equaln$1(size.y, 0)) Log(`注意!!该板件尺寸为0!`, LogType.Warning); this.isRect = equaln$1(this.width * this.height, this.ContourCurve.Area, 0.1); // if (area2 < 0) // this.contourCurve.Reverse();//矩形板的封边与排钻不需要考虑 异形板的排钻封边可能会错误(无所谓了) //修正轮廓基点 if (!equalv3(box.min, ZeroVec)) { this.contourCurve.Position = this.contourCurve.Position.sub(box.min); TransformVector(box.min, this.OCSNoClone); this._Matrix.setPosition(this.Position.add(box.min)); } } get IsRect() { return this.isRect; } /** * 这个拉伸实体的面域形状 */ get Shape() { return this.GetShape(false); } GetShape(filterSmallGroove = false) { let contour = Contour.CreateContour(this.ContourCurve.Clone(), false); let holes = []; for (let g of this.grooves) { if (equaln$1(g.thickness, this.thickness, 1e-3) && (!filterSmallGroove || g.ContourCurve.Area > HostApplicationServices.smallGrooveThreshold)) 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$1(this.thickness, target.thickness, 1e-3) && equaln$1(0, targetZMin, 2e-3)) { 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(); //防止重复添加 this.grooves = this.grooves.filter((g) => !equaln$1(g.thickness, target.thickness, 1e-3)); //过滤掉一样的 因为已经在shape里 // [ + ] 产生网洞. 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; for (let tg of target.grooves) if (!equaln$1(tg.thickness, target.thickness, 1e-3)) //过滤掉一样的 因为已经在shape里 this.grooves.push(tg.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) { //del_exp2_start 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, relevanceSideModelMap } = this.ParseGrooves(extrudes); if (!grooves.length && !relevanceSideModelMap.size) return false; this.AppendSideModel(relevanceSideModelMap); let area1 = this.ContourCurve.Area; let sum1 = this.Volume; this.AppendGrooves(grooves, output); let sum2 = this.Volume; let area2 = this.ContourCurve.Area; if (!equaln$1(sum1, sum2) || !equaln$1(area1, area2) || relevanceSideModelMap.size) { if (!this.ReadFileIng && this instanceof Board) { if (this.Id) InteractionLog([{ msg: `${this.Name}(${this.Id.Index})`, entity: [this] }, { msg: "被切割成功" }], LogType.Info); else if (this.__OriginalId__) InteractionLog([{ msg: `${this.Name}(${this.__OriginalId__.Index})`, entity: [this] }, { msg: "关联切割成功更新槽!" }], LogType.Info); } return true; } //del_exp2_end return false; } //将相交部分转换为正反面槽或侧槽 ParseGrooves(knifeExtrudes) { let faces; let con; let inverseZ; let cus; let mirrorMtxZ; let grooves = []; let relevanceSideModelMap = new Map(); //切割的侧槽 const GetSideModelList = (extrude) => { let knifeCon = extrude.ContourCurve.Clone(); let knifeConIsCircle = knifeCon instanceof Circle; if (this instanceof Board) { let intersectFaceIndex = 0; let intersectFaceIndexs = []; if (!faces) { faces = new ParseBoardSideFace(this); con = GetBoardContour(this); if (!con) return; inverseZ = con.Area2 < 0; cus = con.Explode(); mirrorMtxZ = MakeMirrorMtx(ZAxis); } //相交SCG //使用克隆 防止正反面槽mesh丢失 let cloneEnt = this.Clone(); let interCSG = CSGIntersect(cloneEnt.CSG, extrude.CSG, this.OCSInv.multiply(extrude.OCS)); //转换至相交面的尺寸 let topology = new BSPGroupParse(interCSG); let ptsList = topology.Parse(); for (let pts of ptsList) { // ------- 求相交面索引 begin ---------- intersectFaceIndexs = []; let checkBox = new Box3Ext().setFromPoints(pts); for (let i = 0; i < faces.Faces.length; i++) { let face = faces.Faces[i]; let region = face.Region.Clone(); region.ApplyMatrix(this.OCSInv.multiply(face.OCS)); if (checkBox.intersectsBox(region.BoundingBox, 1e-3)) intersectFaceIndexs.push(i); } if (!intersectFaceIndexs.length) { InteractionLog([{ msg: `${this.Name}`, entity: [this] }, { msg: "侧面造型切割面解析失败, 无法生成侧面造型!" }], LogType.Error); continue; } else if (intersectFaceIndexs.length === 1) intersectFaceIndex = intersectFaceIndexs[0]; else { //截面小的为正面 intersectFaceIndexs.sort((i, j) => { let faceOCSInv1 = faces.Faces[i].OCSInv; let faceOCSInv2 = faces.Faces[j].OCSInv; let box1 = checkBox.clone().applyMatrix4(this.OCS).applyMatrix4(faceOCSInv1).getSize(new Vector3); let box2 = checkBox.clone().applyMatrix4(this.OCS).applyMatrix4(faceOCSInv2).getSize(new Vector3); return knifeConIsCircle ? box1.x - box2.x : box2.x - box1.x; }); intersectFaceIndex = intersectFaceIndexs[0]; } intersectFaceIndexs = []; // ------- 求相交面索引 end ---------- const faceMtx = GetSideCuFaceMtx(cus[intersectFaceIndex], inverseZ); const faceMtxInv = new Matrix4().getInverse(faceMtx); const faceMtxOnBoard = this.OCS.multiply(faceMtx).multiply(mirrorMtxZ); const mtx = new Matrix4().getInverse(faceMtx.clone().multiply(mirrorMtxZ)); //测试绘制 // TestDrawGeom3s([interCSG], 1); let sideModelCon; let yv = extrude.Normal; let zv = new Vector3().setFromMatrixColumn(faceMtxOnBoard, 2); if (isParallelTo(yv, zv)) { sideModelCon = knifeCon.Clone(); yv = new Vector3().setFromMatrixColumn(extrude.OCS, 0); } let xv = yv.clone().cross(zv); yv.copy(zv).cross(xv); xv.normalize(); yv.normalize(); zv.normalize(); const grooveMtx = new Matrix4().makeBasis(xv, yv, zv).copyPosition(faceMtxOnBoard); const grooveMtxInv = new Matrix4().getInverse(grooveMtx).multiply(faceMtxOnBoard).multiply(mtx); for (let p of pts) p.applyMatrix4(grooveMtxInv); let box = new Box3Ext().setFromPoints(pts); if (!box.isSolid(0.1)) continue; let size = box.getSize(new Vector3()); let solid = new ExtrudeSolid_1(); solid.groovesAddDepth = extrude.groovesAddDepth; solid.groovesAddLength = extrude.groovesAddLength; solid.groovesAddWidth = extrude.groovesAddWidth; solid.knifeRadius = extrude.knifeRadius; solid.ConverToRectSolid(size.x, size.y, size.z); if (sideModelCon) { if (sideModelCon instanceof Circle) { if (equaln$1(size.x, size.y)) solid.ContourCurve = sideModelCon; else { //保留圆形切割 const r = sideModelCon.Radius * 2; const cuLength = cus[intersectFaceIndex].Length; let splitLeft = false; let intersectX = 0; if (equaln$1(box.min.x, 0)) intersectX = r - box.max.x + sideModelCon.BoundingBox.min.x; else { splitLeft = true; intersectX = cuLength - box.min.x + sideModelCon.BoundingBox.min.x; } let starKnifePls = new Polyline([{ pt: AsVector2({ x: intersectX, y: 1 }), bul: 0 }, { pt: AsVector2({ x: intersectX, y: 0 }), bul: 0 }]); let faceRegions = SplitPolyline(sideModelCon, [starKnifePls]); faceRegions = faceRegions.filter((faceRegion) => { let x = faceRegion.BoundingBox.getCenter(new Vector3).x; return splitLeft ? x < intersectX : x > intersectX; }); if (faceRegions.length) solid.ContourCurve = faceRegions[0]; } solid.ApplyMatrix(grooveMtx.clone().setPosition(box.min.applyMatrix4(grooveMtx)).multiply(mirrorMtxZ)); solid.ApplyMatrix(this.OCSInv); solid.ApplyMatrix(faceMtxInv); } else { sideModelCon.ApplyMatrix(new Matrix4().setPosition(sideModelCon.BoundingBox.min.negate())); sideModelCon.ApplyMatrix(extrude.OCS); sideModelCon.ApplyMatrix(this.OCSInv); sideModelCon.ApplyMatrix(new Matrix4().getInverse(faceMtx)); solid.ContourCurve = sideModelCon; //确保侧面造型槽solid在XY平面上 solid.Z0(); } } else { solid.ApplyMatrix(grooveMtx.clone().setPosition(box.min.applyMatrix4(grooveMtx)).multiply(mirrorMtxZ)); solid.ApplyMatrix(this.OCSInv); solid.ApplyMatrix(faceMtxInv); } let sideModelList = relevanceSideModelMap.get(intersectFaceIndex); if (sideModelList) sideModelList.push(solid); else sideModelList = [solid]; relevanceSideModelMap.set(intersectFaceIndex, sideModelList); } } }; for (let extrude of knifeExtrudes) { let gs = this.ConverToLocalGroove(extrude); if (gs.length) { let sideGrooves = []; for (let g of gs) { //侧槽 if (this.GrooveCheckPosition(g) === Status.Side) sideGrooves.push(g); else grooves.push(g); } if (sideGrooves.length) { if (sideGrooves.length === gs.length) GetSideModelList(extrude); else { for (let g of sideGrooves) GetSideModelList(g); } } } } return { grooves, relevanceSideModelMap }; } 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); } /** * 添加侧面槽 * 通常槽已经校验过准确性,所以不在校验 */ AppendRelevanceSideModel(relevanceSideModelMap) { if (this instanceof Board) { this.ClearSideModelingCache(); this.RelevanceSideModelMap = relevanceSideModelMap; } } AppendSideModel(relevanceSideModelMap) { if (relevanceSideModelMap.size && this instanceof Board) { this.WriteAllObjectRecord(); const sideModelMap = this.SideModelingMap; for (let [num, soilds] of relevanceSideModelMap) { let ss = this.SideModelingMap.get(num) ?? []; for (let soild of soilds) ss.push(soild); sideModelMap.set(num, ss); } this.SideModelingMap = sideModelMap; } } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum) { switch (snapMode) { case ObjectSnapMode.End: return this.GetGripOrStretchPoints(DragPointType.End); 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) { if (!frustum || frustum.intersectsBox(g.BoundingBox)) pts.push(...g.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum)); } 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 pts; if (dragType === DragPointType.Grip) pts = this.ContourCurve.GetGripPoints(); else if (dragType === DragPointType.Stretch) pts = this.ContourCurve.GetStretchPoints(); else if (dragType === DragPointType.End) { if (this.contourCurve instanceof Circle) pts = this.ContourCurve.GetGripPoints(); else pts = this.ContourCurve.GetStretchPoints(); } else pts = []; 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); const isGrip = dragType === DragPointType.Grip; if (!isGrip && indexList.length === arraySum(counts)) { this.Position = this.Position.add(vec); return; } arraySortByNumber(indexList); let updateBak = this.AutoUpdate; this.AutoUpdate = false; if (!this.grooves.length && !this.HasSideModel) { this.MoveGripOrStretchPointsOnly(indexList, vec, dragType); } else { let i = 0; let icount = indexList.length; let offset = 0; let grooveIndex = -1; let sideModelIndex = -1; let cus; let baseIndexList = new Set(); let sideModelSealCurveMtxCache = new Map(); //获取侧面的OCS const GetSideModelSealCurveMtx = (num) => { if (!cus) cus = GetBoardContour(this)?.Explode(); let mtx = sideModelSealCurveMtxCache.get(num); if (!mtx && cus?.length) { let cu = cus[num]; if (!cu) return new Matrix4; let x = cu.GetFirstDeriv(0).normalize().applyMatrix4(this.OCS.setPosition(0, 0, 0)); let y = this.Normal; let z = x.clone().cross(y); mtx = new Matrix4().getInverse(new Matrix4().makeBasis(x, y, z)); sideModelSealCurveMtxCache.set(num, mtx); } return mtx ?? new Matrix4; }; 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) { let orgCus; if (this.HasSideModel) orgCus = GetBoardContour(this)?.Explode(); this.MoveGripOrStretchPointsOnly(ilist, vec, dragType); if (this.HasSideModel) { //修正点的索引 判断侧面造型的起点是否被移动 let stretchCount = this.ContourCurve.GetDragPointCount(dragType); for (let num of ilist) { if (num < stretchCount) baseIndexList.add(num); else baseIndexList.add(num - stretchCount); } let isChangeThiness = this.IsStretchThickness(Array.from(baseIndexList)); //起点被拉伸时反向移动 达到相对静止状态 const sideModelingMap = this.SideModelingMap; for (let [num, soilds] of sideModelingMap) { let firstIndex = num; let secondIndex = (num + 1) === stretchCount ? 0 : num + 1; if (isGrip) { firstIndex = num * 2; //拉取中点时 secondIndex = (firstIndex - 1) < 0 ? (stretchCount - 1) : (firstIndex - 1); } //Grip的时候点选中点时两边相连的 也反向移动 firstIndex - 1 if (!isGrip && (baseIndexList.has(firstIndex) && !baseIndexList.has(secondIndex)) || isGrip && (baseIndexList.has(firstIndex) || baseIndexList.has(secondIndex)) || isChangeThiness && ilist[0] < stretchCount) { for (let s of soilds) { let mtx = GetSideModelSealCurveMtx(num); let v = vec.clone().applyMatrix4(mtx).setZ(0); if (isChangeThiness) { v.setX(0); } else { if (cus?.length && cus[num] && orgCus) v = new Vector3(orgCus[num].Length - cus[num].Length); else v.setY(0); } s.ApplyMatrix(new Matrix4().setPosition(v.clone().multiplyScalar(-1))); } } } } } else if (grooveIndex < this.grooves.length) this.grooves[grooveIndex].MoveGripOrStretchPoints(ilist, vec, dragType); else { //侧面造型拉伸 const sideModelingMap = this.SideModelingMap; let soildCount = 0; const isMainChangeThiness = this.IsStretchThickness(Array.from(baseIndexList)); for (let [num, soilds] of sideModelingMap) { soildCount += soilds.length; if (sideModelIndex < soildCount) { let mtx = GetSideModelSealCurveMtx(num); let v = vec.clone().applyMatrix4(mtx); if (isMainChangeThiness) v.setX(0); let soild = soilds[soilds.length - (soildCount - sideModelIndex)]; const stretchPtLength = soild.ContourCurve.GetStretchPoints().length; const firstIndex = num; const secondIndex = (num + 1) === stretchPtLength ? 0 : num + 1; const mainSoildIList = []; const grooveSoildIList = []; for (let k of ilist) { if (k < stretchPtLength * 2) mainSoildIList.push(k); else grooveSoildIList.push(k); } //改变侧面造型厚度 if (soild.IsStretchThickness(mainSoildIList)) { if (mainSoildIList.every(i => i >= stretchPtLength)) { //造型 底边 v.setZ(-v.z); } else if ((baseIndexList.has(firstIndex) && baseIndexList.has(secondIndex))) { //造型见光面 和侧面一起移动 ilist = []; for (let k = 0; k < stretchPtLength; k++) ilist.push(k + stretchPtLength); //有选中子槽端点 默认一起改变 if (grooveSoildIList.length) { const grooveStretchPtLength = soild.grooves[0].ContourCurve.GetStretchPoints().length; for (let k = 0; k < grooveStretchPtLength; k++) ilist.push(k + grooveStretchPtLength + stretchPtLength * 2); } } else break; } else if (!isMainChangeThiness && baseIndexList.has(firstIndex) && baseIndexList.has(secondIndex)) break; else v.setZ(0); soild.MoveGripOrStretchPoints(ilist, v, dragType); break; } } } } grooveIndex++; if (grooveIndex >= this.grooves.length) sideModelIndex++; } } if (this.objectId || this.IsEmbedEntity) { this.CheckContourCurve(); let splitEntitys = []; this.GrooveCheckAll(splitEntitys); if (this.HasSideModel) { let board = this; board.SplitBoardSideModelUtil.Init(board); board.SplitBoardSideModelUtil.SpiltSideModelOfBrContour(board); board.SplitBoardSideModelUtil.SpiltSideModelOfBrThickness(board, board.Thickness); } 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.GetBulgeAt(Math.floor(param)) === 0) { let der = this.ContourCurve.GetFirstDeriv(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 = Geometry2CSG2(this.MeshGeometry); return this.csg; } /** * (步骤1.2.) * 将目标拉伸实体转换成在板件内部可用的凹槽实体 * @param target 该对象可能被修改(内部不拷贝该实体) * @param useClone 转换后的实体是目标实体拷贝后修改的 */ ConverToLocalGroove(target) { // 针对圆弧板 if (target instanceof Board && this instanceof Board && (target.IsArcBoard || this.IsArcBoard)) { const boardCuttingForSweep = new BoardCuttingForSweep(this, target); return boardCuttingForSweep.ConverToLocalGroove(); } 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 p = target.Position; p.add(n2.clone().multiplyScalar(target.thickness * 0.5)); target.ApplyMatrix(MakeMirrorMtx(n2, p)); } const status = this.GrooveCheckPosition(target); if (status === Status.Side) return [target]; else if (status !== 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$1(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 < 0.99 || size.y < 0.99 || 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); //使用克隆 防止正反面槽mesh丢失 let cloneEnt = this.Clone(); let interCSG = CSGIntersect(cloneEnt.CSG, target.CSG, this.OCSInv.multiply(target.OCSNoClone)); //测试绘制 // TestDraw(new Mesh(CSG2Geometry2(interBSP), ColorMaterial.GetConceptualMaterial(1, DoubleSide))); let topology = new BSPGroupParse(interCSG); 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); if (!(project.Area > 1)) { Log("无法切割板", LogType.Info, [this, target]); return []; } 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$1(size.x, 0, 1e-2) || equaln$1(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; } } //del_exp2_end /** * (步骤2) * 更新凹槽位置和厚度(校验凹槽的Z轴位置是否存在交集) */ GrooveCheckPosition(target) { if (target.Width < 1e-1 || target.Height < 1e-1 || target.Thickness < 1e-1) 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$1(minZ, 0))) target.ApplyMatrix(MoveMatrix(this.Normal.multiplyScalar(-minZ))); } else if (maxZ >= (this.thickness - 1e-2) && minZ > 0 && (this.thickness - minZ) > 0.01) //正面 target.Thickness = this.thickness - minZ; else if (minZ > 1e-2 && maxZ < (this.thickness - 1e-2)) return Status.Side; else return Status.False; if (equaln$1(target.thickness, this.thickness, 1e-2)) target.thickness = this.thickness; return target.thickness > 1e-2 ? Status.True : Status.False; } /** * (步骤3) * 计算凹槽合并 */ GrooveCheckMerge(checkGrooveContourCurve = true) { 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$1(startG.knifeRadius, ng.knifeRadius, 1e-3) && startG.Join(ng) === Status.True) { ng.TempData.used = true; stack.push(ng); } } g.TempData = undefined; //总是保证被使用的造型这个数据为空 } } if (checkGrooveContourCurve && retGs.length !== this.grooves.length) { this.grooves = retGs; for (let g of this.grooves) g.CheckContourCurve(); } } /** * (步骤4.1) * 计算凹槽轮廓在本实体中的约束状态(可能分裂) * @param groove 凹槽(不拷贝,直接被修改) * @returns this[] 凹槽在本实体中正确的约束状态.(可能分裂成为多个) */ GrooveCheckContour(groove) { let matrixToTarget = groove.OCSInv.multiply(this.OCS); matrixToTarget.elements[14] = 0; //z->0 //理论上只有大洞才需要优化,小洞无所谓了 let thisShape = this.GetShape(true).ApplyMatrix(matrixToTarget); let targetShape = new Shape(Contour.CreateContour([groove.ContourCurve.Clone()], false)); let inters = thisShape.IntersectionBoolOperation(targetShape); if (inters.length === 1) { groove.ContourCurve = inters[0].Outline.Curve; let grooves = [groove]; groove.GrooveCheckAll(grooves); return grooves; } else { let grooves = []; for (let contour of inters) { let ext = groove.Clone().ClearDraw(); ext.ContourCurve = contour.Outline.Curve; ext.GrooveCheckAll(grooves); grooves.push(ext); } return grooves; } } /** (步骤4.2.1) 针对圆弧板-修正路径 */ AdjustPath(ext, outline) { } /** (步骤4.2.2) 针对圆弧板-修正位置 */ AdjustPosition(ext, basePoint) { } /** * (步骤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 allDepthGrooves = []; //全深槽 arrayRemoveIf(this.grooves, groove => { if (equaln$1(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), groove.grooves.filter(g => equaln$1(g.thickness, groove.thickness)).map(g => { let gc = g.ContourCurve.Clone(); let mtx = g.OCS.premultiply(groove.OCSInv).premultiply(matrixToLocal); gc.ApplyMatrix(mtx); return Contour.CreateContour([gc], false); }))); allDepthGrooves.push(groove); return true; } return false; }); // 测试绘制黄色为原始轮廓 红色为切割轮廓 // TestDraw(new Region(shapeManager).Clone(), 2); // TestDraw(new Region(subtractShape).Clone(), 1); shapeManager.SubstactBoolOperation(subtractShape); let shapes = shapeManager.ShapeList; //不做任何改变 if (shapeManager.ShapeCount === 1 && shapes[0].Holes.length === allDepthGrooves.length) { //在拉伸夹点后,全深槽如果不改原始的板的信息,那么它无法在递归检查,这个时候如果不校验,那么它将不会在被校验 for (let g of allDepthGrooves) g.CheckContourCurve(); this.grooves.push(...allDepthGrooves); 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(); //#endregion 保持旧的槽数据 (必须在这里写代码 因为下面会修改hole.Curve) let p = hole.Curve.StartPoint; let oldGrooveIndex = subtractShape.ShapeList.findIndex(s => s.Outline.Curve.PtOnCurve(p)); let oldGroove = allDepthGrooves[Math.max(oldGrooveIndex, 0)]; groove.knifeRadius = oldGroove.knifeRadius; groove.groovesAddWidth = oldGroove.groovesAddWidth; groove.groovesAddDepth = oldGroove.groovesAddDepth; groove.groovesAddLength = oldGroove.groovesAddLength; //#endregion groove.OCS = this.OCSNoClone; groove.ContourCurve = hole.Curve; groove.thickness = this.thickness; ext.grooves.push(groove); } const shapeOutline = shape.Outline.Curve.Clone(); // 针对圆弧板-修正路径 this.AdjustPath(ext, shapeOutline); const shapeBasePoint = shape.Outline.Curve.StartPoint; ext.ContourCurve = shape.Outline.Curve; ext.GrooveCheckAll(splitEntitys); // 针对圆弧板-修正位置 this.AdjustPosition(ext, shapeBasePoint); ext.Update(); splitEntitys.push(ext); } if (shapes.length > 0) { let shape = shapes[0]; for (let hole of shape.Holes) { let groove = new ExtrudeSolid_1(); //#endregion 保持旧的槽数据 (必须在这里写代码 因为下面会修改hole.Curve) let p = hole.Curve.StartPoint; let oldGrooveIndex = subtractShape.ShapeList.findIndex(s => s.Outline.Curve.PtOnCurve(p)); let oldGroove = allDepthGrooves[Math.max(oldGrooveIndex, 0)]; groove.knifeRadius = oldGroove.knifeRadius; groove.groovesAddWidth = oldGroove.groovesAddWidth; groove.groovesAddDepth = oldGroove.groovesAddDepth; groove.groovesAddLength = oldGroove.groovesAddLength; //#endregion groove.OCS = this.OCSNoClone; groove.ContourCurve = hole.Curve; groove.thickness = this.thickness; this.grooves.push(groove); } if (!equaln$1(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$1(g.thickness, this.thickness, 1e-3)) ; else arrayRemoveIf(g.grooves, subg => !equaln$1(g.thickness, subg.thickness, 1e-3)); } //合并 this.GrooveCheckMerge(); //修改本实体轮廓 if (this.grooves.some(g => equaln$1(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$1(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$1(gg.contourCurve.Area, thisArea)) { //判断正反面 let p = gg.Position.applyMatrix4(this.OCSInv); if (equaln$1(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() { this.IsLazyGrooveCheck = false; if (this.IsNeedGrooveCheck) this.GrooveCheckAllAutoSplit(); } //#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.DrawColorIndex, this.Freeze)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, this.GetConceptualMaterial()), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } else if (renderType === RenderType.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); 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); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); return new Object3D().add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetPhysical2EdgeMaterial())); } } get UCGenerator() { return boardUVGenerator; } get NeedUpdateRelevanceGroove() { //在同步反应器中,当存在关联拉槽的实体和本实体一起被删除后,会更新本实体,然后导致同步认为它没有子实体 if (this._isErase) return false; 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() { //避免Jig实体更新,导致性能暴跌. if (!this.Id) return; this.__CacheKnifVersion__ = {}; let knifs = []; this.GetRelevanceKnifes(knifs); //如果是切割圆弧板或刀是圆弧板,先不切割 // if (this instanceof Board && this.IsArcBoard) knifs = []; // knifs = knifs.filter(e => !(e instanceof Board && e.IsArcBoard)); if (knifs.length > 0) { for (let k of knifs) //复合实体(五金)的子实体没有id this.__CacheKnifVersion__[k.Id?.Index ?? k.__TempIndexVersion__?.Index] = k.__TempIndexVersion__?.Version ?? k.__UpdateVersion__; let tempExtrude = this.Clone(true); tempExtrude.RelevanceKnifs.length = 0; //避免递归 if (tempExtrude instanceof Board) tempExtrude.SideModelingMap.clear(); if (!this.ReadFileIng) tempExtrude.__OriginalId__ = this.Id; //在读取文件时不打印日志 let output = [tempExtrude]; let ok = tempExtrude.Subtract(knifs, output); this.__CacheSplitExtrudes = output; if (tempExtrude instanceof Board) this.AppendRelevanceSideModel(tempExtrude.SideModelingMap); 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); } //因为这里更新了mesh geom 所以我们必须更新它. this._MeshGeometry = this.UpdateMeshGeom(this._MeshGeometry); this._MeshGeometry["IsMesh"] = true; //我们加入一些拓展信息,以便排钻能够使用(或者其他的,比如发送到效果图?,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 = this.Id ?? this.__OriginalId__; if (!this.ReadFileIng && id && this instanceof Board && this.__CacheVolume__ !== undefined && !equaln$1(this.__CacheVolume__, this.Volume)) InteractionLog([{ msg: `${this.Name}(${id.Index})`, entity: [this] }, { msg: "关联槽已逃离!" }], LogType.Warning); this.__CacheVolume__ = undefined; this.__CacheSplitExtrudes = [this]; } } else { if (!this.ReadFileIng && this.Id && this instanceof Board && this.__CacheVolume__ !== undefined && !equaln$1(this.__CacheVolume__, this.Volume)) { InteractionLog([{ msg: `${this.Name}(${this.Id.Index})`, entity: [this] }, { msg: "关联槽已逃离或者被清除!" }], LogType.Warning); //清除关联侧槽 this.AppendRelevanceSideModel(new Map()); } this.__CacheSplitExtrudes = [this]; this.__CacheVolume__ = undefined; } } /** * 如果实体被切割,那么将返回分裂的实体数组,否则返回自身 */ get SplitExtrudes() { if (this.NeedUpdateRelevanceGroove) this.Update(UpdateDraw.Geometry); //我们先直接更新绘制 if (this.NeedUpdateRelevanceGroove) //如果更新失败,那么我们更新这个槽(似乎也证明了我们没有绘制实体) this.CalcRelevanceGroove(); //注意,这也将更新绘制的实体(EdgeGeo,MeshGeo)(如果拆单也用这个,可能会带来性能损耗) if (this.__CacheSplitExtrudes) for (let e of this.__CacheSplitExtrudes) e._MaterialId = this._MaterialId; return this.__CacheSplitExtrudes; } GetRelevanceKnifes(knifs) { for (let e of this.RelevanceKnifs) { if (!e.IsErase) knifs.push(e.Object); else if (this.__CacheKnifVersion__) this.__CacheKnifVersion__[e.Index] = e?.Object?.__UpdateVersion__; } } ClearRelevance(en) { if (en) { let oldLen = this.RelevanceKnifs.length; arrayRemoveIf(this.RelevanceKnifs, id => !id?.Object || id.Index === en.Id.Index); if (this.RelevanceKnifs.length !== oldLen) arrayRemoveIf(en.RelevanceMeats, id => !id?.Object || id.Index === this.Id.Index); oldLen = this.RelevanceMeats.length; arrayRemoveIf(this.RelevanceMeats, id => !id?.Object || id.Index === en.Id.Index); if (oldLen !== this.RelevanceMeats.length) arrayRemoveIf(en.RelevanceKnifs, id => !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?.Object || i.Index === this.Id.Index); } for (let id of this.RelevanceMeats) { let en = id.Object; if (en) { arrayRemoveIf(en.RelevanceKnifs, i => !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; this._EdgeGeometry = undefined; //清理掉(以便在被csg切割后得到edgegeom) let grooves = this.Grooves; /** * 如果要板件圆造型的挖穿跟不挖穿的效果一致 * 则把下段代码的判断,改为 if (grooves.length === 0) */ if (grooves.every(g => equaln$1(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 < MaxDrawGrooveCount) 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, UVGenerator: this.UCGenerator, depth: this.bevelEnabled ? this.thickness - 4 : this.thickness, bevelEnabled: this.bevelEnabled, bevelThickness: 2, bevelSize: 2, bevelOffset: -2, bevelSegments: 1, }; shape.DisplayAccuracy = this._DisplayAccuracy; let geo = new ExtrudeGeometry(shape.Shape, extrudeSettings); geo.applyMatrix4(contour.OCSNoClone); if (this.bevelEnabled) geo.translate(0, 0, 2); this.UpdateUV(geo, contour.OCSNoClone); //板在某些时候需要布尔运算(二维刀路) this._MeshGeometry = this.UpdateMeshGeom(geo); this._MeshGeometry["IsMesh"] = true; let mtl = this.Material?.Object ?? this.Db?.DefaultMaterial; if (mtl?.UseWorldUV) { //类型转换 if (this._MeshGeometry instanceof Geometry) this._MeshGeometry = new BufferGeometry().fromGeometry(this._MeshGeometry); let gen = new GenUVForWorld(); gen.GenGeoUV(this._MeshGeometry, mtl.Material); } //edge geometry if (grooves.length < MaxDrawGrooveCount && !this._EdgeGeometry) //这个代码保证线框和概念对齐 { let coords = FastExtrudeEdgeGeometryOfShape(shape.Shape, 0, this.thickness, 12, true); let edgeGeo = new BufferGeometry(); edgeGeo.setAttribute('position', new Float32BufferAttribute(coords, 3)); edgeGeo.applyMatrix4(contour.OCSNoClone); this._EdgeGeometry = edgeGeo; } return this._MeshGeometry; } let builder = new ExtrudeGeometryBuilder(this); this._MeshGeometry = builder.MeshGeometry; this._MeshGeometry = this.UpdateMeshGeom(this._MeshGeometry); if (grooves.length < MaxDrawGrooveCount && !this._EdgeGeometry) this._EdgeGeometry = builder.EdgeGeometry; this.UpdateUV(null, null); this.GenWorldUV(this._MeshGeometry); return this._MeshGeometry; } //子类重载,用于二次计算几何体(例如二维刀路布尔运算) UpdateMeshGeom(geo) { return geo; } get Has2DPath() { return false; } get HasSideModel() { return false; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this.CalcRelevanceGroove(); if (this._EdgeGeometry) return this._EdgeGeometry; //这里我们超过100就用这个,为了性能 和MaxDrawGrooveCount不一致 if (this.grooves.length > 100 || (!this.Has2DPath && !this.HasSideModel && (this.grooves.length === 0 || this.grooves.every(g => equaln$1(g.thickness, this.thickness))))) { let coords = FastExtrudeEdgeGeometry(this, this.DrawColorIndex, 12, 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) { let mtl = this.Material?.Object ?? this.Db?.DefaultMaterial; if (mtl?.IsFull) { if (geo) ScaleUV2(geo, ocs, this.width, this.height, isRev); else this.UpdateBufferGeometryUvs(isRev); } else { if (geo) ScaleUV(geo); } } GenWorldUV(geo) { let mtl = this.Material?.Object ?? this.Db?.DefaultMaterial; if (mtl?.UseWorldUV) { let gen = new GenUVForWorld; gen.GenGeoUV(geo, mtl.Material); } } 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(); } //del_exp2_start UpdateDrawGeometry() { this.csg = undefined; if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } //del_exp2_end ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (renderType === RenderType.Wireframe) { let l = obj; l.geometry = this.EdgeGeometry; l.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex, this.Freeze); } else if (renderType === RenderType.Conceptual) { return obj.add(new Mesh(this.MeshGeometry, this.GetConceptualMaterial()), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } else if (renderType === RenderType.ModelGroove) { obj.add( // new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(9, FrontSide, true, this.Freeze)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); obj.add(this.GetModelGroove()); //侧槽 obj.add(this.GetSideModelGroove()); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } 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); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); return obj.add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetPhysical2EdgeMaterial())); } return obj; } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex, this.Freeze); } else if (renderType === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = this.GetConceptualMaterial(); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide); } else if (renderType === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else { let mesh = obj; mesh.material = this.MeshMaterial; } } //获取概念视图材质 冻结状态使用257色号 半透明 GetConceptualMaterial() { let mtl; if (this.Freeze) mtl = ColorMaterial.GetBasicMaterialTransparent2(257, 0.5); else mtl = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex, FrontSide, true); return mtl; } GetModelGroove() { const grooves = []; for (const sp of this.__CacheSplitExtrudes ?? []) { grooves.push(...sp.grooves); } const group = new Group(); for (const groove of grooves) { const grooveClone = groove.Clone(); grooveClone.UpdateDrawGeometry(); const mtx = new Matrix4().premultiply(grooveClone.OCSNoClone).premultiply(this.OCSInv); const edgeGeo = grooveClone.EdgeGeometry; edgeGeo.applyMatrix4(mtx); const line = new LineSegments(edgeGeo, ColorMaterial.GetLineMaterial(1, this.Freeze)); const meshGeo = grooveClone.MeshGeometry; meshGeo.applyMatrix4(mtx); const mesh = new Mesh(meshGeo, ColorMaterial.GetBasicMaterialTransparent2(1, 0.6)); group.add(mesh, line); } return group; } GetSideModelGroove() { let board = this; const AllSideModelGrooveMap = board.AllSideModelGrooveMap; const group = new Group(); if (AllSideModelGrooveMap.size) { let con = GetBoardContour(board); if (!con) return group; let inverseZ = con.Area2 < 0; let cus = con.Explode(); const mirrorMtxZ = MakeMirrorMtx(ZAxis); for (let [index, soilds] of AllSideModelGrooveMap) { let cu = cus[index]; if (!cu) continue; let mt4 = GetSideCuFaceMtx(cus[index], inverseZ); for (let soild of soilds) { const edgeGeo = soild.EdgeGeometry; const line = new LineSegments(edgeGeo, ColorMaterial.GetLineMaterial(1, this.Freeze)); line.applyMatrix4(mirrorMtxZ); line.applyMatrix4(mt4.clone().multiply(soild.OCS)); const meshGeo = soild.MeshGeometry; const mesh = new Mesh(meshGeo, ColorMaterial.GetBasicMaterialTransparent2(1, 0.6)); mesh.applyMatrix4(mirrorMtxZ); mesh.applyMatrix4(mt4.clone().multiply(soild.OCS)); group.add(mesh, line); } } } return group; } 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); } } if (ver > 3) { this._DisplayAccuracy = file.Read(); } } WriteFileOnly(file) { file.Write(4); 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); file.Write(this._DisplayAccuracy); } //对象从文件中读取数据,初始化自身 _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); let ExtrudeHole = class ExtrudeHole extends Hole { constructor() { super(...arguments); this._DisplayAccuracy = 0; this._contourCurve = new Polyline(); this._knifeRadius = 3; this._GoodsId = ""; this._GoodsSn = ""; this.isHole = true; this.isThrough = false; } get KnifeRadius() { return this._knifeRadius; } set KnifeRadius(v) { if (!equaln$1(v, this._knifeRadius)) { this.WriteAllObjectRecord(); this._knifeRadius = v; } } get GoodsId() { return this._GoodsId; } set GoodsId(value) { if (this._GoodsId !== value) { this.WriteAllObjectRecord(); this._GoodsId = value; } } get GoodsSn() { return this._GoodsSn; } set GoodsSn(value) { if (this._GoodsSn !== value) { this.WriteAllObjectRecord(); this._GoodsSn = value; } } 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$1(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: if (this._contourCurve instanceof Circle) return this.GetGripPoints(); 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 DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } 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.OCSNoClone); 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, UVGenerator: worldUVGenerator, }; if (this.ContourCurve instanceof Circle || this.ContourCurve instanceof Polyline) { this.ContourCurve.DisplayAccuracy = this._DisplayAccuracy; } let geo = new ExtrudeGeometry(this.ContourCurve.Shape, extrudeSettings); geo.applyMatrix4(this._contourCurve.OCSNoClone); let mtl = this.Material?.Object ?? this.Db?.DefaultMaterial; if (mtl?.UseWorldUV) { let bgeo = new BufferGeometry().fromGeometry(geo); let gen = new GenUVForWorld(); gen.GenGeoUV(bgeo, mtl.Material); return bgeo; } else ScaleUV(geo); 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.OCSNoClone); }); 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.GetBulgeAt(Math.floor(param)) === 0) { let der = this.ContourCurve.GetFirstDeriv(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.OCSNoClone); 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.DrawColorIndex)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } else if (renderType === RenderType.Physical2) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); return new Object3D().add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetPhysical2EdgeMaterial())); } 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(...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.DrawColorIndex); } else if (renderType === RenderType.Print) { obj.add(...this.GetPrintObject3D()); } 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); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); return obj.add(mesh, new LineSegments(this.EdgeGeometry, ColorMaterial.GetPhysical2EdgeMaterial())); } else if (renderType === RenderType.Conceptual) { obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; return obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide)), 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.DrawColorIndex); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let mesh = obj.children[0]; let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; mesh.material = ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide); } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else if (renderType !== RenderType.Jig && renderType !== RenderType.Print) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } } ClearDraw() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; return super.ClearDraw(); } 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(); if (ver > 3) this.type = file.Read(); else this.type = GangDrillType.Ljg; //回退版本5弃用 if (ver > 4) { let count = file.Read(); for (let i = 0; i < count; i++) file.ReadSoftObjectId(); } if (ver > 5) { this._GoodsId = file.Read(); this._GoodsSn = file.Read(); } if (ver > 6) { this._DisplayAccuracy = file.Read(); } this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(7); file.WriteObject(this._contourCurve); file.Write(this._knifeRadius); file.Write(this.isHole); file.Write(this.isThrough); file.Write(this.type); //ver = 5 弃用 file.Write(0); //ver = 6 file.Write(this._GoodsId); file.Write(this._GoodsSn); //ver = 7 file.Write(this._DisplayAccuracy); } }; __decorate([ AutoRecord ], ExtrudeHole.prototype, "isHole", void 0); __decorate([ AutoRecord ], ExtrudeHole.prototype, "isThrough", void 0); ExtrudeHole = __decorate([ Factory ], ExtrudeHole); let GroupRecord = class GroupRecord extends SymbolTableRecord { constructor() { super(); this.Entitys = new Proxy([], { set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) { if (this.WriteAllObjectRecord()) { if (value instanceof ObjectId && value.Object instanceof Entity) { if (!this.Id) console.warn("请先添加到Database后在进行操作!"); else value.Object.GroupId = this.Id; } } } return Reflect.set(target, key, value, receiver); }, get: (target, key, receiver) => { if (key === ISPROXYKEY) return true; return Reflect.get(target, key, receiver); } }); } get Name() { return this.name; } set Name(name) { if (name !== this.name) { this.WriteAllObjectRecord(); this.name = name; } } Purge() { arrayRemoveIf(this.Entitys, id => !id || id.IsErase); } //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); let count = file.Read(); this.Entitys.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Entitys.push(id); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.Entitys.length); for (let id of this.Entitys) file.WriteObjectId(id); } }; __decorate([ AutoRecord ], GroupRecord.prototype, "Entitys", void 0); GroupRecord = __decorate([ Factory ], GroupRecord); //将嵌入的实体绘制对象添加到当前的绘制对象(由于内嵌的实体可能被重复引用) 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() { //这个代码可能是错误的. 当复合实体是子实体时,复合实体的矩阵和世界坐标垂直,但是父实体没有垂直时,此时的结果是错误的 所以注释掉这个代码 // if ( // (equaln(this._Matrix.elements[0], 1, 1e-5) || // equaln(this._Matrix.elements[1], 1, 1e-5) || // equaln(this._Matrix.elements[2], 1, 1e-5) // //3 // ) // && // (equaln(this._Matrix.elements[4], 1, 1e-5) || // equaln(this._Matrix.elements[5], 1, 1e-5) || // equaln(this._Matrix.elements[6], 1, 1e-5) // //7 // ) // && // (equaln(this._Matrix.elements[8], 1, 1e-5) || // equaln(this._Matrix.elements[9], 1, 1e-5) || // equaln(this._Matrix.elements[10], 1, 1e-5) // ) // ) // return this.GetBoundingBoxInMtx(this.OCSInv); // else { let box = new Box3Ext; for (let e of this.Entitys) box.union(e.BoundingBoxInOCS.applyMatrix4(e.OCSNoClone)); return box; } } //#region 绘制 // OnlyRenderType = true; //我们现在不需要这样,因为我们每个绘制类型的Object的子实体都有子实体的渲染类型(唯一的缺点可能是渲染速度变慢了?) /** * 初始化绘制的threejs实体,子类型重载该函数初始化绘制实体. */ Explode() { return this.Entitys.map(e => { if (!e.Db && this._db) e.SetDatabase(this._db); let cloneE = e.Clone(false); if (!(cloneE instanceof CompositeEntity_1)) cloneE.Material = e.Material; return cloneE.ApplyMatrix(this.OCSNoClone); }); } 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); if (renderType === RenderType.ModelGroove) return; for (let e of this.Entitys) { e.IsEmbedEntity = true; e.ParentEntity = this.ParentEntity || this; // //内嵌实体在某些时候可能被清理,修复它 // if (e.DrawObject.children.length === 0) // e.ClearDraw(); let rtype = renderType; if (renderType === RenderType.Print && e.IsOnlyRender) rtype += 100; let o = e.GetDrawObjectFromRenderType(rtype); if (o) { o.traverse(obj => obj.userData = {}); AddEntityDrawObject(obj, e, rtype); } } } 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) { if (!e.Db) e.SetDatabase(this.Db); e.Material = id; } } get Layer() { return super.Layer; } set Layer(id) { super.Layer = id; for (let e of this.Entitys) { if (!e.Db) e.SetDatabase(this.Db); e.Layer = id; } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; this.WriteAllObjectRecord(); this.Traverse(e => { if (e === this) return; e.SetAllMaterialAtSlot(mtl); }); this.Update(UpdateDraw.Material); } GetMtlLockedStatus() { let partMtlLocked = false; let allMtlLocked = true; this.Traverse(e => { if (e === this) return; const res = e.GetMtlLockedStatus(); if (res.partMtlLocked) { partMtlLocked = true; if (!res.allMtlLocked) allMtlLocked = false; } else allMtlLocked = false; }); return { partMtlLocked, allMtlLocked, }; } GetPhyMtlRecords(containErased = false) { const materials = []; this.Traverse(e => { if (e === this) return; const res = e.GetPhyMtlRecords(containErased); if (res.length) arrayPushArray(materials, res); }); return materials; } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.ModelGroove) return; this.Traverse(e => { if (e === this) return; let o = e.GetDrawObjectFromRenderType(renderType); if (o) e.UpdateDrawObjectMaterial(renderType, o); }); 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, frustum) { let pts = []; for (let e of this.Entitys) arrayPushArray(pts, e.Clone().ApplyMatrix(this.OCSNoClone).GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum)); 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]; ent.IsEmbedEntity = true; ent.ParentEntity = this; 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) { let v = 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); } if (v > 1) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); super.WriteFile(file); file.Write(this.Entitys.length); for (let e of this.Entitys) file.WriteObject(e); // ver2 file.WriteBool(this._LockMaterial); } }; __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(...arguments); this.HardwareOption = { ...DefaultCompositeMetalsOption }; /** * 备注 */ this.DataList = []; this.RelevanceBoards = []; this.RelevanceHardware = []; //当这个实体为复合板时,关联五金的信息 this.RelevanceHandle = []; //关联拉手 } /** * * @param [checkIsHole=false] true:只获取是孔的实体 false:返回所有实体 * @param [checkFilter] 过滤函数 * @returns */ GetAllEntity(checkIsHole = false, checkFilter) { let holes = []; for (let e of this.Entitys) { if (e instanceof HardwareCompositeEntity_1) { if (!checkIsHole || e.HardwareOption.isHole) holes.push(...e.GetAllEntity(checkIsHole, checkFilter).map(h => h.ApplyMatrix(this.OCSNoClone))); } else { if (!checkFilter || checkFilter(e)) holes.push(e.Clone().ApplyMatrix(this.OCSNoClone)); } } return holes; } ApplyMirrorMatrix(m) { super.ApplyMirrorMatrix(m); //"左","右"互换 if (this.HardwareOption.model.includes("左")) this.HardwareOption.model = this.HardwareOption.model.replace("左", "右"); else if (this.HardwareOption.model.includes("右")) this.HardwareOption.model = this.HardwareOption.model.replace("右", "左"); return this; } _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 = [ file.Read(), file.Read() ]; this.DataList.push(d); } if (v > 1) this.HardwareOption.isHole = file.Read(); if (v > 2) { let count = file.Read(); this.RelevanceBoards.length = 0; for (let i = 0; i < count; i++) this.RelevanceBoards.push(file.ReadSoftObjectId()); } if (v > 3) { let count = file.Read(); this.RelevanceHardware.length = 0; for (let i = 0; i < count; i++) this.RelevanceHardware.push(file.ReadSoftObjectId()); } if (v > 4) { let count = file.Read(); this.RelevanceHandle.length = 0; for (let i = 0; i < count; i++) { let objId = file.ReadObjectId(); if (objId) this.RelevanceHandle.push(objId); } } if (v > 5) { this.HardwareOption.goodsId = file.Read(); this.HardwareOption.goodsSn = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(6); 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); file.Write(this.RelevanceHardware.length); for (let id of this.RelevanceHardware) file.WriteSoftObjectId(id); file.Write(this.RelevanceHandle.length); for (let id of this.RelevanceHandle) file.WriteSoftObjectId(id); file.Write(this.HardwareOption.goodsId); file.Write(this.HardwareOption.goodsSn); } }; __decorate([ AutoRecordObject ], HardwareCompositeEntity.prototype, "HardwareOption", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "DataList", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "RelevanceBoards", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "RelevanceHardware", void 0); __decorate([ AutoRecord ], HardwareCompositeEntity.prototype, "RelevanceHandle", void 0); HardwareCompositeEntity = HardwareCompositeEntity_1 = __decorate([ Factory ], HardwareCompositeEntity); let instanceMap = new Map(); /** * 构造单例类的静态类. * # Example: * class A extends Singleton(){}; * //获得单例 * let a = A.GetInstance(); */ class Singleton { constructor() { } //ref: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__; } } const x = new Vector3; const y = new Vector3; let tempPolyline; /** * 转换成多段线点表(pts bul) * @param cu * @param [isOutline=true] 如果是外部轮廓,则返回逆时针点表 * @returns pts buls */ function ConverToPtsBul(cu, isOutline = true) { let ptsBuls; if (cu instanceof Circle) { let pl = ConverCircleToPolyline(cu); ptsBuls = pl.PtsBuls; } else ptsBuls = cu.PtsBuls; let ocs = cu.OCSNoClone; //判断是不是被镜像了 x.setFromMatrixColumn(ocs, 0); y.setFromMatrixColumn(ocs, 1); let z1 = x.cross(y); let isMirror = !equaln$1(z1.z, 1, 1e-3); for (let i = 0; i < ptsBuls.pts.length; i++) { Vector2ApplyMatrix4(ocs, ptsBuls.pts[i]); if (isMirror) ptsBuls.buls[i] *= -1; } if (isOutline && cu.IsClose) { tempPolyline = tempPolyline || new Polyline; tempPolyline.LineData.length = 0; for (let i = 0; i < ptsBuls.pts.length; i++) tempPolyline.LineData.push({ pt: ptsBuls.pts[i], bul: ptsBuls.buls[i] }); if (cu.Area2 < 0) { tempPolyline.Reverse(); ptsBuls = tempPolyline.PtsBuls; } tempPolyline.LineData.length = 0; } return ptsBuls; } //转换成多段线点表(pts bul) function ConverArcToPtsBul(arc, hasEnd = false) { let result = { pts: [], buls: [] }; let bul = arc.Bul; result.pts.push(arc.StartPoint); result.buls.push(bul); if (hasEnd) { result.pts.push(arc.EndPoint); result.buls.push(0); } return result; } /** * 优化走刀路径,连接偏移后的曲线数组 * @param offsetCus 偏移后的曲线组 * @param originShape 原始走刀形状 * @param rad 刀半径 * @returns tool path */ function OptimizeToolPath(offsetCus, originShape, rad) { //去掉最外轮廓 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 ((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)); //洞的外圈走一刀 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 (plList[i].TempData?.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 j = 0; j < cuPtsBul.pts.length; j++) { //坐标系对齐 let p = cuPtsBul.pts[j]; p.copy(AsVector2(AsVector3(p).applyMatrix4(alMat))); firstPl.LineData.push({ pt: p, bul: cuPtsBul.buls[j] }); } } } result.push(firstPl); for (let pl of result) pl.RemoveRepeatPos(1e-3); return result; } /** * 设定走刀路径起始点 * 为了统一刀路起点,最外轮廓左左点为起始点,其余轮廓以最接近最外轮廓起始点的点左起始点 * @param plList */ function ChangePlListStartPt(plList) { let firstPl = plList[0]; if (firstPl.IsClose) { let minP = undefined; let compare = ComparePointFnGenerate("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) { let r = RectOffset(cu, rectInfo, Math.abs(dist)); return r ? [r] : []; } return cu.GetFeedingToolPath(dist).filter(c => !equaln$1(c.Length, 0, 1e-5)); } else return cu.GetOffsetCurves(dist); } /** 获得曲线内偏移方向*/ function GetCurveToInDir(cu) { return cu.IsClockWise ? 1 : -1; } /**矩形偏移,正为内偏移 */ function RectOffset(rect, rectInfo, dist) { if (!rectInfo || equaln$1(dist, 0)) return; let box = rectInfo.box; let size = rectInfo.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$1(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(rectInfo.OCS); } else if (equaln$1(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(rectInfo.OCS); } else { min.add(new Vector3(dist, dist)); max.add(new Vector3(-dist, -dist)); return new Polyline().RectangleFrom2Pt(min, max).ApplyMatrix(rectInfo.OCS); } } /** *计算走刀工具类 */ class FeedingToolPath extends Singleton { /** * 处理形状,内偏移 * @param shape 造型Shape * @param knifRadius 刀半径/偏移距离 * @param [isOut=true] 是否是最外轮廓,如果是,洞需要外偏移一个刀半径,多段线偏移保留不闭合轮廓 */ HandleShape(shape, knifRadius, isOut, redundancyKnif = 0) { 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) { offsetDist += knifRadius * 2 - redundancyKnif; } 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 && offsetDist > knifRadius) { const rectMinLengthHalf = Math.min(rectInfo.size.x, rectInfo.size.y) / 2; //如果最后一个矩形最小边区间一半小于刀半径减去冗余值的一半,则偏移到中心处切一次 if (!equaln$1(offsetDist, rectMinLengthHalf, 1e-5) && offsetDist - rectMinLengthHalf - 1e-5 < knifRadius - redundancyKnif / 2) { retCus.push(...GetOffsetCurves(outline, rectMinLengthHalf * dir, rectInfo)); } } if (retCus.length === 0) break; //是否和孤岛相交 let isInt = false; for (let c of retCus) { if (holes.length > 0) { isInt = holes.some(h => { let ipts = h.Curve.IntersectWith2(c, 0); return ipts.length > 0 || h.ContainerCurve(c, false, ipts); }); 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))); //testcode // let reg = new Region(shapeMg).Clone(); // let reg2 = new Region(holesMg).Clone(); // TestDraw(reg, 1); // TestDraw(reg2, 2); shapeMg.BoolOper(holesMg, BoolOpeartionType.Subtract); // let reg3 = new Region(shapeMg).Clone(); // TestDraw(reg3, 3); 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 = []; //内偏(走刀方式) let outlineOffsetCusOfTool = GetOffsetCurves(outline, dir * knifRadius).filter(c => c.IsClose); let maxArea = Math.max(...(outlineOffsetCusOfTool.map(c => c.Area))); 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 holeOffsetCurve = holeOffsetCus[i]; //网洞走刀曲线 for (let j = 0; j < outlineOffsetCusOfTool.length; j++) { let outlineCurveOffsetInside = outlineOffsetCusOfTool[j]; if (h.Curve.IntersectWith(outlineCurveOffsetInside, 0).length > 0) { if (!(equalCurve(holeOffsetCurve, outlineCurveOffsetInside) || isTargetCurInOrOnSourceCur(outlineCurveOffsetInside, h.Curve))) { isVail = false; break; } else if (isTargetCurInOrOnSourceCur(h.Curve, outlineCurveOffsetInside)) { offsetCus.push(outlineCurveOffsetInside); isVail = false; break; } } else if (holeOffsetCurve.Area > maxArea) { isVail = false; break; } } } if (isVail) vailHoles.push(h); } offsetCus.push(...vailHoles.map(h => h.Curve)); return offsetCus; } /**用于测试走刀路径 */ TestCalcPath(br, isCd = false, rk = 0) { let modelings = br.BoardModeling; let { modeling } = GetModelingFromCustomDrill(br); modelings.push(...modeling); if (isCd && HostApplicationServices.chaidanOption.useDefaultRad) modelings.forEach(m => m.knifeRadius = HostApplicationServices.chaidanOption.radius); if (isCd) arrayRemoveIf(modelings, m => { let c = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && c instanceof Circle && c.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) return true; return false; }); return this.CalcPath(modelings, br, rk); } /** * 计算走刀路径 */ CalcPath(modelings, br, rk = 0) { let cus = []; for (let m of modelings) { cus.push(...this.GetModelFeedPath(br, m, rk)); } return cus; } //获取造型走刀 GetModelFeedPath(br, modeling, redundancyKnif = 0) { const brThickness = br.Thickness; let cus = []; //返回走刀路径 let { shape, thickness, knifeRadius, addLen, addWidth, addDepth } = modeling; 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$1(outline.Radius, modeling.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, true, redundancyKnif); if (offsetCus.length > 1) cus.push(...OptimizeToolPath(offsetCus, shape, knifeRadius)); } return cus; } //获取侧面造型走刀 GetSideModelFeedPath(solid, faceContour, redundancyKnif = 0) { let cus = []; //返回走刀路径 let shape = solid.Shape; let thickness = solid.Thickness; let knifeRadius = solid.KnifeRadius; let addLen = solid.GroovesAddLength; let addDepth = solid.GroovesAddDepth; let addWidth = solid.GroovesAddWidth; if (!knifeRadius) knifeRadius = 3; if (addDepth) thickness += addDepth; if (thickness < 1e-5) return cus; shape = shape.Clone().ApplyMatrix(solid.OCSNoClone); shape.Z0(); this.GrooveAddSize(shape, addLen, addWidth); this.HandleThoughGroove(faceContour, shape, knifeRadius); //造型半径和刀半径相等,返回重合点的线 let outline = shape.Outline.Curve; if (outline instanceof Circle && equaln$1(outline.Radius, knifeRadius)) return [new Polyline([{ pt: AsVector2(outline.Center), bul: 0 }, { pt: AsVector2(outline.Center), bul: 0 }])]; // { // todo 全深槽 // } let offsetCus = this.HandleShape(shape, knifeRadius, true, redundancyKnif); 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); //addWidth可能为undefined 导致box计算错误 let length = addLen ?? 0; let width = addWidth ?? 0; //若是矩形,应用槽加长 if (length != 0 || width != 0) { let rectInfo = IsRect(shape.Outline.Curve); if (rectInfo) { let box = rectInfo.box; let size = rectInfo.size; if (size.x > size.y) { box.max.add(new Vector3(length / 2, width / 2)); box.min.add(new Vector3(-length / 2, -width / 2)); } else { box.max.add(new Vector3(width / 2, length / 2)); box.min.add(new Vector3(-width / 2, -length / 2)); } let pl = new Polyline().RectangleFrom2Pt(box.min, box.max).ApplyMatrix(rectInfo.OCS); shape.Outline = Contour.CreateContour(pl); } } } /** * 获取所有的轮廓 * @param cus * @param retCus 不能组成轮廓的线被添加到这里 * @returns */ 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 (HostApplicationServices.chaidanOption.useDefaultRad) modelings[i].knifeRadius = HostApplicationServices.chaidanOption.radius; let m = modelings[i]; let cu = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) continue; let cus = this.GetModelFeedPath(br, m); if (cus.length === 0) errorIndexs.push(i); } return errorIndexs; } //检查侧面造型 CheckSideModeling(br) { let allSideModelGrooveMap = br.AllSideModelGrooveMap; if (!allSideModelGrooveMap?.size) return []; let errorSideModel = []; let faces = new ParseBoardSideFace(br); let con = GetBoardContour(br); if (!con) return errorSideModel; let inverseZ = con.Area2 < 0; let cus = con.Explode(); const mirrorMtxZ = MakeMirrorMtx(ZAxis); for (let [n, solids] of allSideModelGrooveMap) { let faceContour = faces.Faces[n].Region.ShapeManager.ShapeList[0].Outline.Curve; let mt4 = GetSideCuFaceMtx(cus[n], inverseZ); for (let solid of solids) { // 圆造型拆成孔 // let cu = solid.Shape.Outline.Curve; // if (!solid.Shape.Holes.length && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) // continue; let paths = this.GetSideModelFeedPath(solid, faceContour); //走刀路径 if (!paths.length) { let s = solid.Clone(); s.ApplyMatrix(mirrorMtxZ); s.ApplyMatrix(mt4); s.ApplyMatrix(br.OCS); errorSideModel.push(s); } } } return errorSideModel; } CheckCustomHole(br) { let { modeling } = GetModelingFromCustomDrill(br); let errHoles = []; for (let m of modeling) { let cu = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) continue; if (HostApplicationServices.chaidanOption.useDefaultRad) m.knifeRadius = HostApplicationServices.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 ?? c).Midpoint; curveBak = undefined; let param = brCon.GetParamAtPoint(mp); let curveOnContour = false; if (brCon.ParamOnCurve(param)) { //#I7MYN9 在长的槽里面,我们防止它加长,短的槽就无所谓了 if (c.Length > 100) { let curve = brCon.GetCurveAtParam(param); curveOnContour = curve.PtOnCurve(c.GetPointAtParam(0.2), 0.1) && curve.PtOnCurve(c.GetPointAtParam(0.8), 0.1); } else curveOnContour = true; } if (curveOnContour) { 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 brInv = br.OCSInv; let originOutline = GetBoardContour(br); let outline = originOutline; let modeling = []; let sideModeling = []; const holes = []; let bbox = br.BoundingBoxInOCS; let holeBoxMap = new WeakMap(); if (br.IsArcBoard && br.DrillList.size) ParseArcBoardHoles(br, new Vector3); const { partialSplitValueCanTakesEffect, cancelHoleProcessing } = HostApplicationServices.chaidanOption; for (let [objetId, idss] of br.DrillList) { let linkBoard = objetId?.Object; if (cancelHoleProcessing && linkBoard) { const [L, W, H] = [linkBoard.BoardProcessOption.spliteHeight, linkBoard.BoardProcessOption.spliteWidth, linkBoard.BoardProcessOption.spliteThickness]; if ((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) continue; } for (let ids of idss) { for (let id of ids) { if (!id?.Object || id.Object.IsErase) continue; let hole = id.Object; if (hole instanceof ExtrudeHole && hole.isHole) { if (!(hole.ContourCurve instanceof Circle)) { hole["__CacheChaiDanName__"] = ids.Name; if (br.IsArcBoard) { let ocs = hole["__CacheBrOCS__"]; if (!ocs) continue; brInv = new Matrix4().getInverse(ocs); let holeBox = hole.GetBoundingBoxInMtx(brInv); holeBoxMap.set(hole, holeBox); if (hole.BoundingBox.clone().intersect(br.BoundingBox).isSolid(0.1)) holes.push(hole); } else { let holeBox = hole.GetBoundingBoxInMtx(brInv); holeBoxMap.set(hole, holeBox); if (holeBox.clone().intersect(bbox).isSolid(0.1)) holes.push(hole); } continue; } } hole["__CacheAddPos__"] = undefined; hole["__CacheBrOCS__"] = undefined; hole["__CacheContour__"] = undefined; } } } for (let hole of holes) { let box = holeBoxMap.get(hole); let max = box.max; let min = box.min; let dir; let shape = hole.Shape; let faceRegions; let faceRegionsBox; let addPos; let chaiDanName = hole["__CacheChaiDanName__"]; hole["__CacheChaiDanName__"] = undefined; if (br.IsArcBoard) { addPos = hole["__CacheAddPos__"]; faceRegions = hole["__CacheContour__"]; let ocs = hole["__CacheBrOCS__"]; hole["__CacheAddPos__"] = undefined; hole["__CacheBrOCS__"] = undefined; hole["__CacheContour__"] = undefined; if (!ocs || !addPos || !faceRegions?.length) return; else { brInv = new Matrix4().getInverse(ocs); normal = new Vector3().setFromMatrixColumn(ocs, 2); faceRegionsBox = new Box3(); for (let pl of faceRegions) faceRegionsBox.union(pl.BoundingBox); let boxSize = faceRegionsBox.getSize(new Vector3); let extrude = Board.CreateBoard(boxSize.y, boxSize.x, 1); outline = extrude.ContourCurve; } } let diff = brInv.clone().multiply(hole.OCS); shape.ApplyMatrix(diff); let thickness; if (isParallelTo(normal, hole.Normal)) { if (min.z > br.Thickness - 1e-6) continue; //在板件的世界,0.01的误差应该不能被看出来,所以我们允许0.01的容差(这样应该是没问题的) //也避免了一些二维转三维出现的缝隙排钻不能被拆解的问题 if (max.z >= br.Thickness - CanDrawHoleFuzz) { dir = FaceDirection.Front; shape.Position = shape.Position.setZ(min.z); thickness = br.Thickness - min.z; } else if (min.z < CanDrawHoleFuzz) { dir = FaceDirection.Back; thickness = max.z; } else continue; if (thickness > 1e-6 && isTargetCurInOrOnSourceCur(outline, shape.Outline.Curve.Clone().Z0())) { if (br.IsArcBoard) { //弧形板需要单独增加差值 shape.Position = shape.Position.add(addPos); if (br.SweepAngle) { let ocsInv = new Matrix4().getInverse(br.ArcBuild.OCS2RotateMtx); shape.ApplyMatrix(ocsInv); } } modeling.push({ shape, thickness, dir, knifeRadius: hole.KnifeRadius, addLen: 0, addDepth: 0, addWidth: 0, originEn: hole, }); } } else { if (min.z <= 0 || max.z >= br.Thickness) continue; let spt = hole.Position.applyMatrix4(brInv).setZ(0); // 有可能Z向量朝向轮廓内部 // if (outline.PtOnCurve(spt)) continue; let line = new Line(spt, hole.Position.add(hole.Normal.multiplyScalar(hole.Height)).applyMatrix4(brInv).setZ(0)); let pt = outline.IntersectWith(line, IntersectOption.ExtendNone, 1e-5)[0]; if (!pt) continue; let thickness = 0; for (let p of [line.StartPoint, line.EndPoint]) { if (outline.PtInCurve(p)) { thickness = p.distanceTo(pt); break; } } //漏网之鱼 过滤掉不在板内的排钻 :677计算的弧形板包围盒不准确导致 if (thickness < 1e-3) continue; let index = Math.floor(outline.GetParamAtPoint(pt)); let vec = line.GetFirstDeriv(0).normalize().multiplyScalar(thickness); shape.Position = shape.Position.add(vec); if (br.IsArcBoard) { //弧形板需要单独增加差值 shape.Position = shape.Position.add(addPos); pt = pt.add(addPos); if (br.SweepAngle) { let ocsInv = new Matrix4().getInverse(br.ArcBuild.OCS2RotateMtx); pt.applyMatrix4(ocsInv); shape.ApplyMatrix(ocsInv); } } if (br.IsArcBoard) { //侧面造型仅在多段线直线上 //因为侧面造型起点特殊性 要处理一下 index = Math.floor(originOutline.GetParamAtPoint(pt)); let cu = originOutline.GetCurveAtIndex(index); if (cu) shape.ApplyMatrix(new Matrix4().getInverse(GetSideCuFaceMtx(cu))); else { console.error("圆弧板非圆侧孔求交失败"); continue; } } else { let cu = outline.GetCurveAtIndex(index); shape.ApplyMatrix(new Matrix4().getInverse(GetSideCuFaceMtx(cu))); } sideModeling.push({ outline: ConverToPtsBul(shape.Outline.Curve, false), holes: shape.Holes.map((cu) => ConverToPtsBul(cu.Curve, false)), thickness, dir: index, knifeRadius: hole.KnifeRadius, addLen: 0, addDepth: 0, addWidth: 0, chaiDanName, modelType: ModelType.drill }); } } return { modeling, sideModeling }; } var ModelType; (function (ModelType) { ModelType[ModelType["frontBackModel"] = 0] = "frontBackModel"; ModelType[ModelType["sideModel"] = 1] = "sideModel"; ModelType[ModelType["drill"] = 2] = "drill"; ModelType[ModelType["sideHoleModel"] = 3] = "sideHoleModel"; })(ModelType || (ModelType = {})); var Production; (function (Production) { /** * 获取板件拆单数据 * @param {Board} br 板件 * @param {number} [redundancyKnif=0] 刀冗余 * @param {boolean} [calReserveEdge=true] 是否计算预留边 * @return {*} {(ISpliteOrderData | undefined)} */ function GetBoardSplitOrderData(br, redundancyKnif = 0, calReserveEdge = true) { let sealedData = GetSealedBoardContour(br, calReserveEdge); if (!sealedData) { ToasterShowEntityMsg({ msg: br.Name + " 轮廓错误,可能存在轮廓自交,请检查后重新拆单!(错误的板已经选中,您可以按住鼠标中键查看该板!)(使用FISC命令可以修复自交轮廓!)", timeout: 8000, intent: Intent.DANGER, ent: br.__OriginalEnt__ ?? br }); return undefined; } let { brContour: orgContour, sealedContour: sealedOutline } = sealedData; let offsetTanslation = sealedOutline.BoundingBox.min; sealedOutline.Position = sealedOutline.Position.sub(offsetTanslation); let sealedOutlinePtsBul = ConverToPtsBul(sealedOutline); //不分裂圆弧转点表 //外轮廓去掉最后的闭合点 sealedOutlinePtsBul.pts.pop(); sealedOutlinePtsBul.buls.pop(); let size = sealedOutline.BoundingBox.getSize(new Vector3); //不扣除封边的轮廓信息 let originOutlinePtsBul = ConverToPtsBul(orgContour); originOutlinePtsBul.pts.pop(); originOutlinePtsBul.buls.pop(); const curveBoardModeling = br.ArcBoardModeling; //正反面造型 自定义不规则排钻孔 let { modeling, sideModeling } = GetBoardModelingData(br, offsetTanslation, redundancyKnif, curveBoardModeling); //侧面造型 let { sideModel, sideHole } = GetBoardSideModelingData(br, true); sideModeling.push(...sideModel); let boardContour; if (HasSpiteSize(br)) boardContour = ConverToPtsBul(br.ContourCurve); //不分裂圆弧转点表 //每段封边信息 let { seals: perSealData, reservedEdges: perReservedEdgeData } = ParseSealData(sealedData, br.BoardProcessOption.color); const perBoardEdgeRemarkData = GetHighBoardEdgeRemark(br, sealedData.brCurves, true); let isReverse = orgContour.Area2 < 0; //因为传递给拆单软件的数据是逆时针,所以我们翻转它 if (isReverse) { perSealData.reverse(); perBoardEdgeRemarkData.reverse(); perReservedEdgeData.reverse(); //对应sealedOutlinePtsBul顺序 解析孔时翻转orgContour orgContour.Reverse(); } let holes = GetBoardHolesData(br, offsetTanslation, orgContour); holes.sideHoles.push(...sideHole); return { info: GetBoardInfo(br, size), originOutlin: originOutlinePtsBul, //拼错了 未扣封边的点表 outline: sealedOutlinePtsBul, //扣完封边的点表 sealing: perSealData, //每段曲线的封边信息 boardEdgeRemark: perBoardEdgeRemarkData, //每段曲线的板边备注信息 reservedEdge: perReservedEdgeData, //每段曲线的预留边信息 modeling, curveBoardModeling, holes, sideModeling, offsetTanslation, metalsData: GetBoardMetals(br), boardContour, modeling2D: Get2DModeing(br, offsetTanslation), modeling3D: Get3DModeing(br, offsetTanslation), isReverse //轮廓是否翻转 }; } Production.GetBoardSplitOrderData = GetBoardSplitOrderData; //生产那边需要一一对应的数据 function ParseSealData(sealData, defaultSealColor = "") { let seals = []; let reservedEdges = []; for (let i = 0; i < sealData.brCurves.length; i++) { let curve = sealData.brCurves[i]; let sealD = sealData.highSeals[i]; let reservedEdgeD = sealData.highReservedEdges[i]; if (curve instanceof Circle) { let seal2 = { length: curve.Length * 0.5, ...sealD, sealColor: sealD.sealColor ? sealD.sealColor : defaultSealColor }; //圆型板拆单时是分成两段圆弧处理 seals.push(seal2); seals.push({ ...seal2 }); reservedEdges.push(reservedEdgeD); reservedEdges.push({ ...reservedEdgeD }); return { seals, reservedEdges }; } else { if (curve instanceof Polyline) //多段线炸开 { for (let subC of curve.Explode()) { let seal2 = { length: subC.Length, ...sealD, sealColor: sealD.sealColor ? sealD.sealColor : defaultSealColor }; seals.push(seal2); reservedEdges.push(reservedEdgeD); } } else //直线 圆弧直接加 { let seal2 = { length: curve.Length, ...sealD, sealColor: sealD.sealColor ? sealD.sealColor : defaultSealColor }; seals.push(seal2); reservedEdges.push(reservedEdgeD); } } } return { seals, reservedEdges }; } Production.ParseSealData = ParseSealData; 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], [EBoardKeyList.Thick]: br.Thickness, 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; 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) => { let data = []; for (let m of ms) { let cu = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) continue; if (HostApplicationServices.chaidanOption.useDefaultRad) m.knifeRadius = HostApplicationServices.chaidanOption.radius; data.push({ outline: ConverToPtsBul(cu, false), holes: m.shape.Holes.map(h => ConverToPtsBul(h.Curve, false)), thickness: m.thickness + (m.addDepth ?? 0), dir: m.dir, knifeRadius: m.knifeRadius, }); } return data; }; let allModeling = GetModelingFromCustomDrill(br); let modeling = getModelings([...br.BoardModeling, ...allModeling.modeling]); let sideModeling = GetBoardSideModelingData(br); return { modeling, sideModeling }; } Production.GetOriginBoardModelingData = GetOriginBoardModelingData; /** * 计算板的造型走刀数据(包括自定义排钻的走刀 侧面走刀) * @param br * @param offsetTanslation */ function GetBoardModelingData(br, offsetTanslation, redundancyKnif = 0, curveBoardModeling) { const tool = FeedingToolPath.GetInstance(); const tMtx = MoveMatrix(offsetTanslation.clone().negate()); const getModelings = (ms) => { let data = []; for (let m of ms) { let cu = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) continue; if (HostApplicationServices.chaidanOption.useDefaultRad) m.knifeRadius = HostApplicationServices.chaidanOption.radius; let paths = tool.GetModelFeedPath(br, m, redundancyKnif); //走刀路径 paths.forEach(path => path.ApplyMatrix(tMtx)); //走刀的ptsbuls let feeding = paths.map((c) => ConverToPtsBul(c, false)); if (feeding.length > 0) data.push({ feeding, thickness: m.thickness + (m.addDepth ?? 0), dir: m.dir, knifeRadius: m.knifeRadius, origin: { outline: ConverToPtsBul(cu, false), holes: m.shape.Holes.map(h => ConverToPtsBul(h.Curve, 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, ...curveBoardModeling]).filter(f => f.feeding.length > 0); // 拆单值有效的板件勾选取消孔槽加工 const { partialSplitValueCanTakesEffect, cancelHoleProcessing } = HostApplicationServices.chaidanOption; const [L, W, H] = [br.BoardProcessOption.spliteHeight, br.BoardProcessOption.spliteWidth, br.BoardProcessOption.spliteThickness]; if (((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) && cancelHoleProcessing) { modeling.length = 0; allModeling.sideModeling.length = 0; } return { modeling, sideModeling: allModeling.sideModeling }; } Production.GetBoardModelingData = GetBoardModelingData; function GetBoardSideModelingData(br, toaster = false) { let sideModel = []; let sideHole = []; let allSideModelGrooveMap = br.AllSideModelGrooveMap; //跳过有拆单尺寸板件 避免拆单错误 if (!allSideModelGrooveMap.size || HasSpiteSize(br)) return { sideModel, sideHole }; const tool = FeedingToolPath.GetInstance(); let faces = new ParseBoardSideFace(br); for (let [num, solids] of allSideModelGrooveMap) { let faceContour = faces.Faces[num].Region.ShapeManager.ShapeList[0].Outline.Curve; for (let solid of solids) { let cu = solid.Shape.Outline.Curve.Clone().ApplyMatrix(solid.OCSNoClone); let modelType = ModelType.sideModel; // 圆造型拆成孔类型 if (!solid.Shape.Holes.length && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) { let mtx = br.OCSInv.multiply(faces.Faces[num].OCS); let position = cu.Position.clone().applyMatrix4(mtx); let endPt = cu.Position.clone().setZ(-solid.Thickness).applyMatrix4(mtx); /**拆单那边需要把侧孔 z 坐标转换为从上到下 */ InvertPosition(position, br.Thickness); InvertPosition(endPt, br.Thickness); // cu.ApplyMatrix(mtx); // TestDraw(cu); // TestDraw(new Point(position)); // TestDraw(new Point(endPt)); sideHole.push({ position, //排钻开始位置 endPt, //排钻结束的位置(在板的坐标系) radius: cu.Radius, //排钻半径 depth: solid.Thickness, //排钻的插入深度 face: num, //板在哪个边上 name: "", type: null }); continue; } let knifeRadius = solid.KnifeRadius; if (HostApplicationServices.chaidanOption.useDefaultRad) knifeRadius = HostApplicationServices.chaidanOption.radius; let paths = tool.GetSideModelFeedPath(solid, faceContour); //走刀路径 if (paths.length) { sideModel.push({ thickness: solid.Thickness + (solid.GroovesAddDepth ?? 0), dir: num, knifeRadius, outline: ConverToPtsBul(cu, false), holes: solid.Shape.Holes.map((cu) => ConverToPtsBul(cu.Curve.Clone().ApplyMatrix(solid.OCSNoClone), false)), addLen: solid.GroovesAddLength, addWidth: solid.GroovesAddWidth, addDepth: solid.GroovesAddDepth, chaiDanName: "", modelType, }); } else if (toaster) { Toaster({ message: "板件有侧面造型或者自定义排钻无法加工,请运行造型检测命令确认", timeout: 5000, intent: Intent.DANGER, key: "侧面造型加工错误" }); } } } return { sideModel, sideHole }; } Production.GetBoardSideModelingData = GetBoardSideModelingData; //是否有拆单尺寸 function HasSpiteSize(br) { let [spHeight, spWidth, spThickness] = [br.BoardProcessOption?.spliteHeight, br.BoardProcessOption?.spliteWidth, br.BoardProcessOption?.spliteThickness]; const isEffect = HostApplicationServices.chaidanOption.partialSplitValueCanTakesEffect; //局部拆单值 const hasSize = (isEffect ? (spHeight || spWidth || spThickness) : (spHeight && spWidth && spThickness)); return Boolean(hasSize); } Production.HasSpiteSize = HasSpiteSize; //获得拆单尺寸 function GetSpiteSize(br) { if (HasSpiteSize(br)) { let [spHeight, spWidth, spThickness] = [br.BoardProcessOption?.spliteHeight, br.BoardProcessOption?.spliteWidth, br.BoardProcessOption?.spliteThickness]; const param = { L: br.Height, W: br.Width, H: br.Thickness }; spHeight = spHeight || br.Height.toString(); spWidth = spWidth || br.Width.toString(); spThickness = spThickness || br.Thickness.toString(); const spliteHeight = safeEval(spHeight, param, "L"); const spliteWidth = safeEval(spWidth, param, "W"); const spliteThickness = safeEval(spThickness, param, "H"); if (spliteHeight && spliteWidth && spliteThickness) { return { spliteHeight, spliteWidth, spliteThickness }; } } } Production.GetSpiteSize = GetSpiteSize; //获得拆单轮廓(如果没有,那么将返回空,如果有,返回多段线) function GetSpliteOutlineBySpliteSize(br) { let size = GetSpiteSize(br); if (size) return new Polyline().Rectangle(size.spliteWidth, size.spliteHeight); return null; } Production.GetSpliteOutlineBySpliteSize = GetSpliteOutlineBySpliteSize; /**孔信息,侧孔的z 均为 从上到下距离 */ function GetBoardHolesData(br, offsetTanslation, sealedContour) { let data = { frontBackHoles: [], sideHoles: [] }; const { partialSplitValueCanTakesEffect, cancelHoleProcessing } = HostApplicationServices.chaidanOption; // 拆单值有效的板件勾选取消孔槽加工 const [L, W, H] = [br.BoardProcessOption.spliteHeight, br.BoardProcessOption.spliteWidth, br.BoardProcessOption.spliteThickness]; if (((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) && cancelHoleProcessing) { return data; } let brNormal = br.Normal; // 性能优化的解析板件网洞类 // new ParseBoardHoleData(br, offsetTanslation, sealedContour); //是弧形板件时解析排钻在路径的相对位置 if (br.IsArcBoard && br.DrillList.size) { offsetTanslation = ParseArcBoardHoles(br, offsetTanslation); } for (let [key, driss] of br.DrillList) { let linkBoard = key?.Object; if (cancelHoleProcessing && linkBoard) { const [L, W, H] = [linkBoard.BoardProcessOption.spliteHeight, linkBoard.BoardProcessOption.spliteWidth, linkBoard.BoardProcessOption.spliteThickness]; if ((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) { continue; } } 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, 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; let linkBoard = nail.MId?.Object; if (cancelHoleProcessing && linkBoard) { const [L, W, H] = [linkBoard.BoardProcessOption.spliteHeight, linkBoard.BoardProcessOption.spliteWidth, linkBoard.BoardProcessOption.spliteThickness]; if ((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) { continue; } } if (!isParallelTo(nail.Normal, brNormal, CanDrawHoleFuzz)) 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]; let face = !equalv3(nail.Normal, brNormal, CanDrawHoleFuzz) ? FaceDirection.Front : FaceDirection.Back; let depth = Math.min(z1, br.Thickness) - Math.max(z0, 0); if (Math.max(z0, 0) < Math.min(z1, br.Thickness) - 1e-6 && br.ContourCurve.PtInCurve(sp.setZ(0)) //层板钉中心点不在造型槽内 && modelings.every(m => { if (m.dir !== face && (depth + m.thickness) < br.Thickness) //不相交 return true; // if (m.shape.Holes.some(h => h.Curve.PtInCurve(sp))) return true; //层板钉在孤岛内 现实中应该不会有 return !m.shape.Outline.Curve.PtInCurve(sp); })) { data.frontBackHoles.push({ type: nail.Type, position: sp.sub(offsetTanslation), radius: nail.Radius, depth, face, name: '层板钉' }); } } for (let m of modelings) { let cu = m.shape.Outline.Curve; if (m.shape.Holes.length === 0 && cu instanceof Circle && cu.Radius < HostApplicationServices.chaidanOption.modeling2HoleRad + 1e-6) { let center = cu.Center.setZ(0).sub(offsetTanslation); data.frontBackHoles.push({ type: GangDrillType.Ljg, //设置为偏心轮导致我们分析大孔面方向和板的大孔面一致时错误,所以改成连接杆 position: center, radius: cu.Radius, depth: m.thickness, face: m.dir, name: GetGroupName(cu) }); } } return data; } Production.GetBoardHolesData = GetBoardHolesData; /**拆单那边需要把侧孔 z 坐标转换为从上到下 */ function InvertPosition(pos, thickness) { pos.z = thickness - pos.z; } function HoleInBoard(center, radius, outline, allowPxl, isYMJ = false) { let cir = new Circle(center, radius - SCALAR); if (isYMJ) { return outline.IntersectWith(cir, 0).length === 0 && outline.PtInCurve(center); } else { if (allowPxl || !HostApplicationServices.forceFilterPxl) return outline.IntersectWith(cir, 0).length > 1 || outline.PtInCurve(center); else return outline.IntersectWith(cir, 0).length === 0 && outline.PtInCurve(center); } } /**分析常规排钻 */ function ParseCylHoles(cyHole, br, offsetTanslation, data, outline) { let brInv = br.OCSInv; let brNormal = br.Normal; let outlineWidth = br.Width; let outlineHeight = br.Height; let faceRegions; let faceRegionsBox; let addPos; if (br.IsArcBoard) { addPos = cyHole["__CacheAddPos__"]; faceRegions = cyHole["__CacheContour__"]; let ocs = cyHole["__CacheBrOCS__"]; cyHole["__CacheAddPos__"] = undefined; cyHole["__CacheBrOCS__"] = undefined; cyHole["__CacheContour__"] = undefined; if (!ocs || !addPos || !faceRegions?.length) return; else { brInv = new Matrix4().getInverse(ocs); brNormal = new Vector3().setFromMatrixColumn(ocs, 2); faceRegionsBox = new Box3(); for (let pl of faceRegions) faceRegionsBox.union(pl.BoundingBox); let boxSize = faceRegionsBox.getSize(new Vector3); outlineWidth = boxSize.x; outlineHeight = boxSize.y; let extrude = Board.CreateBoard(boxSize.y, boxSize.x, 1); outline = extrude.ContourCurve; } } let brInvRo = new Matrix4().extractRotation(brInv); let position = cyHole.Position.applyMatrix4(brInv); let holes = data.frontBackHoles; let face; //孔面方向 let isPush = false; let endPt; let depth = cyHole.Height; let diffMat = brInv.clone().multiply(cyHole.OCSNoClone); let x = new Vector3().setFromMatrixColumn(diffMat, 0); let angle = angleTo(XAxis, x); let cyNormal = cyHole.Normal.applyMatrix4(brInvRo); let pos2 = position.clone().add(cyNormal.multiplyScalar(depth)); if (cyHole.Type === GangDrillType.Pxl || cyHole.Type === GangDrillType.WoodPXL) { if (isParallelTo(cyHole.Normal, brNormal, CanDrawHoleFuzz)) { if (!IsBetweenA2B(position.x, -cyHole.Radius, outlineWidth + cyHole.Radius, 1e-6) || !IsBetweenA2B(position.y, -cyHole.Radius, outlineHeight + cyHole.Radius, 1e-6) || !HoleInBoard(position.clone().setZ(0), cyHole.Radius, outline, cyHole.AllowPxl)) return; position.sub(offsetTanslation); //#region 求得真实的求交范围 let z0 = position.z; let z1 = pos2.z; if (z0 > z1) [z0, z1] = [z1, z0]; let i1 = Math.max(z0, 0); let i2 = Math.min(z1, br.Thickness); if (i2 - i1 < CanDrawHoleFuzz) return; //相交范围小于0.1 if (equaln$1(i1, 0, CanDrawHoleFuzz)) face = FaceDirection.Back; else if (equaln$1(i2, br.Thickness, CanDrawHoleFuzz)) face = FaceDirection.Front; else return; //不在正面 也不在反面 depth = i2 - i1; //真实的相交范围 //#endregion isPush = true; } } else if (cyHole.Type === GangDrillType.Ljg || cyHole.Type === GangDrillType.Wood || cyHole.SameSideHole) { if (isPerpendicularityTo(cyHole.Normal, brNormal, CanDrawHoleFuzz)) //侧孔 { let z = position.z; if (!IsBetweenA2B(z, -cyHole.Radius, br.Thickness + cyHole.Radius, 1e-6)) return; let sp = position.clone().setZ(0); //真实数据 let ep = position.clone().add(cyHole.Normal.multiplyScalar(cyHole.Height).applyMatrix4(brInvRo)).setZ(0); //真实数据 let testLine = new Line(sp, ep); let iPt; if (br.IsArcBoard) { if (faceRegions?.length) { for (let pl of faceRegions) { pl.UpdateOCSTo(new Matrix4().setPosition(faceRegionsBox.min)); let newPl = new Polyline(pl.LineData); iPt = newPl.IntersectWith(testLine, 0, CanDrawHoleFuzz)[0]; if (iPt) { outline = newPl; break; } } } else { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, cyHole] }, { msg: "侧孔与板无交点,无法加工该侧孔!" }], LogType.Warning); return; } } else iPt = outline.IntersectWith(testLine, 0, CanDrawHoleFuzz)[0]; if (!iPt) { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, cyHole] }, { msg: "侧孔与板无交点,无法加工该侧孔!" }], LogType.Warning); return; } position = iPt.clone().setZ(z); //排钻开始的位置 for (let p of [sp, ep]) { if (outline.PtInCurve(p)) { endPt = p.setZ(z); //排钻插入后的位置 break; } } if (!endPt) { //同侧面生成常规单头排钻 连接杆在Curve边缘时不提示错误 if (!cyHole.SameSideHole) InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, cyHole] }, { msg: "侧孔位置有问题,排钻不在板轮廓内!" }], LogType.Warning); return; } holes = data.sideHoles; face = Math.floor(outline.GetParamAtPoint(iPt)); //板在那个边上 isPush = true; depth = position.distanceTo(endPt); angle = undefined; InvertPosition(position, br.Thickness); InvertPosition(endPt, br.Thickness); } else if (cyHole.Type === GangDrillType.Wood) //木销 { if (!outline.PtInCurve(position.clone().setZ(0))) return; position.sub(offsetTanslation); //#region 求得真实的求交范围 let z0 = position.z; let z1 = pos2.z; if (z0 > z1) [z0, z1] = [z1, z0]; let i1 = Math.max(z0, 0); let i2 = Math.min(z1, br.Thickness); if (i2 - i1 < CanDrawHoleFuzz) return; //相交范围小于0.1 if (equaln$1(i1, 0, CanDrawHoleFuzz)) face = FaceDirection.Back; else if (equaln$1(i2, br.Thickness, CanDrawHoleFuzz)) face = FaceDirection.Front; else return; //不在正面 也不在反面 depth = i2 - i1; //真实的相交范围 //#endregion holes = data.frontBackHoles; isPush = true; } } else { if (isParallelTo(cyHole.Normal, brNormal, CanDrawHoleFuzz)) { if (!IsBetweenA2B(position.x, -cyHole.Radius, outlineWidth + cyHole.Radius, CanDrawHoleFuzz) || !IsBetweenA2B(position.y, -cyHole.Radius, outlineHeight + cyHole.Radius, CanDrawHoleFuzz) || !isIntersect2(0, br.Thickness, position.z, pos2.z, -CanDrawHoleFuzz) || !HoleInBoard(position.clone().setZ(0), cyHole.Radius, outline, cyHole.AllowPxl, true)) return; position.sub(offsetTanslation); holes = data.frontBackHoles; face = !equalv3(cyHole.Normal, brNormal, CanDrawHoleFuzz) ? FaceDirection.Front : FaceDirection.Back; isPush = true; } } if (isPush) { if (br.IsArcBoard) { //弧形板需要单独增加差值 position = position.add(addPos); endPt = endPt?.add(addPos); if (br.SweepAngle) { let ocsInv = new Matrix4().getInverse(br.ArcBuild.OCS2RotateMtx); position.applyMatrix4(ocsInv); endPt?.applyMatrix4(ocsInv); } } holes.push({ type: cyHole.Type, position, //排钻开始位置 endPt, //排钻结束的位置(在板的坐标系) radius: cyHole.Radius, //排钻半径 depth, //排钻的插入深度 face, //板在哪个边上 angle, name: GetGroupName(cyHole), goodsId: cyHole.GoodsId, //商品编号 }); } } /**分析自定义圆柱排钻 * * isRelativeHardware 关联的五金使用此类型误差 * CanDrawHoleFuzz)//区间交集必须大于CanDrawHoleFuzz * CanDrawHoleFuzz)//区间交集必须大于CanDrawHoleFuzz * */ function ParseExtrudeHoles(d, br, offsetTanslation, data, outline, isCheckGroove = false, isRelativeHardware = false) { if (!d.isHole) return; let brInv = br.OCSInv; let brNormal = br.Normal; let cir = d.ContourCurve; let outlineWidth = br.Width; let outlineHeight = br.Height; let addPos; let faceRegions; let faceRegionsBox; if (br.IsArcBoard) { addPos = d["__CacheAddPos__"]; faceRegions = d["__CacheContour__"]; let ocs = d["__CacheBrOCS__"]; d["__CacheAddPos__"] = undefined; d["__CacheBrOCS__"] = undefined; d["__CacheContour__"] = undefined; if (!ocs || !addPos || !faceRegions?.length) return; else { brInv = new Matrix4().getInverse(ocs); brNormal = new Vector3().setFromMatrixColumn(ocs, 2); faceRegionsBox = new Box3(); for (let pl of faceRegions) faceRegionsBox.union(pl.BoundingBox); let boxSize = faceRegionsBox.getSize(new Vector3); outlineWidth = boxSize.x; outlineHeight = boxSize.y; let extrude = Board.CreateBoard(boxSize.y, boxSize.x, 1); outline = extrude.ContourCurve; } } if (cir instanceof Circle) { let diffMtx = brInv.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$1(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); //检测排钻中心孔位与板轮廓outline交集关系时,不考虑offsetTanslation的差值 因为传进来的outline都不是封边轮廓 let p = sp.clone().setZ(0); //区间没有交集 if (!(Math.max(z0, 0) < (Math.min(z1, br.Thickness) - CanDrawHoleFuzz))) return; if (!(z0 < CanDrawHoleFuzz || z1 > (br.Thickness - CanDrawHoleFuzz))) //禁止在中间挖洞 { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, d] }, { msg: "的孔嵌在板中间,无法加工,已经跳过!" }], LogType.Warning); return; } if (!(outline.PtInCurve(p))) //在轮廓内 { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, d] }, { msg: "的孔不在板轮廓内,无法加工,已经跳过!" }], LogType.Warning); return; } if (groovesOutlines.some(g => g.PtInCurve(p))) //在洞内 { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, d] }, { msg: "的孔在造型内,无法加工,已经跳过!" }], LogType.Warning); return; } p.sub(offsetTanslation); let depth = z0 < CanDrawHoleFuzz ? z1 : br.Thickness - z0; let angle = angleTo(XAxis, x); if (equaln$1(angle, Math.PI)) angle = 0; if (depth > CanDrawHoleFuzz) { let position = z0 < CanDrawHoleFuzz ? p : p.setZ(br.Thickness); if (br.IsArcBoard) { //弧形板需要单独增加差值 position = position.add(addPos); if (br.SweepAngle) { let ocsInv = new Matrix4().getInverse(br.ArcBuild.OCS2RotateMtx); position.applyMatrix4(ocsInv); } } data.frontBackHoles.push({ type: d.isThrough ? GangDrillType.TK : (isRelativeHardware ? GangDrillType.Ljg : d.Type), position, radius: cir.Radius, depth, face: z0 < CanDrawHoleFuzz ? FaceDirection.Back : FaceDirection.Front, angle: angle, name: GetGroupName(d) }); } } 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(outlineWidth, maxX) + (isRelativeHardware ? -CanDrawHoleFuzz : 1e-6)) //区间交集必须大于CanDrawHoleFuzz && Math.max(minY, 0) < (Math.min(outlineHeight, maxY) + (isRelativeHardware ? -CanDrawHoleFuzz : 1e-6)) //区间交集必须大于CanDrawHoleFuzz ) { sp.setZ(0); ep.setZ(0); let line = new Line(sp, ep); let pt = outline.IntersectWith(line, 0, 1e-5)[0]; if (!pt) { InteractionLog([{ msg: "警告:" }, { msg: `板件${br.Name}`, entity: [br, d] }, { msg: `的${isRelativeHardware ? "五金" : "排钻"}嵌在板件内部,已经跳过!` }], LogType.Warning); 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 (!equalv2(p, position) && outline.PtInCurve(p)) { endPt = p.setZ(oldZ); break; } } if (!endPt) return; let depth = position.distanceTo(endPt); if (equaln$1(depth, 0, CanDrawHoleFuzz)) return; InvertPosition(position, br.Thickness); InvertPosition(endPt, br.Thickness); if (br.IsArcBoard) { //弧形板需要单独增加差值 position = position.add(addPos); endPt = endPt.add(addPos); if (br.SweepAngle) { let ocsInv = new Matrix4().getInverse(br.ArcBuild.OCS2RotateMtx); position.applyMatrix4(ocsInv); endPt.applyMatrix4(ocsInv); } } data.sideHoles.push({ type: GangDrillType.Ljg, endPt, position, radius: cir.Radius, depth, face, name: GetGroupName(d) }); } } } } 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) { const accuracy = HostApplicationServices.chaidanOption.hardwareExpressionFormattingAccuracy; 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 ? FixedNotZero(actualVal, 2) : data.actualExpr; data.spec = ParseExpr(data.spec, accuracy, { L: size.x, W: size.y, H: size.z }); data.model = ParseExpr(data.model, accuracy, { L: size.x, W: size.y, H: size.z }); data.factory = ParseExpr(data.factory, accuracy, { L: size.x, W: size.y, H: size.z }); data.brand = ParseExpr(data.brand, accuracy, { 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; const accuracy = HostApplicationServices.chaidanOption.hardwareExpressionFormattingAccuracy; d.spec = ParseExpr(data.spec, accuracy, { 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$1(p.x, p.y), bul: data.buls[i] }))); if (isClose) pl.CloseMark = true; return pl; } Production.Data2Polyline = Data2Polyline; function Get2DModeing(br, offset) { let res = []; // 拆单值有效的板件勾选取消孔槽加工 const { partialSplitValueCanTakesEffect, cancelHoleProcessing } = HostApplicationServices.chaidanOption; const [L, W, H] = [br.BoardProcessOption.spliteHeight, br.BoardProcessOption.spliteWidth, br.BoardProcessOption.spliteThickness]; if (((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) && cancelHoleProcessing) { return res; } let tmtx = MoveMatrix(offset.clone().negate()); for (let m of br.Modeling2D) { let path = m.path.Clone().ApplyMatrix(tmtx); res.push({ path: ConverToPtsBul(path, false), dir: m.dir, items: m.items.map(item => ({ ...item })) }); } return res; } Production.Get2DModeing = Get2DModeing; function Get3DModeing(br, offset) { let res = []; // 拆单值有效的板件勾选取消孔槽加工 const { partialSplitValueCanTakesEffect, cancelHoleProcessing } = HostApplicationServices.chaidanOption; const [L, W, H] = [br.BoardProcessOption.spliteHeight, br.BoardProcessOption.spliteWidth, br.BoardProcessOption.spliteThickness]; if (((partialSplitValueCanTakesEffect && (L || W || H)) || (L && W && H)) && cancelHoleProcessing) { return 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$1(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 = ConverArcToPtsBul(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, redundancyKnif = 0) { 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 }, redundancyKnif); return paths.map((c) => ConverToPtsBul(c, false)); } Production.GetChaiDanFeedingPath = GetChaiDanFeedingPath; function GetGroupName(ent) { let group = ent.GroupId?.Object; if (!group || !(group instanceof GroupRecord)) return "未知(无拆单名称)"; return group.Name; } Production.GetGroupName = GetGroupName; })(Production || (Production = {})); function ParseArcBoardHoles(br, offsetTanslation) { const ArcBoardBuild = br.ArcBuild; ArcBoardBuild.ParseSweepCurves(); let cus = ArcBoardBuild.SweepCurves1; let ContourCurve = br.Shape.Outline.Curve.Clone().ApplyMatrix(ArcBoardBuild.OCS2RotateMtx); let brBoxSize = ContourCurve.BoundingBox.getSize(new Vector3); let currentLength = 0; if (br.SweepAngle) offsetTanslation = offsetTanslation.clone().applyMatrix4(ArcBoardBuild.OCS2RotateMtx.clone().setPosition(0, 0, 0)); for (let cu of cus) { if (currentLength > brBoxSize.x) break; if (cu instanceof Arc) { currentLength = currentLength + cu.Length; continue; } //直线所在坐标系 let cuBox = new Box3(new Vector3, new Vector3(cu.Length, brBoxSize.y, br.Thickness)); let an = angle(cu.EndPoint.clone().sub(cu.StartPoint)); let m = new Matrix4().makeRotationY(-an); let ocs = br.OCS; if (br.SweepAngle) ocs.multiply(ArcBoardBuild.Rotate2OCSMtx); ocs.multiply(m.setPosition(cu.StartPoint.clone().applyMatrix4(new Matrix4().makeRotationX(Math.PI / 2)))); //正面时翻转Z向量 if (br.SweepVisibleFace === FaceDirection.Front) ocs.setPosition(new Vector3(0, 0, -br.Thickness).applyMatrix4(ocs)); // 测试代码 // let pt = new Vector3().applyMatrix4(ocs); // let l1 = new Line(pt, pt.clone().add(new Vector3().setFromMatrixColumn(ocs, 0).normalize().multiplyScalar(100))); // let l2 = new Line(pt, pt.clone().add(new Vector3().setFromMatrixColumn(ocs, 1).normalize().multiplyScalar(100))); // let l3 = new Line(pt, pt.clone().add(new Vector3().setFromMatrixColumn(ocs, 2).normalize().multiplyScalar(100))); // TestDraw(new Point(pt)); // TestDraw(l1, 1); // TestDraw(l2, 3); // TestDraw(l3, 5); //裁剪 计算每个分段轮廓 let length = currentLength + cu.Length; let starKnifePls = new Polyline([{ pt: AsVector2({ x: currentLength, y: -1 }), bul: 0 }, { pt: AsVector2({ x: currentLength, y: 10000 }), bul: 0 }]); let endKnifePls = new Polyline([{ pt: AsVector2({ x: length, y: -1 }), bul: 0 }, { pt: AsVector2({ x: length, y: 10000 }), bul: 0 }]); //裁剪结果 let faceRegions = SplitPolyline(ContourCurve, [starKnifePls, endKnifePls]); faceRegions = faceRegions.filter((faceRegion) => { let x = faceRegion.BoundingBox.getCenter(new Vector3).x; return x > currentLength && x < length; }); 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["__CacheBrOCS__"]) continue; let b = d.BoundingBox.applyMatrix4(new Matrix4().getInverse(ocs)); if (cuBox.intersectsBox(b)) { d["__CacheBrOCS__"] = ocs; //记录分段路径的起点与原点差值 d["__CacheAddPos__"] = new Vector3(currentLength); d["__CacheContour__"] = faceRegions; } } } } currentLength = length; } return offsetTanslation; } /** * 分析曲线的上下左右位置的线 * @param curves */ function ParseEdgeSealDir(curves) { let boxAll = new Box3; let fb = new Flatbush(curves.length); for (let c of curves) { let box = c.BoundingBox; boxAll.union(box); fb.add(box.min.x, box.min.y, box.max.x, box.max.y); } fb.finish(); let leftids = fb.search(boxAll.min.x - 1, boxAll.min.y - 1, boxAll.min.x + 1, boxAll.max.y + 1); let rightids = fb.search(boxAll.max.x - 1, boxAll.min.y - 1, boxAll.max.x + 1, boxAll.max.y + 1); let topids = fb.search(boxAll.min.x - 1, boxAll.max.y - 1, boxAll.max.x + 1, boxAll.max.y + 1); let bottomids = fb.search(boxAll.min.x - 1, boxAll.min.y - 1, boxAll.max.x + 1, boxAll.min.y + 1); const FindBestCurveIndex = (ids, dirRef) => { if (ids.length === 1) return ids[0]; let maxLength = -Infinity; let bestIndex = -1; for (let id of ids) { let c = curves[id]; let dir = c.EndPoint.sub(c.StartPoint).normalize(); let length = Math.abs(dir.dot(dirRef)); //取模(模越长 表示和dirRef越平行(接近)) if (length > maxLength) { bestIndex = id; maxLength = length; } } return bestIndex; }; let left = FindBestCurveIndex(leftids, YAxis); let right = FindBestCurveIndex(rightids, YAxis); let top = FindBestCurveIndex(topids, XAxis); let bottom = FindBestCurveIndex(bottomids, XAxis); return [left, right, top, bottom]; } /** * 将曲线分段(根据高级封边的特性 (因为圆弧无法单独使用封边,所以和圆弧在一起的曲线必须和圆弧一样的封边,否则偏移失败)) * @l-arc-l,l-arc-arc-l,l-arc-l-arc-l.... * @param in_out_curves 曲线组( 函数结束后 这个数组被改变 ) * @returns 返回编组 curveGroups */ function SubsectionCurvesOfHightSeal(in_out_curves) { let curveGroups = []; let usedCu = new WeakSet(); //归类曲线,返回归类是否成功 const paragraph = (nextCurve, curCurve, curvesGroup, isBack) => { const curIsLine = curCurve instanceof Line; const nextIsLine = nextCurve instanceof Line; if (usedCu.has(nextCurve)) return false; if (curIsLine !== nextIsLine) //直线和圆弧 { if (curIsLine) { if (isBack) { if (!isParallelTo(curCurve.GetFirstDeriv(0).normalize(), nextCurve.GetFirstDeriv(0).normalize())) return false; } else { if (!isParallelTo(curCurve.GetFirstDeriv(0).normalize(), nextCurve.GetFirstDeriv(1).normalize())) return false; } } if (nextIsLine) { if (isBack) { if (!isParallelTo(curCurve.GetFirstDeriv(1).normalize(), nextCurve.GetFirstDeriv(0).normalize())) return false; } else { if (!isParallelTo(curCurve.GetFirstDeriv(0).normalize(), nextCurve.GetFirstDeriv(0).normalize())) return false; } } } else if (nextIsLine) //都是直线 { //共线且相连的直线分为一组 #I11T1Z if (!isParallelTo(nextCurve.GetFirstDeriv(0).normalize(), curCurve.GetFirstDeriv(0).normalize())) return false; let pts = [curCurve.StartPoint, curCurve.EndPoint]; let pts2 = [nextCurve.StartPoint, nextCurve.EndPoint]; if (pts.every(p => pts2.every(p2 => !equalv3(p, p2, 1e-6)))) //2条线完全分离 没有共同点 return false; } //else 都是圆弧 必然成组 if (isBack) curvesGroup.push(nextCurve); else curvesGroup.unshift(nextCurve); usedCu.add(nextCurve); return true; }; let caclCus = in_out_curves.filter(c => !equaln$1(c.Length, 0)); while (caclCus.length > 0) { let curCurve = caclCus.shift(); if (usedCu.has(curCurve)) continue; let curvesGroup = [curCurve]; //编组 usedCu.add(curCurve); //往后搜索 for (let i = 0; i < caclCus.length; i++) { if (!paragraph(caclCus[i], curCurve, curvesGroup, true)) break; curCurve = caclCus[i]; } //只有第一条才需要往前搜索 if (caclCus.length === in_out_curves.length - 1) { curCurve = curvesGroup[0]; //往前搜索 for (let i = caclCus.length - 1; i >= 0; i--) { if (!paragraph(caclCus[i], curCurve, curvesGroup, false)) break; curCurve = caclCus[i]; } } curveGroups.push(curvesGroup); } in_out_curves.length = 0; //同组多条曲线连接为多段线 for (let g of curveGroups) { if (g.length === 1) in_out_curves.push(g[0]); else { let pl = new Polyline(); for (let c of g) pl.Join(c); in_out_curves.push(pl); } } return curveGroups; } //与GetBoardSealingCurves相关 function GetBoardHighSeal(br, sealcus) { const option = br.BoardProcessOption; if (Production.HasSpiteSize(br)) { return [ { size: parseFloat(option.sealedDown), sealColor: option.sealColorDown }, { size: parseFloat(option.sealedRight), sealColor: option.sealColorRight }, { size: parseFloat(option.sealedUp), sealColor: option.sealColorUp }, { size: parseFloat(option.sealedLeft), sealColor: option.sealColorLeft }, ]; } let highSeals = []; for (let d of br.BoardProcessOption.highSealed) if (d.size != null) highSeals.push({ ...d }); //若未设置高级封边,把上下左右封边存入高级封边 if (sealcus.length !== highSeals.length || !br.IsSpecialShape) { 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]); highSeals.length = 0; let dir = Math.sign(br.ContourCurve.Area2); for (let c of sealcus) { let derv = c.GetFirstDeriv(0).multiplyScalar(dir); if (Math.abs(derv.x) > Math.abs(derv.y)) { if (derv.x > 0) highSeals.push({ size: sealDown, sealColor: option.sealColorDown }); else highSeals.push({ size: sealUp, sealColor: option.sealColorUp }); } else { if (derv.y > 0) highSeals.push({ size: sealRight, sealColor: option.sealColorRight }); else highSeals.push({ size: sealLeft, sealColor: option.sealColorLeft }); } } } return highSeals; } function GetBoardHighReservedEdge(br, sealcus) { if (Production.HasSpiteSize(br)) { return [ { size: parseFloat(br.BoardProcessOption.reservedEdgeDown) }, { size: parseFloat(br.BoardProcessOption.reservedEdgeRight) }, { size: parseFloat(br.BoardProcessOption.reservedEdgeUp) }, { size: parseFloat(br.BoardProcessOption.reservedEdgeLeft) }, ]; } let highReservedEdge = []; for (let d of br.BoardProcessOption.highReservedEdge) if (d.size != null) highReservedEdge.push({ ...d }); //若未设置高级封边,把上下左右封边存入高级封边 if (sealcus.length !== highReservedEdge.length || !br.IsSpecialShape) { let reservedEdgeDown = parseFloat(br.BoardProcessOption.reservedEdgeDown); let reservedEdgeUp = parseFloat(br.BoardProcessOption.reservedEdgeUp); let reservedEdgeLeft = parseFloat(br.BoardProcessOption.reservedEdgeLeft); let reservedEdgeRight = parseFloat(br.BoardProcessOption.reservedEdgeRight); highReservedEdge.length = 0; let dir = Math.sign(br.ContourCurve.Area2); for (let c of sealcus) { let derv = c.GetFirstDeriv(0).multiplyScalar(dir); if (Math.abs(derv.x) > Math.abs(derv.y)) { if (derv.x > 0) highReservedEdge.push({ size: reservedEdgeDown }); else highReservedEdge.push({ size: reservedEdgeUp }); } else { if (derv.y > 0) highReservedEdge.push({ size: reservedEdgeRight }); else highReservedEdge.push({ size: reservedEdgeLeft }); } } } return highReservedEdge; } /** * 获取板边备注 * @param br 板 * @param sealcus 封边轮廓线 * @param addAll 添加所有板边备注 * @returns 板边备注 */ function GetHighBoardEdgeRemark(br, sealcus, addAll = false) { const opt = br.BoardProcessOption; //顺序和封边一样 if (Production.HasSpiteSize(br)) { return [ { description: opt.edgeRemarkDown }, { description: opt.edgeRemarkRight }, { description: opt.edgeRemarkUp }, { description: opt.edgeRemarkLeft }, ]; } const highBoardEdgeRemark = []; for (const d of opt.highBoardEdgeRemark) if (d.description != null || addAll) highBoardEdgeRemark.push({ ...d }); //若未设置高级板边备注,把上下左右板边备注存入高级板边备注 if (sealcus.length !== highBoardEdgeRemark.length || !br.IsSpecialShape) { const edgeRemarkDown = opt.edgeRemarkDown; const edgeRemarkRight = opt.edgeRemarkRight; const edgeRemarkUp = opt.edgeRemarkUp; const edgeRemarkLeft = opt.edgeRemarkLeft; highBoardEdgeRemark.length = 0; const dir = Math.sign(br.ContourCurve.Area2); for (const c of sealcus) { const derv = c.GetFirstDeriv(0).multiplyScalar(dir); if (Math.abs(derv.x) > Math.abs(derv.y)) { if (derv.x > 0) highBoardEdgeRemark.push({ description: edgeRemarkDown }); else highBoardEdgeRemark.push({ description: edgeRemarkUp }); } else { if (derv.y > 0) highBoardEdgeRemark.push({ description: edgeRemarkRight }); else highBoardEdgeRemark.push({ description: edgeRemarkLeft }); } } } return highBoardEdgeRemark; } /**偏移前后曲线起点没改变 */ function OffsetOutlineSpNotChange(oldcu, newCu) { if (!newCu) return false; if (oldcu.EndParam !== newCu.EndParam) return false; let sDerv = oldcu.GetFirstDeriv(0).normalize(); let eDerv = oldcu.GetFirstDeriv(oldcu.EndParam).normalize().negate(); sDerv.add(eDerv).normalize(); let mDerv = newCu.StartPoint.sub(oldcu.StartPoint).normalize(); return isParallelTo(mDerv, sDerv); } /** * 获取板件的轮廓曲线数组(合并共线了 已经对封边进行合并了 尊重原始时针) * GetBrContourCurvesBySealGrouped * @param offsetInside 向内偏移1(为了编辑封边) * */ function GetBoardSealingCurves(br, offsetInside = false) { let brContour = Production.GetSpliteOutlineBySpliteSize(br); if (brContour) return brContour.Explode(); //如果是拆单板 则直接炸开返回 brContour = br.ContourCurve; if (brContour instanceof Circle) return [brContour.Clone()]; if (offsetInside) { let dir = Math.sign(brContour.Area2); let offsetedCurve = brContour.GetOffsetCurves(-1 * dir)[0]; if (OffsetOutlineSpNotChange(brContour, offsetedCurve)) brContour = offsetedCurve; } //避免共线导致的侧面数据对应错误 let curves = brContour.Explode(); MergeCurvelist(curves); if (curves.length === 1 && curves[0] instanceof Circle) //变成一个圆 return curves; if (br.IsSpecialShape) //是异形板(其实矩形板也需要分组 避免共线导致的错误) (但是如果删了这个 之前的数据会出现不兼容的问题) SubsectionCurvesOfHightSeal(curves); //分组 join 改变cus return curves; } class OffsetPolyline2 extends OffsetPolyline { InitSubCurves() { this._IsTopoOffset = true; return this; } OffsetSubCurves() { } GeneralCirclesAndVertexs() { } GeneralTrimContours() { super.GeneralTrimContours(); arrayRemoveIf(this._TrimPolylineContours, con => con.Curve.Area < 0.01); //因为局部偏移可能为0,产生0面积的轮廓 } CheckPointDir(pt) { let dir = this.GetPointAtCurveDir(pt); return dir === 0 || dir === this._OffsetDistSign; //因为局部偏移可能为0,所以在线上的我们也保留了 } } /** * 获取板件的轮廓(没有扣封边)(拆单时表现) * 在拆单的时候 我们用这个轮廓(为了数据对应准确性) * 修改时 请注意函数 GetSealedBoardContour */ function GetBoardContour(br) { if (Math.abs(br.ContourCurve.Area) < 10) { InteractionLog([{ msg: `板件:${br.Name}`, entity: [br] }, { msg: "板轮廓面积小于10" }], LogType.Warning); return; } let curves = GetBoardSealingCurves(br); if (curves.length === 1 && curves[0] instanceof Circle) return curves[0]; let allCurvs = []; for (let c of curves) if (c instanceof Polyline) allCurvs.push(...c.Explode()); else allCurvs.push(c); let brContour = Polyline.FastCombine(allCurvs, LINK_FUZZ * 2, false); return brContour; } /** * 获取板件(扣封边后的)轮廓(拆单时) * 修改时 请注意函数 GetBoardContour * @param {Board} br 板件 * @param {boolean} [calReserveEdge=true] 是否计算预留边 * @return {*} {(BrSealedData | undefined)} */ function GetSealedBoardContour(br, calReserveEdge = true) { if (Math.abs(br.ContourCurve.Area) < 10) return; let curves = GetBoardSealingCurves(br); let highSeals = GetBoardHighSeal(br, curves); let highReservedEdges = GetBoardHighReservedEdge(br, curves); if (!calReserveEdge) for (const element of highReservedEdges) element.size = 0; if (curves.length === 1 && curves[0] instanceof Circle) { let res = { brContour: curves[0], sealedContour: curves[0].GetOffsetCurves(-highSeals[0].size)[0], brCurves: curves, highSeals, highReservedEdges, }; return res; } let allCurvs = []; for (let c of curves) if (c instanceof Polyline) allCurvs.push(...c.Explode()); else allCurvs.push(c); let brContour = Polyline.FastCombine(allCurvs, LINK_FUZZ * 2, false); let dir = Math.sign(brContour.Area2); let sealedContours; { let positiveOffset; //正偏移 let negativeOffset; //负偏移 for (let i = 0; i < curves.length; i++) { let seal = highSeals[i]; //封边 let reservedEdge = highReservedEdges[i]; let offDist = -seal.size + reservedEdge.size; if (offDist > 0) positiveOffset = positiveOffset === undefined ? offDist : (positiveOffset > offDist ? positiveOffset : offDist); else negativeOffset = negativeOffset === undefined ? offDist : (negativeOffset < offDist ? negativeOffset : offDist); } //先分解多段线生成对应线段和封边值,防止两次偏移得到的线段数不一致 const newSeal = []; const newReservedEdge = []; const newCurves = []; for (let i = 0; i < curves.length; i++) { let seal = highSeals[i]; let reservedEdge = highReservedEdges[i]; let curve = curves[i]; if (curve instanceof Polyline) { let curveExpds = curve.Explode().filter(c => c.Length >= 1e-4); for (const c of curveExpds) { newSeal.push(seal); newReservedEdge.push(reservedEdge); newCurves.push(c); } } else { newSeal.push(seal); newReservedEdge.push(reservedEdge); newCurves.push(curve); } } const SealedContours = (curves, highSeals, highReservedEdges, dist) => { //局部偏移 let polylineOffset = new OffsetPolyline2(brContour, dir * dist); let subIndex = 0; polylineOffset._TrimCircleContours = []; polylineOffset._TrimArcContours = []; polylineOffset._SubOffsetedCurves = []; polylineOffset._SubCurves = []; polylineOffset._Circles = []; polylineOffset._CacheOCS = IdentityMtx4; polylineOffset._IsClose = true; for (let i = 0; i < curves.length; i++) { let curve = curves[i]; //曲线组 let seal = highSeals[i]; //封边 let reservedEdge = highReservedEdges[i]; let offDist = -seal.size + reservedEdge.size; if (Math.sign(offDist) != Math.sign(dist)) offDist = 0; let preSeal = highSeals[FixIndex$1(i - 1, curves)]; if (curve.Length < 1e-4) continue; polylineOffset._SubCurves.push(curve); //sub //trim Circle if (seal.size && seal.size === preSeal.size) polylineOffset._Circles.push(new Circle(curve.StartPoint, seal.size)); else polylineOffset._Circles.push(undefined); //offset let offsetC = curve.GetOffsetCurves(dir * offDist)[0]; if (offsetC) polylineOffset._SubOffsetedCurves.push({ index: subIndex, curve: offsetC, dist: -offDist, }); else polylineOffset._TrimArcContours.push(Contour.CreateContour([curve, new Line(curve.StartPoint, curve.EndPoint)], false)); subIndex++; } polylineOffset._Vertexs = polylineOffset._SubCurves.map(c => c.StartPoint); polylineOffset.Do(); return polylineOffset; }; let polylineOffset1; let offCurves = newCurves; //先判断负偏移 if (negativeOffset != undefined) { polylineOffset1 = SealedContours(newCurves, newSeal, newReservedEdge, negativeOffset); offCurves = polylineOffset1._SubOffsetedCurves.map((s) => s.curve); } //如果存在正偏移,再偏移一次 if (positiveOffset != undefined) { let polylineOffset2 = SealedContours(offCurves, newSeal, newReservedEdge, positiveOffset); sealedContours = polylineOffset2._RetCurves; } else sealedContours = polylineOffset1._RetCurves; } let hasSealedErr = false; //如果有多个 取最大 if (sealedContours.length > 1) { hasSealedErr = true; Toaster({ message: `有板计算封边异常,请检查!(点击左下角提示可以查看该板)`, timeout: 15000, intent: Intent.WARNING, key: "sealerror" }); InteractionLog([{ msg: "警告:" }, { msg: `板:${br.Name}`, entity: [br] }, { msg: `在扣除封边计算中,得到了${sealedContours.length}条轮廓,请检查!` }], LogType.Warning); let areas = sealedContours.map(p => p.Area); let maxIndex = Max(areas, (a1, a2) => a2 > a1); sealedContours = [sealedContours[maxIndex]]; } //如果不闭合 则尝试闭合 let sealedContour = sealedContours[0]; if (sealedContour && !sealedContour.IsClose) { if (sealedContour.StartPoint.distanceTo(sealedContour.EndPoint) < 0.1) sealedContour.CloseMark = true; else sealedContour = CreateContour2([sealedContour])?.Curve; } if (!sealedContour) return; //逆时针 if (sealedContour && sealedContour.Area2 < 0) sealedContour.Reverse(); let res = { brContour, sealedContour, brCurves: curves, highSeals, highReservedEdges, hasSealedErr }; return res; } /** * 设置板的上下左右封边(解析来自高级封边) * @param br * @param sealDatas 封边数据 * @param [sealCurves] 封边的曲线 * @param [brContourCurve] 传递封边的曲线轮廓 */ function SetBoardTopDownLeftRightSealData(br, sealDatas, sealCurves, brContourCurve) { let dir = Math.sign((brContourCurve ?? br.ContourCurve).Area2); sealCurves = sealCurves ?? GetBoardSealingCurves(br); //现在我们不管是否有拆单尺寸,我们总是关系封边值 // let param = { L: br.Height, W: br.Width, H: br.Thickness }; // let spliteHeight = safeEval(br.BoardProcessOption.spliteHeight, param, "L"); // let spliteWidth = safeEval(br.BoardProcessOption.spliteWidth, param, "W"); // let spliteThickness = safeEval(br.BoardProcessOption.spliteThickness, param, "H"); // if ((spliteHeight && spliteWidth && spliteThickness) || !br.IsSpecialShape && cus.length === 4) if (br.IsRect && sealCurves.length === 4) { for (let i = 0; i < 4; i++) { const size = sealDatas[i].size.toString(); const { sealColor: color = "" } = sealDatas[i]; let derv = sealCurves[i].GetFirstDeriv(0).normalize(); if (isParallelTo(derv, XAxis, 1e-4)) { if (derv.x * dir > 0) { br.BoardProcessOption[EBoardKeyList.DownSealed] = size; br.BoardProcessOption.sealColorDown = color; } else { br.BoardProcessOption[EBoardKeyList.UpSealed] = size; br.BoardProcessOption.sealColorUp = color; } } else { if (derv.y * dir > 0) { br.BoardProcessOption[EBoardKeyList.RightSealed] = size; br.BoardProcessOption.sealColorRight = color; } else { br.BoardProcessOption[EBoardKeyList.LeftSealed] = size; br.BoardProcessOption.sealColorLeft = color; } } } } else { if (sealCurves.length === 0) return; let [left, right, top, bottom] = ParseEdgeSealDir(sealCurves); br.BoardProcessOption[EBoardKeyList.LeftSealed] = sealDatas[left].size.toString(); br.BoardProcessOption[EBoardKeyList.RightSealed] = sealDatas[right].size.toString(); br.BoardProcessOption[EBoardKeyList.UpSealed] = sealDatas[top].size.toString(); br.BoardProcessOption[EBoardKeyList.DownSealed] = sealDatas[bottom].size.toString(); br.BoardProcessOption.sealColorLeft = sealDatas[left].sealColor ?? ""; br.BoardProcessOption.sealColorRight = sealDatas[right].sealColor ?? ""; br.BoardProcessOption.sealColorUp = sealDatas[top].sealColor ?? ""; br.BoardProcessOption.sealColorDown = sealDatas[bottom].sealColor ?? ""; } } function SetBoardReservedEdgeData(br, sealDatas, sealCurves, brContourCurve) { let dir = Math.sign((brContourCurve ?? br.ContourCurve).Area2); sealCurves = sealCurves ?? GetBoardSealingCurves(br); if (br.IsRect && sealCurves.length === 4) { for (let i = 0; i < 4; i++) { let derv = sealCurves[i].GetFirstDeriv(0).normalize(); if (isParallelTo(derv, XAxis, 1e-4)) { if (derv.x * dir > 0) br.BoardProcessOption.reservedEdgeDown = sealDatas[i].size.toString(); else br.BoardProcessOption.reservedEdgeUp = sealDatas[i].size.toString(); } else { if (derv.y * dir > 0) br.BoardProcessOption.reservedEdgeRight = sealDatas[i].size.toString(); else br.BoardProcessOption.reservedEdgeLeft = sealDatas[i].size.toString(); } } } else { if (sealCurves.length === 0) return; let [left, right, top, bottom] = ParseEdgeSealDir(sealCurves); br.BoardProcessOption.reservedEdgeLeft = sealDatas[left].size.toString(); br.BoardProcessOption.reservedEdgeRight = sealDatas[right].size.toString(); br.BoardProcessOption.reservedEdgeUp = sealDatas[top].size.toString(); br.BoardProcessOption.reservedEdgeDown = sealDatas[bottom].size.toString(); } } function SetBoardEdgeRemarkData(br, remarkDatas, sealCurves, brContourCurve) { let dir = Math.sign((brContourCurve ?? br.ContourCurve).Area2); sealCurves = sealCurves ?? GetBoardSealingCurves(br); const opt = br.BoardProcessOption; if (br.IsRect && sealCurves.length === 4) { for (let i = 0; i < 4; i++) { let derv = sealCurves[i].GetFirstDeriv(0).normalize(); if (isParallelTo(derv, XAxis, 1e-4)) { if (derv.x * dir > 0) opt.edgeRemarkDown = remarkDatas[i].description; else opt.edgeRemarkUp = remarkDatas[i].description; } else { if (derv.y * dir > 0) opt.edgeRemarkRight = remarkDatas[i].description; else opt.edgeRemarkLeft = remarkDatas[i].description; } } } else { if (sealCurves.length === 0) return; let [left, right, top, bottom] = ParseEdgeSealDir(sealCurves); opt.edgeRemarkLeft = remarkDatas[left].description; opt.edgeRemarkRight = remarkDatas[right].description; opt.edgeRemarkUp = remarkDatas[top].description; opt.edgeRemarkDown = remarkDatas[bottom].description; } } //侧面造型分裂 class SplitBoardSideModelUtil { constructor(br) { //备份原始二维刀路在世界坐标系中 this.OrgBoardOCS = new Matrix4(); this.CacheSideModel = new Map(); this.OldSealCurves = []; this.Init(br); } Init(br, isSpecialShape = false) { this.OrgBoardOCS = br.OCS; let curves = GetBoardSealingCurves(br); //取消异型操作会提前令isSpecialShape = false if (isSpecialShape) SubsectionCurvesOfHightSeal(curves); let oldSealCurves = []; for (let c of curves) if (c instanceof Polyline) oldSealCurves.push(...c.Explode()); else oldSealCurves.push(c); this.OldSealCurves = oldSealCurves; const sideModelMap = new Map(); for (let [num, soilds] of br.SideModelingMap) sideModelMap.set(num, soilds); this.CacheSideModel = sideModelMap; } CheckSideModel() { let maxSideIndex = 0; for (let [num, soilds] of this.CacheSideModel) maxSideIndex = Math.max(num, maxSideIndex); return this.OldSealCurves.length >= maxSideIndex; } SetBoardSideModel(br) { if (!this.CacheSideModel.size) return; if (this.CheckSideModel()) { this.SpiltSideModelOfBrContour(br); } } //新轮廓切割原始轮廓 SpiltSideModelOfBrContour(br) { let curves = GetBoardSealingCurves(br); let newSealCurves = []; for (let c of curves) if (c instanceof Polyline) newSealCurves.push(...c.Explode()); else newSealCurves.push(c); let sideMadelMap = new Map(); for (let [nmu, soilds] of this.CacheSideModel) { if (soilds?.length) { let oldCu = this.OldSealCurves[nmu]?.Clone(); if (!oldCu) continue; oldCu.ApplyMatrix(this.OrgBoardOCS); for (let i = 0; i < newSealCurves.length; i++) { if (newSealCurves[i] instanceof Arc) continue; let newCu = newSealCurves[i].Clone().ApplyMatrix(br.OCSNoClone); let p = newCu.GetPointAtParam(newCu.EndParam * 0.5); let spliteEnts = []; if (oldCu.PtOnCurve(p)) { let startX = oldCu.GetDistAtPoint(newCu.StartPoint); let endX = oldCu.GetDistAtPoint(newCu.EndPoint); if (startX > endX) { let backStart = startX; startX = endX; endX = backStart; } let box = new Box3Ext(new Vector3(startX), new Vector3(endX, br.Thickness)); let knifePls = []; for (let soild of soilds) { if (soild.Thickness <= 0) continue; let sCon = soild.ContourCurve.Clone().ApplyMatrix(soild.OCSNoClone); let thickness = soild.Thickness; let newNeighborCus = []; newNeighborCus.push(newSealCurves[FixIndex(i - 1, newSealCurves)].Clone().ApplyMatrix(br.OCSNoClone)); newNeighborCus.push(newSealCurves[FixIndex(i + 1, newSealCurves)].Clone().ApplyMatrix(br.OCSNoClone)); const offsetCus = newCu.GetOffsetCurves(-thickness); const xPtList = [startX, endX]; for (let cu of offsetCus) { for (let nbCu of newNeighborCus) { const intersectPts = cu.IntersectWith(nbCu, IntersectOption.ExtendThis); for (let pt of intersectPts) { let { closestPt } = newCu.GetClosestAtPoint(pt, true); let ptX = oldCu.GetDistAtPoint(closestPt); xPtList.push(ptX); } } } xPtList.sort((a, b) => a - b); for (let x of [xPtList[0], arrayLast(xPtList)]) knifePls.push(new Polyline([{ pt: AsVector2({ x, y: 0 }), bul: 0 }, { pt: AsVector2({ x, y: 1 }), bul: 0 }])); let splitSideModelCons = SplitPolyline(sCon, knifePls); if (!splitSideModelCons.length) splitSideModelCons.push(sCon); for (let j = 0; j < splitSideModelCons.length; j++) { let intersectBox = box.clone().intersect(splitSideModelCons[j].BoundingBox); if (box.intersectsBox(splitSideModelCons[j].BoundingBox, 1) && !equaln$1(intersectBox.max.x, intersectBox.min.x)) { let soildClone = soild.Clone(); soildClone.ContourCurve = splitSideModelCons[j].ApplyMatrix(soild.OCSInv); soildClone.GrooveCheckAllAutoSplit(); soildClone.ApplyMatrix(new Matrix4().setPosition(-startX, 0, 0)); spliteEnts.push(soildClone); } } } if (spliteEnts.length) sideMadelMap.set(i, spliteEnts); } } } } br.SideModelingMap = sideMadelMap; } //修改板厚度时裁剪侧面造型 SpiltSideModelOfBrThickness(br, thickness) { this.Init(br); let sideMadelMap = new Map(); for (let [nmu, soilds] of this.CacheSideModel) { if (soilds?.length) { let cu = this.OldSealCurves[nmu]?.Clone(); if (!cu) continue; cu.ApplyMatrix(this.OrgBoardOCS); let spliteEnts = []; let knifePls = []; let box = new Box3Ext(new Vector3(), new Vector3(cu.Length, thickness)); for (let soild of soilds) { if (soild.Thickness <= 0) continue; let sCon = soild.ContourCurve.Clone().ApplyMatrix(soild.OCSNoClone); knifePls.push(new Polyline([{ pt: AsVector2({ x: 0, y: 0 }), bul: 0 }, { pt: AsVector2({ x: 1, y: 0 }), bul: 0 }])); knifePls.push(new Polyline([{ pt: AsVector2({ x: 0, y: thickness }), bul: 0 }, { pt: AsVector2({ x: 1, y: thickness }), bul: 0 }])); let splitSideModelCons = SplitPolyline(sCon, knifePls); if (!splitSideModelCons.length) splitSideModelCons.push(sCon); for (let con of splitSideModelCons) { let intersectBox = box.clone().intersect(con.BoundingBox); if (box.intersectsBox(con.BoundingBox, 1) && !equaln$1(intersectBox.max.y, intersectBox.min.y)) { let soildClone = soild.Clone(); soildClone.ContourCurve = con.ApplyMatrix(soild.OCSInv); soildClone.GrooveCheckAllAutoSplit(); spliteEnts.push(soildClone); } } } if (spliteEnts.length) sideMadelMap.set(nmu, spliteEnts); } } br.SideModelingMap = sideMadelMap; } } /** * 把板件炸开成面域,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.GetFirstDeriv(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; } /**序列化板件数据 */ 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(n.sealColor); } 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(); let sealColor = ""; if (ver > 23) { sealColor = file.Read(); } if (ver < 4) { file.Read(); } processData[EBoardKeyList.HighSealed].push({ size, sealColor }); } 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); file.Write(item.depthExpr); } } } 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(); let depthExpr; if (ver > 19) { depthExpr = file.Read(); } else { depthExpr = depth.toString(); } m.items.push({ depth, offset, knife: { id: knifeId, radius: knifeRad, angle: knifeAngle, name: knifeName }, depthExpr }); } 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 } }); } } function SerializeBoardSideModeingData(file, sideModelingMap) { file.Write(sideModelingMap.size); for (let [index, sideModelingList] of sideModelingMap) { file.Write(index); file.Write(sideModelingList.length); for (let data of sideModelingList) file.WriteObject(data); } } function DeserializationBoardSideModeingData(file, sideModelingMap) { sideModelingMap.clear(); const count = file.Read(); for (let i = 0; i < count; i++) { let index = file.Read(); let listCount = file.Read(); let sideModelingList = []; for (let j = 0; j < listCount; j++) { let obj = file.ReadObject(); sideModelingList.push(obj); } sideModelingMap.set(index, sideModelingList); } } /** * 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.GetFirstDeriv(1).multiplyScalar(100)), curP.clone()); } if (c2 instanceof Arc) c2 = new Line(curP.clone(), curP.clone().add(c2.GetFirstDeriv(0).multiplyScalar(100))); ptsbul.push(d); if (!isClose && i === cus.length - 1) //最后一条 { ptsbul.push({ pt: c1.EndPoint, bul: 0 }); break; } //圆弧与直线相切,此时不要提刀 if (isParallelTo(c1.GetFirstDeriv(0), c2.GetFirstDeriv(0))) continue; //计算提刀部分: //向量与平分线,参照倒角代码 let derv1 = c1.GetFirstDeriv(0).normalize(); let derv2 = c2.GetFirstDeriv(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$1(data[i].bul, 0)) { curves.push(new Line(p1, p2)); } else { curves.push(new Arc().ParseFromBul(p1, p2, data[i].bul)); } } return curves; } const HALF_PI = Math.PI / 2; //根据二维刀路路径和刀截面建模 function Board2DModelCSGBuilder(board) { let _2DPathCsgs = []; let modelKnifePtsCache = new Map(); let tempVec = new Vector3; //geom3缓存 let geom3Cache = new Map(); //端点geom3缓存 let pathGripsSet = new Set(); let hasError = false; for (let vm of board.Modeling2D) { let path = vm.path; let fuzzy = new FuzzyFactory; //获取端点部分的csg(使用旋转建模) const GetGripsCsgs = (pts, geom3s, knifeGeom, knifeId, rMtxs, rAngle) => { for (let i = 0; i < pts.length; i++) { let pt = pts[i]; if (!pt) continue; let key = fuzzy.lookupOrCreate([pt.x, pt.y], pt); if (!pathGripsSet.has(key)) { let id = knifeId; if (rAngle && i === 1) id = `${knifeId}_${rAngle.toFixed(3)}`; let rotateGeom = geom3Cache.get(id); if (!rotateGeom) { let normalAngle = 2 * Math.PI; if (rAngle && i === 1) normalAngle = rAngle; try { rotateGeom = extrudeRotate({ segments: 8, angle: normalAngle }, knifeGeom); rotateGeom = retessellate$1(rotateGeom); geom3Cache.set(id, rotateGeom); } catch (error) { hasError = true; return; } } // TestDrawGeom3s([rotateGeom]); let rMtx; rMtx = rMtxs[i] ?? rMtxs[0]; rotateGeom = transform$1((rMtx ?? new Matrix4).setPosition(pt).elements, rotateGeom); geom3s.push(rotateGeom); pathGripsSet.add(key); } } }; for (let item of vm.items) { let tempPath = GetOffsetPath(board, path, item); if (tempPath) { let sweepContour = board.KnifePolylineMap.get(item.knife.id); { if (!board._2DPathDrawObject) board._2DPathDrawObject = new Object3D; let curves = VData2Curve(VKnifToolPath(tempPath, item.depth, item.knife.angle / 2)); //走刀+提刀曲线 let pathObject = new Object3D(); //走刀路径绘制线 for (let c of curves) { // 修正精度-走刀路径 if (c instanceof Arc || c instanceof Polyline) c.DisplayAccuracy = board.DisplayAccuracy; c.ColorIndex = tempPath.ColorIndex; pathObject.add(c.GetDrawObjectFromRenderType(RenderType.Wireframe)); } if (vm.dir === FaceDirection.Back) { pathObject.applyMatrix4(ZMirrorMatrix); pathObject.position.z = item.depth; } else pathObject.position.z = board.Thickness - item.depth; pathObject.updateMatrix(); board._2DPathDrawObject.add(pathObject); } if (!sweepContour) continue; let geom3s = []; //刀截面轮廓Pts let knifeGeomPts = modelKnifePtsCache.get(item.knife.id); if (!knifeGeomPts?.length) { let shapePts = sweepContour.Shape.getPoints(); for (let p of shapePts) Vector2ApplyMatrix4(sweepContour.OCSNoClone, p); knifeGeomPts = shapePts.map(p => [p.x, p.y]); if (sweepContour.IsClockWise) knifeGeomPts.reverse(); } //刀截面geom let knifeGeom = geom2.fromPoints(knifeGeomPts); //分解路径 let cus = tempPath.Explode(); // 修正精度-切割CSG cus.forEach(c => (c instanceof Arc || c instanceof Polyline) && (c.DisplayAccuracy = board.DisplayAccuracy)); if (item.knife.angle) { //提刀建模 let sweepGeom = new SweepGeometrySimple(sweepContour, cus); //这里我们对盖子进行精简 let lidPolys = []; let fuzz = new FuzzyFactory; let lidVerts = sweepGeom.shapePts2d.map((p, i) => { fuzz.lookupOrCreate([p.x, p.y], i); return [p.x, p.y, 0]; }); for (let face of sweepGeom.TriFaces) lidPolys.push(poly3.create(face.map(index => lidVerts[index]))); let lidGeom3 = retessellate$1(geom3.create(lidPolys)); let hasBadIndex = false; let lidPolygons = lidGeom3.polygons.map(p => p.vertices.map(v => { let index = fuzz.lookupOrCreate([v[0], v[1]], -1); if (index === -1) hasBadIndex = true; return index; })); //精简结束 //遍历每个水管 for (let i = 0; i < sweepGeom.SidePolygons.length; i++) { let polygons = sweepGeom.SidePolygons[i]; let polys = []; for (let polygon of polygons) { let pts = polygon.map(i => sweepGeom.vertices[i]); //这是水管盖子的点 //水管前进的方向 let dir = polygon["dir"]; //如果水管的侧面自交了,我们选择性的删掉一个无用面 let dir1 = tempVec.subVectors(pts[3], pts[0]).normalize(); if (!equalv3(dir, dir1)) //如果反向1 { let l1 = new Line(pts[0], pts[1]); let l2 = new Line(pts[2], pts[3]); let p = l1.IntersectWith(l2, 0)[0]; if (!p) continue; //弃用了这个多边形 // polys.push(poly3.create([vertices[3], p.toArray() as Vec3, vertices[0]]));//弃用了无效的自交三角形 polys.push(poly3.create([p.toArray(), pts[1].toArray(), pts[2].toArray()])); continue; } let dir2 = tempVec.subVectors(pts[2], pts[1]).normalize(); if (!equalv3(dir, dir2)) //如果反向2 { let l1 = new Line(pts[0], pts[1]); let l2 = new Line(pts[2], pts[3]); let p = l1.IntersectWith(l2, 0)[0]; if (!p) continue; //弃用了这个多边形 polys.push(poly3.create([pts[0].toArray(), p.toArray(), pts[3].toArray()])); // polys.push(poly3.create([p.toArray() as Vec3, vertices[2], vertices[1]]));//弃用了无效的自交三角形 continue; } polys.push(poly3.create(pts.map(p => p.toArray()))); //如果没有反向,那么我们直接返回多边形 } //构建水管的盖子 let curVerts = sweepGeom.shapeVerts[FixIndex$1(i, sweepGeom.shapeVerts)]; let nextVerts = sweepGeom.shapeVerts[FixIndex$1(i + 1, sweepGeom.shapeVerts)]; let lid1 = []; let lid2 = []; if (!hasBadIndex) for (let lidPolygon of lidPolygons) //我们使用已经精简过的盖子索引 { lid1.push(poly3.create(lidPolygon.map(index => curVerts[index].toArray()))); lid2.push(poly3.create(lidPolygon.reverse().map(index => nextVerts[index].toArray()))); lidPolygon.reverse(); //这里偷懒了 } else { for (let face of sweepGeom.TriFaces) { lid1.push(poly3.create(face.map(index => curVerts[index].toArray()))); lid2.push(poly3.create(face.map(index => nextVerts[index].toArray()).reverse())); } } let lidGeom1 = geom3.create(lid1); let lidGeom2 = geom3.create(lid2); if (hasBadIndex) { lidGeom1 = retessellate$1(lidGeom1); lidGeom2 = retessellate$1(lidGeom2); } let intGeom = intersect(lidGeom1, lidGeom2); if (intGeom.polygons.length) polys.push(...intGeom.polygons); else polys.push(...lidGeom1.polygons, ...lidGeom2.polygons); geom3s.push(geom3.create(polys)); } //获取端点旋转矩阵 使8段圆对齐曲线 let rMtx1 = GetRMtxOfCurveOfAngle(cus[0]); let rMtx2 = GetRMtxOfCurveOfAngle(arrayLast(cus), false); //提刀轮廓 未闭合路径增加圆角 端点部分 if (!tempPath.IsClose) GetGripsCsgs([tempPath.StartPoint, tempPath.EndPoint], geom3s, knifeGeom, item.knife.id, [rMtx1, rMtx2]); } else { //非提刀建模 let mt4Z = new Matrix4().makeRotationZ(HALF_PI); let mt4Y = new Matrix4().makeRotationY(HALF_PI); for (let i = 0; i < cus.length; i++) { let cu = cus[i]; if (cu instanceof Line) { //直线部分 //旋转至 X为正方向 let lineShape = extrudeLinear({ height: cu.Length }, knifeGeom); lineShape = retessellate$1(lineShape); lineShape = transform$1(mt4Z.elements, lineShape); lineShape = transform$1(mt4Y.elements, lineShape); //平移到原点为中心 let mtx = new Matrix4().setPosition((new Vector3(-cu.Length / 2, 0, 0))); lineShape = transform$1(mtx.elements, lineShape); //旋转直线正确方向 let pt = cu.StartPoint.sub(cu.EndPoint); let rMtx = new Matrix4().makeRotationZ(Math.atan2(pt.y, pt.x)); lineShape = transform$1(rMtx.elements, lineShape); //平移到线段中点 mtx = new Matrix4().setPosition(cu.Midpoint); lineShape = transform$1(mtx.elements, lineShape); geom3s.push(lineShape); let attribute = { rRange: 0, drawCircularAngle: 0 }; let secondMtx; let j = i + 1; if (j < cus.length) { let cu2 = cus[j]; attribute = GetRMtxOfCurve(cu, cu2); secondMtx = new Matrix4().makeRotationZ(attribute?.rRange ?? 0); } //闭合曲线 if (j === cus.length && path.IsClose) { let cu2 = cus[0]; attribute = GetRMtxOfCurve(cu, cu2); secondMtx = new Matrix4().makeRotationZ(attribute?.rRange ?? 0); } let startPt = (j < cus.length && i > 0 || i === 0 && path.IsClose) ? undefined : cu.StartPoint; //端点部分 GetGripsCsgs([startPt, cu.EndPoint], geom3s, knifeGeom, item.knife.id, [rMtx, secondMtx], attribute?.drawCircularAngle); } else if (cu instanceof Arc) { //弧线部分 //获取针对弧形扫掠Geom let mt4 = new Matrix4().setPosition(cu.Radius, 0, 0); let arcKnifeGeom = geom2.transform(mt4.elements, knifeGeom); // 修正精度-切割CSG const displayAccuracy = board.DisplayAccuracy || GetArcDrawCount(cu.Radius); const resolution = Math.ceil(displayAccuracy / 4) * 4; let rotateGeom = extrudeRotate({ segments: resolution, angle: cu.AllAngle }, arcKnifeGeom); //圆弧的旋转分量 let extractRotation = new Matrix4().extractRotation(cu.OCSNoClone); rotateGeom = retessellate$1(rotateGeom); //放置Geom let rotateMat = new Matrix4().makeRotationZ(cu.IsClockWise ? cu.EndAngle : cu.StartAngle).setPosition(cu.Position).multiply(extractRotation); rotateGeom = transform$1(rotateMat.elements, rotateGeom); geom3s.push(rotateGeom); let startAngleMtx = new Matrix4().makeRotationZ(cu.StartAngle).multiply(extractRotation); let endAngleMtx = new Matrix4().makeRotationZ(cu.EndAngle).multiply(extractRotation); //夹角的端点旋转角度和圆的大小分量 let attribute = { rRange: 0, drawCircularAngle: 0 }; //获取端点旋转矩阵 使8段圆对齐曲线 (非提刀部分) let j = i + 1; if (j < cus.length) { let cu2 = cus[j]; attribute = GetRMtxOfCurve(cu, cu2); endAngleMtx = new Matrix4().makeRotationZ(attribute?.rRange ?? 0); } //闭合曲线 if (j === cus.length && path.IsClose) { let cu2 = cus[0]; attribute = GetRMtxOfCurve(cu, cu2); endAngleMtx = new Matrix4().makeRotationZ(attribute?.rRange ?? 0); } let startPt = (j < cus.length && i > 0 || i === 0 && path.IsClose) ? undefined : cu.StartPoint; //端点部分 GetGripsCsgs([startPt, cu.EndPoint], geom3s, knifeGeom, item.knife.id, [startAngleMtx, endAngleMtx], attribute?.drawCircularAngle); } } } //正反面设置 let mtx; if (vm.dir === FaceDirection.Front) mtx = new Matrix4().setPosition(0, 0, board.Thickness - item.depth); else mtx = MakeMirrorMtx(ZAxis, new Vector3(0, 0, item.depth * 0.5)); for (let geom of geom3s) { geom = transform$1(mtx.elements, geom); _2DPathCsgs.push(geom); } } } } if (hasError) Log(`板:${board.Name} 二维刀路建模异常!`, LogType.Error, [board]); return _2DPathCsgs; } function GetOffsetPath(br, path, item) { if (item.offset === 0) { return path; } else { let cache = br.OffsetPathCache.get(path); if (!cache) { cache = {}; br.OffsetPathCache.set(path, cache); } let tempPath = cache[item.offset.toString()]; if (!tempPath) { tempPath = path.GetOffsetCurves(item.offset)[0]; cache[item.offset.toString()] = tempPath; } return tempPath; } } //获取端点旋转矩阵 使8段圆对齐曲线 (提刀部分) function GetRMtxOfCurveOfAngle(cu, startPt = true) { if (cu instanceof Line) { let pt1 = cu.StartPoint.sub(cu.EndPoint); return new Matrix4().makeRotationZ(Math.atan2(pt1.y, pt1.x)); } else if (cu instanceof Arc) { let extractRotation = new Matrix4().extractRotation(cu.OCSNoClone); let angle = 0; let fuzzAngle = Math.PI / GetArcDrawCount(cu.Radius); if (startPt) angle = cu.IsClockWise ? (cu.StartAngle - fuzzAngle) : (cu.StartAngle + fuzzAngle); else angle = cu.IsClockWise ? (fuzzAngle + cu.EndAngle) : (cu.EndAngle - fuzzAngle); return new Matrix4().makeRotationZ(angle).multiply(extractRotation); } } //获取端点旋转矩阵 使8段圆对齐曲线 (非提刀部分) function GetRMtxOfCurve(cu1, cu2) { if (cu1 instanceof Line) { if (cu2 instanceof Line) { let v1 = cu1.StartPoint.sub(cu1.EndPoint); let v2 = cu2.EndPoint.sub(cu2.StartPoint); let angleToV = angleTo(v1, v2); //补角的圆度数 let drawCircularAngle = Math.PI - Math.abs(angleToV); let addAng = 0; let cuV1 = angle(v1); if (angleToV > 0) addAng = Math.PI - drawCircularAngle; let rRange = cuV1 + HALF_PI + addAng; return { rRange, drawCircularAngle }; } else if (cu2 instanceof Arc) { let v1 = cu1.StartPoint.sub(cu1.EndPoint); let v2 = cu2.Center.sub(cu2.StartPoint).applyMatrix4(new Matrix4().makeRotationZ(-HALF_PI)); let angleToV = angleTo(v1, v2); //补角的圆度数 let drawCircularAngle = cu2.IsClockWise ? Math.abs(angleToV) : (Math.PI - Math.abs(angleToV)); let addAng = 0; let cuV1 = angle(v1); if (angleToV > 0) addAng = cu2.IsClockWise ? HALF_PI : (-HALF_PI - drawCircularAngle); else addAng = cu2.IsClockWise ? (angleToV - HALF_PI) : HALF_PI; let rRange = cuV1 + addAng; return { rRange, drawCircularAngle }; } } else if (cu1 instanceof Arc) { if (cu2 instanceof Line) { let v1 = cu1.Center.sub(cu1.EndPoint).applyMatrix4(new Matrix4().makeRotationZ(-HALF_PI)); let v2 = cu2.EndPoint.sub(cu2.StartPoint); let angleToV = angleTo(v1, v2); //补角的圆度数 let drawCircularAngle = cu1.IsClockWise ? Math.PI - Math.abs(angleToV) : Math.abs(angleToV); let cuV2 = angle(v2); let addAng = 0; if (angleToV > 0) addAng = cu1.IsClockWise ? HALF_PI : -(drawCircularAngle + HALF_PI); else addAng = cu1.IsClockWise ? -(drawCircularAngle + HALF_PI) : HALF_PI; let rRange = cuV2 + addAng; return { rRange, drawCircularAngle }; } else if (cu2 instanceof Arc) { let v1 = cu1.Center.sub(cu1.EndPoint).applyMatrix4(new Matrix4().makeRotationZ(cu1.IsClockWise ? -HALF_PI : HALF_PI)); let v2 = cu2.Center.sub(cu1.EndPoint).applyMatrix4(new Matrix4().makeRotationZ(cu2.IsClockWise ? HALF_PI : -HALF_PI)); let angleToV = angleTo(v1, v2); //补角的圆度数 let drawCircularAngle = Math.PI - Math.abs(angleToV); let addAng = 0; let cuV1 = angle(v1); if (angleToV > 0) addAng = Math.PI - drawCircularAngle; let rRange = cuV1 + HALF_PI + addAng; return { rRange, drawCircularAngle }; } } } const _CSGSubtractInjectInteractionFunctions = []; function AddCSGSubtractTask(task) { for (let f of _CSGSubtractInjectInteractionFunctions) f(task); } const _TerminateTaskInjectInteractionFunctions = []; function TerminateCSGTask(task) { for (let f of _TerminateTaskInjectInteractionFunctions) f(task); } //ref: https://github.com/mrdoob/js/issues/10517 const keys = ['a', 'b', 'c']; class EdgesGeometry extends BufferGeometry { /** * 在使用Extrude实体的时候,有可能导致面无限分裂,并且有可能造成丢线问题,使用FromCSG方法可解. */ FromGeometry(geometry, thresholdAngle = 1) { let geometry2; if (geometry.isBufferGeometry) geometry2 = new Geometry().fromBufferGeometry(geometry); else geometry2 = geometry.clone(); geometry2.mergeVertices(); geometry2.computeFaceNormals(); let vertices = geometry2.vertices; let faces = geometry2.faces; let count = faces.length; for (let i = 0; i < faces.length; i++) { if (faces.length > count * 2) { console.warn("EdgeGeometry的分裂已经到达2倍!"); break; } let face = faces[i]; if (FaceArea(face, vertices) < 1e-5) continue; for (let j = 0; j < 3; j++) { let e1 = face[keys[j]]; let e2 = face[keys[(j + 1) % 3]]; let e3 = face[keys[(j + 2) % 3]]; let line = new Line3(vertices[e1], vertices[e2]); //split triangle for (let e = 0, l = vertices.length; e < l; e++) { if (e === e1 || e === e2 || e === e3) continue; let p = vertices[e]; let closestPoint = line.closestPointToPoint(p, true, new Vector3()); if (equalv3(closestPoint, vertices[e1], 1e-5) || equalv3(closestPoint, vertices[e2])) continue; if (equalv3(closestPoint, p, 1e-5)) { face["splitted"] = true; let f1 = new Face3(e, e3, e1, face.normal); let f2 = new Face3(e, e3, e2, face.normal); faces.push(f1, f2); break; } } if (face["splitted"]) break; } } let edge = [0, 0]; let hash = {}; for (let i = 0, l = faces.length; i < l; i++) { let face = faces[i]; if (face["splitted"]) continue; for (let j = 0; j < 3; j++) { edge[0] = face[keys[j]]; edge[1] = face[keys[(j + 1) % 3]]; arraySortByNumber(edge); let key = edge.toString(); if (hash[key] === undefined) hash[key] = { vert1: edge[0], vert2: edge[1], face1: face }; else hash[key].face2 = face; } } let coords = []; let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle); for (let key in hash) { let h = hash[key]; // An edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree. if (h.face2 && h.face1.normal.dot(h.face2.normal) <= thresholdDot) { let vertex = vertices[h.vert1]; coords.push(vertex.x); coords.push(vertex.y); coords.push(vertex.z); vertex = vertices[h.vert2]; coords.push(vertex.x); coords.push(vertex.y); coords.push(vertex.z); } } this.setAttribute('position', new Float32BufferAttribute(coords, 3)); return this; } /** * 解决原来算法性能低下并且结果不理想的问题 */ FromCSG(csg) { let fuzzyfactory = new FuzzyCSGFactory(); let polygonsPerPlane = new Map(); for (let polygon of csg.polygons) { polygon.plane = fuzzyfactory.planefactory.lookupOrCreate(polygon.plane, polygon.plane); let arr = polygonsPerPlane.get(polygon.plane); if (!arr) { arr = []; polygonsPerPlane.set(polygon.plane, arr); } arr.push(polygon); } let coords = []; for (let [, polygonArr] of polygonsPerPlane) { this.PolygonsOutline(polygonArr, coords); } this.setAttribute('position', new Float32BufferAttribute(coords, 3)); return this; } PolygonsOutline(polygons, coords) { let pts = []; let ff = new FuzzyFactory(3, 1e-7); let record = {}; for (let polygon of polygons) { for (let i = 0, count = polygon.vertices.length; i < count; i++) { let p = polygon.vertices[i]; let vp = ff.lookupOrCreate(p, new Vector3().fromArray(p)); //@ts-ignore p.pos = vp; if (!("_added_" in vp)) { vp["_added_"] = pts.length; pts.push(vp); } } } for (let polygon of polygons) { for (let i = 0, count = polygon.vertices.length; i < count; i++) { //@ts-ignore let p1 = polygon.vertices[i].pos; //@ts-ignore let p2 = polygon.vertices[FixIndex$1(i + 1, count)].pos; let delta = p2.clone().sub(p1); let lengthSq = delta.dot(delta); let splitPts = []; let box = new Box3().setFromPoints([p1, p2]).expandByVector(new Vector3(1, 1, 1)); for (let p of pts) { if (p === p1 || p === p2) continue; if (!box.containsPoint(p)) continue; let delta2 = p.clone().sub(p1); let len2 = delta2.dot(delta); let t = len2 / lengthSq; if (t > 0 && t < 1) { let closestPoint = delta.clone().multiplyScalar(t).add(p1); if (equalv3(closestPoint, p, 1e-3)) splitPts.push({ param: t, pt: p }); } } splitPts.sort((p1, p2) => p1.param - p2.param); splitPts.push({ param: 1, pt: p2 }); let lastP = p1; for (let p of splitPts) { let tag1 = lastP["_added_"]; let tag2 = p.pt["_added_"]; let key; if (tag1 < tag2) key = `${tag1},${tag2}`; else key = `${tag2},${tag1}`; if (key in record) record[key].count++; else record[key] = { p1: lastP, p2: p.pt, count: 1 }; lastP = p.pt; } } } for (let key in record) { let d = record[key]; if (d.count === 1) coords.push(d.p1.x, d.p1.y, d.p1.z, d.p2.x, d.p2.y, d.p2.z); } } } let triangle = new Triangle(); function FaceArea(f, pts) { triangle.a = pts[f.a]; triangle.b = pts[f.b]; triangle.c = pts[f.c]; return triangle.getArea(); } 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), ]; } } //开门方向纹路 static LinesOpenDirPts(len, width, openDir) { if (openDir === BoardOpenDir.Right) { return [ new Vector3(-width / 2, -len / 2), new Vector3(width / 2, 0), new Vector3(width / 2, 0), new Vector3(-width / 2, len / 2) ]; } else if (openDir === BoardOpenDir.Left) { return [ new Vector3(width / 2, -len / 2), new Vector3(-width / 2, 0), new Vector3(-width / 2, 0), new Vector3(width / 2, len / 2) ]; } else if (openDir === BoardOpenDir.Up) { return [ new Vector3(-width / 2, -len / 2), new Vector3(0, len / 2), new Vector3(0, len / 2), new Vector3(width / 2, -len / 2) ]; } else if (openDir === BoardOpenDir.Down) { return [ new Vector3(-width / 2, len / 2), new Vector3(0, -len / 2), new Vector3(0, -len / 2), new Vector3(width / 2, len / 2) ]; } } } var FontStyleKeyCode; (function (FontStyleKeyCode) { FontStyleKeyCode[FontStyleKeyCode["Height"] = 1] = "Height"; FontStyleKeyCode[FontStyleKeyCode["WidthFactor"] = 2] = "WidthFactor"; FontStyleKeyCode[FontStyleKeyCode["FontType"] = 3] = "FontType"; })(FontStyleKeyCode || (FontStyleKeyCode = {})); /** * 字体样式 */ let FontStyleRecord = class FontStyleRecord extends SymbolTableRecord { constructor() { super(...arguments); /** 字体高度 1 */ this.Height = 60; //1 /** 宽度因子 2 字符串间距 */ this.WidthFactor = 1; /** 样式类型 3 */ this.FontType = FontType.YaHei; //#endregion } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.Height = file.Read(); this.WidthFactor = file.Read(); this.FontType = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.Height); file.Write(this.WidthFactor); file.Write(this.FontType); } }; __decorate([ AutoRecord ], FontStyleRecord.prototype, "Height", void 0); __decorate([ AutoRecord ], FontStyleRecord.prototype, "WidthFactor", void 0); __decorate([ AutoRecord ], FontStyleRecord.prototype, "FontType", void 0); FontStyleRecord = __decorate([ Factory ], FontStyleRecord); const TEXT_HEIGHT_SCALE = 1.35; var TextAligen; (function (TextAligen) { TextAligen[TextAligen["LeftTop"] = 3] = "LeftTop"; TextAligen[TextAligen["Top"] = 1] = "Top"; TextAligen[TextAligen["RightTop"] = 5] = "RightTop"; TextAligen[TextAligen["LeftMid"] = 2] = "LeftMid"; TextAligen[TextAligen["Mid"] = 0] = "Mid"; TextAligen[TextAligen["RightMid"] = 4] = "RightMid"; TextAligen[TextAligen["LeftDown"] = 10] = "LeftDown"; TextAligen[TextAligen["Down"] = 8] = "Down"; TextAligen[TextAligen["RightDown"] = 12] = "RightDown"; })(TextAligen || (TextAligen = {})); // 多层lineHeight比例计算方式: // src\DatabaseServices\Text\FontExt.ts createPaths函数中 // textHeight * (textType.boundingBox.yMax - textType.boundingBox.yMin + textType.underlineThickness) / textType.resolution const LineHeightScaleMap = { [FontType.YaHei]: 1.875, // (1439+355+81) / 1000 [FontType.SongTi]: 1.834, // (1369+437+28) / 1000 [FontType.KaiTi]: 1.514, // (1194 +255 + 65) / 1000 [FontType.FangSong]: 1.487, // (1194 +228 + 65) / 1000 [FontType.LiShu]: 1.58, // (1194 +315 + 71) / 1000 [FontType.HeiTi]: 1.476, // (1194 +217 +65 ) / 1000 [FontType.HuaWenLiShu]: 1.499, // (1111 +360 +28 ) / 1000 [FontType.HuaWenXingKai]: 1.543, // (1114 +401 +28 ) / 1000 }; const ShapeCache = new Map(); function DisposeTextShapeCache() { for (let [key, geo] of ShapeCache) geo.dispose(); ShapeCache.clear(); } /** * 单/多行文字实体 */ let Text = class Text extends Entity { constructor(pos, _TextString = "", _TextRotation = 0, fontName, widthFactor) { super(); this._TextString = _TextString; this._TextRotation = _TextRotation; this.OnlyRenderType = true; this._FontStyleOverride = new Map; //字体样式替代 this._RoomName = ""; this._CabinetName = ""; this._Align = TextAligen.LeftDown; this.IsDoubleMesh = false; //文字使用双面网格体 this.IsFsText = false; //文字写在俯视图上 pos && this._Matrix.setPosition(pos); this.FontStyle = HostApplicationServices.CurrentFontStyle; if (fontName) this.FontName = fontName; if (widthFactor) this.WidthFactor = widthFactor; } get RoomName() { return this._RoomName; } set RoomName(value) { if (value === this._RoomName) return; this.WriteAllObjectRecord(); this._RoomName = value; } get CabinetName() { return this._CabinetName; } set CabinetName(value) { if (value === this._CabinetName) return; this.WriteAllObjectRecord(); this._CabinetName = value; } get TextRotation() { return this._TextRotation; } set TextRotation(v) { if (this._TextRotation === v) return; this.WriteAllObjectRecord(); this._TextRotation = v; this.UpdateTranslate(); } get TextString() { return this._TextString; } set TextString(str) { if (str !== this._TextString) { this.WriteAllObjectRecord(); this._TextString = str; this.Update(); } } set TextAligen(al) { if (al === this._Align) return; this.WriteAllObjectRecord(); this._Align = al; this.UpdateTranslate(); } get TextAligen() { return this._Align; } /**设置字体样式 */ set FontStyle(styleId) { if (styleId === this._FontStyle) return; this.WriteAllObjectRecord(); this._FontStyle = styleId; this.Update(); } /**大概多行文字的高度 */ get MultiHeight() { // this.Height 为用户初始化设置的高度。 // 但是this.Height和实际渲染高度不一致。所以使用高度乘以比例scale,这样行高会精确一点。 // 如果需要精确的,可以绘制完后,再获取实际高度。 const scale = LineHeightScaleMap[this.FontName] || LineHeightScaleMap[FontType.SongTi]; if (this._TextString.includes("\n")) { let texts = this._TextString.split("\n"); const scale = LineHeightScaleMap[this.FontName] || LineHeightScaleMap[FontType.SongTi]; return texts.length * this.Height * scale; } return this.Height * scale; } get FontStyle() { return this._FontStyle; } //字体样式名称 get FontName() { return this.GetFontStyleValue(FontStyleKeyCode.FontType); } set FontName(fontName) { this.SetFontStyleOverrideValue(FontStyleKeyCode.FontType, fontName); } //字体尺寸大小 get Height() { return this.GetFontStyleValue(FontStyleKeyCode.Height); } set Height(value) { this.SetFontStyleOverrideValue(FontStyleKeyCode.Height, value); } //字体间距 get WidthFactor() { return this.GetFontStyleValue(FontStyleKeyCode.WidthFactor); } set WidthFactor(value) { this.SetFontStyleOverrideValue(FontStyleKeyCode.WidthFactor, value); } get FontStyleOverride() { return this._FontStyleOverride; } GetFontStyleOverrideValue(key) { return this._FontStyleOverride.get(key); } GetFontStyleValue(key) { let value = this.GetFontStyleOverrideValue(key); if (value !== undefined) return value; if (this._FontStyle?.Object) return this._FontStyle.Object[FontStyleKeyCode[key]]; if (HostApplicationServices.CurrentFontStyle && HostApplicationServices.CurrentFontStyle.Object instanceof FontStyleRecord) return HostApplicationServices.CurrentFontStyle.Object[FontStyleKeyCode[key]]; } //设置覆盖的样式 SetFontStyleOverrideValue(key, value) { if (this.GetFontStyleOverrideValue(key) === value) return; this.WriteAllObjectRecord(); let oldV = this.GetFontStyleValue(key); this._FontStyleOverride.set(key, value); if (oldV !== value) this.Update(); } ClearFontStyleOverride() { if (this._FontStyleOverride.size === 0) return; this.WriteAllObjectRecord(); this._FontStyleOverride.clear(); this.Update(); } //创建字体对象 async AsyncUpdateDrawObject(obj, renderType) { } /**大概宽度 */ get Width() { let count = 0; const textStr = this._TextString.split("\n"); for (const txt of textStr) { let cTemp = 0; for (let i = 0; i < txt.length; i++) { const code = txt.charCodeAt(i); if (code > 255) cTemp++; else cTemp += 0.5; } count = Math.max(count, cTemp); } return count * this.Height * TEXT_HEIGHT_SCALE * this.WidthFactor; } get HasBoundingBox() { return this._CacheDrawObject.has(RenderType.Wireframe); } get BoundingBox() { let obj = this._CacheDrawObject.get(RenderType.Wireframe); if (obj && obj.children.length === 1) return GetBox(obj); return this.BoundingBoxInOCS.applyMatrix4(new Matrix4().makeRotationZ(this.TextRotation)).applyMatrix4((this.OCSNoClone)); } get BoundingBoxInOCS() { let width = this.Width; let height = this.MultiHeight; let obj = this._CacheDrawObject.get(RenderType.Wireframe); if (obj && obj.children.length === 1) { let geo = obj.children[0].geometry; if (geo) { if (!geo.boundingBox) geo.computeBoundingBox(); width = geo.boundingBox.max.x - geo.boundingBox.min.x; height = geo.boundingBox.max.y - geo.boundingBox.min.y; } } let w = Math.max(Math.abs(width / 2), 1); let h = Math.max(Math.abs(height / 2), 1); let box = new Box3Ext(new Vector3(-w, -h, 0), new Vector3(w, h, 0)); let offset = new Vector3; if (this.TextAligen & TextAligen.LeftMid) offset.x = w; if (this.TextAligen & TextAligen.RightMid) offset.x = -w; if (this.TextAligen & TextAligen.Top) offset.y = -h; if (this.TextAligen & TextAligen.Down) offset.y = h; box.translate(offset); return box; } UpdateTranslate() { for (let [type, obj] of this._CacheDrawObject) { this.UpdateObjectTranslate(obj); } } UpdateObjectTranslate(obj) { if (obj.children.length > 0) { let [mesh, mesh2] = obj.children; let box = mesh.geometry.boundingBox; let p = new Vector3(); if (this._Align & TextAligen.LeftMid) p.x = box.min.x; else if (this._Align & TextAligen.RightMid) p.x = box.max.x; else p.x = (box.min.x + box.max.x) / 2; if (this._Align & TextAligen.Top) p.y = box.max.y; else if (this._Align & TextAligen.Down) p.y = box.min.y; else p.y = (box.min.y + box.max.y) / 2; mesh.matrix = new Matrix4().makeRotationZ(this.TextRotation).multiply(new Matrix4().setPosition(p.negate())); if (mesh2 && this.IsDoubleMesh) { //左右视图时应该这样,俯视图是X,-Y,-Z(y+=(xxx) if (this.IsFsText) { p.y += (box.max.y - box.min.y); mesh2.matrix = new Matrix4().makeBasis(XAxis, YAxisN, ZAxisN).premultiply(new Matrix4().makeRotationZ(this.TextRotation).multiply(new Matrix4().setPosition(p))); } else { p.x += (box.max.x - box.min.x); mesh2.matrix = new Matrix4().makeBasis(XAxisN, YAxis, ZAxisN).premultiply(new Matrix4().makeRotationZ(this.TextRotation).multiply(new Matrix4().setPosition(p))); } } obj.updateMatrixWorld(true); } } ApplyMatrix(m) { super.ApplyMatrix(m); return this; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let p1 = this.Position; let p2 = new Vector3(0, this.Height).applyMatrix4(this.OCS); [p1, p2].forEach(p => p.applyMatrix4(m)); this.Position = p1; this.Height = p1.distanceTo(p2); return this; } InitDrawObject(renderType = RenderType.Wireframe) { let g = new Object3D(); if (renderType !== RenderType.Wireframe) { let obj = this.CacheDrawObject.get(RenderType.Wireframe); if (obj && obj.children.length === 1 && obj.children[0].geometry) { let color = (renderType > 100 || renderType === RenderType.Print) ? 0 : this.DrawColorIndex; let mesh = new Mesh(obj.children[0].geometry, ColorMaterial.GetBasicMaterial(color)); g.add((mesh)); g.updateMatrixWorld(true); this.UpdateObjectTranslate(g); return g; } } this.AsyncUpdateDrawObject(g, renderType); return g; } UpdateDrawObject(type, obj) { this.AsyncUpdateDrawObject(obj, type); } UpdateDrawObjectMaterial(renderType, en) { if (en && en.children.length === 1) { let mesh = en.children[0]; //因为我们是OnlyRnderType 所以Print会变成WireframePrint,所以要用下面的写法 let color = (renderType > 100 || renderType === RenderType.Print) ? 0 : this.DrawColorIndex; mesh.material = ColorMaterial.GetBasicMaterialDoubleSide(color); } } GetGripPoints() { return [this.Position, new Vector3(0, this.Height).applyMatrix4(this.OCS)]; } MoveGripPoints(indexList, vec) { if (indexList[0] === 0) this.Position = this.Position.add(vec); else { let v = vec.clone().applyMatrix4(this.OCSInv.setPosition(new Vector3)); this.Height = this.Height + v.y; } } //捕捉点 GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { if (snapMode === ObjectSnapMode.End) { let box = this.BoundingBoxInOCS; let p2 = new Vector3(box.min.x, box.max.y); let p3 = new Vector3(box.max.x, box.min.y); let pts = [box.min, p2, p3, box.max]; for (let p of pts) p.applyMatrix4(this.OCSNoClone); return pts; } return []; } GetStretchPoints() { return [this.Position]; } MoveStretchPoints(indexList, vec) { this.ApplyMatrix(MoveMatrix(vec)); } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 ReadFontStyle(file) { this._FontStyleOverride.clear(); let size = file.Read(); for (let i = 0; i < size; i++) { let k = file.Read(); let v = file.Read(); this._FontStyleOverride.set(k, v); } } WriteFontStyle(file) { file.Write(this._FontStyleOverride.size); for (let [k, v] of this._FontStyleOverride) { file.Write(k); file.Write(v); } } //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 this._TextString = file.Read(); if (ver < 2) this.Height = file.Read(); this.TextRotation = file.Read(); if (ver < 2) this.FontName = file.Read(); this._Align = file.Read(); //从版本2开始使用FontStyle if (ver >= 2) { this.ReadFontStyle(file); this._FontStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentFontStyle; } if (ver > 2) { this._RoomName = file.Read(); this._CabinetName = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(3); //ver file.Write(this._TextString); file.Write(this.TextRotation); file.Write(this._Align); this.WriteFontStyle(file); file.WriteHardObjectId(this._FontStyle); file.Write(this._RoomName); file.Write(this._CabinetName); } }; Text = __decorate([ Factory ], Text); const DbText = Text; var Board_1; //排钻配置名是合法的 可用的 function IsValidDriName(name) { return name === DrillType.None || HostApplicationServices.DrillConfigs.has(name); } /** * 板件实体 */ let Board = Board_1 = class Board extends ExtrudeSolid { constructor() { super(); this.HasEdgeRenderType = true; this.HasPlaceFaceRenderType = true; this.HasBigHoleFaceRenderType = true; this._Rotation = { x: 0, y: 0, z: 0 }; this._Name = ""; //板件排钻表,与之碰撞板件为key this._DrillList = new Map(); this._LayerNails = []; this.RelativeHardware = []; this._OpenDir = BoardOpenDir.None; this.RelativeHandle = []; this._IsChaiDan = true; this._2DModelingList = []; this._3DModelingList = []; //侧面造型 this._SideModelingMap = new Map(); this._CustomNumber = null; //自定义编号 this._DrillLock = false; //排钻独立锁 this._DrillAssociationLock = new Set(); //排钻关联锁 //放样路径 基于OPt X Z -Y 坐标系为基准的路径 this._SweepPath = undefined; this._SweepAngle = 0; /** 见光面 */ this._SweepVisibleFace = FaceDirection.Front; this._SweepArcBoardBuild = undefined; //用于建模和计算的类 不保证其正确性(绘制更新可能存在延迟) //圆弧板每一段圆弧的槽配置 this._ArcBoardOptions = new Map(); this._isDrawArcGroove = true; //仅在渲染器中使用倒角 this.bevelEnabled = true; //二维刀路 id -> polyline this._KnifePolylineMap = new Map(); this._FixContourByArcSweepPath_Ing = false; //侧面关联槽 this.RelevanceSideModelMap = new Map(); this.UpdateSplitBoardSideModelUtil = true; this._asyncSideModelIng = false; this._workerCalcedGeom = null; //worker计算后,暂时存入到这里 this._async2DPathIng = false; //偏移缓存 this.OffsetPathCache = new Map(); this.InitBoardData(); } get BoundingBoxInOCS() { if (this._SweepPath) return new Box3Ext().copy(this.MeshGeometry.boundingBox); return super.BoundingBoxInOCS; } get BoundingBox() { if (this._SweepPath) { let geom = this.MeshGeometry; const position = geom.attributes.position; if (position) { let vec = new Vector3; let box = new Box3Ext; for (let i = 0, il = position.count; i < il; i++) { vec.fromBufferAttribute(position, i); vec.applyMatrix4(this.OCSNoClone); box.expandByPoint(vec); } return box; } } return super.BoundingBox; } get IsDrawArcGroove() { return this._isDrawArcGroove; } set IsDrawArcGroove(v) { this.WriteAllObjectRecord(); this._isDrawArcGroove = v; } /** * path 发生改变,更新圆弧配置 * @param {{ key: number, arc: Arc; }[]} oldArcs 旧圆弧和对应的board options key * @param {Polyline} nPath 新路径(未进行起点偏移) * @return {*} {void} */ UpdateArcBoardOptionsByPath(oldArcs, nPath) { if (oldArcs.length === 0) return; const nCurves = nPath.Explode(); const newArcs = []; for (let i = 0; i < nCurves.length; i++) if (nCurves[i] instanceof Arc) newArcs.push({ key: i, arc: nCurves[i] }); const newOpts = new Map(); const oldOpts = this._ArcBoardOptions; newOpts.set(-1, oldOpts.get(-1)); // 设置新圆弧对应的原始配置 for (let i = 0; i < newArcs.length; i++) for (const item of oldArcs) { const oldArc = item.arc; const oldKey = item.key; const newArc = newArcs[i].arc; const newKey = newArcs[i].key; if (equalv3(newArc.StartPoint, oldArc.StartPoint, 1e-5) || equalv3(newArc.EndPoint, oldArc.EndPoint, 1e-5)) // 若圆弧的头或尾部位置相同,说明是同一段圆弧 { newOpts.set(newKey, { ...oldOpts.get(oldKey), arcLength: parseFloat(FixedNotZero(newArc.Length, 5)) }); break; } } // 曲线从头拉到尾部后面,或者从尾拉到头部前面 if (newArcs.length === 1 && newOpts.size === 1) { let oldArcIndex = 0; // 第一个圆弧 if (equalv3(newArcs[0].arc.StartPoint, arrayLast(oldArcs).arc.EndPoint, 1e-3)) // 从头拉伸到尾部后面,则新圆弧的起点和旧圆弧的尾点相等。 oldArcIndex = oldArcs.length - 1; // 最后一个圆弧 const oldKey = oldArcs[oldArcIndex].key; const newKey = newArcs[0].key; newOpts.set(newKey, { ...oldOpts.get(oldKey), arcLength: parseFloat(FixedNotZero(newArcs[0].arc.Length, 5)) }); } this._ArcBoardOptions.clear(); this._ArcBoardOptions = newOpts; } get ArcBoardOptions() { if (this._ArcBoardOptions.size > 0) return this._ArcBoardOptions; let cus = new ArcBoardBuild(this).ParseSweepCurves().SweepCurves1; //key=-1 为通用转角槽配置 this._ArcBoardOptions.set(-1, { ...defultArcBoardOption }); cus.forEach((cu, i) => { if (cu instanceof Arc) this._ArcBoardOptions.set(i, { ...defultArcBoardOption, arcLength: parseFloat(FixedNotZero(cu.Length, 5)) }); }); return this._ArcBoardOptions; } set ArcBoardOptions(opt) { this.WriteAllObjectRecord(); this._ArcBoardOptions = opt; this.UpdateArcBoardOptions(false); this.Update(); } UpdateArcBoardOptions(isNewPath) { //更新ArcBuild曲线数据 this._SweepArcBoardBuild = new ArcBoardBuild(this).ParseSweepCurves(); let cus = this.GetSweepPathInWCS().Explode(); let newOpts = new Map(); newOpts.set(-1, this._ArcBoardOptions.get(-1)); //如果是新的多段线信息,就更新全部数据 if (isNewPath) { cus.forEach((cu, i) => { if (cu instanceof Arc) newOpts.set(i, { ...this._ArcBoardOptions.get(i), arcLength: parseFloat(FixedNotZero(cu.Length, 5)) }); }); } else if (cus.filter(cu => cu instanceof Arc).length <= this._ArcBoardOptions.size - 1) { cus.forEach((cu, i) => { if (cu instanceof Arc && this._ArcBoardOptions.has(i)) newOpts.set(i, { ...this._ArcBoardOptions.get(i), arcLength: parseFloat(FixedNotZero(cu.Length, 5)) }); }); } this._ArcBoardOptions = newOpts; } get IsArcBoard() { return this._SweepPath != undefined; } get ArcBuild() { if (!this._SweepArcBoardBuild) this._SweepArcBoardBuild = new ArcBoardBuild(this); return this._SweepArcBoardBuild; } SetSweepPath(path, sweepAngle) { this.WriteAllObjectRecord(); this._SweepPath = path; this._SweepAngle = sweepAngle; this.Update(); } FixContourByArcSweepPath() { if (!this._SweepPath) return; //标记正在修改轮廓 避免重复进入 this._FixContourByArcSweepPath_Ing = true; let build = new ArcBoardBuild(this); let length = build.SweepLength; if (this._SweepAngle === 0) { if (!equaln$1(length, this.width, 1e-3)) this.Width = length; //直接拉大拉小 } else if (equaln$1(Math.abs(this._SweepAngle), Math.PI / 2)) { if (!equaln$1(length, this.height, 1e-3)) this.Height = length; //直接拉大拉小 } else { let con = this.ContourCurve.Clone(); let ro = new Matrix4().makeRotationZ(-this._SweepAngle); con.ApplyMatrix(ro); con.Move(con.StartPoint.negate()); let box = con.BoundingBox; let size = box.max.x - box.min.x; if (!equaln$1(size, length)) { let contour = Contour.CreateContour(con, false); contour.UnEqualProportionScale(size / 2, length - size, "x"); let ro = new Matrix4().makeRotationZ(this._SweepAngle); contour.Curve.ApplyMatrix(ro); contour.Curve.Move(contour.Curve.BoundingBox.min.negate()); this.SetContourCurve(contour.Curve); } } this._FixContourByArcSweepPath_Ing = false; } FixArcSweepPathLength() { if (!this._SweepPath) return; let build = new ArcBoardBuild(this); let length = build.SweepLength; let brLength = this.ParseBoardLengthInArcSweep(); if (equaln$1(brLength, length, 1e-3)) return; if (brLength > length) { //延伸路径 let endp = this._SweepPath.EndPoint; let derv = this._SweepPath.GetFirstDeriv(this._SweepPath.EndParam).normalize(); if (this._SweepPath.GetCurveAtParam(this._SweepPath.EndParam - 0.5) instanceof Arc) this._SweepPath.AddVertexAt(this._SweepPath.LineData.length, AsVector2(endp.add(derv.multiplyScalar(brLength - length)))); else this._SweepPath.LineData[this._SweepPath.LineData.length - 1].pt.copy(AsVector2(endp.add(derv.multiplyScalar(brLength - length)))); } else { if (this._SweepVisibleFace === FaceDirection.Back) { //裁剪路径 let param = this._SweepPath.GetParamAtDist(brLength); this._SweepPath = this._SweepPath.GetSplitCurves(param)[0]; } else { //裁剪路径 let param = build.SweepPath1.GetParamAtDist(brLength); let path = build.SweepPath1.GetSplitCurves(param)[0]; this._SweepPath = ArcBoardBuild.OffsetPolyline(path, this.thickness); } } } ParseBoardLengthInArcSweep() { let brLength; if (this._SweepAngle === 0) brLength = this.width; else if (equaln$1(Math.abs(this._SweepAngle), Math.PI / 2)) brLength = this.height; else { let con = this.ContourCurve.Clone(); let ro = new Matrix4().makeRotationZ(-this._SweepAngle); con.ApplyMatrix(ro); let box = con.BoundingBox; brLength = box.max.x - box.min.x; } return brLength; } ClearSweepPath() { this.WriteAllObjectRecord(); this._SweepPath = undefined; this._SweepAngle = 0; this.Update(); } GetSweepPath() { return this._SweepPath; } //获得见光path GetSweepPathInWCS() { if (!this._SweepPath) return; let path = this.ArcBuild.SweepPath1.Clone(); let mtx = new Matrix4().makeBasis(XAxis, ZAxis, YAxisN) .premultiply(this.ArcBuild.Rotate2OCSMtx) .premultiply(this.OCSNoClone); path.ApplyMatrix(mtx); return path; } //背面 GetSweepPath1InWCS() { if (!this._SweepPath) return; let path = this._SweepPath.Clone(); let mtx = new Matrix4().makeBasis(XAxis, ZAxis, YAxisN) .premultiply(this.ArcBuild.Rotate2OCSMtx) .premultiply(this.OCSNoClone); path.ApplyMatrix(mtx); return path; } //正面 GetSweepPath2InWCS() { if (!this._SweepPath) return; let path = this._SweepVisibleFace === FaceDirection.Back ? this.ArcBuild.SweepPath2.Clone() : this.ArcBuild.SweepPath1.Clone(); let mtx = new Matrix4().makeBasis(XAxis, ZAxis, YAxisN) .premultiply(this.ArcBuild.Rotate2OCSMtx) .premultiply(this.OCSNoClone); path.ApplyMatrix(mtx); return path; } /** 获取见光面 */ get SweepVisibleFace() { return this._SweepVisibleFace; } /** 设置见光面 */ set SweepVisibleFace(dir) { if (dir === this._SweepVisibleFace) return; this.WriteAllObjectRecord(); this._SweepVisibleFace = dir; this.FixContourByArcSweepPath(); this.Update(); } get SweepAngle() { return this._SweepAngle; } set SweepAngle(v) { if (equaln$1(v, this._SweepAngle)) return; this.WriteAllObjectRecord(); this._SweepAngle = v; this.FixArcSweepPathLength(); this.Update(); } AdjustPath(br, outline) { if (this.IsArcBoard) { const path = this.GetSweepPath(); // 1.将放样路径展开 const pathWCS = this.GetSweepPathInWCS(); pathWCS.ApplyMatrix(pathWCS.OCSInv); const expandPath = CurveManager.CreateExpandPl(pathWCS); // 2.将轮廓进行旋转 outline.ApplyMatrix(new Matrix4().makeRotationZ(-this.SweepAngle)); // 3.根据轮廓的左右两侧,截取取对应的放样路径 const leftParam = expandPath.GetParamAtDist(outline.BoundingBox.min.x); const rightParam = expandPath.GetParamAtDist(outline.BoundingBox.max.x); const leftPt = path.GetPointAtParam(leftParam); const rightPt = path.GetPointAtParam(rightParam); const newPath = CurveManager.Get_Pl_InPtAtoPtB(path, leftPt, rightPt); // 4.路径起点为原点 newPath.Move(newPath.StartPoint.negate()); br.SetSweepPath(newPath, this.SweepAngle); } } AdjustPosition(br, basePoint) { if (this.IsArcBoard) { // 映射shape上的基点 basePoint.applyMatrix4(this.ArcBuild.OCS2RotateMtx); this.ArcBuild.ParseAllX_Map([basePoint.x]); this.ArcBuild.PosMap2ArcPos(basePoint); basePoint.applyMatrix4(this.ArcBuild.Rotate2OCSMtx); basePoint.applyMatrix4(this.OCSNoClone); // 映射board上的基点 const basePointInBoard = br.ContourCurve.StartPoint; basePointInBoard.applyMatrix4(br.ArcBuild.OCS2RotateMtx); br.ArcBuild.ParseAllX_Map([basePointInBoard.x]); br.ArcBuild.PosMap2ArcPos(basePointInBoard); basePointInBoard.applyMatrix4(br.ArcBuild.Rotate2OCSMtx); basePointInBoard.applyMatrix4(br.OCSNoClone); // 移动(boardBasePoint -> basePoint) br.Move(basePoint.sub(basePointInBoard)); } } /** * 创建一个代理数组,数组改变时被监听 */ CreateArray() { return new Proxy([], { set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) this.WriteAllObjectRecord(); return Reflect.set(target, key, value, receiver); } }); } InitBoardData() { let defaultData = { roomName: "", cabinetName: "", boardName: "", material: "", color: "", lines: LinesType.Positive, bigHoleDir: FaceDirection.Front, composingFace: ComposingType.Arbitrary, highSealed: this.CreateArray(), sealedUp: "1", sealedDown: "1", sealedLeft: "1", sealedRight: "1", spliteHeight: "", spliteWidth: "", spliteThickness: "", highDrill: this.CreateArray(), frontDrill: true, backDrill: true, drillType: "", remarks: this.CreateArray(), edgeRemarkUp: "", edgeRemarkDown: "", edgeRemarkLeft: "", edgeRemarkRight: "", highBoardEdgeRemark: this.CreateArray(), reservedEdgeUp: "0", reservedEdgeDown: "0", reservedEdgeRight: "0", reservedEdgeLeft: "0", highReservedEdge: this.CreateArray(), sealColorUp: "", sealColorDown: "", sealColorLeft: "", sealColorRight: "", sealColorType: "", }; this._BoardProcessOption = new Proxy(defaultData, { get: function (target, key, receiver) { return Reflect.get(target, key, receiver); }, set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) { this.WriteAllObjectRecord(); if (key === "highDrill" || key === EBoardKeyList.HighSealed || key === "highBoardEdgeRemark" || key === "highReservedEdge") { let arr = this.CreateArray(); arr.push(...value); target[key] = arr; //更新封边检查的显示 if (!this.__ReadFileIng__ && key === EBoardKeyList.HighSealed) { let obj = this.CacheDrawObject.get(RenderType.Edge); if (obj) { this.UpdateDrawObject(RenderType.Edge, obj); obj.updateMatrixWorld(true); } } return true; } let result = Reflect.set(target, key, value, receiver); if (!this.__ReadFileIng__) //看起来使用 this.ReadFileIng 似乎也是没问题的 { if (key === EBoardKeyList.Lines) this.Update(UpdateDraw.Geometry); else if (key === EBoardKeyList.ComposingFace) { let obj = this.CacheDrawObject.get(RenderType.PlaceFace); if (obj) { this.UpdateDrawObject(RenderType.PlaceFace, obj); obj.updateMatrixWorld(true); } } else if (key === EBoardKeyList.BigHole) { let obj = this.CacheDrawObject.get(RenderType.BigHoleFace); if (obj) { this.UpdateDrawObject(RenderType.BigHoleFace, obj); obj.updateMatrixWorld(true); } } } return result; } return true; } }); } //初始化板件 来自长宽高 InitBoard(length, width, thickness, boardType = BoardType.Layer) { if (!length || !width || !thickness) throw `无法使用该尺寸构建板,长:${length},宽:${width},厚:${thickness}`; this.WriteAllObjectRecord(); this._BoardType = boardType; if (boardType === BoardType.Layer) { this.ColorIndex = 2; this._Name = "层板"; } else if (boardType === BoardType.Vertical) { this.ColorIndex = 11; this._Name = "立板"; } else { this.ColorIndex = 3; this._Name = "背板"; } let types = [...HostApplicationServices.DrillConfigs.keys(), "不排"]; let type = types.includes(this.BoardProcessOption.drillType) ? this.BoardProcessOption.drillType : types[0]; this._BoardProcessOption.drillType = type; this._BoardProcessOption.highDrill = Array(4).fill(type); this.ConverToRectSolid(width, length, thickness); this.Update(UpdateDraw.Geometry); return this; } static CreateBoard(length, width, thickness, boardType = BoardType.Layer) { let board = new Board_1(); board.InitBoard(length, width, thickness, boardType); board.ApplyMatrix(board.RotateMat); board._SpaceOCS.identity(); return board; } get DrillList() { return this._DrillList; } get DrillLock() { return this._DrillLock; } set DrillLock(v) { this.WriteAllObjectRecord(); this._DrillLock = v; } get isDrillAssociationLock() { //有一块关联的板就返回true for (const o of this._DrillAssociationLock) { if (o.Object && !o.Object.IsErase) return true; } return false; } get DrillAssociationLock() { return this._DrillAssociationLock; } AppendDrillAssociationLock(o) { this.WriteAllObjectRecord(); this._DrillAssociationLock.add(o); } ClearDrillAssociationLock(o) { this.WriteAllObjectRecord(); this._DrillAssociationLock.delete(o); } ClearAllAssociDrillLock() { this.WriteAllObjectRecord(); for (const o of this._DrillAssociationLock) { this.ClearDrillAssociationLock(o); if (o && o.Object) { let br = o.Object; br.ClearDrillAssociationLock(this.Id); } } } get LayerNails() { return this._LayerNails; } AppendNails(ids) { this.WriteAllObjectRecord(); this._LayerNails.push(...ids); } ClearLayerNails() { this.WriteAllObjectRecord(); for (let nail of this._LayerNails) { if (nail.Object && !nail.IsErase) nail.Object.Erase(); } this._LayerNails.length = 0; } get CustomNumber() { return this._CustomNumber; } set CustomNumber(n) { if (n !== this._CustomNumber) { this.WriteAllObjectRecord(); this._CustomNumber = n; if (this._CustomNumberTextEntity) { this._CustomNumberTextEntity.TextString = n?.toString() ?? ""; this._CustomNumberTextEntity.Visible = this._CustomNumberTextEntity.TextString !== ""; this._CustomNumberTextEntity.Height = HostApplicationServices.boardCustomNumberTextHeight; } } } /** * 你可以安心的修改它,这样会直接影响到板件,因为板件对这个对象添加了代理. */ get BoardProcessOption() { return this._BoardProcessOption; } set BoardProcessOption(obj) { Object.assign(this._BoardProcessOption, obj, { [EBoardKeyList.HighSealed]: (obj[EBoardKeyList.HighSealed]).slice(), highBoardEdgeRemark: (obj.highBoardEdgeRemark).slice(), highReservedEdge: (obj.highReservedEdge).slice() }); } get NeedUpdateRelevanceGroove() { if (super.NeedUpdateRelevanceGroove) return true; for (let k of this.RelativeHardware) { if (!k || !k.Object) continue; if (this.__CacheKnifVersion__[k.Index] !== (k.Object).__UpdateVersion__) return true; } return false; } GetRelevanceKnifes(knifs) { super.GetRelevanceKnifes(knifs); for (let e of this.RelativeHardware) { if (e.IsErase) continue; let hardware = e.Object; if (hardware instanceof HardwareCompositeEntity) { if (hardware.HardwareOption.isHole) { let holes = hardware.GetAllEntity(true, e => e instanceof ExtrudeHole || e instanceof ExtrudeSolid); for (let i = 0; i < holes.length; i++) { let h = holes[i]; let g = h instanceof ExtrudeHole ? h.Convert2ExtrudeSolid() : h; g.__TempIndexVersion__ = { Index: hardware.Id.Index, Version: hardware.__UpdateVersion__ }; knifs.push(g); } } } } } ClearRelevance(en) { for (let id of [...this.RelativeHardware, ...this.RelativeHandle]) { let e = id.Object; if (e instanceof HardwareCompositeEntity) { arrayRemoveIf(e.RelevanceBoards, i => !i || i.Index === this.Id.Index); } } this.RelativeHardware.length = 0; this.RelativeHandle.length = 0; super.ClearRelevance(en); } get SplitBoards() { let brs = this.SplitExtrudes; let highDrills; let ocsInv; if (brs.some(br => br.__OriginalEnt__)) { if (this._BoardProcessOption.highDrill && this._BoardProcessOption.highDrill.length > 1 && !this._BoardProcessOption.highDrill.every(d => d === this._BoardProcessOption.drillType)) { highDrills = this._BoardProcessOption.highDrill; } ocsInv = this.OCSInv; } let oldContour = this.ContourCurve; //旧的轮廓 let oldHightSealCurves = GetBoardSealingCurves(this); //旧的封边轮廓 let oldHightSealDatas = GetBoardHighSeal(this, oldHightSealCurves); //旧的封边数据 //拆单或者bbs的时候会重新加入最新的原板件的排钻和层板钉数据 for (let br of brs) { if (br.__OriginalEnt__) { br._Name = this._Name; br._DrillList = new Map(this._DrillList.entries()); br._LayerNails = [...this._LayerNails]; br.ProcessingGroupList = [...this.ProcessingGroupList]; br.AlignLineObject = this.AlignLineObject; br._BoardProcessOption = { ...this._BoardProcessOption }; br._CustomNumber = this._CustomNumber; //因为CustomNumber不会刷新绘制,所以拷贝这个 br._DrillLock = this._DrillLock; //!2726 关联切割后的引用实体需要复制这个属性,否则反应器无法更新 br._SideModelingMap = this.SideModelingMap; br.RelevanceSideModelMap = this.RelevanceSideModelMap; //关联切割侧槽 let new2old_edgeMap; //修正排钻边的数据 if (highDrills) { br.BoardProcessOption.highDrill = []; //因为上面用了拷贝,所以这里不能直接改它的数据(我们新建一个数组来改它,否则原始板件的数据就被改掉了) new2old_edgeMap = ParseNewBr2OldBr_EdgeMap(br, this, ocsInv); for (let index of new2old_edgeMap) { let dri = highDrills[index]; if (dri !== undefined) br._BoardProcessOption.highDrill.push(dri); else br._BoardProcessOption.highDrill.push(br._BoardProcessOption.drillType); } } else //填充默认类型就好了 br._BoardProcessOption.highDrill = Array(br.contourCurve.EndParam).fill(br._BoardProcessOption.drillType); //保持封边属性(代码来自SetContourCurve) br._BoardProcessOption.highSealed = []; if (br.isRect) { SetBoardTopDownLeftRightSealData(br, oldHightSealDatas, oldHightSealCurves, oldContour); } else //变成了异形 { let newhightSealCurves = GetBoardSealingCurves(br); for (let i = 0; i < newhightSealCurves.length; i++) { let newC = newhightSealCurves[i]; let p = newC.GetPointAtParam(newC.EndParam * 0.5); let closesIndex = 0; let closesDistance = Infinity; for (let j = 0; j < oldHightSealCurves.length; j++) { let oldC = oldHightSealCurves[j]; let d = oldC.GetClosestPointTo(p, false).distanceTo(p); if (d < closesDistance) { closesIndex = j; closesDistance = d; } } br._BoardProcessOption.highSealed.push(oldHightSealDatas[closesIndex]); } } } } return brs; } get ArcBoardModeling() { if (!this.IsArcBoard || !this.IsDrawArcGroove) return []; const { _SweepPath: path, _SweepAngle: angle, SweepVisibleFace: dir, ArcBoardOptions } = this; return ParseBoardArcFeed(this, path, angle, dir, ArcBoardOptions, false); } get BoardModeling() { let models = []; for (let g of this.grooves) { let cu = g.ContourCurve.Clone().ApplyMatrix(this.OCSInv.multiply(g.OCSNoClone)); cu.Erase(false); //当内部造型超过100个时,不校验时,这个曲线就是erase的状态,所以设置这个状态,避免无法绘制出来 if (cu instanceof Circle) //这里保证这个圆是正常坐标系 I3BUSY#note_4525213 cu.OCS = new Matrix4().setPosition(cu.Position); let outline = Contour.CreateContour(cu, false); let holes = []; for (let subG of g.Grooves) { let holeCu = subG.ContourCurve.Clone().ApplyMatrix(this.OCSInv.multiply(subG.OCSNoClone)); holes.push(Contour.CreateContour(holeCu, false)); } let s = new Shape(outline, holes); models.push({ shape: s, thickness: g.Thickness, dir: equaln$1(g.Position.applyMatrix4(this.OCSInv).z, 0) && g.Thickness < this.thickness - 1e-6 ? FaceDirection.Back : FaceDirection.Front, knifeRadius: g.KnifeRadius, addLen: g.GroovesAddLength, addWidth: g.GroovesAddWidth, addDepth: g.GroovesAddDepth, }); } return models; } set BoardModeling(models) { this.WriteAllObjectRecord(); this.grooves.length = 0; for (let model of models) { let g = new ExtrudeSolid(); g.ContourCurve = model.shape.Outline.Curve; g.Thickness = model.thickness; g.GroovesAddLength = model.addLen; g.KnifeRadius = model.knifeRadius; for (let hole of model.shape.Holes) { let subG = new ExtrudeSolid(); subG.ContourCurve = hole.Curve; subG.Thickness = model.thickness; g.AppendGroove(subG); } let gz = 0; //槽的z轴位置 (由于旋转轮廓或者镜像轮廓在Set时会被清除掉坐标系,我们还是需要正确的搞定z轴) if (model.dir === FaceDirection.Front) gz = this.thickness - g.Thickness; let moveZ = gz - g.Position.z; if (!equaln$1(moveZ, 0)) g.Move({ x: 0, y: 0, z: moveZ }); g.ApplyMatrix(this.OCSNoClone); this.grooves.push(g); } this.GrooveCheckAllAutoSplit(); this.Update(); } //二维刀路 get Modeling2D() { for (const m of this._2DModelingList) { for (const item of m.items) { let d = safeEval(item.depthExpr, { BH: this.thickness }); if (!isNaN(d) && d != item.depth) { item.depth = d; } } } return [...this._2DModelingList]; } set Modeling2D(ms) { this.WriteAllObjectRecord(); this._2DModelingList = ms; this.Clear2DPathCache(); this.Update(UpdateDraw.Geometry); } get Modeling3D() { return [...this._3DModelingList]; } set Modeling3D(ms) { this.WriteAllObjectRecord(); this.Clear3DPathCache(); this._3DModelingList = ms; this.Update(UpdateDraw.Geometry); } //侧面造型 get SideModelingMap() { return this._SideModelingMap; } set SideModelingMap(sideModelingMap) { this.WriteAllObjectRecord(); this.ClearSideModelingCache(); this._SideModelingMap = sideModelingMap; this.Update(UpdateDraw.Geometry); } //获取侧面关联槽和侧面造型 get AllSideModelGrooveMap() { let allSideModelGrooveMap = new Map(); for (let [n, soilds] of this._SideModelingMap) allSideModelGrooveMap.set(n, [...soilds]); for (let [n, soilds] of this.RelevanceSideModelMap) { let ss = allSideModelGrooveMap.get(n) ?? []; for (let soild of soilds) ss.push(soild); allSideModelGrooveMap.set(n, ss); } return allSideModelGrooveMap; } ClearSideModeling() { if (!this._SideModelingMap.size) return; this.WriteAllObjectRecord(); this.ClearSideModelingCache(); this._SideModelingMap.clear(); this.Update(UpdateDraw.Geometry); } ClearModeling2DList() { if (this._2DModelingList.length === 0) return; this.WriteAllObjectRecord(); this._2DModelingList.length = 0; this.Clear2DPathCache(); this.Update(UpdateDraw.Geometry); } ClearModeling3DList() { if (this._3DModelingList.length === 0) return; this.WriteAllObjectRecord(); this._3DModelingList.length = 0; this.Clear3DPathCache(); this.Update(UpdateDraw.Geometry); } get IsChaiDan() { return this._IsChaiDan; } set IsChaiDan(v) { if (this._IsChaiDan !== v) { this.WriteAllObjectRecord(); this._IsChaiDan = v; this.Update(UpdateDraw.Geometry); } } get OpenDir() { return this._OpenDir; } set OpenDir(v) { if (this._OpenDir !== v) { this.WriteAllObjectRecord(); this._OpenDir = v; //开门方向改变更新 this.Update(UpdateDraw.Geometry); } } ClearBoardModeling() { if (this.grooves.length === 0) return; this.WriteAllObjectRecord(); this.grooves.length = 0; this.Update(UpdateDraw.Geometry); } /** * 注意传入的排钻列表,避免指针被引用 */ AppendDrillList(k, drs) { this.WriteAllObjectRecord(); let oldDrs = this._DrillList.get(k); if (oldDrs) oldDrs.push(...drs); //同类型板件时,会触发这里. else this._DrillList.set(k, drs); } ClearDrillList(k) { let drids = this._DrillList.get(k); if (drids) { this.WriteAllObjectRecord(); for (let drillents of drids) { for (let objId of drillents) { if (objId && !objId.IsErase) objId.Object.Erase(); } } this._DrillList.delete(k); if (k && k.Object) { //必须在这里删除 let br = k.Object; br.ClearDrillList(this.Id); } } } ClearAllDrillList() { for (const [id] of this._DrillList) { this.ClearDrillList(id); } } Erase(isErase = true) { if (isErase === this.IsErase) return; super.Erase(isErase); if (!isErase) return; //记录数据,避免下面记录的时候,排钻已经被删除,导致排钻数据被优化掉. this.WriteAllObjectRecord(); for (const [, driss] of this._DrillList) { for (let dris of driss) for (let d of dris) if (d && d.Object) d.Object.Erase(); } this.ClearLayerNails(); } get RotateMat() { let roMat = new Matrix4(); switch (this._BoardType) { case BoardType.Layer: roMat.makeBasis(YAxis, XAxisN, ZAxis); break; case BoardType.Vertical: roMat.makeBasis(YAxis, ZAxis, XAxis); break; case BoardType.Behind: roMat.makeBasis(XAxis, ZAxis, YAxisN); } return roMat; } get Height() { return this.height; } set Height(v) { if (this.ContourCurve instanceof Circle) return; if (!equaln$1(v, this.height, 1e-2)) { this.WriteAllObjectRecord(); let refHeight = this.height / 2; let dist = v - this.height; let contour = Contour.CreateContour(this.ContourCurve, false); let isSuccess = contour.UnEqualProportionScale(refHeight, dist, "y"); if (isSuccess) { this.height = v; this.GrooveCheckAllAutoSplit(); if (this._SweepPath && !this._FixContourByArcSweepPath_Ing) this.FixArcSweepPathLength(); if (this.HasSideModel) this.SplitBoardSideModelUtil.SpiltSideModelOfBrContour(this); this.Update(); } } } get Width() { return this.width; } set Width(v) { if (this.ContourCurve instanceof Circle) return; if (!equaln$1(v, this.width, 1e-2)) { this.WriteAllObjectRecord(); let refDist = this.width / 2; let dist = v - this.width; let contour = Contour.CreateContour(this.ContourCurve, false); let isSuccess = contour.UnEqualProportionScale(refDist, dist, "x"); if (isSuccess) { this.width = v; this.GrooveCheckAllAutoSplit(); if (this._SweepPath && !this._FixContourByArcSweepPath_Ing) this.FixArcSweepPathLength(); if (this.HasSideModel) this.SplitBoardSideModelUtil.SpiltSideModelOfBrContour(this); this.Update(); } } } get Thickness() { return super.Thickness; } set Thickness(thickness) { if (!equaln$1(thickness, this.thickness, 1e-4)) //避免18.0009 无法改成 18 { if (this.HasSideModel) this.SplitBoardSideModelUtil.SpiltSideModelOfBrThickness(this, thickness); super.Thickness = thickness; if (this._SweepPath && !this._FixContourByArcSweepPath_Ing) this.FixContourByArcSweepPath(); } } get BoardType() { return this._BoardType; } set BoardType(type) { this.WriteAllObjectRecord(); if (type !== this._BoardType) { let spaceCS = this._SpaceOCS.clone(); this._BoardType = type; this.ApplyMatrix(this.OCSInv); this.ApplyMatrix(this.RotateMat); this._SpaceOCS.identity(); this.ApplyMatrix(spaceCS); this.Update(); } } //设置板件类型并且不做任何的事情 SetBoardType(type) { if (type === this._BoardType) return; this.WriteAllObjectRecord(); this._BoardType = type; } //最左下角的点 get MinPoint() { switch (this._BoardType) { case BoardType.Layer: return new Vector3(0, this.height).applyMatrix4(this.OCS); case BoardType.Vertical: return this.Position; case BoardType.Behind: return new Vector3(0, 0, this.thickness).applyMatrix4(this.OCS); } } get MaxPoint() { let pt = new Vector3(this.width, this.height, -this.thickness); pt.applyMatrix4(this.OCS); return pt; } get IsRect() { return this.isRect; } get IsSpecialShape() { return !this.isRect; } get HasGroove() { return this.grooves.length > 0; } get Name() { return this._Name; } set Name(n) { if (n === this._Name) return; this.WriteAllObjectRecord(); this._Name = n; } get SplitBoardSideModelUtil() { if (!this._SplitBoardSideModelUtil) this._SplitBoardSideModelUtil = new SplitBoardSideModelUtil(this); return this._SplitBoardSideModelUtil; } GeneralRectContour() { //取消异型时,强制使用矩形轮廓 导致原始轮廓数据丢失 if (this.HasSideModel) { this.UpdateSplitBoardSideModelUtil = false; this.SplitBoardSideModelUtil.Init(this, true); super.GeneralRectContour(); this.UpdateSplitBoardSideModelUtil = true; } else super.GeneralRectContour(); } /** * 板件的轮廓,在板件坐标系中的表现方式. */ get ContourCurve() { return super.ContourCurve; } //设置新的板件轮廓,这里重载为了保持正确的排钻封边映射 set ContourCurve(newContour) { /** * 保持排钻边和封边数据对应正确性 * (2x2种可能性) * 矩形->矩形 * 矩形->异形 * 异形->异形 * 异形->矩形 */ //上下左右排钻属性(在矩形时生效) let rectHoleOpt = {}; ParseBoardRectHoleType(this, rectHoleOpt); //分析旧的上下左右排钻 let oldHightSealCurves = GetBoardSealingCurves(this); //旧的封边轮廓 let oldHightSealDatas = GetBoardHighSeal(this, oldHightSealCurves); //旧的封边数据 let oldHighBoardEdgeRemarkDatas = GetHighBoardEdgeRemark(this, oldHightSealCurves); //旧的板边备注数据 let oldHighReservedEdgeDatas = GetBoardHighReservedEdge(this, oldHightSealCurves); //旧的预留边数据 let splitSideModel = false; if (this.UpdateSplitBoardSideModelUtil && this.HasSideModel) { this.SplitBoardSideModelUtil.Init(this); //旧的侧面造型 //记录侧面造型后清空 防止在分裂侧面造型时带入更新mesh this.WriteAllObjectRecord(); this._SideModelingMap.clear(); splitSideModel = true; } let oldContour = this.ContourCurve; //旧的轮廓 let defaultDrillType = this._BoardProcessOption.drillType; if (!IsValidDriName(defaultDrillType) && this._BoardProcessOption.highDrill) for (let name of this._BoardProcessOption.highDrill) if (IsValidDriName(name)) { defaultDrillType = name; break; } if (!IsValidDriName(defaultDrillType)) defaultDrillType = HostApplicationServices.DrillConfigs.size ? HostApplicationServices.DrillConfigs.entries().next().value[0] : DrillType.None; super.ContourCurve = newContour; //设置新的轮廓 //保持排钻边属性 if (this.isRect && rectHoleOpt.up) //矩形->矩形 SetBrHighHoleTypeFromRectHoleType(this, rectHoleOpt); //直接应用旧的矩形数据 else //异形->矩形 矩形->异形 异形->异形 { let indexMap = []; for (let i = 0; i < newContour.EndParam; i++) { let p = newContour.GetPointAtParam(i + 0.5); let cp = oldContour.GetClosestPointTo(p, false); let cparam = oldContour.GetParamAtPoint2(cp); indexMap.push(Math.floor(cparam)); } let highDrill = []; for (let index of indexMap) highDrill.push(this._BoardProcessOption.highDrill[index] ?? defaultDrillType); this._BoardProcessOption.highDrill = highDrill; } this._BoardProcessOption.highSealed.length = 0; this._BoardProcessOption.highBoardEdgeRemark.length = 0; this._BoardProcessOption.highReservedEdge.length = 0; //保持封边属性 if (this.isRect) { SetBoardTopDownLeftRightSealData(this, oldHightSealDatas, oldHightSealCurves, oldContour); SetBoardEdgeRemarkData(this, oldHighBoardEdgeRemarkDatas, oldHightSealCurves, oldContour); SetBoardReservedEdgeData(this, oldHighReservedEdgeDatas, oldHightSealCurves, oldContour); } else //变成了异形 { let newhightSealCurves = GetBoardSealingCurves(this); for (let i = 0; i < newhightSealCurves.length; i++) { let newC = newhightSealCurves[i]; let p = newC.GetPointAtParam(newC.EndParam * 0.5); let closesIndex = 0; let closesDistance = Infinity; for (let j = 0; j < oldHightSealCurves.length; j++) { let oldC = oldHightSealCurves[j]; let d = oldC.GetClosestPointTo(p, false).distanceTo(p); if (d < closesDistance) { closesIndex = j; closesDistance = d; } } this._BoardProcessOption.highSealed.push(oldHightSealDatas[closesIndex]); this._BoardProcessOption.highBoardEdgeRemark.push(oldHighBoardEdgeRemarkDatas[closesIndex]); this._BoardProcessOption.highReservedEdge.push(oldHighReservedEdgeDatas[closesIndex]); } } //分裂侧面造型 if (splitSideModel || this.HasSideModel) this.SplitBoardSideModelUtil.SetBoardSideModel(this); } Explode() { return Board2Regions(this); } /** * 在不改变Normal和实体显示的情况下,修改X轴的指向 * @param xAxis */ SetXAxis(xAxis, isKeepLines = false) { let ocsInv = this.OCSInv; let x = TransformVector(xAxis.clone(), ocsInv).setZ(0).normalize(); if (equalv3(ZeroVec, x, 1e-5)) return this; this.WriteAllObjectRecord(); let highSeals = GetBoardHighSeal(this, GetBoardSealingCurves(this)); let a = Math.atan2(x.y, x.x); if (isKeepLines && this.BoardProcessOption.lines !== LinesType.CanReversal && (equaln$1(x.y, 1, 1e-5) || equaln$1(x.y, -1, 1e-5))) this.BoardProcessOption.lines = 1 - this.BoardProcessOption.lines; //翻转纹路 1=>0 0=>1 x.transformDirection(this._Matrix); 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; this.ContourCurve.ApplyMatrix(tempMatrix1.makeRotationZ(-a)); //复用了这个矩阵 this.CheckContourCurve(); if (this.contourCurve instanceof Polyline) this.contourCurve.UpdateOCSTo(IdentityMtx4); SetBoardTopDownLeftRightSealData(this, highSeals); //这里不可以用缓存的曲线 否则分析错误,必须重新开始分析曲线 this.Update(); return this; } RotateBoard(rox, roy, roz) { this.WriteAllObjectRecord(); this._Rotation.x = rox; this._Rotation.y = roy; this._Rotation.z = roz; let spcocs = this.SpaceOCS; let roMatX = new Matrix4().makeRotationX(rox); let roMatY = new Matrix4().makeRotationY(roy); let roMatZ = new Matrix4().makeRotationZ(roz); this.ApplyMatrix(this.OCSInv) .ApplyMatrix(this.RotateMat) .ApplyMatrix(roMatX) .ApplyMatrix(roMatY) .ApplyMatrix(roMatZ) .ApplyMatrix(spcocs); this._SpaceOCS.copy(spcocs); this.Update(); } /**实际上这个值可能被改变,应该适当的去校验它(仅在重新设计模块时,这个值会被改变!) */ get Rotation() { let roMtx = this.RotateMat; let roMtxInv = roMtx.getInverse(roMtx); let csInSpace = this.SpaceOCSInv.multiply(this.OCSNoClone); //逆到模块坐标系 csInSpace.multiply(roMtxInv); //(正确) csInSpace.setPosition(0, 0, 0); let euRoMtx = new Matrix4().makeRotationFromEuler(new Euler(this._Rotation.x, this._Rotation.y, this._Rotation.z, "ZYX")); if (euRoMtx.elements.every((v, i) => equaln$1(v, csInSpace.elements[i]))) return this._Rotation; else { let eu = new Euler(0, 0, 0, "ZYX").setFromRotationMatrix(csInSpace); return eu; } } ApplyMirrorMatrix(m) { if (!this.Id) { // super.ApplyMirrorMatrix(m); //这个变更导致镜像错误 因为实体没有正常的被更新(更新下面的属性?). 所以需要注意的是,如果需要镜像变更,需要给实体一个id!!! return this; //为了优化性能,在jig模式下不去计算封边排钻等属性,不重绘板 } this.ContourCurve; //因为下面翻转孔面的代码,所以必须初始化这个 let hasSplitSize = (this.BoardProcessOption[EBoardKeyList.SpliteHeight] && this.BoardProcessOption[EBoardKeyList.SpliteWidth] && this.BoardProcessOption[EBoardKeyList.SpliteThickness]); let highSealsCurves = GetBoardSealingCurves(this); let highSeals = GetBoardHighSeal(this, highSealsCurves); let isStartSealToBack = !equalv3(highSealsCurves[0].StartPoint, this.contourCurve.StartPoint, 1e-4); //第一段封边往后搜索了 super.ApplyMirrorMatrix(m); if (this.contourCurve.Area2 < 0) { this.contourCurve.Reverse(); highSeals.reverse(); if (isStartSealToBack) //如果第一段封边往后搜索了,那么封边在镜像后 第一段封边保持不变 highSeals.unshift(highSeals.pop()); this.BoardProcessOption.highDrill?.reverse(); if (hasSplitSize) { [ this.BoardProcessOption.sealedUp, this.BoardProcessOption.sealedLeft, this.BoardProcessOption.sealedDown, this.BoardProcessOption.sealedRight, ] = [ this.BoardProcessOption.sealedUp, this.BoardProcessOption.sealedRight, this.BoardProcessOption.sealedDown, this.BoardProcessOption.sealedLeft, ]; } } else this.BoardProcessOption[EBoardKeyList.BigHole] = 1 - this.BoardProcessOption[EBoardKeyList.BigHole]; //反转大孔面 this.BoardProcessOption.highSealed = highSeals; if (!hasSplitSize) //&& this.isRect SetBoardTopDownLeftRightSealData(this, highSeals); //重新构建SpaceOCS this._SpaceOCS.multiplyMatrices(this._Matrix, new Matrix4().getInverse(this.RotateMat)); //"左","右"互换 if (this.Name.includes("左")) this.Name = this.Name.replace("左", "右"); else if (this.Name.includes("右")) this.Name = this.Name.replace("右", "左"); //开门方向"左","右"互换 if (this.OpenDir === BoardOpenDir.Left) this.OpenDir = BoardOpenDir.Right; else if (this.OpenDir === BoardOpenDir.Right) this.OpenDir = BoardOpenDir.Left; return this; } get UCGenerator() { if (this.BoardProcessOption.lines === LinesType.Positive) return boardUVGenerator; else return boardUVGenerator2; } UpdateUV(geo, ocs, isRev = false) { super.UpdateUV(geo, ocs, this.BoardProcessOption.lines === LinesType.Reverse); } //从一个实体拷贝数据,实体类型必须相同. CopyFrom(obj) { this.WriteAllObjectRecord(); let drillBak = this._DrillList; this._DrillList = new Map(); let layerNailsBak = this._LayerNails; this._LayerNails = []; super.CopyFrom(obj); this._DrillList = drillBak; this._LayerNails = layerNailsBak; } Clone(cloneDraw = true) { let br = super.Clone(cloneDraw); br._DrillAssociationLock.clear(); br._DrillList.clear(); br._LayerNails.length = 0; br.RelativeHardware.length = 0; return br; } Join(target) { let res = super.Join(target); if (res && target.RelativeHardware) { for (let hw of target.RelativeHardware) { if (!this.RelativeHardware.includes(hw)) this.RelativeHardware.push(hw); } } return res; } GetLinesDir() { let l; let len; let width; let brWidth = this.width; let brHeight = this.height; switch (this.BoardProcessOption.lines) { case LinesType.Positive: len = brHeight / 3; width = Math.min(brWidth, brHeight) / 8; break; case LinesType.Reverse: len = brWidth / 2; width = Math.min(brWidth, brHeight) / 8; break; case LinesType.CanReversal: len = brHeight / 3; width = brWidth / 2; } l = new LineSegments(BufferGeometryUtils.CreateFromPts(PointShapeUtils.LinesDirPts(len, width, this.BoardProcessOption.lines)), ColorMaterial.GetLineMaterial(8)); let l1 = l.clone(); l1.material = ColorMaterial.GetLineMaterial(7); l.position.set(brWidth / 2, brHeight / 2, 0); l1.position.set(brWidth / 2, brHeight / 2, this.thickness); if (this._SweepPath) { let pts = [l.position, l1.position]; let x, y; if (this._SweepAngle !== 0) { l.position.z -= 1; l1.position.z += 1; x = l.position.clone().add(XAxis); y = l.position.clone().add(YAxis); pts.push(x, y); for (let p of pts) p.applyMatrix4(this.ArcBuild.OCS2RotateMtx); } else { x = l.position.clone().add(XAxis); y = l.position.clone().add(YAxis); pts.push(x, y); } let xs = pts.map(p => p.x); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs, equaln$1); this.ArcBuild.ParseAllX_Map(xs); for (let p of pts) this.ArcBuild.PosMap2ArcPos(p); if (this._SweepAngle !== 0) for (let p of pts) p.applyMatrix4(this.ArcBuild.Rotate2OCSMtx); x.sub(l.position).normalize(); y.sub(l.position).normalize(); let z = new Vector3().crossVectors(x, y); let mtx = new Matrix4().makeBasis(x, y, z); l.rotation.setFromRotationMatrix(mtx); l1.rotation.setFromRotationMatrix(mtx); } l.updateMatrix(); l1.updateMatrix(); return [l, l1]; } GetLinesOpenDir(renderType) { const openDirReverse = { 1: BoardOpenDir.Right, 2: BoardOpenDir.Left, 3: BoardOpenDir.Down, 4: BoardOpenDir.Up }; const openDir = HostApplicationServices.doorLinesOption.reverseOpenDirLines ? openDirReverse[this.OpenDir] : this.OpenDir; const mtl = (renderType === RenderType.Physical || renderType === RenderType.Physical2) ? ColorMaterial.GetPhysical2EdgeMaterial() : ColorMaterial.GetWallLineMtl(9); const l = new Line$1(BufferGeometryUtils.CreateFromPts(PointShapeUtils.LinesOpenDirPts(this.height, this.width, openDir)), mtl); l.computeLineDistances(); let l1 = l.clone(); l.position.set(this.width / 2, this.height / 2, 0); l1.position.set(this.width / 2, this.height / 2, this.thickness); l.updateMatrix(); l1.updateMatrix(); return [l, l1]; } get KnifePolylineMap() { return new Map(this._KnifePolylineMap); } set KnifePolylineMap(map) { if (map.size === 0 && this._KnifePolylineMap.size === 0) return; this.WriteAllObjectRecord(); this._KnifePolylineMap = map; //不进行更新 通常由其他的方法更新 } Clear3DPathCache() { if (!this._3DPathObject) return; DisposeThreeObj(this._3DPathObject); this._3DPathObject = undefined; } //获取三维刀路的绘制对象 Get3DPathDrawObject() { if (this._3DPathObject) return this._3DPathObject; this._3DPathObject = new Object3D(); let tempIndex = 1; let tempMap = new Map(); for (let vm of this._3DModelingList) { let key = `${vm.dir}-${vm.knife.id}`; let color = tempMap.get(key); if (!color) { color = tempIndex; tempIndex++; tempMap.set(key, color); } for (let i = 0; i < vm.path.length - 1; i++) { let d1 = vm.path[i]; let d2 = vm.path[i + 1]; if (equaln$1(d1.bul, 0)) { let geo = BufferGeometryUtils.CreateFromPts([d1.pt, d2.pt]); this._3DPathObject.add(new Line$1(geo, ColorMaterial.GetLineMaterial(color))); } else { let arc = new Arc().ParseFromBul(d1.pt, d2.pt, d1.bul); arc.ColorIndex = color; this._3DPathObject.add(arc.GetDrawObjectFromRenderType(RenderType.Wireframe)); } } } return this._3DPathObject; } //#region 二维刀路缓存 //清除二维刀路的缓存 Clear2DPathCache() { this._2DPathCsgs = undefined; if (this._2DPathDrawObject) { DisposeThreeObj(this._2DPathDrawObject); this._2DPathDrawObject = undefined; } this.OffsetPathCache.clear(); } /** * 这个函数生成了二维刀路的csg数组,并且同时生成了_2DPathDrawObject(二维刀路提刀线框显示对象) */ Get2DPathCsgs() { if (this._2DPathCsgs) return this._2DPathCsgs; this._2DPathCsgs = []; //del_exp2_start if (this._2DModelingList.length === 0) return this._2DPathCsgs; this._2DPathCsgs = Board2DModelCSGBuilder(this); //del_exp2_end return this._2DPathCsgs; } ; GetSideModeingCsgs() { if (this._SideModeingCsgs) return this._SideModeingCsgs; this._SideModeingCsgs = []; if (!this._SideModelingMap.size && !this.RelevanceSideModelMap.size) return this._SideModeingCsgs; this._SideModeingCsgs = BoardSideModelCSGBuilder(this); return this._SideModeingCsgs; } //清除侧面造型Csgs的缓存 ClearSideModelingCache() { this._SideModeingCsgs = undefined; } //#endregion 侧面造型 get HasSideModel() { return this._SideModelingMap.size > 0; } get AsyncSideModeling() { return this._asyncSideModelIng; } get Has2DPath() { return this._2DModelingList.length > 0; } get Async2DPathing() { return this._async2DPathIng; } GoodBye() { super.GoodBye(); TerminateCSGTask({ key: this }); } async Load2DPathPromise() { if (!this._2DPathBuildPromise) this._2DPathBuildPromise = new Promise((res) => { this._Promise2DPathRes = res; }); return this._2DPathBuildPromise; } //del_exp2_start //二维刀路切割后返回几何体 UpdateMeshGeom(geo) { if (!this._workerCalcedGeom) { const AllSubtractCSGs = []; this._async2DPathIng = false; this._asyncSideModelIng = false; if (HostApplicationServices.show2DPathObject) for (let csg of this.Get2DPathCsgs()) { AllSubtractCSGs.push(csg); this._async2DPathIng = true; } for (let csg of this.GetSideModeingCsgs()) { AllSubtractCSGs.push(csg); this._asyncSideModelIng = true; } if (!AllSubtractCSGs.length) { if (geo instanceof Geometry) return new BufferGeometry().fromGeometry(geo); return geo; } let geom = Geometry2CSG2(geo); // 使用线程池 const task = { key: this, data: [AllSubtractCSGs, geom], then: (e) => { let data = e.data; if (data.status || !data.geom.polygons) { this._AsyncIngTextEntity.TextString = "二维刀路或侧面造型建模失败"; Log(`板:${this.Name}二维刀路或侧面造型建模失败!`, LogType.Error, [this]); return; } this.UpdateDrawGeometry(); this._workerCalcedGeom = data.geom; this.Update(); this.AsyncUpdated(); this.MeshGeometry; //保证刷新这个 this._workerCalcedGeom = null; if (this._Promise2DPathRes) { this._Promise2DPathRes(true); this._Promise2DPathRes = undefined; this._2DPathBuildPromise = undefined; } }, //TODO: 被取消的事件 }; AddCSGSubtractTask(task); // 往线程池里添加任务 //进入到异步后,直接先返回 if (geo instanceof Geometry) return new BufferGeometry().fromGeometry(geo); return geo; } this._async2DPathIng = false; this._asyncSideModelIng = false; const newGeom = this._workerCalcedGeom; this._workerCalcedGeom = undefined; //保护 this._EdgeGeometry = new EdgesGeometry().FromCSG(newGeom); const geometry = CSG2Geometry2(newGeom); const bufferGeometry = new BufferGeometry().fromGeometry(geometry); const gen = new GenLocalUv(); gen.CalculateUv(bufferGeometry, this.BoardProcessOption.lines === LinesType.Reverse); return bufferGeometry; } //del_exp2_end //分裂后重新将排钻实体 关联五金 设置给不同的实体 HandleSpliteEntitys(splitEntitys) { if (!splitEntitys.length) return; this.WriteAllObjectRecord(); //层板钉 let nails = []; for (let nail of this._LayerNails) { if (nail?.Object && !nail.IsErase) nails.push(nail); } //如果没有开排钻反应器,那么需要重新关联排钻 //排钻列表 let dris = []; if (!HostApplicationServices.openDrillingReactor || this.DrillLock || this.isDrillAssociationLock) { for (let d of this._DrillList) { dris.push(d); //离婚,分割财产 let fbr = d[0]?.Object; if (fbr) { fbr.WriteAllObjectRecord(); fbr._DrillList.delete(this.Id); } } this._DrillList.clear(); //开启反应器时,这个行为由排钻重排控制,没有开启时,我们暂时清空,下面会重新计算关联 } //清除所有层板钉(因为下面会重新关联)(这个和排钻反应器没有关联,必须全部清除) this._LayerNails.length = 0; //所有的实体,包括自己 let ents = [this].concat(splitEntitys); for (let en of ents) { let ocsInv = en.OCSInv; //把层板钉送给有缘人 nails = nails.filter(id => { let n = id.Object; let position = n.Position.applyMatrix4(ocsInv).setZ(0); if (en.contourCurve.PtInCurve(position)) { if (en === this) { this._LayerNails.push(id); return false; } //异形换位,把排钻给别人 if (n.MId === this.Id) n.MId = en.Id; else if (n.FId === this.Id) n.FId = en.Id; //新的板需要关联这个id en._LayerNails.push(id); return false; } return true; }); //如果没有开排钻反应器,那么需要重新关联排钻 if (!HostApplicationServices.openDrillingReactor || this.DrillLock || this.isDrillAssociationLock) { for (let d of dris) { let [bid, drIdss] = d; let board = bid?.Object; //另一个父亲 drIdss = drIdss.filter(ids => { if (!ids[0]?.Object || ids[0].IsErase) return false; let holes = ids.map(i => i.Object); if (holes[0] instanceof CylinderHole) { let isInBoard = CyHoleInBoard(holes, en, en.OCSInv); if (isInBoard) { if (board) //重新拥有父亲的身份. board.AppendDrillList(en.Id, [ids]); en.AppendDrillList(bid, [ids]); //拥有新的母亲的身份 for (let h of holes) { //成了别人的新娘 if (h.FId === this.Id) h.FId = en.Id; if (h.MId === this.Id) h.MId = en.Id; } return false; } } else { //直接删除,毫不留情 for (let id of ids) { let object = id?.Object; if (object && !object.IsErase) object.Erase(); } return false; } return true; }); d[1] = drIdss; } } } //删除无父母的排钻 for (let d of dris) { for (let ids of d[1]) for (let id of ids) { let object = id?.Object; if (object && !object.IsErase) object.Erase(); } } //重新关联复合实体 arrayRemoveIf(this.RelativeHardware, (hwdObjId) => { if (!hwdObjId || hwdObjId.IsErase) return true; for (let ent of splitEntitys) { let hwd = hwdObjId.Object; if (ent.BoundingBox.intersectsBox(hwd.BoundingBox)) { //原始板件删除这个五金 arrayRemoveIf(hwd.RelevanceBoards, (rbr) => rbr?.Object === this); //五金与分裂的板关联 hwd.RelevanceBoards.push(ent.objectId); ent.RelativeHardware.push(hwdObjId); //如果五金是把手 写入分裂的板的RelativeHandle if (this.RelativeHandle.includes(hwdObjId)) { arrayRemoveIf(this.RelativeHandle, (handle) => handle === hwdObjId); ent.RelativeHandle.push(hwdObjId); } return true; } } }); } BuildArcGeometry() { //针对圆弧板 let build = new ArcBoardBuild(this); [this._MeshGeometry, this._EdgeGeometry] = build.BuildMeshEdgeGeom(); // 圆弧板的二维刀路与侧面造型 if (build.CSGBoard.Async2DPathing || build.CSGBoard.AsyncSideModeling) { build.CSGBoard.Load2DPathPromise().then(() => { /** @todo 圆弧板暂时先不显示二维刀路的线框吧 */ this._2DPathDrawObject = undefined; // 完毕后,不再显示二维刀路/侧面造型建模中的文字 this._async2DPathIng = false; this._asyncSideModelIng = false; this.UpdateDrawGeometry(); //销毁旧的 [this._MeshGeometry, this._EdgeGeometry] = build.BuildMeshEdgeGeom(); // 更新 for (let [type, obj] of this.CacheDrawObject) { this.UpdateDrawObject(type, obj); obj.updateMatrixWorld(true); obj.traverse(UpdateBoundingSphere); } }); } this._SweepArcBoardBuild = build; //记录 } //del_exp2_start InitDrawObject(renderType = RenderType.Wireframe) { let obj; if (renderType === RenderType.Edge //封边检查 || renderType === RenderType.PlaceFace //排版面检查 || renderType === RenderType.BigHoleFace //大孔面检查 || renderType === RenderType.CustomNumber //自定义编号 || renderType === RenderType.CustomNumberPrint //自定义编号打印 || renderType === RenderType.ConceptualTransparent // 概念透明 || renderType === RenderType.ModelGroove //造型槽 ) { obj = new Object3D(); this.UpdateDrawObject(renderType, obj); } else { obj = super.InitDrawObject(renderType); this.UpdateDrawObjectByBoardInfo(renderType, obj); this.DrawAsyncText(obj); } return obj; } //del_exp2_start UpdateDrawGeometry() { super.UpdateDrawGeometry(); //当更新时,我们通知之前的二维刀路构建 结束任务 if (this._SweepArcBoardBuild?._csgBoard?.Async2DPathing) TerminateCSGTask({ key: this._SweepArcBoardBuild._csgBoard }); } //del_exp2_end UpdateDrawObject(renderType, obj) { // 圆弧板和复合实体内的圆弧板,Jig时,实时显示见光面路径 if (this._SweepPath && (renderType === RenderType.Jig)) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (this._jigSweepPath) { const path2BoardMtx = new Matrix4(); const invMtx = new Matrix4().getInverse(this._Matrix); path2BoardMtx.multiplyMatrices(invMtx, this._jigPath2WCSMtx); const pts = this._jigSweepPath.Shape.getPoints().map(AsVector3); const geo = new BufferGeometry().setFromPoints(pts); const line = new Line$1(geo, ColorMaterial.GetLineMaterial(this.ColorIndex)); line.matrix = path2BoardMtx; obj.add(line); this._jigSweepPath = undefined; this._jigPath2WCSMtx = undefined; return; } } let o = super.UpdateDrawObject(renderType, obj); if (renderType === RenderType.Edge) { obj.add(new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(8))); this.CheckSealing(obj); } else if (renderType === RenderType.PlaceFace) { let isArbitrary = this._BoardProcessOption[EBoardKeyList.ComposingFace] === ComposingType.Arbitrary; obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(8)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); if (!isArbitrary) //如果不是任意面 obj.add(this.GetPlaceFace()); } else if (renderType === RenderType.BigHoleFace) { obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(8)), new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(7))); obj.add(this.GetBigHoleFace()); } else if (renderType === RenderType.CustomNumber || renderType === RenderType.CustomNumberPrint) { if (renderType === RenderType.CustomNumberPrint) obj.add(...this.GetPrintObject3D()); else obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex, FrontSide, true))); //#region 添加文字 if (!this._CustomNumberTextEntity) { this._CustomNumberTextEntity = new Text(); this._CustomNumberTextEntity.TextAligen = TextAligen.Mid; this._CustomNumberTextEntity.IsDoubleMesh = true; this._CustomNumberTextEntity.IsFsText = this._BoardType === BoardType.Layer; this._CustomNumberTextEntity.IsEmbedEntity = true; } if (this._BoardType === BoardType.Layer) this._CustomNumberTextEntity.OCSNoClone.makeRotationZ(-Math.PI / 2).setPosition(this.width * 0.5, this.height * 0.5, this.thickness * 0.5); else this._CustomNumberTextEntity.OCSNoClone.identity().setPosition(this.width * 0.5, this.height * 0.5, this.thickness * 0.5); this._CustomNumberTextEntity.TextString = this._CustomNumber?.toString() ?? (this.objectId?.Index > 0 ? "未编号" : " "); if (this._CustomNumberTextEntity.TextString === "未编号") this._CustomNumberTextEntity.Height = 60; else this._CustomNumberTextEntity.Height = HostApplicationServices.boardCustomNumberTextHeight; let o = this._CustomNumberTextEntity.GetDrawObjectFromRenderType(RenderType.Conceptual); if (o) { o.traverse(obj => obj.userData = {}); AddEntityDrawObject(obj, this._CustomNumberTextEntity, RenderType.Conceptual); } //#endregion } this.UpdateDrawObjectByBoardInfo(renderType, obj); this.DrawAsyncText(obj); return o; } DrawAsyncText(obj) { if (this._async2DPathIng || this._asyncSideModelIng) { //#region 添加文字 if (!this._AsyncIngTextEntity) { this._AsyncIngTextEntity = new Text(); this._AsyncIngTextEntity.TextAligen = TextAligen.Mid; this._AsyncIngTextEntity.IsDoubleMesh = true; this._AsyncIngTextEntity.IsFsText = this._BoardType === BoardType.Layer; this._AsyncIngTextEntity.IsEmbedEntity = true; } if (this._BoardType === BoardType.Layer) this._AsyncIngTextEntity.OCSNoClone.makeRotationZ(-Math.PI / 2).setPosition(this.width * 0.5, this.height * 0.5, this.thickness * 0.5); else this._AsyncIngTextEntity.OCSNoClone.identity().setPosition(this.width * 0.5, this.height * 0.5, this.thickness * 0.5); if (this._async2DPathIng) this._AsyncIngTextEntity.TextString = "二维刀路建模中!"; else if (this._asyncSideModelIng) this._AsyncIngTextEntity.TextString = "侧面造型建模中!"; let o = this._AsyncIngTextEntity.GetDrawObjectFromRenderType(RenderType.Conceptual); if (o) { o.traverse(obj => obj.userData = {}); AddEntityDrawObject(obj, this._AsyncIngTextEntity, RenderType.Conceptual); } //#endregion } } //del_exp2_end //绘制排版面时使用的颜色 get PlaceColor() { return this._Color === 8 ? 9 : this.DrawColorIndex; } //排版面网格 GetPlaceFace() { let shapeGeom = new ShapeBufferGeometry(this.contourCurve.Shape); let isBack = this._BoardProcessOption[EBoardKeyList.ComposingFace] !== ComposingType.Positive; if (isBack) { const indices = Array.from(shapeGeom.getIndex().array); // 翻转面片索引的顺序 const flippedIndices = []; for (let i = 0; i < indices.length; i += 3) flippedIndices.push(indices[i], indices[i + 2], indices[i + 1]); // 创建新的面片索引属性 const newIndices = new BufferAttribute(new Uint16Array(flippedIndices), 1); // 更新面片索引属性 shapeGeom.setIndex(newIndices); } shapeGeom.applyMatrix4(this.contourCurve.OCSNoClone); shapeGeom.translate(0, 0, isBack ? -1e-3 : this.thickness + 1e-3); let mesh = new Mesh(shapeGeom, ColorMaterial.GetBasicMaterial(this.PlaceColor)); return mesh; } /** 获取大孔面的几何体 */ get BigHoleFaceGeo() { let shapeGeom = new ShapeBufferGeometry(this.contourCurve.Shape); let isBack = this._BoardProcessOption[EBoardKeyList.BigHole] === FaceDirection.Back; if (isBack) { const indices = Array.from(shapeGeom.getIndex().array); // 翻转面片索引的顺序 const flippedIndices = []; for (let i = 0; i < indices.length; i += 3) flippedIndices.push(indices[i], indices[i + 2], indices[i + 1]); // 创建新的面片索引属性 const newIndices = new BufferAttribute(new Uint16Array(flippedIndices), 1); // 更新面片索引属性 shapeGeom.setIndex(newIndices); } shapeGeom.applyMatrix4(this.contourCurve.OCSNoClone); shapeGeom.translate(0, 0, isBack ? -1e-3 : this.thickness + 1e-3); return shapeGeom; } //大孔面网格 GetBigHoleFace() { if (this._SweepPath) //针对圆弧板 { const geometry = this._SweepArcBoardBuild.BuildBigHoleFace(); let mesh = new Mesh(geometry, ColorMaterial.GetBasicMaterial(this.PlaceColor)); return mesh; } let shapeGeom = this.BigHoleFaceGeo; let mesh = new Mesh(shapeGeom, ColorMaterial.GetBasicMaterial(this.PlaceColor)); return mesh; } /** * 根据板的信息修改绘制的实体 * 1.非拆单板 灰色 * 2.纹路 开门方向纹路 * 3.二维 三维刀路 */ UpdateDrawObjectByBoardInfo(renderType, obj) { if (!obj) return; if (!this.IsChaiDan) //非拆单板绘制更新 { if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { obj.children.length = 1; obj.children[0].material = ColorMaterial.GrayTransparentMeshMaterial; //灰色半透明 } else if (renderType !== RenderType.Wireframe) // renderType === RenderType.Physical(理论上只有这个) obj.material = ColorMaterial.GrayTransparentMeshMaterial; //灰色半透明(会不会有bug?) } //线框及概念且可拆单 if (([RenderType.Wireframe, RenderType.Conceptual, RenderType.Print].includes(renderType)) && this.IsChaiDan) { //绘制纹路 if (HostApplicationServices.showLines && renderType !== RenderType.Print) obj.add(...this.GetLinesDir()); //绘制开门方向纹路 if (HostApplicationServices.showOpenDirLines && this.OpenDir !== BoardOpenDir.None) obj.add(...this.GetLinesOpenDir(renderType)); } const doorLinesOption = HostApplicationServices.doorLinesOption; if (doorLinesOption.physicalShowLines && renderType === RenderType.Physical || doorLinesOption.physicalShowLines2 && renderType === RenderType.Physical2) { //绘制开门方向纹路 if (HostApplicationServices.showOpenDirLines && this.OpenDir !== BoardOpenDir.None) obj.add(...this.GetLinesOpenDir(renderType)); } if (this.objectId) //二维和三维刀路 { //三维刀路 let path3d = this.Get3DPathDrawObject(); if (path3d.parent) obj.children.push(path3d); else obj.add(path3d); //二维刀路 this.Get2DPathCsgs(); let path2d = this._2DPathDrawObject; if (path2d && HostApplicationServices.show2DPathLine) { if (path2d.parent) obj.children.push(path2d); else obj.add(path2d); } this.GetSideModeingCsgs(); } } UpdateDrawObjectMaterial(renderType, obj) { super.UpdateDrawObjectMaterial(renderType, obj); if (renderType === RenderType.PlaceFace || renderType === RenderType.BigHoleFace) { let face = obj.children[2]; if (!face) return; face.material = ColorMaterial.GetBasicMaterial(this.PlaceColor); } if (!this.IsChaiDan) { if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { obj.children[0].material = ColorMaterial.GrayTransparentMeshMaterial; } else if (renderType !== RenderType.Wireframe) { obj.material = ColorMaterial.GrayTransparentMeshMaterial; } } } //封边检查(为obj 对象添加封边检查的曲线) CheckSealing(obj) { let sealingInfo = new Map(HostApplicationServices.sealingColorMap.filter(d => d[0] && d[1])); if (sealingInfo.size === 0) return; let cus = GetBoardSealingCurves(this); let highSeals = GetBoardHighSeal(this, cus); for (let i = 0; i < cus.length; i++) { let size = highSeals[i].size.toString(); let color = sealingInfo.get(size); if (color) { cus[i].Position = cus[i].Position.add(new Vector3(0, 0, this.thickness / 2)); cus[i].ColorIndex = parseInt(color) ?? 7; let l = cus[i].GetDrawObjectFromRenderType(RenderType.Wireframe); obj.add(l); } } } AddSideModelGripPoints(pts, dragPointType) { let con = GetBoardContour(this); if (!con) return; let inverseZ = con.Area2 < 0; let cus = con.Explode(); for (let [index, soilds] of this.SideModelingMap) { let cu = cus[index]; if (!cu) continue; let mt4 = GetSideCuFaceMtx(cus[index], inverseZ); for (let soild of soilds) { const MirrorMtxZ = MakeMirrorMtx(ZAxis); let s = soild.Clone(); s.ApplyMatrix(MirrorMtxZ); s.ApplyMatrix(mt4); s.ApplyMatrix(this.OCSNoClone); pts.push(...s.GetGripOrStretchPoints(dragPointType)); } } } GetStrectchPointCountList(dragType) { let counts = super.GetStrectchPointCountList(dragType); if (this.HasSideModel) { for (let [num, soilds] of this.SideModelingMap) { for (let soild of soilds) { let c = soild.ContourCurve.GetDragPointCount(dragType) * 2; for (let g of soild.Grooves) c += g.ContourCurve.GetDragPointCount(dragType) * 2; counts.push(c); } } } return counts; } //因为圆弧板 我们重载了它 GetGripPoints() { let pts = super.GetGripPoints(); pts = this.MapToArcPoints(pts, DragPointType.Grip); return pts; } MapToArcPoints(pts, dragPointType) { if (this._SweepPath) { if (!this._SweepArcBoardBuild) this._SweepArcBoardBuild = new ArcBoardBuild(this); let inv = this.OCSInv; let mtx = this.OCSNoClone; if (this._SweepArcBoardBuild._OCS2RotateMtx) { inv.multiplyMatrices(this._SweepArcBoardBuild._OCS2RotateMtx, inv); mtx = new Matrix4().multiplyMatrices(mtx, this._SweepArcBoardBuild._Rotate2OCSMtx); } for (let p of pts) p.applyMatrix4(inv); let xs = pts.map(p => p.x); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs); this._SweepArcBoardBuild.ParseAllX_Map(xs); for (let p of pts) { this._SweepArcBoardBuild.PosMap2ArcPos(p); p.applyMatrix4(mtx); } } else if (this.HasSideModel) this.AddSideModelGripPoints(pts, dragPointType); return pts; } MoveGripPoints(indexList, vec) { if (indexList.length === 0) return; this.ClearSideModelingCache(); if (this._SweepPath) { this.MoveArcBoardPoints(indexList, vec, DragPointType.Grip); this.Update(); } else super.MoveGripPoints(indexList, vec); } GetStretchPoints() { let pts = this.GetGripOrStretchPoints(DragPointType.Stretch); pts = this.MapToArcPoints(pts, DragPointType.Stretch); for (let m of this._2DModelingList) { pts.push(...m.path.GetStretchPoints().map(p => p.add(new Vector3(0, 0, m.dir === FaceDirection.Front ? this.thickness : 0)).applyMatrix4(this.OCSNoClone))); } return pts; } MoveStretchPoints(indexList, vec) { if (indexList.length === 0) { let undoData = this.UndoRecord(); if (undoData) undoData.WriteObjectHistoryPath(this, new HistorycRecord); return; } let exCount = arraySum(this.GetStrectchPointCountList(DragPointType.Stretch)); let originIndexList = []; let mIndexList = []; for (let i of indexList) { if (i < exCount) originIndexList.push(i); else mIndexList.push(i - exCount); } let oldOcs = this.OCS; if (this._SweepPath) this.MoveArcBoardPoints(originIndexList, vec, DragPointType.Stretch); else super.MoveStretchPoints(originIndexList, vec); if (!this.Id) return; arraySortByNumber(mIndexList); let ocsInv = this.OCSInv; let localVec = TransformVector(vec.clone(), ocsInv); let offset = 0; let icount = mIndexList.length; let i = 0; for (let m of this._2DModelingList) { let count = m.path.GetDragPointCount(DragPointType.Stretch); offset += count; let iList = []; for (; i < icount; i++) { if (mIndexList[i] < offset) iList.push(mIndexList[i] - offset + count); else break; } if (iList.length > 0) { m.path.MoveStretchPoints(iList, localVec); } m.path.ApplyMatrix(oldOcs).ApplyMatrix(ocsInv); } this.Clear2DPathCache(); this.Clear3DPathCache(); this.ClearSideModelingCache(); this.Update(UpdateDraw.Geometry); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum) { if (this._SweepPath) { if (snapMode === ObjectSnapMode.Nea) { let pts = []; //这里实现对线框的快速捕捉 let edgeGeom = this.EdgeGeometry; let pos = edgeGeom.getAttribute("position"); let p1 = new Vector3; let p2 = new Vector3; let line = new Line(p1, p2); line.ApplyMatrix(this.OCSNoClone); let sel = frustum["_select_"]; for (let y = 0; y < pos.count; y += 2) { p1.fromArray(pos.array, y * 3); p2.fromArray(pos.array, (y + 1) * 3); p1.applyMatrix4(this._Matrix); p2.applyMatrix4(this._Matrix); sel.WorldToScreenPoint(p1); sel.WorldToScreenPoint(p2); if (sel.IntersectLine(p1, p2)) { p1.fromArray(pos.array, y * 3); p2.fromArray(pos.array, (y + 1) * 3); arrayPushArray(pts, line.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } } return pts; } } let pts = super.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform, frustum); if (snapMode === ObjectSnapMode.End) { for (let vm of this._2DModelingList) { if (!this.OffsetPathCache.has(vm.path)) continue; for (let item of vm.items) { if (item.offset === 0) continue; let paths = this.OffsetPathCache.get(vm.path); let polyline = paths[item.offset.toString()]; if (!polyline) continue; //多段线可能偏移失败 let ps = polyline.GetStretchPoints(); for (let p of ps) { if (vm.dir === FaceDirection.Front) p.add(new Vector3(0, 0, this.thickness)); p.applyMatrix4(this.OCS); } pts.push(...ps); } } } else if (snapMode === ObjectSnapMode.Mid) { for (let vm of this._2DModelingList) { let ps = vm.path.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); for (let p of ps) { if (vm.dir === FaceDirection.Front) p.add(new Vector3(0, 0, this.thickness)); p.applyMatrix4(this.OCS); } pts.push(...ps); } } //为圆弧板映射新的点表 if (this._SweepPath) { let inv = this.OCSInv; let mtx = this._Matrix; if (this._SweepAngle !== 0) { inv.multiplyMatrices(this.ArcBuild.OCS2RotateMtx, inv); mtx = new Matrix4().multiplyMatrices(mtx, this.ArcBuild.Rotate2OCSMtx); } for (let p of pts) p.applyMatrix4(inv); let xs = pts.map(p => p.x); arraySortByNumber(xs); arrayRemoveDuplicateBySort(xs, equaln$1); this.ArcBuild.ParseAllX_Map(xs); for (let p of pts) { this.ArcBuild.PosMap2ArcPos(p); p.applyMatrix4(mtx); } } return pts; } MoveArcBoardPoints(indexList, vec, dragType) { this.WriteAllObjectRecord(); const isGrip = dragType === DragPointType.Grip; const oldPts = isGrip ? this.GetGripPoints() : this.GetStretchPoints(); const MoveBack = () => isGrip ? super.MoveGripPoints(indexList, offsetVec.clone().negate()) : super.MoveStretchPoints(indexList, offsetVec.clone().negate()); let path = this.ArcBuild.SweepPath1.Clone(); // 1、计算沿着SweepPath偏移的向量 const offsetVec = this.GetOffsetVecAlongPath(oldPts[indexList[0]].clone(), vec, path); const oldPos = this.Position; const oldCon = this.ContourCurve.Clone(); const ocs2rot = this.ArcBuild.OCS2RotateMtx; oldCon.ApplyMatrix(ocs2rot); const oldBox = oldCon.BoundingBox; // 2、移动平板上的点 isGrip ? super.MoveGripPoints(indexList, offsetVec) : super.MoveStretchPoints(indexList, offsetVec); const newPos = this.Position; const conMoveVec = TransformVector(newPos.clone().sub(oldPos), this.OCSInv); //轮廓在OCS中移动的向量 const newCon = this.ContourCurve.Clone(); newCon.Move(conMoveVec); newCon.ApplyMatrix(ocs2rot); const newBox = newCon.BoundingBox; this._jigSweepPath = path.Clone(); // 特殊场景:闭合SweepPath,其对应的首尾不能发生变化,否者会发生path和Contour映射错误。 if (path.CloseMark && !(equaln$1(newBox.min.x, oldBox.min.x) && equaln$1(newBox.max.x, oldBox.max.x))) { MoveBack(); // 若头尾被拖拽过,需要恢复回去 return; } // 特殊场景:某些操作,对弧形板进行移动(如当拖拽中间点),未发生路径改变,则重新移动回去 const newSize = newBox.getSize(new Vector3); const oldSize = oldBox.getSize(new Vector3); if (equaln$1(newSize.x, oldSize.x, 1e-3)) { const newPts = isGrip ? this.GetGripPoints() : this.GetStretchPoints(); if (oldPts.length === newPts.length) { const vec = oldPts[0].clone().sub(newPts[0]); const isMoveArcBoard = oldPts.every((pt, i) => equalv3(pt.clone().sub(newPts[i]), vec)); if (isMoveArcBoard) { MoveBack(); return; } } } // 3、修正SweepPath path = this.FixSweepPathByContourBondingbox(newBox, oldBox, path); // 特殊场景:path 计算发生错误,需要恢复板的状态 if (!path) { MoveBack(); return; } // 4、计算弧形板偏移矩阵 if (this.objectId || (this.IsEmbedEntity)) { const pts1 = isGrip ? newCon.GetGripPoints() : newCon.GetStretchPoints(); const pts2 = isGrip ? oldCon.GetGripPoints() : oldCon.GetStretchPoints(); const count = Math.min(pts1.length, pts2.length); this._SweepArcBoardBuild = undefined; for (let i = 0; i < count; i++) { if (equalv2(pts1[i], pts2[i], 1e-3)) // 找到新板和旧板上不变的点,计算出偏移矩阵 { const pts = isGrip ? this.GetGripPoints() : this.GetStretchPoints(); this.Move(oldPts[i].sub(pts[i])); break; } } this._SweepArcBoardBuild = undefined; } } GetOffsetVecAlongPath(oldP, vec, path) { const p = oldP.clone().add(vec); //拉伸后的点 在世界坐标系中 const path2WCSMtx = new Matrix4().makeBasis(XAxis, ZAxis, YAxisN); if (this._SweepAngle !== 0) path2WCSMtx.premultiply(this.ArcBuild.Rotate2OCSMtx); path2WCSMtx.premultiply(this.OCSNoClone); const wcs2PathMtx = new Matrix4().getInverse(path2WCSMtx); this._jigPath2WCSMtx = path2WCSMtx.clone(); //变换到路径坐标系 p.applyMatrix4(wcs2PathMtx); oldP.applyMatrix4(wcs2PathMtx); // 限制p, 在见光面的曲面中,计算偏移向量 const cp = path.GetClosestPointTo(p, true); const oldZ = oldP.z; const newZ = p.z; const oldPCp = path.GetClosestPointTo(oldP.setZ(0), false); //旧的最近点 const oldDist = path.GetDistAtPoint2(oldPCp); // 移动前的Dist const movedDist = path.GetDistAtPoint2(cp); // 移动后的Dist const moveVec = new Vector3(movedDist - oldDist, -newZ + oldZ, 0); //将moveVec转换到世界坐标系 if (this._SweepAngle !== 0) TransformVector(moveVec, this.ArcBuild.Rotate2OCSMtx); TransformVector(moveVec, this.OCSNoClone); return moveVec; } /** * @private 通过新旧轮廓的Bondingbox修正路径 * @param {Box3Ext} newBox 路径坐标系下,新轮廓的Bondingbox * @param {Box3Ext} oldBox 路径坐标系下,旧轮廓的Bondingbox * @param {Polyline} path 见光面路径 * @return {*} {Polyline} 修正好的路径 */ FixSweepPathByContourBondingbox(newBox, oldBox, path) { // 若头部和尾部重合,则直接不处理 if (equaln$1(newBox.min.x, newBox.max.x, 0.1)) return; const GetArcAndKeys = () => { const curves = path.Explode(); const arcs = []; for (let i = 0; i < curves.length; i++) if (curves[i] instanceof Arc) arcs.push({ key: i, arc: curves[i].Clone() }); return arcs; }; const arcKeys = GetArcAndKeys(); const MovePath = (pathToMove, mVec) => { // 修正SWeepPath pathToMove.Move(mVec); const { pts: pathPts, buls } = pathToMove.MatrixAlignTo2(new Matrix4); pathToMove.OCSNoClone.identity(); for (let i = 0; i < pathToMove.LineData.length; i++) { pathToMove.LineData[i].pt.copy(pathPts[i]); pathToMove.LineData[i].bul = buls[i]; } }; let jigSpt = undefined; const FixHead = () => { if (!equaln$1(newBox.min.x, 0, 1e-3)) // 头部 { const c1 = path.GetCurveAtIndex(0); if (newBox.min.x < 0) path.Extend(newBox.min.x / c1.Length); // 延伸 else path = path.GetSplitCurves(path.GetParamAtDist(newBox.min.x))[1]; // 裁剪 jigSpt = path.StartPoint.clone(); MovePath(path, path.StartPoint.clone().negate()); // 修正Path } }; const FixTail = () => { if (!equaln$1(newBox.max.x, oldBox.max.x, 1e-3)) // 尾部 { const dist = newBox.max.x - oldBox.max.x; const ce = path.GetCurveAtIndex(path.EndParam - 1); if (dist > 0) path.Extend(path.EndParam + ((dist) / ce.Length)); // 延伸 else path = path.GetSplitCurves(path.GetParamAtDist(newBox.max.x - newBox.min.x))[0]; // 裁剪, PS: 从尾部点拉伸到头部点之前,newBox.min.x不为零 } }; /** * 某些异形板左侧头部拉伸到尾部后面示意图: * ___________ * A | | * |_______ | * | | * old B |___| * _____ * | | A * ___|_____| * | | * new B |___| */ const outofTail = newBox.min.x >= oldBox.max.x || equaln$1(newBox.min.x, oldBox.max.x, 1e-3) // 矩形板:拉伸后,新板头和旧板尾部相同 || newBox.min.x > oldBox.min.x && newBox.max.x > oldBox.max.x; // 某些异形板:新板头会在旧板中间某处,新板尾超过旧版尾 if (outofTail) { // 头部拉伸在超过尾部,即新头部大于等于原尾部。则先对尾部延伸,再裁剪头部(若先裁剪头部,则path可能会为空) FixTail(); FixHead(); } else { FixHead(); FixTail(); } // 计算 this._SweepPath let sweepPath = undefined; if (this._SweepVisibleFace === FaceDirection.Back) { sweepPath = path; // 做offset偏移,查看path是否会被裁剪。若被裁剪,会破坏板和路径的映射关系,则不能继续其他操作 if (this.objectId || (this.IsEmbedEntity && this.ParentEntity.objectId)) { const frontPath = ArcBoardBuild.OffsetPolyline(sweepPath, -this.thickness); // 正面path const backPath = ArcBoardBuild.OffsetPolyline(frontPath, this.thickness); // 背面path if (!equaln$1(backPath.Length, sweepPath.Length, 1e-3)) return; } } else { sweepPath = ArcBoardBuild.OffsetPolyline(path, this.thickness); // 做offset偏移,查看path是否会被裁剪。若被裁剪,会破坏板和路径的映射关系,则不能继续其他操作 if (this.objectId || (this.IsEmbedEntity && this.ParentEntity.objectId)) { const frontPath = ArcBoardBuild.OffsetPolyline(sweepPath, -this.thickness); // 正面path if (!equaln$1(frontPath.Length, path.Length, 1e-3)) return; } } this._SweepPath = sweepPath; this._jigSweepPath = path.Clone(); if (jigSpt) MovePath(this._jigSweepPath, jigSpt); // 新路径,但是未进行起始点偏移。方便拖拽时,实时显示当前路径 // 更新圆弧配置 if (this.objectId || (this.IsEmbedEntity && this.ParentEntity.objectId)) this.UpdateArcBoardOptionsByPath(arcKeys, this._jigSweepPath); return path; } DeferUpdate() { if (this.NeedUpdateFlag & UpdateDraw.Matrix) { if (this.RelativeHardware.some(id => !id.IsErase)) this.NeedUpdateFlag |= UpdateDraw.Geometry; } super.DeferUpdate(); } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); if (ver < 6) this._SpaceOCS.fromArray(file.Read()); this._BoardType = file.Read(); this._Name = file.Read(); //兼容旧版本 if (ver > 2) { deserializationBoardData(file, this._BoardProcessOption, ver); } else { let opt = file.Read(); this._BoardProcessOption = Object.assign(this._BoardProcessOption, typeof opt === "string" ? JSON.parse(opt) : opt); } //读取排钻列表 this._DrillList.clear(); let size = file.Read(); //没有与任何板件关联的排钻 let noRelevancyDrillings = []; for (let i = 0; i < size; i++) { let id = file.ReadObjectId(); let drIdList = []; let count = file.Read(); for (let i = 0; i < count; i++) { let drIDs = []; let count1 = file.Read(); for (let j = 0; j < count1; j++) { let fileId = file.ReadObjectId(); fileId && drIDs.push(fileId); } if (ver > 21) drIDs.Name = file.Read(); if (drIDs.length > 0) drIdList.push(drIDs); } if (drIdList.length === 0) continue; if (!id) noRelevancyDrillings.push(...drIdList); else this._DrillList.set(id, drIdList); } if (noRelevancyDrillings.length > 0) this._DrillList.set(undefined, noRelevancyDrillings); if (ver > 1) { this._LayerNails.length = 0; let nailsCount = file.Read(); for (let i = 0; i < nailsCount; i++) { let objId = file.ReadObjectId(); if (objId) this._LayerNails.push(objId); } } if (ver > 4) this._Rotation = { x: file.Read(), y: file.Read(), z: file.Read() }; if (ver >= 7) { let count = file.Read(); this.RelativeHardware.length = 0; for (let i = 0; i < count; i++) { let objId = file.ReadObjectId(); if (objId) this.RelativeHardware.push(objId); } } if (ver >= 8) this.OpenDir = file.Read(); if (ver >= 9) this._IsChaiDan = file.Read(); if (ver >= 10) { DeserializationBoard2DModeingData(file, this._2DModelingList, ver); DeserializationBoard3DModeingData(file, this._3DModelingList); } this.Clear2DPathCache(); this.Clear3DPathCache(); if (ver > 10) this._CustomNumber = file.Read(); else this._CustomNumber = null; this._KnifePolylineMap.clear(); if (ver > 11) { let size = file.Read(); for (let i = 0; i < size; i++) { let id = file.Read(); let pl = file.ReadObject(); this._KnifePolylineMap.set(id, pl); } } // if (this.width === 0 || this.height === 0) //板件变成0长度,无法绘制 // this._isErase = true; if (ver > 12) { let count = file.Read(); this.RelativeHandle.length = 0; for (let i = 0; i < count; i++) { let objId = file.ReadObjectId(); if (objId) this.RelativeHandle.push(objId); } } const processData = this._BoardProcessOption; if (ver > 13) { processData.edgeRemarkUp = file.Read(); processData.edgeRemarkDown = file.Read(); processData.edgeRemarkLeft = file.Read(); processData.edgeRemarkRight = file.Read(); let count = file.Read(); processData.highBoardEdgeRemark.length = 0; processData.highBoardEdgeRemark = file.ReadArray(count); } this._DrillAssociationLock.clear(); if (ver > 14) { this._DrillLock = file.Read(); //读取关联排钻锁映射 const size = file.Read(); for (let i = 0; i < size; i++) { const id = file.ReadObjectId(); if (id) this._DrillAssociationLock.add(id); } } if (ver > 15) { this._SweepPath = file.ReadObject(); if (this._SweepPath) { this._SweepAngle = file.Read(); if (ver > 16) this._SweepVisibleFace = file.Read(); if (ver > 17) this._isDrawArcGroove = file.Read(); this._ArcBoardOptions.clear(); const count = file.Read(); for (let i = 0; i < count; i++) { let key = file.Read(); let opt = { arcLength: file.Read(), grooveSpacing: file.Read(), grooveWidth: file.Read(), retainedThickness: file.Read(), grooveAddDepth: file.Read(), grooveAddLength: file.Read(), grooveAddWidth: file.Read(), knifeRadius: file.Read(), arcExtension: 0, }; if (ver > 18) opt.arcExtension = file.Read(); this._ArcBoardOptions.set(key, opt); } } } if (ver > 19) this.AlignLineObject = file.ReadHardObjectId(); if (ver > 20) DeserializationBoardSideModeingData(file, this._SideModelingMap); this.ClearSideModelingCache(); if (ver > 22) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; if (ver > 23) { processData.reservedEdgeUp = file.Read(); processData.reservedEdgeDown = file.Read(); processData.reservedEdgeLeft = file.Read(); processData.reservedEdgeRight = file.Read(); let count = file.Read(); processData.highReservedEdge.length = 0; for (let i = 0; i < count; i++) { let size = file.Read(); processData.highReservedEdge.push({ size }); } processData.sealColorUp = file.Read(); processData.sealColorDown = file.Read(); processData.sealColorLeft = file.Read(); processData.sealColorRight = file.Read(); processData.sealColorType = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(24); // file.Write(this._SpaceOCS.toArray()); ver < 6 file.Write(this._BoardType); file.Write(this._Name); serializeBoardData(file, this._BoardProcessOption); file.Write(this._DrillList.size); for (let [id, idList] of this._DrillList) { file.WriteObjectId(id); file.Write(idList.length); for (let ids of idList) { file.Write(ids.length); for (let id of ids) file.WriteObjectId(id); file.Write(ids.Name); } } file.Write(this._LayerNails.length); for (let nail of this._LayerNails) file.WriteHardObjectId(nail); file.Write(this._Rotation.x); file.Write(this._Rotation.y); file.Write(this._Rotation.z); file.Write(this.RelativeHardware.length); for (let id of this.RelativeHardware) file.WriteObjectId(id); file.Write(this.OpenDir); file.Write(this._IsChaiDan); SerializeBoard2DModeingData(file, this._2DModelingList); SerializeBoard3DModeingData(file, this._3DModelingList); file.Write(this._CustomNumber); file.Write(this._KnifePolylineMap.size); for (let [id, pl] of this._KnifePolylineMap) { file.Write(id); file.WriteObject(pl); } file.Write(this.RelativeHandle.length); for (let id of this.RelativeHandle) file.WriteObjectId(id); const processData = this._BoardProcessOption; file.Write(processData.edgeRemarkUp); file.Write(processData.edgeRemarkDown); file.Write(processData.edgeRemarkLeft); file.Write(processData.edgeRemarkRight); file.Write(processData.highBoardEdgeRemark.length); for (let r of processData.highBoardEdgeRemark) { file.Write(r); } //ver>14 { file.Write(this._DrillLock); const count = this._DrillAssociationLock.size; file.Write(count); for (const id of this._DrillAssociationLock) { file.WriteObjectId(id); } } //ver 16 file.WriteObject(this._SweepPath); if (this._SweepPath) { file.Write(this._SweepAngle); file.Write(this._SweepVisibleFace); file.Write(this._isDrawArcGroove); file.Write(this._ArcBoardOptions.size); for (const [k, v] of this._ArcBoardOptions) { file.Write(k); file.Write(v.arcLength); file.Write(v.grooveSpacing); file.Write(v.grooveWidth); file.Write(v.retainedThickness); file.Write(v.grooveAddDepth); file.Write(v.grooveAddLength); file.Write(v.grooveAddWidth); file.Write(v.knifeRadius); file.Write(v.arcExtension); } } //ver 20 file.WriteHardObjectId(this.AlignLineObject); //ver 21 SerializeBoardSideModeingData(file, this._SideModelingMap); // ver 23 file.WriteBool(this._LockMaterial); //ver 24 file.Write(processData.reservedEdgeUp); file.Write(processData.reservedEdgeDown); file.Write(processData.reservedEdgeLeft); file.Write(processData.reservedEdgeRight); file.Write(processData.highReservedEdge.length); for (let r of processData.highReservedEdge) { file.Write(r.size); } file.Write(processData.sealColorUp); file.Write(processData.sealColorDown); file.Write(processData.sealColorLeft); file.Write(processData.sealColorRight); file.Write(processData.sealColorType); } }; __decorate([ AutoRecord ], Board.prototype, "RelativeHardware", void 0); __decorate([ AutoRecord ], Board.prototype, "RelativeHandle", void 0); __decorate([ AutoRecord ], Board.prototype, "AlignLineObject", void 0); Board = Board_1 = __decorate([ Factory ], Board); //解析新的板的边映射到旧板边的映射情况 function ParseNewBr2OldBr_EdgeMap(newBr, oldBr, oldBrOcsInv) { let newBrCu = newBr.ContourCurve; let oldBrCu = oldBr.ContourCurve; let indexMap = []; //矩阵对齐 let m = newBr.OCS.premultiply(oldBrOcsInv); for (let i = 0; i < newBrCu.EndParam; i++) { let p = newBrCu.GetPointAtParam(i + 0.5).applyMatrix4(m); let cp = oldBrCu.GetClosestPointTo(p, false); let cparam = oldBrCu.GetParamAtPoint2(cp); indexMap.push(Math.floor(cparam)); } return indexMap; } const COMPARE_FUNC = (sparam, range) => sparam - range[0]; //寻找插入位置 function InsertRangeIndex(ranges, sparam) { return InsertSortedIndex(ranges, sparam, COMPARE_FUNC); } //返回可以插入的位置 function InsertSortedIndex(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 if (compareresult === 0) //因为函数 FindBestRange 会取index-1 来取范围 leftbound = testindex + 1; else rightbound = testindex; } return leftbound; } /** * 插入范围,并且合并范围 */ function InsertRangeAndUnion(ranges, sparam, eparam, insertIndex = InsertRangeIndex(ranges, sparam)) { if (insertIndex !== 0 && (sparam - 1e-6) <= (ranges[insertIndex - 1][1])) //和前面一个范围合并 { //传染 let s = insertIndex; for (; s < ranges.length; s++) { let r = ranges[s]; if ((r[0] - 1e-6) < eparam) { if (r[1] >= (eparam - 1e-6)) { eparam = r[1]; s++; break; } //else continue } else break; } ranges.splice(insertIndex, s - insertIndex); ranges[insertIndex - 1][1] = eparam; //合并成功 } else if (insertIndex !== ranges.length && eparam + 1e-6 > ranges[insertIndex][0]) //和后面的合并 { let nextRange = ranges[insertIndex]; nextRange[0] = sparam; nextRange[1] = Math.max(eparam, nextRange[1]); eparam = nextRange[1]; //传染 insertIndex++; let s = insertIndex; for (; s < ranges.length; s++) { let r = ranges[s]; if (r[0] <= (eparam + 1e-6)) { if ((r[1] + 1e-6) >= eparam) { eparam = r[1]; s++; break; } //else continue } else break; } ranges.splice(insertIndex, s - insertIndex); ranges[insertIndex - 1][1] = eparam; //合并成功 } else { ranges.splice(insertIndex, 0, [sparam, eparam]); } return ranges; } //范围交集 function Intersection(range1, range2) { let min = Math.max(range1[0], range2[0]); let max = Math.min(range1[1], range2[1]); if (max > min) return [min, max]; } class CurveTrim { constructor(_curve) { this._curve = _curve; this._TrimParams = []; this._IsErase = false; this._Box = _curve.BoundingBox.expandByVector(new Vector3(0.01, 0.01)); } TrimBy(contour, box, saveSyntropy = false) { if (this._IsErase) return; //交点参数列表 let iParams = this._curve.IntersectWith2(contour.Curve, IntersectOption.ExtendNone).map(p => p.thisParam).filter(p => p > 1e-6 && p < 0.999999); iParams.push(0, 1); iParams.sort((a, b) => a - b); arrayRemoveDuplicateBySort(iParams, (a1, a2) => equaln(a1, a2, 1e-6)); if (iParams.length === 2) //[0,1]全包含 或者在外部 { let p = this.GetPointAtParam(0.5); if (this.PointInContour(p, contour, box, saveSyntropy)) this._IsErase = true; return; } for (let i = 0; i < iParams.length - 1; i++) { let sparam = iParams[i], eparam = iParams[i + 1]; let insertIndex = InsertRangeIndex(this._TrimParams, iParams[i]); if (insertIndex !== 0 && this._TrimParams[insertIndex - 1][1] > eparam) //包含在已经被切割的范围内,那么直接不用重复判断 (只可能被前一段包含) continue; let midParam = (sparam + eparam) * 0.5; let p = this.GetPointAtParam(midParam); if (this.PointInContour(p, contour, box, saveSyntropy)) { InsertRangeAndUnion(this._TrimParams, sparam, eparam, insertIndex); if (this._TrimParams.length === 1 && this._TrimParams[0][0] === 0 && this._TrimParams[0][1] === 1) { this._IsErase = true; return; } } } } PointInContour(p, contour, contourBox, saveSyntropy = false) { return contourBox.containsPoint(p) && (contour.Curve.PtInCurve(p) && !contour.Curve.PtOnCurve(p)); } TrimParam(sparam, eparam) { let insertIndex = InsertRangeIndex(this._TrimParams, sparam); if (insertIndex !== 0 && this._TrimParams[insertIndex - 1][1] > eparam) //包含在已经被切割的范围内,那么直接不用重复判断 (只可能被前一段包含) return; InsertRangeAndUnion(this._TrimParams, sparam, eparam, insertIndex); if (this._TrimParams.length === 1 && this._TrimParams[0][0] < 1e-6 && this._TrimParams[0][1] > (1 - 1e-6)) { this._IsErase = true; } } GetPointAtParam(param) { return this._curve.GetPointAtParam(param); } get Curves() { return []; } } class CurveTrimLine extends CurveTrim { constructor(curve, derv, _Sp = curve.StartPoint, _Fd = curve.GetFirstDeriv(0)) { super(curve); this.derv = derv; this._Sp = _Sp; this._Fd = _Fd; } GetPointAtParam(param) { return this._Fd.clone().multiplyScalar(param).add(this._Sp); } PointInContour(p, contour, contourBox, saveSyntropy = false) { if (!contourBox.containsPoint(p)) return false; let pl = contour.Curve; for (let i = 0; i < pl.EndParam; i++) { let cu = pl.GetCurveAtIndex(i); if (cu instanceof Line) { let d = cu.GetClosestAtPoint(p, true); if (cu.ParamOnCurve(d.param) && equalv3(d.closestPt, p, 1e-5)) //点在线上 { let derv = cu.GetFirstDeriv(d.param).normalize(); return equalv3(derv, this.derv, 1e-4) || (saveSyntropy && equalv3(derv.negate(), this.derv, 1e-4)); //因为墙体为顺时针 轮廓为逆时针 所以这里相等=反向 } } else { if (cu.PtOnCurve(p, 1e-5)) { let derv = cu.GetFirstDeriv(cu.GetParamAtPoint2(p)).normalize(); return equalv3(this.derv, derv, 1e-4) || (saveSyntropy && equalv3(derv.negate(), this.derv, 1e-4)); } } } return contour.Curve.PtInCurve(p); } get Curves() { if (this._IsErase) return []; if (this._TrimParams.length === 0) return [this._curve]; let lines = []; if (this._TrimParams[0][0] !== 0) lines.push(new Line(this.GetPointAtParam(0), this.GetPointAtParam(this._TrimParams[0][0]))); for (let i = 0; i < this._TrimParams.length - 1; i++) { let param1 = this._TrimParams[i][1]; let param2 = this._TrimParams[i + 1][0]; lines.push(new Line(this.GetPointAtParam(param1), this.GetPointAtParam(param2))); } if (this._TrimParams[this._TrimParams.length - 1][1] !== 1) lines.push(new Line(this.GetPointAtParam(this._TrimParams[this._TrimParams.length - 1][1]), this.GetPointAtParam(1))); return lines; } } class CurveTrimArc extends CurveTrim { constructor(_curve, _IsLeft = false) { super(_curve); this._curve = _curve; this._IsLeft = _IsLeft; } PointInContour(p, contour, contourBox, saveSyntropy = false) { if (!contourBox.containsPoint(p)) return false; let pl = contour.Curve; let thisDerv = this._curve.GetFirstDeriv(p).normalize(); if (this._IsLeft) thisDerv.negate(); for (let i = 0; i < pl.EndParam; i++) { let cu = pl.GetCurveAtIndex(i); if (cu instanceof Line) { let d = cu.GetClosestAtPoint(p, true); if (cu.ParamOnCurve(d.param) && equalv3(d.closestPt, p, 1e-5)) //点在线上 //这个代码似乎是错误的,因为直线和圆弧不可能重合 { let derv = cu.GetFirstDeriv(d.param).normalize(); return equalv3(derv, thisDerv, 1e-4) || (saveSyntropy && equalv3(derv.negate(), thisDerv, 1e-4)); //因为墙体为顺时针 轮廓为逆时针 所以这里相等=反向 } } else { if (cu.PtOnCurve(p, 1e-5)) { let cuDerv = cu.GetFirstDeriv(cu.GetParamAtPoint2(p)).normalize(); return equalv3(thisDerv, cuDerv, 1e-4) || (saveSyntropy && equalv3(cuDerv.negate(), thisDerv, 1e-4)); } } } return contour.Curve.PtInCurve(p); } get Curves() { if (this._IsErase) return []; if (this._TrimParams.length === 0) return [this._curve]; let arcs = []; let center = this._curve.Center; for (let i = 0; i < this._TrimParams.length - 1; i++) { let param1 = this._TrimParams[i][1]; let param2 = this._TrimParams[i + 1][0]; arcs.push(new Arc(center, this._curve.Radius, this._curve.GetAngleAtParam(param1), this._curve.GetAngleAtParam(param2), this._curve.IsClockWise)); } if (this._TrimParams[0][0] !== 0) arcs.push(new Arc(center, this._curve.Radius, this._curve.StartAngle, this._curve.GetAngleAtParam(this._TrimParams[0][0]), this._curve.IsClockWise)); if (this._TrimParams[this._TrimParams.length - 1][1] !== 1) arcs.push(new Arc(center, this._curve.Radius, this._curve.GetAngleAtParam(this._TrimParams[this._TrimParams.length - 1][1]), this._curve.EndAngle, this._curve.IsClockWise)); return arcs; } } function EntityUpdateWrap(ent, exec) { let oldAutoUpdate = ent.AutoUpdate; ent.AutoUpdate = false; exec(); if (oldAutoUpdate) //如果原先是自动更新的,那么我们更新它,否则还是不更新(避免层层嵌套导致的性能优化丢失) ent.DeferUpdate(); ent.AutoUpdate = oldAutoUpdate; } function EntitysUpdateWrap(ens, exec) { let baks = ens.map(e => { let oldAutoUpdate = e.AutoUpdate; e.AutoUpdate = false; return oldAutoUpdate; }); exec(); for (let i = 0; i < ens.length; i++) { if (baks[i]) { let en = ens[i]; en.DeferUpdate(); en.AutoUpdate = true; } } } /** * 户型元件的基类 * 墙 平面(地板 天花) */ let RoomBase = class RoomBase extends Entity { get IsVisible() { return HostApplicationServices.IsRoomEntityVisible && super.IsVisible; } }; RoomBase = __decorate([ Factory ], RoomBase); var WallSnapMode; (function (WallSnapMode) { WallSnapMode[WallSnapMode["None"] = 0] = "None"; WallSnapMode[WallSnapMode["Center"] = 1] = "Center"; WallSnapMode[WallSnapMode["Out"] = 2] = "Out"; WallSnapMode[WallSnapMode["All"] = 3] = "All"; })(WallSnapMode || (WallSnapMode = {})); const CURVE_FACE_TYPE_KEY = "__CURVE_FACE_TYPE_KEY__"; //用来存储墙体类型的key const CURVE_DIR_TYPE_KEY = "__CURVE_DIR_TYPE_KEY__"; //方向 const CURVE_WALL_TYPE_KEY = "__CURVE_WALL_TYPE_KEY__"; //墙 var WallFaceType; (function (WallFaceType) { WallFaceType[WallFaceType["Inside"] = 0] = "Inside"; WallFaceType[WallFaceType["Pillar"] = 1] = "Pillar"; WallFaceType[WallFaceType["Outside"] = 2] = "Outside"; })(WallFaceType || (WallFaceType = {})); let RoomWallBase = class RoomWallBase extends RoomBase { constructor() { super(...arguments); this.RelevancyHoles = []; this.Holes = []; this._Thickness = 120; this._Height = 2700; this.OverWriteMaterial = new Map(); //section index -> materialId //#endregion } get RealHoles() { return this.Holes.filter(h => h.EndParam > h.StartParam && h.Top > h.Bottom); } get Height() { return this._Height; } set Height(_newHeight) { if (_newHeight <= 0.1 || equaln$1(this._Height, _newHeight)) return; this.WriteAllObjectRecord(); this._Height = _newHeight; this.Update(); } get Thickness() { return this._Thickness; } set Thickness(t) { if (t <= 0.1 || equaln$1(this._Thickness, t)) return; this.WriteAllObjectRecord(); this._Thickness = t; this.Update(); } //中心轴线 get CenterAxisCurve() { return; } //从曲线更新墙体 UpdateCurve(cu) { } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe) { let l = obj.children[1]; 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) ; else { let mesh = obj; mesh.material = this.MeshMaterial; } } IsMtlLockAtSlot(slotIndex) { if (this.LockMaterial) return true; const curMtl = this.OverWriteMaterial.get(slotIndex); if (curMtl) { if (curMtl?.Object?.IsMaterialLock) return true; } else if (this.Material?.Object?.IsMaterialLock) return true; return false; } SetMaterialAtSlot(mtl, slotIndex) { if (this.IsMtlLockAtSlot(slotIndex)) return; if (this.OverWriteMaterial.get(slotIndex) !== mtl) { this.WriteAllObjectRecord(); this.OverWriteMaterial.set(slotIndex, mtl); this.Update(UpdateDraw.Material); } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; this.WriteAllObjectRecord(); for (const [k, v] of this.OverWriteMaterial) { if (v?.Object?.IsMaterialLock) continue; this.OverWriteMaterial.set(k, mtl); } if (!this.Material?.Object?.IsMaterialLock) this.Material = mtl; this.Update(UpdateDraw.Material); } GetMtlLockedStatus() { let partMtlLocked = false; let allMtlLocked = true; for (const [, v] of this.OverWriteMaterial) { if (v?.Object?.IsMaterialLock) { partMtlLocked = true; continue; } allMtlLocked = false; } const meshSize = this.MeshMaterials.length; if (this.OverWriteMaterial.size !== meshSize) { if (super.Material?.Object?.IsMaterialLock) partMtlLocked = true; else allMtlLocked = false; } return { partMtlLocked, allMtlLocked, }; } GetPhyMtlRecords(containErased = false) { const materials = []; for (const [, v] of this.OverWriteMaterial) { if (v?.Object) if (!v?.IsErase || containErased) materials.push(v?.Object); } const meshSize = this.MeshMaterials.length; if (this.OverWriteMaterial.size !== meshSize && this._MaterialId?.Object) { if (containErased || !this._MaterialId.IsErase) materials.push(this._MaterialId?.Object); } return materials; } get MeshMaterial() { const mtlId = this._MaterialId; if (!mtlId?.IsErase && mtlId?.Object) return mtlId.Object.Material; return HostApplicationServices.DefaultWallMaterial ?? HostApplicationServices.DefaultMeshMaterial; } _GetValidOWMtl(index) { const obj = this.OverWriteMaterial.get(index)?.Object; const owMtl = !obj?.IsErase && obj?.Material; if (!owMtl) return; return owMtl; } get MeshMaterials() { let defaultMtl = this.MeshMaterial; let materials = []; let materialIndex = 0; const addMaterial = (curves) => { if (curves) for (let c of curves) { if (c[CURVE_FACE_TYPE_KEY] === WallFaceType.Outside) continue; materials.push(this._GetValidOWMtl(materialIndex) ?? defaultMtl); materialIndex++; //这里不使用1 if (materialIndex === 1) { materials.push(defaultMtl); materialIndex++; } } }; addMaterial(this.LeftCurves); addMaterial(this.RightCurves); addMaterial(this.LidCurves); if (this.Region) { materials.push(this._GetValidOWMtl(materialIndex) ?? defaultMtl); if (HostApplicationServices.DrawWallBottomFace) materials.push(this._GetValidOWMtl(materialIndex + 1) ?? defaultMtl); } return materials; } //标记算法 求出分裂或合并墙时材质状态 ResetSign(oldSign, newSign, allChange, isLid) { let materials = Array.from(this.OverWriteMaterial).sort((a, b) => a[0] - b[0]); //单面墙面增加时 找到增加的位置n n+1面使用n的材质 n+1后材质OverWriteMaterial索引都加1 if (oldSign.size < newSign.size) { for (let [signNum, data] of oldSign) { let num = signNum + (allChange ? 1 : 0); let sign = newSign.get(num); if (!equaln$1(sign, data, 1e-5)) { this.WriteAllObjectRecord(); for (let [n, m] of materials) { if (n > num) this.OverWriteMaterial.set(n + 1, m); } let index = num + 1; if (index === 1) { index++; } this.OverWriteMaterial.set(index, this.OverWriteMaterial.get(num)); break; } } } else if (oldSign.size > newSign.size) { //单面墙面减少时 找到减少的位置n n+1后材质OverWriteMaterial索引都减1 if (!materials.length) return; if (isLid) for (let [signNum, data] of oldSign) { this.WriteAllObjectRecord(); if (materials.filter((m) => m[0] === signNum).length) { this.OverWriteMaterial.delete(materials[materials.length - 1][0]); break; } } else for (let [signNum, data] of newSign) { let num = signNum + (allChange ? 1 : 0); let sign = oldSign.get(num); if (!equaln$1(sign, data, 1e-5)) { this.WriteAllObjectRecord(); for (let [n, m] of materials) { if (n > num) { let index = n; if (index === 2) index--; this.OverWriteMaterial.set(index - 1, m); } } this.OverWriteMaterial.delete(materials[materials.length - 1][0]); break; } } } } //绘制相关_end //为了支持F后正确的保持洞 CopyFrom(obj) { let bak = this.RelevancyHoles.concat(); super.CopyFrom(obj); this.RelevancyHoles = bak; } //为了支持F,看起来不大行 // override Clone(): this // { // let ent = super.Clone(); // ent.Holes = this.Holes.concat(); // return ent; // } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._Thickness = file.Read(); if (ver > 1) { let count = file.Read(); this.RelevancyHoles.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.RelevancyHoles.push(id); } } if (ver > 2) { this._Height = file.Read(); } if (ver > 3) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this._Thickness); file.Write(this.RelevancyHoles.length); for (let id of this.RelevancyHoles) file.WriteObjectId(id); file.Write(this._Height); // ver4 file.WriteBool(this._LockMaterial); } }; RoomWallBase.SnapMode = WallSnapMode.All; __decorate([ AutoRecord ], RoomWallBase.prototype, "RelevancyHoles", void 0); RoomWallBase = __decorate([ Factory ], RoomWallBase); const CURVE_MESH_NAMES = [ "Shape", "GetDrawCount", "Midpoint", "MidParam", "StartParam", "EndParam", "Area", "Area2", "Length", "IsClose", "IsClockWise", "GetPointAtParam", "GetPointAtDistance", "GetDistAtParam", "GetDistAtPoint", "GetParamAtPoint", "GetParamAtPoint2", "GetParamAtDist", "GetClosestAtPoint", "GetFirstDeriv", "GetFirstDerivAngle", "GetSplitCurves", "GetCurveAtParamRange", "GetSplitCurvesByPts", "Extend", "Join", "Reverse", "PtOnCurve", "PtOnCurve2", "PtOnCurve3", "ParamOnCurve", "GetOffsetCurves", "GetClosestPointTo", "IntersectWith", "IntersectWith2", "SplitParamSort", "SetStartEndPoint", ]; // This can live anywhere in your codebase: function applyMixins(derivedCtor, constructor, methons = CURVE_MESH_NAMES) { for (let name of methons) Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(constructor.prototype, name) ?? Object.getOwnPropertyDescriptor(Curve.prototype, name) ?? Object.create(null)); } const SnapTempLine = new Line; const TempP$1 = new Vector3; let RoomWallLine = class RoomWallLine extends RoomWallBase { constructor(_StartPoint = new Vector3, _EndPoint = new Vector3, _Thickness = 120, _Height = 2700) { super(); this._StartPoint = _StartPoint; this._EndPoint = _EndPoint; this._LeftCurveSign = new Map(); this._RightCurveSign = new Map(); this._LidCurveSign = new Map(); this.Thickness = _Thickness; this.Height = _Height; } UpdateOCSToMinBox() { this.WriteAllObjectRecord(); let sp = this.StartPoint; let ep = this.EndPoint; let x = this.GetFirstDeriv(0).normalize(); let z = ZAxis; let y = z.clone().cross(x).normalize(); this._Matrix.makeBasis(x, y, z).setPosition(sp); let inv = this.OCSInv; this._StartPoint.copy(sp).applyMatrix4(inv); this._EndPoint.copy(ep).applyMatrix4(inv); } get StartPoint() { return this._StartPoint.clone().applyMatrix4(this.OCSNoClone); } get EndPoint() { return this._EndPoint.clone().applyMatrix4(this.OCSNoClone); } set StartPoint(p) { p = TempP$1.copy(p).applyMatrix4(this.OCSInv).setZ(0); if (!equalv3(p, this._StartPoint, 1e-4)) { this.WriteAllObjectRecord(); this._StartPoint.copy(p); this.Update(); } } set EndPoint(p) { p = TempP$1.copy(p).applyMatrix4(this.OCSInv).setZ(0); if (!equalv3(p, this._EndPoint, 1e-4)) { this.WriteAllObjectRecord(); this._EndPoint.copy(p); this.Update(); } } Reverse() { this.WriteAllObjectRecord(); [this._EndPoint, this._StartPoint] = [this._StartPoint, this._EndPoint]; return this; } //中心轴线 get CenterAxisCurve() { let line = new Line(this._StartPoint.clone(), this._EndPoint.clone()); line.OCSNoClone.copy(this.OCSNoClone); return line; } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } get BoundingBoxInOCS() { let line = new Line(this._StartPoint, this._EndPoint); let parse = new GetLineParam(line); if (parse.Length > 1e-5) { let p1 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * 0.5); //left let p2 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * -0.5); //right let p3 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * 0.5); //left let p4 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * -0.5); //right p1.z = this._Height; return new Box3Ext().setFromPoints([p1, p2, p3, p4]); } return new Box3Ext().setFromPoints([this._StartPoint, this._EndPoint]); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let pts = []; pts = pts.concat(Line.prototype.GetObjectSnapPoints.call(this, snapMode, pickPoint, lastPoint, viewXform)); const CurveSnap = (curve) => { let bakZ = curve.Z; //底部线 arrayPushArray(pts, curve.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); //顶部线 curve.Z += this._Height; arrayPushArray(pts, curve.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); curve.Z = bakZ; //柱子线 SnapTempLine.OCSNoClone.copy(curve.OCSNoClone); //@ts-ignore SnapTempLine._StartPoint.copy(curve._StartPoint); //@ts-ignore SnapTempLine._EndPoint.copy(curve._StartPoint); //@ts-ignore SnapTempLine._EndPoint.z += this._Height; arrayPushArray(pts, SnapTempLine.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); //@ts-ignore SnapTempLine._StartPoint.copy(curve._EndPoint); //@ts-ignore SnapTempLine._EndPoint.copy(curve._EndPoint); //@ts-ignore SnapTempLine._EndPoint.z += this._Height; arrayPushArray(pts, SnapTempLine.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); }; if (this.LeftCurves && (RoomWallBase.SnapMode & WallSnapMode.Out) > 0) { this.LeftCurves.forEach(CurveSnap); this.RightCurves.forEach(CurveSnap); this.LidCurves.forEach(CurveSnap); } return pts; } GetGripPoints() { let sp = this.StartPoint; let ep = this.EndPoint; let pts = [sp, midPoint(sp, ep), ep]; for (let i = 0; i < 3; i++) { let p = pts[i].clone(); pts.push(p.setZ(p.z + this._Height)); } return pts; } MoveGripPoints(indexs, vec) { if (equalv3(vec, ZeroVec, 1e-4)) return; this.WriteAllObjectRecord(); let set = new Set(); for (let i of indexs) if (i > 2) set.add(i - 3); else set.add(i); EntityUpdateWrap(this, () => { for (let index of set) { 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) { if (equalv3(vec, ZeroVec, 1e-4)) return; this.WriteAllObjectRecord(); EntityUpdateWrap(this, () => { for (let index of indexList) { if (index === 0) this.StartPoint = this.StartPoint.add(vec); else this.EndPoint = this.EndPoint.add(vec); } }); } //#region //绘制 UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; for (let hole of this.RealHoles) if (hole.StartParam > hole.EndParam) [hole.StartParam, hole.EndParam] = [hole.EndParam, hole.StartParam]; let pts; if (!this.LeftCurves) { let line = new Line(this._StartPoint, this._EndPoint); let parse = new GetLineParam(line); if (parse.Length > 0.1) { let p1 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * 0.5); //left let p2 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * -0.5); //right let p3 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * 0.5); //left let p4 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * -0.5); //right let [pz1, pz2, pz3, pz4] = [p1, p2, p3, p4].map(p => p.clone().setZ(this._Height)); pts = [p1, p2, p2, p4, p4, p3, p3, p1, pz1, pz2, pz2, pz4, pz4, pz3, pz3, pz1, p1, pz1, p2, pz2, p3, pz3, p4, pz4 ]; } } else { pts = []; let inv = this.OCSInv; const DrawCurve = (curve, _leftRanges, _rightRanges) => { if (curve instanceof Line) { let p1 = curve.StartPoint.applyMatrix4(inv); let p2 = curve.EndPoint.applyMatrix4(inv); pts.push(p1, p2); for (let range of _leftRanges) pts.push(p1.clone().setZ(range[0]), p1.clone().setZ(range[1])); for (let range of _rightRanges) pts.push(p2.clone().setZ(range[0]), p2.clone().setZ(range[1])); pts.push(p1.clone().setZ(this._Height), p2.clone().setZ(this._Height)); } }; let lidRanges = [[0, this._Height]]; let leftRanges = lidRanges; let rightRanges = lidRanges; for (let hole of this.RealHoles) { if (equaln$1(hole.StartParam, 0)) { let newLeftRanges = []; for (let range of leftRanges) arrayPushArray(newLeftRanges, SubtractRange(range[0], range[1], hole.Bottom, hole.Top, 1e5)); leftRanges = newLeftRanges; } if (equaln$1(hole.EndParam, 1)) { let newRightRanges = []; for (let range of rightRanges) arrayPushArray(newRightRanges, SubtractRange(range[0], range[1], hole.Bottom, hole.Top, 1e5)); rightRanges = newRightRanges; } } for (let i = 0; i < this.LeftCurves.length; i++) { let curve = this.LeftCurves[i]; DrawCurve(curve, i === 0 ? leftRanges : lidRanges, (i === this.LeftCurves.length - 1) ? rightRanges : lidRanges); } for (let i = 0; i < this.RightCurves.length; i++) { let curve = this.RightCurves[i]; DrawCurve(curve, i === 0 ? leftRanges : lidRanges, (i === this.RightCurves.length - 1) ? rightRanges : lidRanges); } for (let i = 0; i < this.LidCurves.length; i++) { let curve = this.LidCurves[i]; DrawCurve(curve, lidRanges, lidRanges); } } this._EdgeGeometry = BufferGeometryUtils.CreateFromPts(pts ?? []); return this._EdgeGeometry; } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this._MeshGeometry = new Geometry; let geo = this._MeshGeometry; let normal = this.Normal; let normaln = normal.clone().negate(); let line = new Line(this._StartPoint, this._EndPoint); let parse = new GetLineParam(line); let materialIndex = 0; let expMaterialIndex = 0; if (!this.LeftCurves) { if (parse.Length > 0.1) { let p1 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * 0.5); //left let p2 = parse.OffsetPoint(this._StartPoint.clone(), this.Thickness * -0.5); //right let p3 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * 0.5); //left let p4 = parse.OffsetPoint(this._EndPoint.clone(), this.Thickness * -0.5); //right let [pz1, pz2, pz3, pz4] = [p1, p2, p3, p4].map(p => p.clone().setZ(this._Height)); geo.vertices.push(p1, p2, p3, p4, pz1, pz2, pz3, pz4); geo.faces.push( //底部 new Face3(0, 2, 1, normaln), new Face3(1, 2, 3, normaln), //顶部 new Face3(0 + 4, 1 + 4, 2 + 4, normal), new Face3(1 + 4, 3 + 4, 2 + 4, normal), //开始盖子 new Face3(0, 1, 5, parse.Direction.clone().negate()), new Face3(0, 5, 4, parse.Direction.clone().negate()), //结束盖子 new Face3(2, 7, 3, parse.Direction), new Face3(2, 6, 7, parse.Direction), //left new Face3(0, 6, 2, parse.LeftDir), new Face3(0, 4, 6, parse.LeftDir), //right new Face3(1, 3, 7, parse.LeftDir.clone().negate()), new Face3(1, 7, 5, parse.LeftDir.clone().negate())); //x let x = parse.Length * 1e-3; let y = this.Thickness * 1e-3; let z = this._Height * 1e-3; geo.faceVertexUvs[0].push( //floor [new Vector2$1(0, 0), new Vector2$1(x, 0), new Vector2$1(0, y)], [new Vector2$1(0, y), new Vector2$1(x, 0), new Vector2$1(x, y)], //top [new Vector2$1(0, 0), new Vector2$1(0, y), new Vector2$1(x, 0)], [new Vector2$1(0, y), new Vector2$1(x, y), new Vector2$1(x, 0)], //start lid [new Vector2$1(0, 0), new Vector2$1(y, 0), new Vector2$1(y, z)], [new Vector2$1(0, 0), new Vector2$1(y, z), new Vector2$1(0, z)], //end lid [new Vector2$1(y, 0), new Vector2$1(0, z), new Vector2$1(0, 0)], [new Vector2$1(y, 0), new Vector2$1(y, z), new Vector2$1(0, z)], //left [new Vector2$1(0, 0), new Vector2$1(x, z), new Vector2$1(x, 0)], [new Vector2$1(0, 0), new Vector2$1(0, z), new Vector2$1(x, z)], //right [new Vector2$1(0, 0), new Vector2$1(x, 0), new Vector2$1(0, z)], [new Vector2$1(0, 0), new Vector2$1(0, z), new Vector2$1(0, z)]); } } else { let inv = this.OCSInv; let thisParam = new GetLineParam(this); let newLeftSign = new Map(); let newRightSign = new Map(); let newLidSign = new Map(); const BuildLeftFace = (curve) => { if (curve[CURVE_FACE_TYPE_KEY] === WallFaceType.Outside) { materialIndex = 1; } if (curve instanceof Line) { let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetLineParam(curve); if (curveParam.Length === 0) return; let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)), 0, 1); let end = equaln$1(hole.EndParam, 1) ? 1 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)), 0, 1); if (equaln$1(start, end, 1e-8)) continue; //重复的点造成了绘制错误 if (start > end) [start, end] = [end, start]; start = Math.max(0, start); end = Math.min(1, end); if (start >= end) continue; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } } for (let tape of tapes) { let startIndex = geo.vertices.length; let p1 = curveParam.GetPointAtParam(tape.start).applyMatrix4(inv); let p2 = curveParam.GetPointAtParam(tape.end).applyMatrix4(inv); geo.vertices.push(p1.setZ(tape.bottom), p2.setZ(tape.bottom)); geo.vertices.push(p1.clone().setZ(tape.top)); geo.vertices.push(p2.clone().setZ(tape.top)); let startX = curveParam.Length * 1e-3 * tape.start; let endX = curveParam.Length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; //记录左墙线段面域 let n = newLeftSign.get(materialIndex) ?? 0; if (n < endX) newLeftSign.set(materialIndex, endX); geo.faces.push(new Face3(startIndex, startIndex + 2, startIndex + 1, parse.LeftDir, undefined, materialIndex), new Face3(startIndex + 1, startIndex + 2, startIndex + 3, parse.LeftDir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, startZ)], [new Vector2$1(endX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, endZ)]); } //如果是1的话 是渲染器走的 if (materialIndex === 1) { materialIndex = expMaterialIndex; } else { materialIndex++; expMaterialIndex++; //这里不使用1 if (materialIndex === 1) { materialIndex++; expMaterialIndex++; } } } }; const BuildRightFace = (curve, isLid) => { if (curve[CURVE_FACE_TYPE_KEY] === WallFaceType.Outside) { materialIndex = 1; } if (curve instanceof Line) { let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetLineParam(curve); if (curveParam.Length === 0) return; let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)), 0, 1); let end = equaln$1(hole.EndParam, 1) ? 1 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)), 0, 1); if (equaln$1(start, end, 1e-8)) continue; //重复的点造成了绘制错误 if (start > end) [start, end] = [end, start]; start = Math.max(0, start); end = Math.min(1, end); if (start >= end) continue; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } } for (let tape of tapes) { let startIndex = geo.vertices.length; let p1 = curveParam.GetPointAtParam(tape.start).applyMatrix4(inv); let p2 = curveParam.GetPointAtParam(tape.end).applyMatrix4(inv); geo.vertices.push(p1.setZ(tape.bottom), p2.setZ(tape.bottom)); geo.vertices.push(p1.clone().setZ(tape.top)); geo.vertices.push(p2.clone().setZ(tape.top)); let startX = curveParam.Length * 1e-3 * tape.start; let endX = curveParam.Length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; //记录右墙线段面域 或墙边面域 if (isLid) { let n = newLidSign.get(materialIndex) ?? 0; if (n < endX) newLidSign.set(materialIndex, endX); } else { let n = newRightSign.get(materialIndex) ?? 0; if (n < endX) newRightSign.set(materialIndex, endX); } geo.faces.push(new Face3(startIndex, startIndex + 1, startIndex + 2, curveParam.RightDir, undefined, materialIndex), new Face3(startIndex + 1, startIndex + 3, startIndex + 2, curveParam.RightDir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(endX, startZ), new Vector2$1(startX, endZ)], [new Vector2$1(endX, startZ), new Vector2$1(endX, endZ), new Vector2$1(startX, endZ)]); } //如果是1的话 是渲染器走的 if (materialIndex === 1) { materialIndex = expMaterialIndex; } else { materialIndex++; expMaterialIndex++; //这里不使用1 if (materialIndex === 1) { materialIndex++; expMaterialIndex++; } } } }; this.LeftCurves.forEach(BuildLeftFace); this.RightCurves.forEach((cu) => BuildRightFace(cu)); this.LidCurves.forEach((cu) => BuildRightFace(cu, true)); this.ResetSign(this._LeftCurveSign, newLeftSign); let allChange = this._LeftCurveSign.size !== newLeftSign.size && this._RightCurveSign.size !== newRightSign.size; this.ResetSign(this._RightCurveSign, newRightSign, allChange); this.ResetSign(this._LidCurveSign, newLidSign, false, true); this._LeftCurveSign = newLeftSign; this._RightCurveSign = newRightSign; this._LidCurveSign = newLidSign; if (this.Region) { let bakZ = this.Region.OCSNoClone.elements[14]; this.Region.OCSNoClone.elements[14] = this._Matrix.elements[14]; let pts = this.Region.MatrixAlignTo2(this.OCSNoClone).pts; this.Region.OCSNoClone.elements[14] = bakZ; let faces = ShapeUtils.triangulateShape(pts, []); let startIndex = geo.vertices.length; for (let p of pts) geo.vertices.push(new Vector3(p.x, p.y, this._Height)); if (HostApplicationServices.DrawWallBottomFace) for (let p of pts) geo.vertices.push(new Vector3(p.x, p.y, 0)); for (let i = 0; i < faces.length; i++) { let [a, b, c] = faces[i]; geo.faces.push(new Face3(startIndex + a, startIndex + b, startIndex + c, normal, new Color, materialIndex)); let uvs = faces[i].map(index => pts[index].clone()); geo.faceVertexUvs[0].push(uvs); if (HostApplicationServices.DrawWallBottomFace) { geo.faces.push(new Face3(startIndex + pts.length + c, startIndex + pts.length + b, startIndex + pts.length + a, normaln, new Color, materialIndex + 1)); geo.faceVertexUvs[0].push(uvs.concat().reverse().map(v => v.clone())); } } // //todo:为了优化显示 我们可以把侧面也画出来 (应该使用和酷家乐一样的技术 在视线对准时,隐藏整个墙) // let d = this._EndPoint.clone().sub(this._StartPoint).normalize(); // let pre = pts[pts.length - 1]; // let tempV = new Vector3; // for (let i = 0; i < pts.length; i++) // { // let p = pts[i]; // tempV.set(p.x - pre.x, p.y - pre.y, 0).normalize(); // //todo:盖子会重复绘制 // if (!isParallelTo(d, tempV, 1e-3) && !isPerpendicularityTo(d, tempV, 1e-3)) // { // startIndex = geo.vertices.length; // geo.vertices.push(AsVector3(pre), AsVector3(p)); // geo.vertices.push(AsVector3(pre).setZ(this._Height)); // geo.vertices.push(AsVector3(p).setZ(this._Height)); // LEFT_ROTATE_MTX2.applyVector(tempV); // tempV.negate(); // let n = tempV.clone(); // geo.faces.push( // new Face3(startIndex, startIndex + 1, startIndex + 2, n), // new Face3(startIndex + 1, startIndex + 3, startIndex + 2, n), // ); // geo.faceVertexUvs[0].push( // [new Vector2(), new Vector2(0, 0), new Vector2(0, 0)], // [new Vector2(0, 0), new Vector2(0, 0), new Vector2(0, 0)], // ); // } // pre = p; // } } } geo.computeFaceNormals(); geo.verticesNeedUpdate = true; geo.uvsNeedUpdate = true; return geo; } 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.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterials); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } let obj = new Object3D; if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { let pts = [this._StartPoint, this._EndPoint]; let geo = new BufferGeometry().setFromPoints(pts); let axisLine = new Line$1(geo, ColorMaterial.GetWallLineMtl(1)); axisLine.computeLineDistances(); obj.add(axisLine); let line = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); obj.add(line); } else if (renderType === RenderType.Print) { return new Object3D().add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Conceptual) { let mesh = new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)); let line = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); obj.add(mesh, line); } else if (renderType === RenderType.Physical2) { const mesh = new Mesh(this.MeshGeometry, this.MeshMaterials); mesh.castShadow = true; mesh.receiveShadow = true; obj.add(mesh); const outline = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); obj.add(outline); } return obj; } /** * 重载:更新绘制的实体 * @param {RenderType} renderType * @param {Object3D} obj */ UpdateDrawObject(renderType, obj) { if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { let [axisLine, outWallLine] = obj.children; BufferGeometryUtils.UpdatePts(axisLine.geometry, [this._StartPoint, this._EndPoint]); axisLine.computeLineDistances(); if (outWallLine.geometry !== this.EdgeGeometry) { outWallLine.geometry.dispose(); outWallLine.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { let mesh = obj.children[0]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let line = obj.children[1]; if (line.geometry !== this.EdgeGeometry) { line.geometry.dispose(); line.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Physical) { let mesh = obj; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } } else if (renderType === RenderType.Print) { //优化代码 保证在布局是打印模式时,在模型空间删除洞时候 布局空间能正常更新. let mesh = obj.children[1]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let line = obj.children[0]; 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)); line.geometry.dispose(); line.geometry = geometry; } } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Physical) { let mesh = obj; mesh.material = this.MeshMaterials; } else if (renderType === RenderType.Physical2) { const mesh = obj.children[0]; mesh.material = this.MeshMaterials; } } ClearDraw() { if (this._MeshGeometry) this._MeshGeometry = undefined; if (this._EdgeGeometry) this._EdgeGeometry = undefined; return super.ClearDraw(); } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._StartPoint.set(file.Read(), file.Read(), file.Read()); this._EndPoint.set(file.Read(), file.Read(), file.Read()); this.OverWriteMaterial.clear(); this._LeftCurveSign.clear(); this._RightCurveSign.clear(); this._LidCurveSign.clear(); this.LeftCurves = []; this.RightCurves = []; this.LidCurves = []; if (ver > 1) { let size = file.Read(); for (let i = 0; i < size; i++) { let index = file.Read(); let id = file.ReadHardObjectId(); this.OverWriteMaterial.set(index, id); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); super.WriteFile(file); file.WriteVec3(this._StartPoint); file.WriteVec3(this._EndPoint); file.Write(this.OverWriteMaterial.size); for (let [index, id] of this.OverWriteMaterial) { file.Write(index); file.WriteHardObjectId(id); } } }; RoomWallLine = __decorate([ Factory ], RoomWallLine); applyMixins(RoomWallLine, Line); const LEFT_ROTATE_MTX2 = new Matrix2().set(0, 1, -1, 0); const TempDiffVec = new Vector3; function CreateGetCurveParam(curve) { if (curve instanceof Line || curve instanceof RoomWallLine) return new GetLineParam(curve); else return new GetArcParam(curve); } class GetLineParam { constructor(line) { this.Direction = line.GetFirstDeriv(0); this.Length = this.Direction.length(); this.Direction.divideScalar(this.Length); this._StartPoint = line.StartPoint; this.LeftDir = this.Direction.clone(); LEFT_ROTATE_MTX2.applyVector(this.LeftDir); } get RightDir() { if (!this._RightDir) this._RightDir = this.LeftDir.clone().negate(); return this._RightDir; } get NegDirection() { if (!this._NegDirection) this._NegDirection = this.Direction.clone().negate(); return this._NegDirection; } GetParamAtPoint(p) { TempDiffVec.subVectors(p, this._StartPoint); let param = this.Direction.dot(TempDiffVec); return param / this.Length; } //获取参数 并且判断点是不是在线上 GetParamAtPoint2(p) { let param = this.GetParamAtPoint(p); let np = this.GetPointAtParam(param); return [param, Vector2$1.prototype.distanceToSquared.call(np, p) < 1e-4]; } GetPointAtParam(param) { return this.Direction.clone().multiplyScalar(param * this.Length).add(this._StartPoint); } OffsetPoint(p, dis) { return p.add(this.LeftDir.clone().multiplyScalar(dis)); } } class GetArcParam { constructor(_Arc) { this._Arc = _Arc; } GetParamAtPoint2(p) { let param = this._Arc.GetParamAtAngle(this._Arc.GetAngleAtPoint(p)); return [param, this._Arc.ParamOnCurve(param)]; } GetPointAtParam(param) { return this._Arc.GetPointAtParam(param); } GetParamAtPoint(p) { return this._Arc.GetParamAtAngle(this._Arc.GetAngleAtPoint(p)); } } let clipperCpp = {}; function InitClipperCpp() { if (clipperCpp.lib) return; if (!globalThis.document) globalThis.document = {}; return new Promise((res, rej) => { clipperLib.loadNativeClipperLibInstanceAsync( // let it autodetect which one to use, but also available WasmOnly and AsmJsOnly clipperLib.NativeClipperLibRequestedFormat.AsmJsOnly).then(c => { clipperCpp.lib = c; res(); // console.log("载入成功!");//不再需要 }); }); } //点表面积 function Area(pts) { let cnt = pts.length; if (cnt < 3) return 0; let a = 0; for (let i = 0, j = cnt - 1; i < cnt; ++i) { a += (pts[j].x + pts[i].x) * (pts[j].y - pts[i].y); j = i; } return -a * 0.5; } class Vector2 { constructor(x = 0, y = 0) { this.isVector2 = true; this.x = x; this.y = y; } get width() { return this.x; } set width(value) { this.x = value; } get height() { return this.y; } set height(value) { this.y = value; } set(x, y) { this.x = x; this.y = y; return this; } setScalar(scalar) { this.x = scalar; this.y = scalar; return this; } setX(x) { this.x = x; return this; } setY(y) { this.y = y; return this; } setComponent(index, value) { switch (index) { case 0: this.x = value; break; case 1: this.y = value; break; default: throw new Error('index is out of range: ' + index); } return this; } getComponent(index) { switch (index) { case 0: return this.x; case 1: return this.y; default: throw new Error('index is out of range: ' + index); } } clone() { return new this.constructor().copy(this); } copy(v) { this.x = v.x; this.y = v.y; return this; } add(v) { this.x += v.x; this.y += v.y; return this; } addScalar(s) { this.x += s; this.y += s; return this; } addVectors(a, b) { this.x = a.x + b.x; this.y = a.y + b.y; return this; } addScaledVector(v, s) { this.x += v.x * s; this.y += v.y * s; return this; } sub(v) { this.x -= v.x; this.y -= v.y; return this; } subScalar(s) { this.x -= s; this.y -= s; return this; } subVectors(a, b) { this.x = a.x - b.x; this.y = a.y - b.y; return this; } multiply(v) { this.x *= v.x; this.y *= v.y; return this; } multiplyScalar(scalar) { if (isFinite(scalar)) { this.x *= scalar; this.y *= scalar; } else { this.x = 0; this.y = 0; } return this; } divide(v) { this.x /= v.x; this.y /= v.y; return this; } divideScalar(scalar) { return this.multiplyScalar(1 / scalar); } min(v) { this.x = Math.min(this.x, v.x); this.y = Math.min(this.y, v.y); return this; } max(v) { this.x = Math.max(this.x, v.x); this.y = Math.max(this.y, v.y); return this; } clamp(min, max) { // This function assumes min < max, if this assumption isn't true it will not operate correctly this.x = Math.max(min.x, Math.min(max.x, this.x)); this.y = Math.max(min.y, Math.min(max.y, this.y)); return this; } clampScalar(minVal, maxVal) { const min = Vector2.clampScalar_min.set(minVal, minVal); const max = Vector2.clampScalar_max.set(maxVal, maxVal); return this.clamp(min, max); } clampLength(min, max) { const length = this.length(); return this.multiplyScalar(Math.max(min, Math.min(max, length)) / length); } floor() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); return this; } ceil() { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); return this; } round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } roundToZero() { this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x); this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y); return this; } negate() { this.x = -this.x; this.y = -this.y; return this; } dot(v) { return this.x * v.x + this.y * v.y; } lengthSq() { return this.x * this.x + this.y * this.y; } length() { return Math.sqrt(this.x * this.x + this.y * this.y); } lengthManhattan() { return Math.abs(this.x) + Math.abs(this.y); } normalize() { return this.divideScalar(this.length()); } angle() { // computes the angle in radians with respect to the positive x-axis let angle = Math.atan2(this.y, this.x); if (angle < 0) angle += 2 * Math.PI; return angle; } distanceTo(v) { return Math.sqrt(this.distanceToSquared(v)); } distanceToSquared(v) { const dx = this.x - v.x, dy = this.y - v.y; return dx * dx + dy * dy; } distanceToManhattan(v) { return Math.abs(this.x - v.x) + Math.abs(this.y - v.y); } setLength(length) { return this.multiplyScalar(length / this.length()); } lerp(v, alpha) { this.x += (v.x - this.x) * alpha; this.y += (v.y - this.y) * alpha; return this; } lerpVectors(v1, v2, alpha) { return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1); } equals(v) { return ((v.x === this.x) && (v.y === this.y)); } fromArray(array, offset = 0) { this.x = array[offset]; this.y = array[offset + 1]; return this; } toArray(array = [], offset = 0) { array[offset] = this.x; array[offset + 1] = this.y; return array; } fromAttribute(attribute, index, offset = 0) { index = index * attribute.itemSize + offset; this.x = attribute.array[index]; this.y = attribute.array[index + 1]; return this; } rotateAround(center, angle) { const c = Math.cos(angle), s = Math.sin(angle); const x = this.x - center.x; const y = this.y - center.y; this.x = x * c - y * s + center.x; this.y = x * s + y * c + center.y; return this; } } Vector2.clampScalar_min = new Vector2(); Vector2.clampScalar_max = new Vector2(); class Box2 { constructor(min = new Vector2(+Infinity, +Infinity), max = new Vector2(-Infinity, -Infinity)) { this.min = min; this.max = max; } get area() { return (this.max.x - this.min.x) * (this.max.y - this.min.y); } set(min, max) { this.min.copy(min); this.max.copy(max); return this; } setFromPoints(points) { this.makeEmpty(); for (let p of points) { this.expandByPoint(p); } return this; } setFromCenterAndSize(center, size) { const v1 = Box2._setFromCenterAndSize_v1; const halfSize = v1.copy(size).multiplyScalar(0.5); this.min.copy(center).sub(halfSize); this.max.copy(center).add(halfSize); return this; } clone() { return new this.constructor().copy(this); } copy(box) { this.min.copy(box.min); this.max.copy(box.max); return this; } makeEmpty() { this.min.x = this.min.y = +Infinity; this.max.x = this.max.y = -Infinity; return this; } isEmpty() { // this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes return (this.max.x < this.min.x) || (this.max.y < this.min.y); } getCenter(result = new Vector2()) { return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5); } getSize(result = new Vector2()) { return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min); } expandByPoint(point) { this.min.min(point); this.max.max(point); return this; } expandByVector(vector) { this.min.sub(vector); this.max.add(vector); return this; } expandByScalar(scalar) { this.min.addScalar(-scalar); this.max.addScalar(scalar); return this; } containsPoint(point) { if (point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y) { return false; } return true; } containsBox(box) { if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x) && (this.min.y <= box.min.y) && (box.max.y <= this.max.y)) { return true; } return false; } getParameter(point, result = new Vector2()) { // This can potentially have a divide by zero if the box // has a size dimension of 0. return result.set((point.x - this.min.x) / (this.max.x - this.min.x), (point.y - this.min.y) / (this.max.y - this.min.y)); } intersectsBox(box) { // using 6 splitting planes to rule out intersections. if (box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y) { return false; } return true; } clampPoint(point, result = new Vector2()) { return result.copy(point).clamp(this.min, this.max); } distanceToPoint(point) { const v1 = Box2._distanceToPoint_v1; const clampedPoint = v1.copy(point).clamp(this.min, this.max); return clampedPoint.sub(point).length(); } intersect(box) { this.min.max(box.min); this.max.min(box.max); return this; } union(box) { this.min.min(box.min); this.max.max(box.max); return this; } translate(offset) { this.min.add(offset); this.max.add(offset); return this; } equals(box) { return box.min.equals(this.min) && box.max.equals(this.max); } } Box2._setFromCenterAndSize_v1 = new Vector2(); Box2._distanceToPoint_v1 = new Vector2(); /** * 轮廓路径类 * 可以求NFP,和保存NFPCahce * 因为NFP结果是按照最低点移动的,所以将点旋转后,按照盒子将点移动到0点. */ class Path { constructor(OrigionPoints, rotation = 0) { this.OrigionPoints = OrigionPoints; this.OutsideNFPCache = {}; this.InsideNFPCache = {}; if (OrigionPoints) this.Init(OrigionPoints, rotation); } Init(origionPoints, rotation) { this.Rotation = rotation; if (rotation === 0) this.Points = origionPoints.map(p => { return { ...p }; }); else { let c = Math.cos(rotation); let s = Math.sin(rotation); let npts = []; for (let p of origionPoints) { let x = p.x; let y = p.y; const x1 = x * c - y * s; const y1 = x * s + y * c; npts.push({ x: x1, y: y1 }); } this.Points = npts; } let box = new Box2(); let v2 = new Vector2(); for (let p of this.Points) { v2.x = p.x; v2.y = p.y; box.expandByPoint(v2); } this.OrigionMinPoint = box.min; this.Size = box.max.sub(box.min); for (let p of this.Points) { p.x -= box.min.x; p.y -= box.min.y; } } GetNFPs(path, outside) { // 寻找内轮廓时,面积应该比本path小,这个判断移交给使用者自己判断 // if (!outside && this.Area < path.Area) return []; let nfps = clipperCpp.lib.minkowskiSumPath(this.BigIntPoints, path.MirrorPoints, true); //必须删除自交,否则将会出错 nfps = clipperCpp.lib.simplifyPolygons(nfps); nfps = nfps.filter((nfp) => { let area = Area(nfp); // if (area > 1) return outside;//第一个不一定是外轮廓,但是面积为正时肯定为外轮廓 (因为使用了简化多段线,所以这个代码已经不能有了) if (Math.abs(area) < 10) return false; //应该不用在移除这个了 let { x, y } = nfp[0]; if (outside) { if (this.Area > path.Area) { let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }; if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y) return true; let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints); return dir === 0; } else { let p = { x: this.InPoint.x - x, y: this.InPoint.y - y }; if (p.x < 0 || p.y < 0 || p.x > path.BigSize.x || p.y > path.BigSize.y) return true; let dir = clipperCpp.lib.pointInPolygon(p, path.BigIntPoints); return dir === 0; } } else { let p = { x: path.InPoint.x + x, y: path.InPoint.y + y }; if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y) return false; let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints); return dir === 1; } }); return nfps; } GetOutsideNFP(path) { let nfps = this.OutsideNFPCache[path.Id]; if (nfps) return nfps; if (this.IsRect && path.IsRect) { let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]; let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]; nfps = [[ { x: -bx, y: -by }, { x: ax, y: -by }, { x: ax, y: ay }, { x: -bx, y: ay }, ]]; } else nfps = this.GetNFPs(path, true); this.OutsideNFPCache[path.Id] = nfps; //虽然有这种神奇的特性,但是好像并不会提高性能。 // path.OutsideNFPCache[this.id] = (this, nfps.map(nfp => // { // return nfp.map(p => // { // return { x: -p.x, y: -p.y }; // }); // })); return nfps; } GetInsideNFP(path) { if (path.Area > this.Area) return; let nfp = this.InsideNFPCache[path.Id]; if (nfp) return nfp; let nfps; if (this.IsRect) { let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4]; let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4]; let l = ax - bx; let h = ay - by; const MinNumber = 200; //清理的数值是100,所以200是可以接受的, 200=0.020问题不大(过盈配合) if (l < -MinNumber || h < -MinNumber) return; if (l < MinNumber) l = MinNumber; else l += MinNumber; if (h < MinNumber) h = MinNumber; else h += MinNumber; nfps = [[ { x: 0, y: 0 }, { x: l, y: 0 }, { x: l, y: h }, { x: 0, y: h } ]]; } else nfps = this.GetNFPs(path, false); if (path.Id !== undefined) this.InsideNFPCache[path.Id] = nfps; return nfps; } /** * 用这个点来检测是否在Path内部 */ get InPoint() { if (this._InPoint) return this._InPoint; let mp = { x: (this.Points[0].x + this.Points[1].x) / 2, y: (this.Points[0].y + this.Points[1].y) / 2 }; let normal = new Vector2(this.Points[1].x - this.Points[0].x, this.Points[1].y - this.Points[0].y).normalize(); // [normal.x, normal.y] = [normal.y, -normal.x]; mp.x -= normal.y; mp.y += normal.x; mp.x *= 1e4; mp.y *= 1e4; this._InPoint = mp; return mp; } get BigIntPoints() { if (this._BigIntPoints) return this._BigIntPoints; this._BigIntPoints = this.Points.map(p => { return { x: Math.round(p.x * 1e4), y: Math.round(p.y * 1e4), }; }); return this._BigIntPoints; } get BigSize() { if (this._BigSize) return this._BigSize; this._BigSize = new Vector2(this.Size.x * 1e4, this.Size.y * 1e4); return this._BigSize; } get MirrorPoints() { if (!this._MirrorPoints) this._MirrorPoints = this.BigIntPoints.map(p => { return { x: -p.x, y: -p.y }; }); return this._MirrorPoints; } get BoundingBox() { if (!this._BoundingBox) this._BoundingBox = new Box2(new Vector2, this.Size); return this._BoundingBox; } get Area() { if (this._Area === undefined) this._Area = Area(this.Points); return this._Area; } set Area(a) { this._Area = a; } get IsRect() { if (this._IsRect === undefined) { let s = this.BoundingBox.getSize(new Vector2); this._IsRect = equaln(this.Area, s.x * s.y, 1); } return this._IsRect; } ReadFile(file) { file.Read(); this.Id = file.Read(); let arr = file.Read(); this.Points = []; for (let i = 0; i < arr.length; i += 2) { let p = { x: arr[i], y: arr[i + 1] }; this.Points.push(p); } this.Size = new Vector2(file.Read(), file.Read()); this._Area = file.Read(); let id = file.Read(); if (id !== -1) { this.Origion = id; this.Rotation = file.Read(); this.OrigionMinPoint = new Vector2(file.Read(), file.Read()); } } WriteFile(file) { file.Write(1); //ver file.Write(this.Id); let arr = []; for (let p of this.Points) arr.push(p.x, p.y); file.Write(arr); file.Write(this.Size.x); file.Write(this.Size.y); file.Write(this._Area); if (this.Origion && this.Origion.Id) { //如果有原始的id,则传递它,以便后续进行NFP复用. file.Write(this.Origion.Id); file.Write(this.Rotation); file.Write(this.OrigionMinPoint.x); file.Write(this.OrigionMinPoint.y); } else file.Write(-1); } } function PathTranslate_Self(pts, mx, my) { for (let pt of pts) { pt.x += mx; pt.y += my; } return pts; } //缩放点表,返回原始点表 function PathScale(pts, scale) { for (let p of pts) { p.x *= scale; p.y *= scale; } return pts; } function Polyline2Points(pl, outside, knifeRadius) { let pts = []; if (!outside) knifeRadius = -knifeRadius; if (pl.IsClockWise) pl.Reverse(); for (let i = 0; i < pl.EndParam; i++) { pts.push(pl.GetPointAtParam(i)); let bul = pl.GetBulgeAt(i); if (bul !== 0) { let arc = pl.GetCurveAtIndex(i); // //小圆弧内嵌时忽略小圆(直线连接) 有可能产生自交 放弃这个 // if (!outside && arc.Radius < 30 && bul > 0) continue; // if (outside && arc.Radius < 30 && bul < 0) continue; let allAngle = arc.AllAngle; let arcLength = arc.Length; let minCount = Math.floor(allAngle * 4 / Math.PI); let splitCount = Math.round(allAngle / 0.4); if (arcLength < 300) splitCount = Math.max(2, minCount); else splitCount = Math.max(Math.floor(arcLength / 200), splitCount, 2, minCount); let radius = arc.Radius; if (outside === bul > 0) radius = radius / Math.cos(allAngle / (splitCount * 2)); let cp = arc.Center; for (let j = 0.5; j < splitCount; j++) { let a = arc.GetAngleAtParam(j * (1 / splitCount)); let p = polar(cp.clone(), a, radius); pts.push(p); } } } if (knifeRadius !== 0) { pts = clipperCpp.lib.offsetToPaths({ delta: knifeRadius * 1e4, offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }] })[0]; PathScale(pts, 1e-4); } return [pl, pts]; } const TempPolyline = new Polyline; function UpdateTempPolyline(_ContourData) { TempPolyline.LineData.length = 0; for (let i = 0; i < _ContourData.pts.length; i++) TempPolyline.LineData.push({ pt: _ContourData.pts[i], bul: _ContourData.buls[i] }); return TempPolyline; } /** * 平面实体基类 * 子类:地板 天花 */ let RoomFlatBase = class RoomFlatBase extends RoomBase { constructor(_Contour, //为了防止contour被删除(所以我们直接备份这个) _Holes = []) { super(); if (_Contour) this.Contour = _Contour; this._HoleDatas = _Holes.map(h => h.MatrixAlignTo2(this.OCSNoClone)); } get ContourData() { return this._ContourData; } set ContourData(value) { } get HoleDatas() { return this._HoleDatas; } set HoleDatas(value) { } get Area() { let area = this.Contour.Area; for (let hole of this._HoleDatas) area -= UpdateTempPolyline(hole).Area; return area; } get BoundingBoxInOCS() { return this.Contour.BoundingBoxInOCS; } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this._Matrix); } UpdateContourHoles(_Contour, _Holes) { let conData = _Contour.MatrixAlignTo2(this.OCSNoClone); let holeData = _Holes.map(h => h.MatrixAlignTo2(this.OCSNoClone)); this.WriteAllObjectRecord(); this._ContourData = conData; this._HoleDatas = holeData; this.Update(); } set Contour(_Contour) { this.WriteAllObjectRecord(); this._ContourData = _Contour.MatrixAlignTo2(this.OCSNoClone); this.Update(); } get Contour() { return UpdateTempPolyline(this._ContourData); } set Holes(_Holes) { this.WriteAllObjectRecord(); this._HoleDatas = _Holes.map(h => h.MatrixAlignTo2(this.OCSNoClone)); this.Update(); } get RegionId() { return this._RegionId; } set RegionId(value) { if (value === this._RegionId) return; this.WriteAllObjectRecord(); this._RegionId = value; } GetGripPoints() { let pts = []; const Add2Pts = (p) => { pts.push(AsVector3(p).applyMatrix4(this.OCSNoClone)); }; this._ContourData.pts.forEach(Add2Pts); this._HoleDatas.forEach(h => h.pts.forEach(Add2Pts)); return pts; } MoveGripPoints(indexList, vec) { } GetStretchPoints() { return this.GetGripPoints(); // return [new Vector3(1e10, 1e10, 1e10)];//我们允许拉伸内部轮廓 但是不允许拉伸外部轮廓 } MoveStretchPoints(indexList, vec) { } //绘制 UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; let pts = this.Contour.Shape.getPoints(30).map(AsVector3); this._EdgeGeometry = BufferGeometryUtils.CreateFromPts(pts); return this._EdgeGeometry; } get MeshGeometry() { if (!this._MeshGeometry) { let shape = this.Contour.Shape; for (let hole of this._HoleDatas) shape.holes.push(UpdateTempPolyline(hole).Shape); this._MeshGeometry = new ShapeGeometry(shape); ScaleUV(this.MeshGeometry, 1e-3); } return this._MeshGeometry; } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe) { let l = obj; l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual) ; else if (renderType === RenderType.Physical2) { const mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else if (renderType === RenderType.Physical) { let mesh = obj; mesh.material = this.MeshMaterial; } } InitDrawObject(renderType = RenderType.Wireframe) { if (renderType === RenderType.Wireframe) { // return new Object3D;//在二维线框中,如果显示这个线框,操作时会选到这个,所以不绘制 //避免在二维线框下无法选中! return new Line$1(BufferGeometryUtils.CreateFromPts([AsVector3(this.ContourData.pts[0]), AsVector3(this.ContourData.pts[0])]), ColorMaterial.GetLineMaterial(this.ColorIndex)); } else if (renderType === RenderType.Conceptual) { return new Object3D().add(new Mesh(this.MeshGeometry, ColorMaterial.GetBasicMaterialTransparent2(this.ColorIndex, 0.1)), new Line$1(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial())); } else if (renderType === RenderType.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); return mesh; } else if (renderType === RenderType.Print) { return new Mesh(this.MeshGeometry, ColorMaterial.GetPrintConceptualMaterial()); } else if (renderType === RenderType.Jig) { return new Object3D; } else if (renderType === RenderType.Physical2) { const obj = new Object3D; const mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); Object.defineProperty(mesh, "castShadow", { get: () => this.CaseShadow }); Object.defineProperty(mesh, "receiveShadow", { get: () => this.CaseShadow }); obj.add(mesh); const outline = new Line$1(this.EdgeGeometry, ColorMaterial.GetConceptualEdgeMaterial()); obj.add(outline); return obj; } } UpdateDrawObject(renderType, obj) { if (renderType === RenderType.Wireframe) { let l = obj; BufferGeometryUtils.UpdatePts(l.geometry, [AsVector3(this.ContourData.pts[0]), AsVector3(this.ContourData.pts[0])]); // l.geometry = this.EdgeGeometry; // l.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { let mesh = obj.children[0]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let line = obj.children[1]; if (line.geometry !== this.EdgeGeometry) { line.geometry.dispose(); line.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Physical || renderType === RenderType.Print) { let mesh = obj; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } } } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 this._RegionId = file.ReadObjectId(); this._ContourData = ReadContour(); let count = file.Read(); this._HoleDatas = []; for (let i = 0; i < count; i++) this._HoleDatas.push(ReadContour()); function ReadContour() { let count = file.Read(); let conData = { pts: [], buls: [] }; for (let i = 0; i < count; i++) { conData.pts.push(new Vector2$1(file.Read(), file.Read())); conData.buls.push(file.Read()); } return conData; } if (ver > 1) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.WriteObjectId(this._RegionId); WriteContour(this._ContourData); file.Write(this._HoleDatas.length); for (let h of this._HoleDatas) WriteContour(h); function WriteContour(conData) { file.Write(conData.pts.length); for (let i = 0; i < conData.pts.length; i++) { let p = conData.pts[i]; file.Write(p.x); file.Write(p.y); file.Write(conData.buls[i]); } } // ver2 file.WriteBool(this._LockMaterial); } }; RoomFlatBase = __decorate([ Factory ], RoomFlatBase); /** * 地板 */ let RoomFlatFloor = class RoomFlatFloor extends RoomFlatBase { //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); file.Read(); //1 } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(1); //ver } get MeshMaterial() { const mtl = this._MaterialId; if (!mtl?.IsErase && mtl?.Object) return mtl.Object.Material; return HostApplicationServices.DefaultFloorMaterial ?? HostApplicationServices.DefaultMeshMaterial; } }; RoomFlatFloor = __decorate([ Factory ], RoomFlatFloor); /** * 天花板 */ let RoomFlatTop = class RoomFlatTop extends RoomFlatBase { get MeshGeometry() { if (!this._MeshGeometry) { this._MeshGeometry = new Geometry; let shape = this.Contour.Shape; for (let hole of this._HoleDatas) shape.holes.push(UpdateTempPolyline(hole).Shape); const points = shape.extractPoints(30); let shapeVertices = points.shape; const shapeHoles = points.holes; const faces = ShapeUtils.triangulateShape(shapeVertices, shapeHoles); for (let hole of shapeHoles) arrayPushArray(shapeVertices, hole); for (let p of shapeVertices) { this._MeshGeometry.vertices.push(AsVector3(p)); p.multiplyScalar(1e-3); } let normal = new Vector3(0, 0, -1); // incides for (let i = 0, l = faces.length; i < l; i++) { const face = faces[i]; const a = face[2]; const b = face[1]; const c = face[0]; this._MeshGeometry.faces.push(new Face3(a, b, c, normal)); this._MeshGeometry.faceVertexUvs[0].push([shapeVertices[a], shapeVertices[b], shapeVertices[c]]); } } return this._MeshGeometry; } get CaseShadow() { return true; } get ReceiveShadow() { return false; } get MeshMaterial() { const mtl = this._MaterialId; if (!mtl?.IsErase && mtl?.Object) return mtl.Object.Material; return HostApplicationServices.DefaultWallMaterial ?? HostApplicationServices.DefaultMeshMaterial; } }; RoomFlatTop = __decorate([ Factory ], RoomFlatTop); const ROOM_REGION_CURVES_KEY = "__ROOM_REGION_CURVES_KEY__"; /** * 户型区域:厨房,客厅,主卧,卫生间 * 绑定了天花板和地板对象 */ let RoomRegion = class RoomRegion extends RoomBase { constructor(_Name = "", //名称 _Top, //天花板 _Floor, //地板 _Area = 0) { super(); this._Name = _Name; this._Top = _Top; this._Floor = _Floor; this._Area = _Area; } get Area() { return this._Area; } set Area(value) { if (equaln$1(value, this._Area, 1e-3)) return; this.WriteAllObjectRecord(); this._Area = value; this.Update(); } get TextString() { return this._Name; } set TextString(name) { if (name === this._Name) return; this._Name = name; this.Update(); } get Top() { return this._Top; } set Top(value) { this.WriteAllObjectRecord(); this._Top = value; } get Floor() { return this._Floor; } set Floor(value) { this.WriteAllObjectRecord(); this._Floor = value; } get Text() { if (!this._Text) { this._Text = new Text(); this._Text.Height = 100; this._Text.FontName = FontType.YaHei; } return this._Text; } GetGripPoints() { let pts = []; let obj = (this._Floor?.Object ?? this._Top?.Object); if (obj) { const Add2Pts = (p) => { pts.push(AsVector3(p).applyMatrix4(obj.OCSNoClone)); }; obj.ContourData.pts.forEach(Add2Pts); obj.HoleDatas.forEach(h => h.pts.forEach(Add2Pts)); } return pts; } //TODO:绘制时只绘制文字对象 InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D; this.UpdateDrawObject(renderType, obj); return obj; } /** * 重载:更新绘制的实体 * @param {RenderType} type * @param {Object3D} obj */ UpdateDrawObject(renderType, obj) { if (renderType === RenderType.Wireframe || renderType === RenderType.Conceptual) { obj.remove(...obj.children.slice(0)); this.Text.TextAligen = TextAligen.Mid; this._Text.TextString = `${this.TextString || "未命名"} ${FixedNotZero(this._Area * 1e-6, 2)}m²`; AddEntityDrawObject(obj, this._Text, renderType); } else { //避免在真实视图无法选中! let line = obj.children[0]; let pts = this.GetGripPoints(); if (pts.length) { let p = pts[0].applyMatrix4(this.OCSInv); if (!line) obj.add(new Line$1(BufferGeometryUtils.CreateFromPts([p, p]))); else BufferGeometryUtils.UpdatePts(line.geometry, [p, p]); } } } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); //1 this._Name = file.Read(); this._Top = file.ReadObjectId(); this._Floor = file.ReadObjectId(); if (ver > 1) this._Area = file.Read(); this[ROOM_REGION_CURVES_KEY] = undefined; //因为撤销重做时会导致这个曲线关联变更,所以移除这个曲线,如果需要则重新计算 } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.Write(this._Name); file.WriteObjectId(this._Top); file.WriteObjectId(this._Floor); file.Write(this._Area); } }; RoomRegion = __decorate([ Factory ], RoomRegion); function Path2Polyline(path) { let pl = new Polyline(); pl.LineData = path.map(p => { return { pt: AsVector2(p), bul: 0 }; }); pl.CloseMark = true; return pl; } const MAX_CACHE_LEN = 100000; class NestCache { static SetPos(key, value) { if (this._PosCacheCount > MAX_CACHE_LEN) { // this._PosCacheCount = 0; // this._PositionCache = {}; return; } this._PositionCache[key] = value; this._PosCacheCount++; } static GetPos(key) { return this._PositionCache[key]; } static SetNoPut(key, value) { if (this._NoPutCacheCount > MAX_CACHE_LEN) { // this._NoPutCacheCount = 0; // this._NoPutCache = {}; return; } this._NoPutCache[key] = value; this._NoPutCacheCount++; } static GetNoPut(key) { return this._NoPutCache[key]; } /** * 用于创建原点在0点的矩形路径 */ static CreatePath(x, y, knifRadius = 3.5) { let minX = -knifRadius; let maxX = x + knifRadius; let minY = -knifRadius; let maxY = y + knifRadius; return new Path([ { x: minX, y: minY }, { x: maxX, y: minY }, { x: maxX, y: maxY }, { x: minX, y: maxY }, ]); } static Clear() { this.CachePartPosCount = 0; this.CacheNoSetCount = 0; this._PositionCache = {}; this._PosCacheCount = 0; this._NoPutCache = {}; this._NoPutCacheCount = 0; } } //放置零件时,命中缓存的次数 NestCache.CachePartPosCount = 0; //放置零件时,命中无法放置缓存的次数 NestCache.CacheNoSetCount = 0; //noset NestCache._PositionCache = {}; NestCache._NoPutCache = {}; const TEXT_BOX = NestCache.CreatePath(570, 110); /** * 分析文字放置位置 * @param contour 轮廓点表 * @param holes 网洞点表 * @param [textBox=TEXT_BOX] 标签盒子 * @param [allowReturnNullPos=false] 允许返回null 当没有找到合适的位置返回null * @returns Vector3 */ function ParseRegionTextPos(contour, holes, textBox = TEXT_BOX, allowReturnNullPos = false) { let hasTextBox = true; let path = new Path(contour); let nfps = path.GetInsideNFP(textBox)?.map(nfp => Path2Polyline(PathTranslate_Self(PathScale(nfp, 1e-4), path.OrigionMinPoint.x, path.OrigionMinPoint.y))); //可能无法获得 if (!nfps || nfps.length === 0) { if (allowReturnNullPos) return; nfps = [Path2Polyline(contour)]; hasTextBox = false; } let holeNFPs = []; for (let hole of holes) { let hpath = new Path(hole); let nfps = hpath.GetOutsideNFP(textBox); let nfp = nfps[Max(nfps, (n1, n2) => Area(n2) > Area(n1))]; let pl = Path2Polyline(PathTranslate_Self(PathScale(nfp, 1e-4), hpath.OrigionMinPoint.x, hpath.OrigionMinPoint.y)); let box = pl.BoundingBox; let boxpl = new Polyline().RectangleFrom2Pt(new Vector3(box.min.x - 1e5, box.min.y - 1), new Vector3(box.max.x + 1e5, box.min.y + 1)); let con1 = Contour.CreateContour(pl, false); let con2 = Contour.CreateContour(boxpl, false); holeNFPs.push(...con1.UnionBoolOperation(con2).contours); } let shapes = nfps.map(pl => new Shape(Contour.CreateContour(pl, false))); let subShapes = new ShapeManager; holeNFPs.forEach(pl => { subShapes.UnionBoolOperation(new ShapeManager([new Shape(pl)])); }); let resShapes = []; for (let shape of shapes) { // TestDraw(shape.Outline.Curve, 6); resShapes.push(...shape.SubstactBoolOperation(subShapes.ShapeList)); //可能减完丢了 } if (resShapes.length === 0) { //允许返回空的点 因为无法放置 if (allowReturnNullPos) return; resShapes = shapes; } let maxDist = -Infinity; let minp; for (let shape of resShapes) { let pl = shape.Outline.Curve; if (pl.Area < 1) { if (!minp) minp = pl.BoundingBox.getCenter(new Vector3).toArray(); continue; } // TestDraw(pl, 3); //绘制裁剪后的线 let p = polylabel([pl.LineData.map(p => p.pt.toArray())], 1.0); //这里不再需要转换 因为我们传递进来的就是没有凸度的点表 let dist = p["distance"]; if (dist > maxDist) { maxDist = dist; let pos = pl.Position; minp = p; minp[0] += pos.x; minp[1] += pos.y; } } let p = new Vector3(minp[0], minp[1]); //左右均分 // TestDraw(new Point(p)); // let line = new Line(p, p.clone().setX(minp[0] + 1)); // let pts = curve.IntersectWith(line, IntersectOption.ExtendArg); // pts.push(p); // pts.sort(ComparePoint("xyz")); // let index = pts.indexOf(p); // p = midPoint(pts[index - 1], pts[index + 1]); // TestDraw(new Point(p)); // //上下居中 // line = new Line(p, p.clone().setY(p.y + 1)); // pts = curve.IntersectWith(line, IntersectOption.ExtendArg); // pts.push(p); // pts.sort(ComparePoint("xyz")); // index = pts.indexOf(p); // p = midPoint(pts[index - 1], pts[index + 1]); // TestDraw(new Point(p)); if (hasTextBox) { p.x += textBox.Size.x * 0.5; p.y += textBox.Size.y * 0.5; } return p; } //区域更迭(新老新区交换) class RegionReplacement { constructor(oldRegions) { this.oldRegions = oldRegions; this.contours = []; if (oldRegions.length === 0) return; this.fb = new Flatbush(oldRegions.length); oldRegions.forEach(r => { let flat = (r.Floor?.Object ?? r.Top.Object); //TODO:备份轮廓和网洞 let box = flat.BoundingBox; this.fb.add(box.min.x, box.min.y, box.max.x, box.max.y); this.contours.push([flat.Contour.Clone().ApplyMatrix(flat.OCSNoClone), flat.HoleDatas.map(h => UpdateTempPolyline(h).Clone().ApplyMatrix(flat.OCSNoClone))]); }); this.fb.finish(); } //获得旧的区域 GetReplaceOldReg(p) { if (!this.fb) return; let ids = this.fb.search(p.x - 1, p.y - 1, p.x + 1, p.y + 1); for (let id of ids) { let [con, holes] = this.contours[id]; if (con.PtInCurve(p) && !holes.some(h => h.PtInCurve(p))) { return this.oldRegions[id]; } } } } //获取所有的曲线 ?:并且知道每个曲线是墙体的左边还是右边 (有没有用?) //分析面域 //分析可用的内空间(墙面方向指向空间内部为内空间) 否则为外墙空间 //构造面域树? 不需要了? 还是需要的 需要一个最大的天花板 //区域对象 (地面+天花?) //墙内曲线的类型 var WallCurveDirType; (function (WallCurveDirType) { WallCurveDirType[WallCurveDirType["left"] = 0] = "left"; WallCurveDirType[WallCurveDirType["right"] = 1] = "right"; WallCurveDirType[WallCurveDirType["lid"] = 2] = "lid"; })(WallCurveDirType || (WallCurveDirType = {})); /** * 区域分析(房间+外墙+全屋顶) */ class RoomRegionParse { /** * @param _UpdateDb 当提供db时,我们更新了区域的信息 */ constructor(_UpdateDb) { this._UpdateDb = _UpdateDb; if (this._UpdateDb) { this.oldregs = this._UpdateDb.ModelSpace.Entitys.filter(e => !e.IsErase && e instanceof RoomRegion); this.rr = new RegionReplacement(this.oldregs); this.reped = new Set(); } } Do(walls) { let curves = []; let leftCurves = new Set(); for (let wall of walls) { if (!wall.LeftCurves) { continue; } for (let c of wall.LeftCurves) { curves.push(c); leftCurves.add(c); c[CURVE_DIR_TYPE_KEY] = WallCurveDirType.left; c[CURVE_WALL_TYPE_KEY] = wall; } for (let c of wall.RightCurves) { curves.push(c); c[CURVE_DIR_TYPE_KEY] = WallCurveDirType.right; c[CURVE_WALL_TYPE_KEY] = wall; } for (let c of wall.LidCurves) { curves.push(c); c[CURVE_DIR_TYPE_KEY] = WallCurveDirType.lid; c[CURVE_WALL_TYPE_KEY] = wall; } } const REGION_PARSE_NUM = 3; const POLYLINE_JOIN_FUZZ = Math.pow(10, -REGION_PARSE_NUM); let regParse = new RegionParse(curves, REGION_PARSE_NUM); for (let [orgArc, arcs] of regParse.ExpLineMap) { if (leftCurves.has(orgArc)) for (let arc of arcs) leftCurves.add(arc); } let regionPolylines = []; let regPolyline2RoutesMap = new Map(); //区域轮廓多段线->墙线 映射 //分析内外墙1内2外 for (let routes of regParse.RegionsOutline) { let pl = Polyline.Combine(routes.map(r => r.curve), POLYLINE_JOIN_FUZZ); // for (let i = 0; i < routes.length; i++) // { if (leftCurves.has(routes[0].curve)) pl.ColorIndex = routes[0].isReverse ? 1 : 2; else //if (right.has(routes[0].curve)) pl.ColorIndex = routes[0].isReverse ? 2 : 1; // else //因为盖子不分左右 所以我们忽略盖子 (现在盖子和右侧的一致) // continue; // break; // } // TestDraw(routes[0].curve); //test regionPolylines.push(pl); regPolyline2RoutesMap.set(pl, routes); } //不可能有内部轮廓 如果有 就证明错了 for (let routes of regParse.RegionsInternal) { let pl = Polyline.Combine(routes.map(r => r.curve)); pl.ColorIndex = pl.Area2 > 0 ? 3 : 4; // throw "未知错误 出现外部轮廓" } //面域分析炸开的线和原始轮廓的关联关系 for (let [orgArc, newArcs] of regParse.ExpLineMap) { for (let arc of newArcs) { arc[CURVE_DIR_TYPE_KEY] = orgArc[CURVE_DIR_TYPE_KEY]; arc[CURVE_WALL_TYPE_KEY] = orgArc[CURVE_WALL_TYPE_KEY]; } } let cons = regionPolylines.map(pl => new ContourTreeNode(Contour.CreateContour(pl, false))); ContourTreeNode.ParseContourTree(cons); let roofs = []; //解析 天花板区域 内空区域 for (let con of cons) { let maxZ = -Infinity; let minZ = Infinity; let routes = regPolyline2RoutesMap.get(con.contour.Curve); for (let r of routes) { let wall = r.curve[CURVE_WALL_TYPE_KEY]; minZ = Math.min(minZ, wall.Position.z); maxZ = Math.max(maxZ, wall.Position.z + wall.Height); } if (con.contour.Curve.ColorIndex === 2) //天花板区域(或者柱子) { if (con.Depth !== 0 || con.area < 1e6) //柱子 for (let r of routes) r.curve[CURVE_FACE_TYPE_KEY] = WallFaceType.Pillar; else for (let r of routes) r.curve[CURVE_FACE_TYPE_KEY] = WallFaceType.Outside; //我们需要返回这个轮廓,以便在ue中可以绘制真正的屋顶 con.contour.Curve["MaxZ"] = maxZ; con.contour.Curve["MinZ"] = minZ; roofs.push(con.contour.Curve); } else if (con.contour.Curve.ColorIndex === 1) //内空区域 { for (let r of routes) r.curve[CURVE_FACE_TYPE_KEY] = WallFaceType.Inside; if (this._UpdateDb && con.contour.Area > 1e4) { //组合外圈和网洞 画出来 就行了 let floor = new RoomFlatFloor(); let top = new RoomFlatTop(); floor.OCS = con.contour.Curve.OCSNoClone; //设置坐标系 以便我们正常的应用轮廓 top.OCS = con.contour.Curve.OCSNoClone; floor.Contour = con.contour.Curve; top.Contour = con.contour.Curve; floor.Holes = con.children.map(c => c.contour.Curve); top.Holes = con.children.map(c => c.contour.Curve); floor.Z = minZ; top.Z = maxZ; //等轮廓设置完在移动 否则设置失败 let conPts = Polyline2Points(floor.Contour, false, 0)[1]; let holePts = floor.HoleDatas.map(h => Polyline2Points(UpdateTempPolyline(h), false, 0)[1]); let pos = ParseRegionTextPos(conPts, holePts); pos.applyMatrix4(floor.OCSNoClone); let oldR = this.rr.GetReplaceOldReg(pos); let name = ""; if (oldR) { if (this.reped.has(oldR)) //新增 { //继承信息 if (oldR.TextString) //继承名称 { name = oldR.TextString; } } else //替换旧的 { floor = oldR.Floor.Object; top = oldR.Top.Object; EntitysUpdateWrap([floor, top, oldR], () => { top.WriteAllObjectRecord(); //因为修改了ocs 所以我们先记录下原始数据 top.OCS = con.contour.Curve.OCSNoClone; floor.WriteAllObjectRecord(); floor.OCS = con.contour.Curve.OCSNoClone; floor.UpdateContourHoles(con.contour.Curve, con.children.map(c => c.contour.Curve)); top.UpdateContourHoles(con.contour.Curve, con.children.map(c => c.contour.Curve)); floor.Z = minZ; top.Z = maxZ; //等轮廓设置完在移动 否则设置失败 oldR.Position = pos; oldR.Area = floor.Area; }); oldR[ROOM_REGION_CURVES_KEY] = routes.map(r => r.curve); this.reped.add(oldR); continue; } } this._UpdateDb.ModelSpace.Append(floor); this._UpdateDb.ModelSpace.Append(top); let region = new RoomRegion(name, top.Id, floor.Id, floor.Area); region[ROOM_REGION_CURVES_KEY] = routes.map(r => r.curve); region.Position = pos; this._UpdateDb.ModelSpace.Append(region); floor.RegionId = region.Id; top.RegionId = region.Id; } } } for (let [orgArc, arcs] of regParse.ExpLineMap) orgArc[CURVE_FACE_TYPE_KEY] = arcs[0][CURVE_FACE_TYPE_KEY]; //圆弧不可能被两个房间拥有,所以这个写法没问题 for (let wall of walls) { // 因为我们现在没有分裂圆弧 所以我们不需要在合并线 // wall.LeftCurves && arrayRemoveDuplicateBySort(wall.LeftCurves, (cu1: Curve, cu2: Curve) => cu1.Join(cu2) === Status.True); // wall.RightCurves && arrayRemoveDuplicateBySort(wall.RightCurves, (cu1: Curve, cu2: Curve) => cu1.Join(cu2) === Status.True); wall.Update(); } for (let roof of roofs) roof.Z0(); return roofs; } End() { if (this._UpdateDb) { for (let r of this.oldregs) if (!this.reped.has(r)) { r.Erase(); r.Floor?.Object.Erase(); r.Top?.Object.Erase(); } } } } /** * 1.自动合理的延伸墙体,以便保证吸附失败的时候自动吸附(当被MOVE时,我们希望修复吸附失败的问题!) * 可延伸的距离等于墙体的厚度 * * 2.在交点处把墙体打断,以便我们分析区域 */ class RoomWallExtendAndBreak { constructor(curves) { //曲线->分裂曲线的映射 this._Curve2SplitCurveMap = new Map(); //分裂曲线->原始曲线的映射 this._SplitCurve2OrgCurveMap = new Map(); this.ExtendCurves = new Set(); this.AloneCurves = []; this.OrgCurveMapGroup = []; let intersect = new CurveIntersection2(curves, true, IntersectOption.ExtendBoth, 100, true); let breakCurves = []; //延伸+打断 for (let [cu, pmap] of intersect.intersect2) { pmap.sort((p1, p2) => p1[0] - p2[0]); if (pmap.length > 0) breakCurves.push(cu); else this.AloneCurves.push(cu); //#region 1.延伸 let hasExtend = false; let endParam = cu.EndParam; //最接近起点的点(我们使用这个点来决定我们是否延伸,这样有效避免了距离小于间距的一半时,但是距离却又大于设置参数时延伸时的尴尬!) 例如 | --|--------- 该例子不延伸到左边 let index1 = FindMin(pmap, 0); let index2 = FindMin(pmap, endParam); let start = pmap[index1]; let end = pmap[index2]; if (start[0] < -1e-8) { cu.Extend(start[0]); hasExtend = true; } if (end[0] > (endParam + 1e-8)) { cu.Extend(hasExtend ? cu.GetParamAtPoint(end[1]) : end[0]); hasExtend = true; pmap.splice(index2); //移除多余的交点参数 } if (start[0] < -1e-8) pmap.splice(0, index1); //移除多余的交点参数 if (hasExtend) //记录这个曲线被延伸了 this.ExtendCurves.add(cu); } //有效的相交数据(用来切割) let intersectData = new Map(); for (let cu of breakCurves) intersectData.set(cu, []); //去除无效的交点 移除多余的交点参数 for (let [c1, c2, pts] of intersect.intersect3) { let c1arr = intersectData.get(c1); let c2arr = intersectData.get(c2); for (let p of pts) { let c1param = c1.GetParamAtPoint(p); if (!c1.ParamOnCurve(c1param)) continue; let c2param = c2.GetParamAtPoint(p); if (!c2.ParamOnCurve(c2param)) continue; c1arr.push(c1param); c2arr.push(c2param); } } //打断 for (let [cu, params] of intersectData) { params = params.filter(p => p > 1e-3 && p < 0.999); // let isArc = cu instanceof Arc; // if (isArc) // { // let param = cu.GetParamAtPoint2((cu as Arc).Center.add(YAxisN)); // if (cu.ParamOnCurve(param)) // params.push(param);//保证切割 // } if (params.length) { arraySortByNumber(params); arrayRemoveDuplicateBySort(params, (e1, e2) => equaln$1(e1, e2, 1e-4)); // if (equaln(params[params.length - 1], 1, 1e-4)) //已经filter 不再需要 // params[params.length - 1] = 1; } let splitCurves = params.length === 0 ? [cu.Clone()] : cu.GetSplitCurves(params); // if (isArc && splitCurves.length > 1)//将短圆弧转换为直线 我们由上层业务代码决定何时转换 不在此转换 // { // // for (let i = 0; i < splitCurves.length; i++) // // { // // let c = splitCurves[i] as Arc; // // if (c.Radius < 100) //不能歧视 否则轮廓就错了 // // splitCurves[i] = new Line(c.StartPoint, c.EndPoint); // // } // } //记录关联关系 this._Curve2SplitCurveMap.set(cu, splitCurves); for (let scu of splitCurves) this._SplitCurve2OrgCurveMap.set(scu, cu); } //分组(只有相交的部分才会在一个组) let parsed = new Set(); for (let cu of curves) { if (parsed.has(cu)) continue; parsed.add(cu); let group = [cu]; for (let i = 0; i < group.length; i++) { let interMap = intersect.intersect.get(group[i]); if (!interMap) continue; for (let [ic] of interMap) { if (parsed.has(ic)) continue; parsed.add(ic); group.push(ic); } } if (group.length > 1) this.OrgCurveMapGroup.push(group); } } } //找到最接近某个参数的索引位置 function FindMin(params, closeToParam) { if (params.length < 2) return params.length - 1; let minDist = Infinity; let minIndex = -1; for (let i = 0; i < params.length; i++) { let absDist = Math.abs(params[i][0] - closeToParam); if (absDist < minDist) { minDist = absDist; minIndex = i; } } return minIndex; } const ROOM_WALL_INDEX_KEY = "__ROOM_WALL_INDEX_KEY__"; const SAVE_SP_KEY = "__SAVE_SP__"; const SAVE_EP_KEY = "__SAVE_EP__"; /** * 户型分析服务: * 1.自动延伸墙 * 2.合理的构建墙 * 3.自动分析地板 * * 墙体被分裂后合并(join line) * * TODO: * 删除重复的墙(例如 2个rec wall) * 增量更新 diff walls(始作俑者 1) -> dep walls(需要更新 2) ->dep's dep walls(需要被依赖 3) +[可能被影响到的wall 2(比如被切割的墙) (依赖墙 3)] * ->1.用盒子搜索范围依赖(盒子1级依赖) * ->2.用盒子扩大搜索范围依赖(盒子2级依赖) * ->正确的分析1级依赖 2级依赖 (或者不要分析? 直接用盒子来?) * ->分析 * ->更新1级 2级 的墙 */ class RoomWallParse { /** * @param [_ExtendsWalls=true] 更新墙体,在开图时不更新图纸 * @param [_UpdateDb] 更新的图纸(新绘制Region) * @param [_IsCacheWallNodePoints] 需要把节点缓存下来? */ constructor(_ExtendsWalls, _UpdateDb, _IsCacheWallNodePoints) { this._ExtendsWalls = _ExtendsWalls; this._UpdateDb = _UpdateDb; this._IsCacheWallNodePoints = _IsCacheWallNodePoints; } /** * @param walls 需要解析的墙体列表 * @param changeWalls 更新的墙体列表(如果提供 增量模式) */ Parse(walls, changeWalls = undefined) { if (this._IsCacheWallNodePoints) { RoomWallParse._CacheWallNodePoints = []; RoomWallParse._CacheWallMaps = []; RoomWallParse._CacheCurveWallMaps = new Map(); RoomWallParse._CacheRoofs = []; } let regionPrase = new RoomRegionParse(this._UpdateDb); const GroupWalls = (walls, fn) => { let map = new Map(); for (let w of walls) { if (w.Length < 1e-6) { // w.Erase();//删除空长度的墙 //避免窗户0长度错误 w.LeftCurves = []; w.RightCurves = []; w.LidCurves = []; continue; } let value = fn(w); let arr = map.get(value); if (!arr) { arr = []; map.set(value, arr); } arr.push(w); } return map; }; //按Z轴区分 let zgroupMap = GroupWalls(walls, w => Math.round(w.Z * 100)); for (let [, walls] of zgroupMap) { this.PraseWallsFromSameFloor(walls, changeWalls); let roofs = regionPrase.Do(walls); if (this._IsCacheWallNodePoints) arrayPushArray(RoomWallParse._CacheRoofs, roofs); } regionPrase.End(); } PraseWallsFromSameFloor(walls, changeWalls = undefined) { //{墙}->{轴线} {轴线}->{墙} let wallCurveMap = new Map(); let curveWallMap = new Map(); let axisCurves = walls.map(wall => { //清理 wall.LeftCurves = []; wall.RightCurves = []; wall.LidCurves = []; wall.Region = undefined; let cu = wall.CenterAxisCurve; wallCurveMap.set(wall, cu); curveWallMap.set(cu, wall); if (this._IsCacheWallNodePoints) RoomWallParse._CacheCurveWallMaps.set(cu, wall); return cu; }); //打断数据 let breakData = new RoomWallExtendAndBreak(axisCurves); if (this._ExtendsWalls) //墙体延伸 for (let cu of breakData.ExtendCurves) { let wall = curveWallMap.get(cu); wall.StartPoint = cu.StartPoint; wall.EndPoint = cu.EndPoint; } let orgCurveLeftMap = new Map(); let orgCurveRightMap = new Map(); //{打断后的曲线}指向{新生成曲线} let curveLeftCurveMap = new Map; let curveRightCurveMap = new Map; //新生成的曲线的补盖子 let curveStartLidCurveMap = new Map; let curveEndLidCurveMap = new Map; //半盖 let curveStarLeftHalfLidCurveMap = new Map; let curveStarRightHalfLidCurveMap = new Map; let curveEndLeftHalfLidCurveMap = new Map; let curveEndRightHalfLidCurveMap = new Map; //尖角连接 for (let groupCurves of breakData.OrgCurveMapGroup) { let curveMap = new CurveMap(4, true); for (let orgCurve of groupCurves) { let orgWall = curveWallMap.get(orgCurve); let splitCurves = breakData._Curve2SplitCurveMap.get(orgCurve); for (let cu of splitCurves) { //TODO:墙体重复时, 使用大厚度的墙? if (curveMap.AddCurveToMap(cu, cu instanceof Arc, true, true) || true) //避免重叠墙体构建错误 我们设置了true 参考用例 `重叠墙分析丢失墙体` { //左右线 let leftCurve = cu.GetOffsetCurves(-orgWall.Thickness * 0.5)[0]; let rightCurve = cu.GetOffsetCurves(orgWall.Thickness * 0.5)[0]; leftCurve.ColorIndex = 2; rightCurve.ColorIndex = 3; curveLeftCurveMap.set(cu, leftCurve); curveRightCurveMap.set(cu, rightCurve); } } } if (this._IsCacheWallNodePoints) RoomWallParse._CacheWallMaps.push([breakData, curveMap]); //逆时针 for (let v of curveMap._Vertices) { if (this._IsCacheWallNodePoints) RoomWallParse._CacheWallNodePoints.push(v.position); if (v.routes.length === 0) continue; if (v.routes.length < 2) { let r = v.routes[0]; let preCurve = curveLeftCurveMap.get(r.curve); //左边的 let nowCurve = curveRightCurveMap.get(r.curve); //右边的 let sp = r.isReverse ? preCurve.EndPoint : preCurve.StartPoint; let ep = r.isReverse ? nowCurve.EndPoint : nowCurve.StartPoint; let l = new Line(sp, ep); l.ColorIndex = 6; if (r.isReverse) curveEndLidCurveMap.set(r.curve, new Line(ep, sp)); else curveStartLidCurveMap.set(r.curve, new Line(sp, ep)); continue; } 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); //倒角 let pre = v.routes.length - 1; for (let i = 0; i < v.routes.length; i++) { let preR = v.routes[pre]; let nowR = v.routes[i]; let preCurve = preR.isReverse ? curveRightCurveMap.get(preR.curve) : curveLeftCurveMap.get(preR.curve); //左边的 let nowCurve = nowR.isReverse ? curveLeftCurveMap.get(nowR.curve) : curveRightCurveMap.get(nowR.curve); //右边的 let sp = preR.isReverse ? preCurve.EndPoint : preCurve.StartPoint; let ep = nowR.isReverse ? nowCurve.EndPoint : nowCurve.StartPoint; if (equalv3(sp, ep)) //直连 { pre = i; continue; } let iPam = preCurve.IntersectWith2(nowCurve, IntersectOption.ExtendBoth); let iPts = iPam.map(p => p.pt); let tPts = iPam.filter(p => preCurve.ParamOnCurve(p.thisParam) && nowCurve.ParamOnCurve(p.argParam)).map(p => p.pt); let code = EntityEncode2(preCurve, nowCurve); let tp; if (code === 1) //都是直线 { if (tPts.length > 0) { tp = iPts[0]; //奇怪的行为,避免它 if (equalv3(tp, preCurve.StartPoint, 1e-3) || equalv3(tp, preCurve.EndPoint, 1e-3) || equalv3(tp, nowCurve.StartPoint, 1e-3) || equalv3(tp, nowCurve.EndPoint, 1e-3)) tp = undefined; } else { if (iPts.length > 0) { //fuck 没有括号导致的错误 //尖角时才延伸,否则补盖 (延伸或者在线上) let isExtend = (preR.isReverse ? iPam[0].thisParam > 0 : iPam[0].thisParam < 1) && (nowR.isReverse ? iPam[0].argParam > 0 : iPam[0].argParam < 1); let distSq = iPts[0].distanceToSquared(v.position); if (isExtend && distSq < 500 * 500) //尖角 tp = iPts[0]; } } } else //直线与圆弧 { if (tPts.length > 0) //ipts = 1 or ipts = 2 tp = SelectNearP(tPts, v.position); else { if (iPts.length === 0) ; else { let p; if (code === 2 && iPts.length === 2) { let preArc = preCurve; let minArc = new Arc(preArc.Center, preArc.Radius, preR.isReverse ? preArc.EndAngle : preArc.StartAngle, 0, preR.isReverse ? preArc.IsClockWise : !preArc.IsClockWise); let p1 = iPts[0]; let a1 = minArc.GetAngleAtPoint(p1); let anAll1 = preArc.ParamOnCurve(preArc.GetParamAtAngle(a1)) ? Infinity : minArc.ComputeAnlge(a1); let p2 = iPts[1]; let a2 = minArc.GetAngleAtPoint(p2); let anAll2 = preArc.ParamOnCurve(preArc.GetParamAtAngle(a2)) ? Infinity : minArc.ComputeAnlge(a2); if (anAll2 < anAll1) { p = p2; iPam[0] = iPam[1]; } else p = p1; } else { p = SelectNearP(iPts, v.position); if (p === iPts[1]) iPam[0] = iPam[1]; } let isExtend = (preR.isReverse ? iPam[0].thisParam > 0 : iPam[0].thisParam < 1) && (nowR.isReverse ? iPam[0].argParam > 0 : iPam[0].argParam < 1); //tp 必须不能破坏圆弧,否则造成裁剪错误 if (!isExtend || p.distanceToSquared(v.position) > 500 * 500) p = undefined; else //预习 如果破坏的圆弧轮廓,则不允许连接 { let line; if (preCurve instanceof Arc) { let tempC = preCurve.Clone(); if (preR.isReverse) tempC.EndPoint = p; else tempC.StartPoint = p; line = new Line(v.position.clone(), p); let ipts = line.IntersectWith(tempC, IntersectOption.ExtendNone); if (ipts.length === 2) p = undefined; } if (p && nowCurve instanceof Arc) { let tempC = nowCurve.Clone(); if (nowR.isReverse) tempC.EndPoint = p; else tempC.StartPoint = p; if (!line) line = new Line(v.position.clone(), p); let ipts = line.IntersectWith(tempC, IntersectOption.ExtendNone); if (ipts.length === 2) p = undefined; } } tp = p; } } } if (tp) { if (preR.isReverse) preCurve[SAVE_EP_KEY] = tp; else preCurve[SAVE_SP_KEY] = tp; if (nowR.isReverse) nowCurve[SAVE_EP_KEY] = tp; else nowCurve[SAVE_SP_KEY] = tp; } else { //如果两线(直线)平行 无交点时,应该只补厚墙的盖子 if (code === 1 && iPts.length === 0) //&& false 尽管是false 这里的代码还是成立的 没有禁用这个代码是因为能带来一丢丢性能提升 { let w1 = curveWallMap.get(breakData._SplitCurve2OrgCurveMap.get(preR.curve)); let w2 = curveWallMap.get(breakData._SplitCurve2OrgCurveMap.get(nowR.curve)); let sp = preR.isReverse ? preCurve.EndPoint : preCurve.StartPoint; let ep = nowR.isReverse ? nowCurve.EndPoint : nowCurve.StartPoint; if (w1.Thickness > w2.Thickness) { if (preR.isReverse) curveEndLeftHalfLidCurveMap.set(preR.curve, new Line(sp, ep)); else curveStarLeftHalfLidCurveMap.set(preR.curve, new Line(sp, ep)); } else { if (nowR.isReverse) curveEndRightHalfLidCurveMap.set(nowR.curve, new Line(sp, ep)); else curveStarRightHalfLidCurveMap.set(nowR.curve, new Line(sp, ep)); } } // else if (equalv3(sp, ep, 10)) //在sp ep接近时,我们直接连接sp ep 会更好? 看起来是没必要的 // { // if (preR.isReverse) // curveEndLeftHalfLidCurveMap.set(preR.curve, new Line(ep, sp)); // else // curveStarLeftHalfLidCurveMap.set(preR.curve, new Line(sp, ep)); // } else { if (preR.isReverse) curveEndLeftHalfLidCurveMap.set(preR.curve, new Line(sp, v.position.clone())); //yep else curveStarLeftHalfLidCurveMap.set(preR.curve, new Line(sp, v.position.clone())); //yep if (nowR.isReverse) curveEndRightHalfLidCurveMap.set(nowR.curve, new Line(v.position.clone(), ep)); //yep else curveStarRightHalfLidCurveMap.set(nowR.curve, new Line(v.position.clone(), ep)); //yep } } pre = i; } } //延迟连接 for (let orgCurve of groupCurves) { let splitCurves = breakData._Curve2SplitCurveMap.get(orgCurve); for (let cu of splitCurves) { let left = curveLeftCurveMap.get(cu); let right = curveRightCurveMap.get(cu); UpdateStartEndPoint(left); UpdateStartEndPoint(right); } } //现在已经不再需要移除小房间了 // //移除过小房间的内墙(空间过小 或者 没有空间) // let regionData = new RegionParse2(undefined); // const RemoveSmallSpaceInteriorWall = (routes: Route[]) => // { // let polyline = Polyline.Combine(routes.map(r => r.curve), 1e-3); // let space = polyline.GetOffsetCurves(-120)[0]; // if (!space || space.Area < 1e4)//0.1平米 // { // for (let r of routes) // { // let cu: Curve; // //因为是逆时针 所以我们删除右边的线 // if (r.isReverse) // { // cu = curveLeftCurveMap.get(r.curve); // curveLeftCurveMap.delete(r.curve); // } // else // { // cu = curveRightCurveMap.get(r.curve); // curveRightCurveMap.delete(r.curve); // } // //还要删除补的盖子 // if (cu) // { // curveStarLeftHalfLidCurveMap.delete(cu); // curveEndLeftHalfLidCurveMap.delete(cu); // curveStartLidCurveMap.delete(cu); // curveEndLidCurveMap.delete(cu); // } // } // } // }; // regionData.RegionsInternal.forEach(RemoveSmallSpaceInteriorWall); // regionData.RegionsOutline.forEach(RemoveSmallSpaceInteriorWall); } //构建裁剪面 let trimContours = []; let orgCurveParams = []; for (let i = 0; i < axisCurves.length; i++) { let orgCurve = axisCurves[i]; orgCurve[ROOM_WALL_INDEX_KEY] = i; let parse = CreateGetCurveParam(orgCurve); orgCurveParams.push(parse); let splitCurves = breakData._Curve2SplitCurveMap.get(orgCurve); let orgWall = curveWallMap.get(orgCurve); let leftStartParam = 1, leftEndParam = 0, rightStartParam = 1, rightEndParam = 0; if (!splitCurves) { //左右线 let leftCurve = orgCurve.GetOffsetCurves(-orgWall.Thickness * 0.5)[0]; let rightCurve = orgCurve.GetOffsetCurves(orgWall.Thickness * 0.5)[0]; leftCurve.ColorIndex = 2; rightCurve.ColorIndex = 3; curveLeftCurveMap.set(orgCurve, leftCurve); curveRightCurveMap.set(orgCurve, rightCurve); curveStartLidCurveMap.set(orgCurve, new Line(leftCurve.StartPoint, rightCurve.StartPoint)); curveEndLidCurveMap.set(orgCurve, new Line(rightCurve.EndPoint, leftCurve.EndPoint)); breakData._SplitCurve2OrgCurveMap.set(orgCurve, orgCurve); //直接定位到自己 //更新 孤独的墙也应该被切割,所以移除这个代码 // orgWall.Region = undefined; // orgWall.LeftCurves = undefined; // orgWall.RightCurves = undefined; // orgWall.LidCurves = undefined; if (this._IsCacheWallNodePoints) RoomWallParse._CacheWallNodePoints.push(orgCurve.StartPoint, orgCurve.EndPoint); // continue;//孤独的墙(TODO:) //update:孤独的墙也应该裁剪被人 leftStartParam = rightStartParam = 0; leftEndParam = rightEndParam = 1; } else { for (let cu of splitCurves) { let left = curveLeftCurveMap.get(cu); let right = curveRightCurveMap.get(cu); if (left) { leftStartParam = Math.min(leftStartParam, parse.GetParamAtPoint(left.StartPoint)); leftEndParam = Math.max(leftEndParam, parse.GetParamAtPoint(left.EndPoint)); } if (right) { rightStartParam = Math.min(rightStartParam, parse.GetParamAtPoint(right.StartPoint)); rightEndParam = Math.max(rightEndParam, parse.GetParamAtPoint(right.EndPoint)); } } if (leftStartParam > leftEndParam) { leftStartParam = 0; leftEndParam = 1; } if (rightStartParam > rightEndParam) { rightStartParam = 0; rightEndParam = 1; } } let sp = orgCurve.StartPoint; let ep = orgCurve.EndPoint; if (orgCurve instanceof Line) { let lparse = parse; let p1 = parse.GetPointAtParam(leftStartParam); let p2 = parse.GetPointAtParam(leftEndParam); lparse.OffsetPoint(p1, orgWall.Thickness * 0.5); lparse.OffsetPoint(p2, orgWall.Thickness * 0.5); let p3 = lparse.GetPointAtParam(rightStartParam); let p4 = lparse.GetPointAtParam(rightEndParam); lparse.OffsetPoint(p3, orgWall.Thickness * -0.5); lparse.OffsetPoint(p4, orgWall.Thickness * -0.5); let pts = []; if (!equalv3(midPoint(p1, p3), sp)) pts.push(sp); pts.push(p1, p2); if (!equalv3(midPoint(p2, p4), ep)) pts.push(ep); pts.push(p4, p3); let pl = new Polyline(pts.map(p => { return { pt: AsVector2(p), bul: 0 }; })); pl.CloseMark = true; pl.Z = p1.z; let contour = Contour.CreateContour(pl, false); // let reg = Region.CreateFromCurves([contour.Curve]); // if (reg) // TestDraw(reg); trimContours.push(contour); orgWall.Region = pl; orgCurveLeftMap.set(orgCurve, new Line(p1, p2)); orgCurveRightMap.set(orgCurve, new Line(p3, p4)); } else { //左右线 let leftCurve = orgCurve.GetOffsetCurves(-orgWall.Thickness * 0.5)[0]; let rightCurve = orgCurve.GetOffsetCurves(orgWall.Thickness * 0.5)[0]; leftCurve.EndAngle = orgCurve.GetAngleAtParam(leftEndParam); leftCurve.StartAngle = orgCurve.GetAngleAtParam(leftStartParam); rightCurve.EndAngle = orgCurve.GetAngleAtParam(rightEndParam); rightCurve.StartAngle = orgCurve.GetAngleAtParam(rightStartParam); let curves = []; let [p1, p2] = [leftCurve.StartPoint, leftCurve.EndPoint]; let [p3, p4] = [rightCurve.StartPoint, rightCurve.EndPoint]; if (!equalv3(midPoint(p1, p3), sp)) curves.push(new Line(p3, sp), new Line(sp, p1)); else curves.push(new Line(p1, p3)); curves.push(leftCurve); if (!equalv3(midPoint(p2, p4), ep)) curves.push(new Line(p2, ep), new Line(ep, p4)); else curves.push(new Line(p2, p4)); curves.push(rightCurve); let contour = Contour.CreateContour(curves, false); if (!contour) { // for (let i = 0; i < curves.length; i++) // TestDraw(curves[i], i + 1); contour = Contour.CreateContour(new Polyline().RectangleFrom2Pt(new Vector3, new Vector3(100, 100)), false); console.error("生成轮廓失败"); } trimContours.push(contour); orgWall.Region = contour.Curve; orgCurveLeftMap.set(orgCurve, leftCurve); orgCurveRightMap.set(orgCurve, rightCurve); } } this.GenFB(trimContours); let CurveType; (function (CurveType) { CurveType[CurveType["Left"] = 0] = "Left"; CurveType[CurveType["Right"] = 1] = "Right"; CurveType[CurveType["StartLid"] = 2] = "StartLid"; CurveType[CurveType["EndLid"] = 3] = "EndLid"; })(CurveType || (CurveType = {})); const Trim = (splitCurve, offsetCurve, index, type) => { if (offsetCurve.Length < 1e-3) return []; let trim; if (offsetCurve instanceof Line) { let getParam = orgCurveParams[index]; let dir; if (type === CurveType.Left) dir = getParam.Direction; else if (type === CurveType.Right) dir = getParam.NegDirection; else if (type === CurveType.StartLid) dir = getParam.LeftDir; else if (type === CurveType.EndLid) dir = getParam.RightDir; if (!dir) { dir = offsetCurve.GetFirstDeriv(0).normalize().negate(); } trim = new CurveTrimLine(offsetCurve, dir); } else trim = new CurveTrimArc(offsetCurve, type !== CurveType.Left); //需要更快的判断直线会不会被轮廓切割? this.fb.search(trim._Box.min.x - 1e-2, trim._Box.min.y - 1e-2, trim._Box.max.x + 1e-2, trim._Box.max.y + 1e-2, (id => { // if (id !== index) //裁剪的好处是有些细的盖子将会消失 trim.TrimBy(trimContours[id], this.boxs[id], index > id); return false; })); let curves = trim.Curves; return curves; }; for (let [orgCurve, offsetCurve] of orgCurveLeftMap) { let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LeftCurves, Trim(orgCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.Left)); } for (let [orgCurve, offsetCurve] of orgCurveRightMap) { let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.RightCurves, Trim(orgCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.Right)); } for (let [splitCurve, offsetCurve] of curveStartLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.StartLid)); } for (let [splitCurve, offsetCurve] of curveEndLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.EndLid)); } for (let [splitCurve, offsetCurve] of curveStarLeftHalfLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.StartLid)); } for (let [splitCurve, offsetCurve] of curveStarRightHalfLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.StartLid)); } for (let [splitCurve, offsetCurve] of curveEndLeftHalfLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.EndLid)); } for (let [splitCurve, offsetCurve] of curveEndRightHalfLidCurveMap) { let orgCurve = breakData._SplitCurve2OrgCurveMap.get(splitCurve); let orgWall = curveWallMap.get(orgCurve); arrayPushArray(orgWall.LidCurves, Trim(splitCurve, offsetCurve, orgCurve[ROOM_WALL_INDEX_KEY], CurveType.EndLid)); } } GenFB(trimContours) { if (trimContours.length === 0) return; let fb = new Flatbush(trimContours.length); this.boxs = []; let v = new Vector3(1e-2, 1e-2); for (let con of trimContours) { let box = con.BoundingBox; box.expandByVector(v); fb.add(box.min.x, box.min.y, box.max.x, box.max.y); this.boxs.push(box); } fb.finish(); this.fb = fb; } } RoomWallParse._CacheWallNodePoints = []; RoomWallParse._CacheWallMaps = []; RoomWallParse._CacheCurveWallMaps = new Map(); RoomWallParse._CacheRoofs = []; function UpdateStartEndPoint(curve) { let sp = curve[SAVE_SP_KEY]; let ep = curve[SAVE_EP_KEY]; if (sp && ep) { let param1 = curve.GetParamAtPoint(sp); let param2 = curve.GetParamAtPoint(ep); if (curve.ParamOnCurve(param1) && curve.ParamOnCurve(param2) && param1 > param2) [sp, ep] = [ep, sp]; } if (sp) curve.StartPoint = sp; if (ep) curve.EndPoint = ep; } let RoomWallArc = class RoomWallArc extends RoomWallBase { constructor(_Center = new Vector3(), _Radius = 0.1, _StartAngle = 0.1, _EndAngle = 0.1, /** * 曲线为顺时针 */ _Clockwise = true, _Thickness = 120, _Height = 2700) { super(); this._Radius = _Radius; this._StartAngle = _StartAngle; this._EndAngle = _EndAngle; this._Clockwise = _Clockwise; this._LeftCurveSign = new Map(); this._RightCurveSign = new Map(); this._LidCurveSign = new Map(); this.OverWriteMaterial = new Map(); //section index -> materialId this._Matrix.setPosition(_Center); this._StartAngle = clampRad(_StartAngle); this._EndAngle = clampRad(_EndAngle); this.Thickness = _Thickness; this.Height = _Height; } //中心轴线 get CenterAxisCurve() { if (this._Radius < (this.Thickness * 0.6) || Math.abs(this.Bul) < 0.0015) return new Line(this.StartPoint, this.EndPoint); let arc = new Arc(ZeroVec, this._Radius, this._StartAngle, this._EndAngle, this._Clockwise); arc.OCSNoClone.copy(this.OCSNoClone); return arc; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let pts = []; pts = pts.concat(Arc.prototype.GetObjectSnapPoints.call(this, snapMode, pickPoint, lastPoint, viewXform)); const CurveSnap = (curve) => { let bakZ = curve.Z; //底部线 arrayPushArray(pts, curve.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); //顶部线 curve.Z += this._Height; arrayPushArray(pts, curve.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); curve.Z = bakZ; let spep = [curve.StartPoint, curve.EndPoint]; for (let p of spep) { let l = new Line(p, p.clone().setZ(p.z + this._Height)); arrayPushArray(pts, l.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } }; if (this.LeftCurves && (RoomWallBase.SnapMode & WallSnapMode.Out) > 0) { this.LeftCurves.forEach(CurveSnap); this.RightCurves.forEach(CurveSnap); this.LidCurves.forEach(CurveSnap); } return pts; } GetGripPoints() { let pts = [ this.StartPoint, this.GetPointAtParam(0.5), this.EndPoint, this.Center.clone(), ]; let count = pts.length; for (let i = 0; i < count; i++) pts.push(pts[i].clone().setZ(pts[i].z + this._Height)); return pts; } MoveGripPoints(indexList, vec) { if (indexList.length > 0) { let index = indexList[0]; if (index >= 4) index -= 4; Arc.prototype.MoveGripPoints.call(this, [index], vec); } } GetStretchPoints() { return Arc.prototype.GetStretchPoints.call(this); } MoveStretchPoints(indexList, vec) { return Arc.prototype.MoveStretchPoints.call(this, indexList, vec); } //#region //绘制 UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; for (let hole of this.Holes) if (hole.StartParam > hole.EndParam) [hole.StartParam, hole.EndParam] = [hole.EndParam, hole.StartParam]; let pts; if (!this.LeftCurves) { let sign = this._Clockwise ? -1 : 1; if (this.Length > 0.1) { let left = new Arc(ZeroVec, this._Radius - sign * this.Thickness * 0.5, this._StartAngle, this._EndAngle, this._Clockwise); let right = new Arc(ZeroVec, this._Radius + sign * this.Thickness * 0.5, this._StartAngle, this._EndAngle, this._Clockwise); let leftPts = left.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let rightPts = right.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let leftPts2 = leftPts.map(p => p.clone().setZ(this._Height)); let rightPts2 = rightPts.map(p => p.clone().setZ(this._Height)); pts = [ leftPts[0], leftPts[0].clone().setZ(this._Height), leftPts[leftPts.length - 1], leftPts[leftPts.length - 1].clone().setZ(this._Height), rightPts[0], rightPts[0].clone().setZ(this._Height), rightPts[rightPts.length - 1], rightPts[rightPts.length - 1].clone().setZ(this._Height), leftPts.shift() ]; for (let p of leftPts) pts.push(p, p); rightPts.reverse(); for (let p of rightPts) pts.push(p, p); pts.push(pts[0]); //顶部 pts.push(leftPts2.shift()); for (let p of leftPts2) pts.push(p, p); rightPts2.reverse(); for (let p of rightPts2) pts.push(p, p); } else pts = []; } else { pts = []; let inv = this.OCSInv; const DrawCurve = (curve, _leftRanges, _rightRanges) => { if (curve instanceof Line) { let p1 = curve.StartPoint.applyMatrix4(inv); let p2 = curve.EndPoint.applyMatrix4(inv); pts.push(p1, p2); for (let range of _leftRanges) pts.push(p1.clone().setZ(range[0]), p1.clone().setZ(range[1])); for (let range of _rightRanges) pts.push(p2.clone().setZ(range[0]), p2.clone().setZ(range[1])); pts.push(p1.clone().setZ(this._Height), p2.clone().setZ(this._Height)); } else //arc { let cpts = curve.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let topPts = cpts.map(p => p.clone().setZ(this._Height)); //底部 pts.push(cpts[0]); for (let i = 1; i < cpts.length - 1; i++) { let p = cpts[i]; pts.push(p, p); } pts.push(cpts[cpts.length - 1]); //顶部 pts.push(topPts[0]); for (let i = 1; i < topPts.length - 1; i++) { let p = topPts[i]; pts.push(p, p); } pts.push(topPts[topPts.length - 1]); //竖线 for (let range of _leftRanges) pts.push(cpts[0].clone().setZ(range[0]), cpts[0].clone().setZ(range[1])); for (let range of _rightRanges) pts.push(cpts[cpts.length - 1].clone().setZ(range[0]), cpts[cpts.length - 1].clone().setZ(range[1])); } }; let lidRanges = [[0, this._Height]]; let leftRanges = lidRanges; let rightRanges = lidRanges; for (let hole of this.Holes) { if (equaln$1(hole.StartParam, 0)) { let newLeftRanges = []; for (let range of leftRanges) arrayPushArray(newLeftRanges, SubtractRange(range[0], range[1], hole.Bottom, hole.Top, 1e5)); leftRanges = newLeftRanges; } if (equaln$1(hole.EndParam, 1)) { let newRightRanges = []; for (let range of rightRanges) arrayPushArray(newRightRanges, SubtractRange(range[0], range[1], hole.Bottom, hole.Top, 1e5)); rightRanges = newRightRanges; } } for (let i = 0; i < this.LeftCurves.length; i++) { let curve = this.LeftCurves[i]; DrawCurve(curve, i === 0 ? leftRanges : lidRanges, (i === this.LeftCurves.length - 1) ? rightRanges : lidRanges); } for (let i = 0; i < this.RightCurves.length; i++) { let curve = this.RightCurves[i]; DrawCurve(curve, i === 0 ? leftRanges : lidRanges, (i === this.RightCurves.length - 1) ? rightRanges : lidRanges); } for (let i = 0; i < this.LidCurves.length; i++) { let curve = this.LidCurves[i]; DrawCurve(curve, lidRanges, lidRanges); } } this._EdgeGeometry = BufferGeometryUtils.CreateFromPts(pts); return this._EdgeGeometry; } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this._MeshGeometry = new Geometry; let geo = this._MeshGeometry; let normal = this.Normal; let normaln = normal.clone().negate(); this.Center; let inv = this.OCSInv; let thisParam = new GetArcParam(this); let materialIndex = 0; let expMaterialIndex = 0; this.Thickness === 500; let newLeftSign = new Map(); let newRightSign = new Map(); let newLidSign = new Map(); const BuildLeftFace = (curve) => { if (curve[CURVE_FACE_TYPE_KEY] === WallFaceType.Outside) { materialIndex = 1; } if (curve instanceof Line) { let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetLineParam(curve); let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)), 0, 1); let end = equaln$1(hole.EndParam, 1) ? 1 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)), 0, 1); if (start > end) [start, end] = [end, start]; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } } for (let tape of tapes) { let startIndex = geo.vertices.length; let p1 = curveParam.GetPointAtParam(tape.start).applyMatrix4(inv); let p2 = curveParam.GetPointAtParam(tape.end).applyMatrix4(inv); geo.vertices.push(p1.setZ(tape.bottom), p2.setZ(tape.bottom)); geo.vertices.push(p1.clone().setZ(tape.top)); geo.vertices.push(p2.clone().setZ(tape.top)); let startX = curveParam.Length * 1e-3 * tape.start; let endX = curveParam.Length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; geo.faces.push(new Face3(startIndex, startIndex + 2, startIndex + 1, curveParam.LeftDir, undefined, materialIndex), new Face3(startIndex + 1, startIndex + 2, startIndex + 3, curveParam.LeftDir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, startZ)], [new Vector2$1(endX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, endZ)]); } materialIndex++; } else { let arc = curve; let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetArcParam(arc); let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)), 0, 1); let end = equaln$1(hole.EndParam, 1) ? 1 : MathUtils.clamp(curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)), 0, 1); if (start === end) continue; if (start > end) [start, end] = [end, start]; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } } if (this.Holes.length) { let length = arc.Length; let params = SplitArcParams(arc); //需要合并顶点,所以建立一个map let cacheIndex = {}; const GetIndex = (param, z) => { let key = `${param}_${z}`; let index = cacheIndex[key]; if (index === undefined) { index = geo.vertices.length; cacheIndex[key] = index; geo.vertices.push(curveParam.GetPointAtParam(param).applyMatrix4(inv).setZ(z)); } return index; }; for (let tapeaaaaa of tapes) { for (let tape of tapeaaaaa.Split(params)) { let p1Index = GetIndex(tape.start, tape.bottom); let p2Index = GetIndex(tape.end, tape.bottom); let p3Index = GetIndex(tape.start, tape.top); let p4Index = GetIndex(tape.end, tape.top); let startX = length * 1e-3 * tape.start; let endX = length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; //记录左墙线段面域 let n = newLeftSign.get(materialIndex) ?? 0; if (n < endX) newLeftSign.set(materialIndex, endX); let dir = arc.GetPointAtParam((tape.start + tape.end) * 0.5).applyMatrix4(inv).divideScalar(arc.Radius); geo.faces.push(new Face3(p1Index, p3Index, p2Index, dir, undefined, materialIndex), new Face3(p2Index, p3Index, p4Index, dir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, startZ)], [new Vector2$1(endX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, endZ)]); } } } else { let startIndex = geo.vertices.length; let pts = curve.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let pts2 = pts.map(p => p.clone().setZ(this._Height)); arrayPushArray(geo.vertices, pts); arrayPushArray(geo.vertices, pts2); let count = pts.length; let length = curve.Length * 1e-3; let divLength = length / pts.length; //记录左墙线段面域 let n = newLeftSign.get(materialIndex) ?? 0; if (n < length) newLeftSign.set(materialIndex, length); for (let i = 1; i < count; i++) { let pre = i - 1; let preX = pre * divLength; let nowX = i * divLength; let dir = pts[i].clone().add(pts[pre]).multiplyScalar(0.5).divideScalar(arc.Radius); geo.faces.push(new Face3(startIndex + i, startIndex + pre, startIndex + pre + count, dir, undefined, materialIndex), new Face3(startIndex + i + count, startIndex + i, startIndex + pre + count, dir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(nowX, 0), new Vector2$1(preX, 0), new Vector2$1(preX, this._Height * 1e-3)], [new Vector2$1(nowX, this._Height * 1e-3), new Vector2$1(nowX, 0), new Vector2$1(preX, this._Height * 1e-3)]); } } //如果是1的话 是渲染器走的 if (materialIndex === 1) { materialIndex = expMaterialIndex; } else { materialIndex++; expMaterialIndex++; //这里不使用1 if (materialIndex === 1) { materialIndex++; expMaterialIndex++; } } } }; const BuildRightFace = (curve, isLid) => { if (curve[CURVE_FACE_TYPE_KEY] === WallFaceType.Outside) { materialIndex = 1; } if (curve instanceof Line) { let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetLineParam(curve); let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)); let end = equaln$1(hole.EndParam, 1) ? 1 : curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)); if (start > end) [start, end] = [end, start]; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } } for (let tape of tapes) { let startIndex = geo.vertices.length; let p1 = curveParam.GetPointAtParam(tape.start).applyMatrix4(inv); let p2 = curveParam.GetPointAtParam(tape.end).applyMatrix4(inv); geo.vertices.push(p1.setZ(tape.bottom), p2.setZ(tape.bottom)); geo.vertices.push(p1.clone().setZ(tape.top)); geo.vertices.push(p2.clone().setZ(tape.top)); let startX = curveParam.Length * 1e-3 * tape.start; let endX = curveParam.Length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; geo.faces.push(new Face3(startIndex, startIndex + 1, startIndex + 2, curveParam.RightDir, undefined, materialIndex), new Face3(startIndex + 1, startIndex + 3, startIndex + 2, curveParam.RightDir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(endX, startZ), new Vector2$1(startX, endZ)], [new Vector2$1(endX, startZ), new Vector2$1(endX, endZ), new Vector2$1(startX, endZ)]); } } else { let arc = curve; let tapes = [new Tape(0, 1, 0, this._Height)]; let curveParam = new GetArcParam(arc); let holes = this.RealHoles; if (holes.length) { for (let hole of holes) { let start = equaln$1(hole.StartParam, 0) ? 0 : curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.StartParam)); let end = equaln$1(hole.EndParam, 1) ? 1 : curveParam.GetParamAtPoint(thisParam.GetPointAtParam(hole.EndParam)); if (start > end) [start, end] = [end, start]; let holeTape = new Tape(start, end, hole.Bottom, hole.Top); let newTapes = []; for (let tape of tapes) arrayPushArray(newTapes, tape.Clip(holeTape)); tapes = newTapes; } let length = arc.Length; let params = SplitArcParams(arc); //需要合并顶点,所以建立一个map let cacheIndex = {}; const GetIndex = (param, z) => { let key = `${param}_${z}`; let index = cacheIndex[key]; if (index === undefined) { index = geo.vertices.length; cacheIndex[key] = index; geo.vertices.push(curveParam.GetPointAtParam(param).applyMatrix4(inv).setZ(z)); } return index; }; for (let tapeaaaaa of tapes) { for (let tape of tapeaaaaa.Split(params)) { let p1Index = GetIndex(tape.start, tape.bottom); let p2Index = GetIndex(tape.end, tape.bottom); let p3Index = GetIndex(tape.start, tape.top); let p4Index = GetIndex(tape.end, tape.top); let startX = length * 1e-3 * tape.start; let endX = length * 1e-3 * tape.end; let startZ = tape.bottom * 1e-3; let endZ = tape.top * 1e-3; //记录右墙线段面域 或墙边面域 if (isLid) { let n = newLidSign.get(materialIndex) ?? 0; if (n < endX) newLidSign.set(materialIndex, endX); } else { let n = newRightSign.get(materialIndex) ?? 0; if (n < endX) newRightSign.set(materialIndex, endX); } let dir = arc.GetPointAtParam((tape.start + tape.end) * 0.5).applyMatrix4(inv).divideScalar(-arc.Radius); geo.faces.push(new Face3(p1Index, p2Index, p3Index, dir, undefined, materialIndex), new Face3(p2Index, p4Index, p3Index, dir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(endX, startZ), new Vector2$1(startX, endZ)], [new Vector2$1(endX, startZ), new Vector2$1(endX, endZ), new Vector2$1(startX, endZ)]); } } } else { let arc = curve; let startIndex = geo.vertices.length; let pts = curve.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let pts2 = pts.map(p => p.clone().setZ(this._Height)); arrayPushArray(geo.vertices, pts); arrayPushArray(geo.vertices, pts2); let length = curve.Length * 1e-3; let divLength = length / pts.length; //记录右墙线段面域 或墙边面域 if (isLid) { let n = newLidSign.get(materialIndex) ?? 0; if (n < length) newLidSign.set(materialIndex, length); } else { let n = newRightSign.get(materialIndex) ?? 0; if (n < length) newRightSign.set(materialIndex, length); } let count = pts.length; for (let i = 1; i < count; i++) { let pre = i - 1; let preX = pre * divLength; let nowX = i * divLength; let dir = pts[i].clone().add(pts[pre]).multiplyScalar(0.5).divideScalar(-arc.Radius); geo.faces.push(new Face3(startIndex + pre, startIndex + i, startIndex + pre + count, dir, undefined, materialIndex), new Face3(startIndex + i, startIndex + i + count, startIndex + pre + count, dir, undefined, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(preX, 0), new Vector2$1(nowX, 0), new Vector2$1(preX, this._Height * 1e-3)], [new Vector2$1(nowX, 0), new Vector2$1(nowX, this._Height * 1e-3), new Vector2$1(preX, this._Height * 1e-3)]); } } } //如果是1的话 是渲染器走的 if (materialIndex === 1) { materialIndex = expMaterialIndex; } else { materialIndex++; expMaterialIndex++; //这里不使用1 if (materialIndex === 1) { materialIndex++; expMaterialIndex++; } } }; const BuildRegionFace = (region) => { let ocsInv = this.OCSInv; let pts = SplitCurvePoints(region).map(p => AsVector2(p.applyMatrix4(ocsInv))); let faces = ShapeUtils.triangulateShape(pts, []); //top let startIndex = geo.vertices.length; for (let p of pts) geo.vertices.push(new Vector3(p.x, p.y, this._Height)); if (HostApplicationServices.DrawWallBottomFace) for (let p of pts) geo.vertices.push(new Vector3(p.x, p.y, 0)); for (let i = 0; i < faces.length; i++) { let [a, b, c] = faces[i]; geo.faces.push(new Face3(startIndex + a, startIndex + b, startIndex + c, normal, undefined, materialIndex)); let uvs = faces[i].map(index => pts[index].clone()); geo.faceVertexUvs[0].push(uvs); if (HostApplicationServices.DrawWallBottomFace) { geo.faces.push(new Face3(startIndex + pts.length + c, startIndex + pts.length + b, startIndex + pts.length + a, normaln, undefined, materialIndex + 1)); geo.faceVertexUvs[0].push(uvs.concat().reverse().map(v => v.clone())); } } materialIndex++; // //todo:为了优化显示 我们可以把侧面也画出来 (应该使用和酷家乐一样的技术 在视线对准时,隐藏整个墙) // let d = this._EndPoint.clone().sub(this._StartPoint).normalize(); // let pre = pts[pts.length - 1]; // let tempV = new Vector3; // for (let i = 0; i < pts.length; i++) // { // let p = pts[i]; // tempV.set(p.x - pre.x, p.y - pre.y, 0).normalize(); // //todo:盖子会重复绘制 // if (!isParallelTo(d, tempV, 1e-3) && !isPerpendicularityTo(d, tempV, 1e-3)) // { // startIndex = geo.vertices.length; // geo.vertices.push(AsVector3(pre), AsVector3(p)); // geo.vertices.push(AsVector3(pre).setZ(this._Height)); // geo.vertices.push(AsVector3(p).setZ(this._Height)); // LEFT_ROTATE_MTX2.applyVector(tempV); // tempV.negate(); // let n = tempV.clone(); // geo.faces.push( // new Face3(startIndex, startIndex + 1, startIndex + 2, n), // new Face3(startIndex + 1, startIndex + 3, startIndex + 2, n), // ); // geo.faceVertexUvs[0].push( // [new Vector2(), new Vector2(0, 0), new Vector2(0, 0)], // [new Vector2(0, 0), new Vector2(0, 0), new Vector2(0, 0)], // ); // } // pre = p; // } }; if (!this.LeftCurves) { let sign = this._Clockwise ? -1 : 1; if (this.Length > 0.1) { let left = new Arc(ZeroVec, this._Radius - sign * this.Thickness * 0.5, this._StartAngle, this._EndAngle, this._Clockwise); let right = new Arc(ZeroVec, this._Radius + sign * this.Thickness * 0.5, this._StartAngle, this._EndAngle, this._Clockwise); left.OCS = this.OCSNoClone; right.OCS = this.OCSNoClone; BuildLeftFace(left); BuildRightFace(right); let lid1 = new Line(left.StartPoint, right.StartPoint); let lid2 = new Line(right.EndPoint, left.EndPoint); BuildRightFace(lid1); BuildRightFace(lid2); let region = Polyline.Combine([left, lid2, right, lid1]); if (region) BuildRegionFace(region); } } else { this.LeftCurves.forEach(BuildLeftFace); this.RightCurves.forEach((cu) => BuildRightFace(cu)); this.LidCurves.forEach((cu) => BuildRightFace(cu, true)); if (this.Region) BuildRegionFace(this.Region); this.ResetSign(this._LeftCurveSign, newLeftSign); let allChange = this._LeftCurveSign.size !== newLeftSign.size && this._RightCurveSign.size !== newRightSign.size; this.ResetSign(this._RightCurveSign, newRightSign, allChange); this.ResetSign(this._LidCurveSign, newLidSign, false, true); this._LeftCurveSign = newLeftSign; this._RightCurveSign = newRightSign; this._LidCurveSign = newLidSign; } geo.computeVertexNormals(); geo.verticesNeedUpdate = true; geo.uvsNeedUpdate = true; return this._MeshGeometry; } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Physical) { let mesh = obj; mesh.material = this.MeshMaterials; } else if (renderType === RenderType.Physical2) { const mesh = obj.children[0]; mesh.material = this.MeshMaterials; } } 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.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterials); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } let obj = new Object3D; if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { let pts = this.Shape.getPoints(this.GetDrawCount()).map(AsVector3); let geo = new BufferGeometry().setFromPoints(pts); let axisLine = new Line$1(geo, ColorMaterial.GetWallLineMtl(1)); axisLine.computeLineDistances(); obj.add(axisLine); let outline = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); obj.add(outline); } else if (renderType === RenderType.Conceptual) { let mesh = new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.ColorIndex)); obj.add(mesh); let outline = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); obj.add(outline); } else if (renderType === RenderType.Print) { return new Object3D().add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Physical2) { const mesh = new Mesh(this.MeshGeometry, this.MeshMaterials); mesh.castShadow = true; mesh.receiveShadow = true; obj.add(mesh); const outline = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.ColorIndex)); obj.add(outline); } return obj; } /** * 重载:更新绘制的实体 * @param {RenderType} renderType * @param {Object3D} obj */ UpdateDrawObject(renderType, obj) { if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { let [axisLine, outline] = obj.children; let pts = this.Shape.getPoints(this.GetDrawCount()).map(AsVector3); if (!BufferGeometryUtils.UpdatePts(axisLine.geometry, pts)) updateGeometry(axisLine, BufferGeometryUtils.CreateFromPts(pts)); axisLine.computeLineDistances(); // if (this._Radius <= this.Thickness * 0.5) // return; //会退化成直线 所以不return了 if (outline.geometry !== this.EdgeGeometry) { outline.geometry.dispose(); outline.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { let mesh = obj.children[0]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let outline = obj.children[1]; if (outline.geometry !== this.EdgeGeometry) { outline.geometry.dispose(); outline.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Physical) { let mesh = obj; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } } else if (renderType === RenderType.Print) { //优化代码 保证在布局是打印模式时,在模型空间删除洞时候 布局空间能正常更新. let mesh = obj.children[1]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let line = obj.children[0]; 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)); line.geometry.dispose(); line.geometry = geometry; } } //#endregion //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._Radius = file.Read(); this._StartAngle = file.Read(); this._EndAngle = file.Read(); this._Clockwise = file.Read(); this.OverWriteMaterial.clear(); this._LeftCurveSign.clear(); this._RightCurveSign.clear(); this._LidCurveSign.clear(); this.LeftCurves = []; this.RightCurves = []; this.LidCurves = []; if (ver > 2) { let size = file.Read(); for (let i = 0; i < size; i++) { let index = file.Read(); let id = file.ReadHardObjectId(); this.OverWriteMaterial.set(index, id); } } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(3); file.Write(this._Radius); file.Write(this._StartAngle); file.Write(this._EndAngle); file.Write(this._Clockwise); file.Write(this.OverWriteMaterial.size); for (let [index, id] of this.OverWriteMaterial) { file.Write(index); file.WriteHardObjectId(id); } } }; RoomWallArc = __decorate([ Factory ], RoomWallArc); const ARC_MEHTON_NAMES = [ "Center", "Normal", "BoundingBoxPtsInOCS", "Radius", "IsClockWise", "StartAngle", "EndAngle", "StartPoint", "EndPoint", "ApplyScaleMatrix", "ApplyMirrorMatrix", "GetParamAtAngle", "GetParamAtAngle2", "GetAngleAtParam", "GetAngleAtPoint", "AllAngle", "Bul", "ComputeAnlge", "ParseFromBul", "FromThreePoint", ]; applyMixins(RoomWallArc, Arc, ARC_MEHTON_NAMES.concat(CURVE_MESH_NAMES)); /** * 墙上的洞 * * 这个类接管了墙上的洞的绘制,以便可以直接在视图上操作这个洞(选择到洞,并且拖动) 但是同时我们也需要拿到原始墙的信息,保持材质(或者白墙? 独立材质?) */ let RoomHoleBase = class RoomHoleBase extends RoomBase { constructor() { super(); this.RelevancyWalls = []; //关联的墙体列表 this._Height = 800; } get Height() { return this._Height; } set Height(value) { if (equaln$1(value, this._Height, 1e-4)) return; this.WriteAllObjectRecord(); this._Height = value; this.Update(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._Height = file.Read(); if (ver > 1) { let count = file.Read(); this.RelevancyWalls.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.RelevancyWalls.push(id); } } if (ver > 2) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(3); super.WriteFile(file); file.Write(this._Height); file.Write(this.RelevancyWalls.length); for (let id of this.RelevancyWalls) file.WriteObjectId(id); // ver3 file.WriteBool(this._LockMaterial); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; __decorate([ AutoRecord ], RoomHoleBase.prototype, "RelevancyWalls", void 0); RoomHoleBase = __decorate([ Factory ], RoomHoleBase); var HoleType; (function (HoleType) { HoleType[HoleType["Door"] = 1] = "Door"; HoleType[HoleType["Window"] = 2] = "Window"; HoleType[HoleType["LWindow"] = 3] = "LWindow"; HoleType[HoleType["UWindow"] = 4] = "UWindow"; })(HoleType || (HoleType = {})); /** * 折线洞 例如 转角窗 或者U型窗 */ let RoomHolePolyline = class RoomHolePolyline extends RoomHoleBase { constructor() { super(...arguments); //虽然使用了三维的点,但是我们实际使用的是二维的点 z总是等于0 this._Points = []; this._FakerWalls = []; this._WpLeftWall = false; this._WpRightWall = false; this._OverWriteMaterial = new Map(); //section index -> materialId //#endregion } get FakerWalls() { return this._FakerWalls; } set FakerWalls(_FakerWalls) { this._FakerWalls = _FakerWalls; this.LidCurves = []; this.Regions = []; for (let w of this._FakerWalls) { arrayPushArray(this.LidCurves, w.LidCurves); if (w.Region) this.Regions.push(w.Region); w.Z = this.Z; w.Height = this.Height; if (w instanceof RoomWallLine && w.Length > 1e-6) w.UpdateOCSToMinBox(); } this.UpdateWpDraw(); } set WpDist(wpdists) { this.WriteAllObjectRecord(); this._WpDists = wpdists; this.FakerWalls = this._FakerWalls; //这样是保险的 为了避免wpdist = undefine this.Update(); } get WpDist() { return this._WpDists; } get WpLeftWall() { return this._WpLeftWall; } get WpRightWall() { return this._WpRightWall; } set WpLeftWall(v) { if (v === this._WpLeftWall) return; this.WriteAllObjectRecord(); this._WpLeftWall = v; this.Update(); } set WpRightWall(v) { if (v === this._WpRightWall) return; this.WriteAllObjectRecord(); this._WpRightWall = v; this.Update(); } UpdateWpDraw() { //外飘 if (this._WpDists?.length === this._FakerWalls.length) { this.LidCurves = this.LidCurves.map(c => c.Clone()); //拷贝一份 避免修改源数据 if (this._WpLeftWall) this.LidCurves[0].StartPoint = this.LidCurves[0].GetPointAtDistance(-this._WpDists[0]); if (this._WpRightWall) this.LidCurves[1].EndPoint = this.LidCurves[1].GetPointAtDistance(this._WpDists[this._WpDists.length - 1] + this.LidCurves[1].Length); this.Regions = [this.GetWpRegion(this._WpDists)]; } } /** * 根据外飘参数得到区域 * * @param wpdists 外飘距离 * @cts 窗台石距离 */ GetWpRegion(wpdists, cts = 0, ctszytc = 0) { let leftCurves = []; let rightCurves = []; let wallCurves = this._FakerWalls.map((w) => w instanceof RoomWallLine ? new Line(w.StartPoint, w.EndPoint) : new Arc(w.Center, w.Radius, w.StartAngle, w.EndAngle, w.IsClockWise)); if (ctszytc) //窗台石左右延伸 { wallCurves[0].StartPoint = wallCurves[0].GetPointAtDistance(-ctszytc); let lastW = arrayLast(this._FakerWalls); let lastC = arrayLast(wallCurves); lastC.EndPoint = lastC.GetPointAtDistance(lastW.Length + ctszytc); } for (let i = 0; i < this._FakerWalls.length; i++) { let w = this._FakerWalls[i]; let c = wallCurves[i]; //曲线 let d = wpdists[i]; //外飘距离 if (w instanceof RoomWallArc && d > 0) { //弧形墙凸出的一边为窗外 leftCurves.push(c.GetOffsetCurves((w.Thickness * 0.5 + cts) * (w.IsClockWise ? 1 : -1))[0]); rightCurves.push(c.GetOffsetCurves((w.Thickness * 0.5 + d) * (w.IsClockWise ? -1 : 1))[0]); } else { leftCurves.push(c.GetOffsetCurves((w.Thickness * 0.5 + cts))[0]); rightCurves.push(c.GetOffsetCurves(-(w.Thickness * 0.5 + d))[0]); } } //获取合适相交点 const getMinDistPoint = (pts, refer) => { let pt = pts[0]; let length = refer.distanceTo(pt); for (let y = 1; y < pts.length; y++) { if (refer.distanceTo(pts[y]) < length) pt = pts[y]; } return pt; }; for (let i = 1; i < leftCurves.length; i++) { let pre = leftCurves[i - 1]; let now = leftCurves[i]; //直线墙分裂后画L型墙导致无交点,这里修复它. let ipts = pre.IntersectWith(now, IntersectOption.ExtendBoth); if (ipts.length === 0) ipts.push(pre.EndPoint); let iPt = getMinDistPoint(ipts, now.StartPoint); pre.EndPoint = iPt; now.StartPoint = iPt; } for (let i = 1; i < rightCurves.length; i++) { let pre = rightCurves[i - 1]; let now = rightCurves[i]; //直线墙分裂后画L型墙导致无交点,这里修复它. let ipts = pre.IntersectWith(now, IntersectOption.ExtendBoth); if (ipts.length === 0) ipts.push(pre.EndPoint); let iPt = getMinDistPoint(ipts, now.StartPoint); pre.EndPoint = iPt; now.StartPoint = iPt; } let curves = [new Line(rightCurves[0].StartPoint, leftCurves[0].StartPoint)]; arrayPushArray(curves, leftCurves); curves.push(new Line(arrayLast(leftCurves).EndPoint, arrayLast(rightCurves).EndPoint)); arrayPushArray(curves, rightCurves.reverse()); let polyline = Polyline.Combine(curves, 1e-3); return polyline; } get BoundingBoxInOCS() { let box = new Box3Ext; let inv = this.OCSInv; for (let w of this._FakerWalls) box.union(w.GetBoundingBoxInMtx(inv)); return box; } get PointsCount() { return this._Points.length; } get Points() { return this._Points.map(p => p.clone().applyMatrix4(this.OCSNoClone)); } set Points(pts) { this.WriteAllObjectRecord(); let inv = this.OCSInv; this._Points = pts.map(p => p.clone().applyMatrix4(inv).setZ(0)); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let pts = []; if (this.LidCurves) { let line = new Line; let inv = this.OCSInv; for (let lid of this.LidCurves) { let sp = lid.StartPoint.applyMatrix4(inv).setZ(0); let ep = lid.EndPoint.applyMatrix4(inv).setZ(0); line.StartPoint = sp.clone().applyMatrix4(this.OCSNoClone); line.EndPoint = sp.setZ(this._Height).applyMatrix4(this.OCSNoClone); arrayPushArray(pts, line.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); line.StartPoint = ep.clone().applyMatrix4(this.OCSNoClone); line.EndPoint = ep.setZ(this._Height).applyMatrix4(this.OCSNoClone); arrayPushArray(pts, line.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } } if (this.Regions) { for (let region of this.Regions) { region.Z = this.Z; //我们拥有它 arrayPushArray(pts, region.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); region.Z += this._Height; arrayPushArray(pts, region.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); } } return pts; } GetGripPoints() { return this.Points; } MoveGripPoints(ids, vec) { } GetStretchPoints() { return this.Points; } MoveStretchPoints(ids, vec) { } get FakerPoints() { let pts = []; for (let w of this._FakerWalls) pts.push(w.StartPoint); if (this._FakerWalls.length) pts.push(this._FakerWalls[this._FakerWalls.length - 1].EndPoint); return pts; } //使用FakerWalls来更新这个洞的信息 UpdatePoints(checkChange = false) { let pts = this.FakerPoints; this.WriteAllObjectRecord(); if (pts.length === 2) { let x = pts[1].clone().sub(pts[0]).normalize(); let z = ZAxis; let y = z.clone().cross(x).normalize(); this._Matrix.makeBasis(x, y, z).setPosition(pts[0]); } else this._Matrix.setPosition(pts[1]); let ocsInv = this.OCSInv; for (let p of pts) p.applyMatrix4(ocsInv); this._Points = pts; this.Update(); } UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } get EdgeGeometry() { if (this._EdgeGeometry) return this._EdgeGeometry; this._EdgeGeometry = new Geometry(); let inv = this.OCSInv; let pts = this._EdgeGeometry.vertices; if (this.LidCurves) for (let lid of this.LidCurves) { //TODO:是否真的需要切换到本地坐标系 (如果我们使用墙体上的曲线,那么需要 如果我们使用本地坐标系计算曲线 则不需要) let sp = lid.StartPoint.applyMatrix4(inv).setZ(0); let ep = lid.EndPoint.applyMatrix4(inv).setZ(0); pts.push(sp, sp.clone().setZ(this._Height), ep, ep.clone().setZ(this._Height)); } if (this.Regions) { for (let region of this.Regions) { region.Z = this.Z; //我们拥有它 let lined = region.MatrixAlignTo2(this.OCSNoClone); if (region.CloseMark && !equalv2(lined.pts[0], lined.pts[lined.pts.length - 1], 1e-4)) { lined.pts.push(lined.pts[0]); lined.buls.push(0); } let path = CreatePolylinePath(lined.pts, lined.buls); let rpts = path.getPoints(30); // let wallTopHeight = 0; // if (this.FakerHoles[0]) // wallTopHeight = this.FakerHoles[0].Position.z + this.FakerHoles[0].Height - this.Position.z; for (let i = 1; i < rpts.length; i++) { let pre = AsVector3(rpts[i - 1]); let p = AsVector3(rpts[i]); pts.push(pre.setZ(0), p.setZ(0)); pts.push(pre.clone().setZ(this._Height), p.clone().setZ(this._Height)); // if (wallTopHeight) // pts.push(pre.clone().setZ(wallTopHeight), p.clone().setZ(wallTopHeight)); } } } return this._EdgeGeometry; } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; this._MeshGeometry = new Geometry(); let inv = this.OCSInv; let geo = this._MeshGeometry; let materialIndex = 0; //材质槽 if (this.LidCurves) for (let lid of this.LidCurves) { let startIndex = geo.vertices.length; //TODO:是否真的需要切换到本地坐标系 (如果我们使用墙体上的曲线,那么需要 如果我们使用本地坐标系计算曲线 则不需要) let p1 = lid.StartPoint.applyMatrix4(inv).setZ(0); let p2 = lid.EndPoint.applyMatrix4(inv).setZ(0); geo.vertices.push(p1.setZ(0), p2.setZ(0)); geo.vertices.push(p1.clone().setZ(this._Height)); geo.vertices.push(p2.clone().setZ(this._Height)); let startX = 0; let endX = lid.Length * 1e-3; let startZ = 0; let endZ = this._Height * 1e-3; let normal = p2.clone().sub(p1).normalize(); LEFT_ROTATE_MTX2.applyVector(normal); geo.faces.push(new Face3(startIndex, startIndex + 2, startIndex + 1, normal, new Color, materialIndex), new Face3(startIndex + 1, startIndex + 2, startIndex + 3, normal, new Color, materialIndex)); geo.faceVertexUvs[0].push([new Vector2$1(startX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, startZ)], [new Vector2$1(endX, startZ), new Vector2$1(startX, endZ), new Vector2$1(endX, endZ)]); materialIndex++; } if (this.Regions) for (let region of this.Regions) { region.OCSNoClone.elements[14] = this._Matrix.elements[14]; //我们拥有它 let lined = region.MatrixAlignTo2(this.OCSNoClone); if (region.CloseMark && !equalv2(lined.pts[0], lined.pts[lined.pts.length - 1], 1e-4)) { lined.pts.push(lined.pts[0]); lined.buls.push(0); } let path = CreatePolylinePath(lined.pts, lined.buls); let rpts = path.getPoints(30); let faces = ShapeUtils.triangulateShape(rpts, []); let startIndex = geo.vertices.length; for (let p of rpts) geo.vertices.push(new Vector3(p.x, p.y, 0)); for (let p of rpts) geo.vertices.push(new Vector3(p.x, p.y, this._Height)); let normal = this.Normal; let normaln = normal.clone().negate(); for (let i = 0; i < faces.length; i++) { let [a, b, c] = faces[i]; geo.faces.push(new Face3(startIndex + a, startIndex + b, startIndex + c, normal, new Color, materialIndex)); let uvs = faces[i].map(index => rpts[index].clone().multiplyScalar(1e-3)); geo.faceVertexUvs[0].push(uvs); geo.faces.push(new Face3(startIndex + rpts.length + c, startIndex + rpts.length + b, startIndex + rpts.length + a, normaln, new Color, materialIndex + 1)); geo.faceVertexUvs[0].push(uvs.concat().reverse().map(v => v.clone())); } // materialIndex += 2; //避免L型的洞分开材质 } return this._MeshGeometry; } set Material(materialId) { //当设置单个材质时,清除其他的材质?(门洞 门槛石怎么办? (不管了)) if (this._OverWriteMaterial.size) { this.WriteAllObjectRecord(); for (const [k, v] of this._OverWriteMaterial) { if (v?.Object?.IsMaterialLock) continue; this._OverWriteMaterial.set(k, materialId); } } if (super.Material?.Object?.IsMaterialLock) return; super.Material = materialId; } get Material() { if (this._MaterialId?.IsErase) return; return this._MaterialId; } get OverWriteMaterial() { return this._OverWriteMaterial; } IsMtlLockAtSlot(slotIndex) { if (this.LockMaterial) return true; const curMtl = this._OverWriteMaterial.get(slotIndex); if (curMtl) { if (curMtl?.Object?.IsMaterialLock) return true; } else if (this.Material?.Object?.IsMaterialLock) return true; return false; } SetMaterialAtSlot(mtl, slotIndex) { if (this.IsMtlLockAtSlot(slotIndex)) return; if (this._OverWriteMaterial.get(slotIndex) !== mtl) { this.WriteAllObjectRecord(); this._OverWriteMaterial.set(slotIndex, mtl); this.Update(UpdateDraw.Material); } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; this.WriteAllObjectRecord(); this.Material = mtl; // 不需要clear OverWriteMaterial,set material 时会自动clear this.Update(UpdateDraw.Material); } GetMtlLockedStatus() { let partMtlLocked = false; let allMtlLocked = true; for (const [, v] of this._OverWriteMaterial) { if (v?.Object?.IsMaterialLock) { partMtlLocked = true; continue; } allMtlLocked = false; } const meshSize = this.MeshMaterial.length; if (this._OverWriteMaterial.size !== meshSize) { if (super.Material?.Object?.IsMaterialLock) partMtlLocked = true; else allMtlLocked = false; } return { allMtlLocked, partMtlLocked, }; } GetPhyMtlRecords(containErased = false) { const materials = []; for (const [, v] of this._OverWriteMaterial) { if (v?.Object) if (!v?.IsErase || containErased) materials.push(v?.Object); } const meshSize = this.MeshMaterial.length; if (this._OverWriteMaterial.size !== meshSize && this._MaterialId?.Object) { if (containErased || !this._MaterialId.IsErase) materials.push(this._MaterialId?.Object); } return materials; } _GetValidOWMtl(index) { const obj = this.OverWriteMaterial.get(index)?.Object; const owMtl = !obj?.IsErase && obj?.Material; if (!owMtl) return; return owMtl; } get MeshMaterial() { let defaultMtl; const mtlId = this._MaterialId; if (!mtlId?.IsErase && mtlId?.Object) defaultMtl = mtlId.Object.Material; else defaultMtl = HostApplicationServices.DefaultWallMaterial ?? HostApplicationServices.DefaultMeshMaterial; let materials = []; let materialIndex = 0; if (this.LidCurves) for (let c of this.LidCurves) { materials.push(this._GetValidOWMtl(materialIndex) ?? defaultMtl); materialIndex++; } if (this.Regions) { for (let r of this.LidCurves) { materials.push(this._GetValidOWMtl(materialIndex) ?? defaultMtl); materials.push(this._GetValidOWMtl(materialIndex + 1) ?? defaultMtl); //地板? materialIndex += 2; } } return materials; } get MaterialsIds() { let mtls = this.MeshMaterial; return mtls.map(mtl => { //@ts-ignore return mtl.ObjectId ?? 71; }); } GetPrintObject3D() { let geometry = new LineGeometry(); let pts = []; for (let p of this.EdgeGeometry.vertices) pts.push(p.x, p.y, p.z); let lineSegments = new Float32Array(pts); 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.Physical) { let mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { return new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(4)); } let obj = new Object3D; if (renderType === RenderType.Conceptual) { let mesh = new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex)); let line = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); obj.add(mesh, line); } else if (renderType === RenderType.Print) { obj.add(...this.GetPrintObject3D()); } else if (renderType === RenderType.Physical2) { const mesh = new Mesh(this.MeshGeometry, this.MeshMaterial); mesh.castShadow = true; mesh.receiveShadow = true; const line = new LineSegments(this.EdgeGeometry, ColorMaterial.GetLineMaterial(this.DrawColorIndex)); obj.add(mesh, line); } return obj; } /** * 重载:更新绘制的实体 * @param {RenderType} renderType * @param {Object3D} obj */ UpdateDrawObject(renderType, obj) { if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { let line = obj; if (line.geometry !== this.EdgeGeometry) { line.geometry.dispose(); line.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Conceptual || renderType === RenderType.Physical2) { let mesh = obj.children[0]; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } let line = obj.children[1]; if (line.geometry !== this.EdgeGeometry) { line.geometry.dispose(); line.geometry = this.EdgeGeometry; } } else if (renderType === RenderType.Physical) { let mesh = obj; if (mesh.geometry !== this.MeshGeometry) { mesh.geometry.dispose(); mesh.geometry = this.MeshGeometry; } } else if (renderType === RenderType.Print) { DisposeThreeObj(obj); Object3DRemoveAll(obj); return obj.add(...this.GetPrintObject3D()); } } UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Wireframe) ; else if (renderType === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } else if (renderType === RenderType.Physical2) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else { let mesh = obj; mesh.material = this.MeshMaterial; } } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._Points.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let p = new Vector3(file.Read(), file.Read(), 0); this._Points.push(p); } if (ver > 1) { this._WpDists = []; count = file.Read(); for (let i = 0; i < count; i++) this._WpDists.push(file.Read()); } if (ver > 2) { let wpLeftRightWall = file.Read(); this._WpLeftWall = (wpLeftRightWall & 1) !== 0; this._WpRightWall = (wpLeftRightWall & 2) !== 0; } this._OverWriteMaterial.clear(); if (ver > 3) { let size = file.Read(); for (let i = 0; i < size; i++) { let index = file.Read(); let id = file.ReadHardObjectId(); this._OverWriteMaterial.set(index, id); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this._Points.length); for (let p of this._Points) { file.Write(p.x); file.Write(p.y); } file.Write(this._WpDists?.length ?? 0); if (this._WpDists) for (let d of this._WpDists) file.Write(d); let wpLeftRightWall = 0; if (this._WpLeftWall) wpLeftRightWall = 1; if (this._WpRightWall) wpLeftRightWall += 2; file.Write(wpLeftRightWall); file.Write(this._OverWriteMaterial.size); for (let [index, id] of this._OverWriteMaterial) { file.Write(index); file.WriteHardObjectId(id); } } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; RoomHolePolyline = __decorate([ Factory ], RoomHolePolyline); //找到一个合适的位置放置直线洞 function FindBestRange(param, ranges, fuzz = 1e-5) { //二分搜索 let index = InsertSortedIndex(ranges, param, (v, range) => v - range[0] + fuzz); let range = ranges[index - 1]; //参数在范围内 if (range && (range[0] - fuzz) <= param && param <= (range[1] + fuzz)) return range; // //参数在范围外 // if (index === 0) // { // return ranges[0]; // } // else if (index === ranges.length) // { // return ranges[ranges.length - 1]; // } // else // { // let nextRange = ranges[index + 1]; // if (nextRange[0] - param < param - range[1]) // return nextRange; // else // return range; // } } function ParseWallRange(wall, getParam = CreateGetCurveParam(wall)) { let leftParams = wall.LeftCurves.map(c => { let range = [getParam.GetParamAtPoint(c.StartPoint), getParam.GetParamAtPoint(c.EndPoint)]; range[CURVE_FACE_TYPE_KEY] = c[CURVE_FACE_TYPE_KEY]; return range; }); let rightParams = wall.RightCurves.map(c => { let range = [getParam.GetParamAtPoint(c.StartPoint), getParam.GetParamAtPoint(c.EndPoint)]; range[CURVE_FACE_TYPE_KEY] = c[CURVE_FACE_TYPE_KEY]; return range; }); leftParams.sort((a, b) => a[0] - b[0]); rightParams.sort((a, b) => a[0] - b[0]); let ranges = []; for (let seg1 of rightParams) { let [min, max] = seg1; for (let seg2 of leftParams) { let [min2, max2] = seg2; if (min > max2) continue; if (max < min2) break; let range = Intersection(seg1, seg2); if (range) { ranges.push(range); range["left"] = seg2[CURVE_FACE_TYPE_KEY]; range["right"] = seg1[CURVE_FACE_TYPE_KEY]; } } } return ranges; } /** * 更新墙与洞的关联性 */ async function UpdateRelevanceWallHole(ents, isUndoRedo = false) { let updated = new Set(); //更新墙,顺便更新洞 const UpdateWall = async (wall) => { if (updated.has(wall)) return; updated.add(wall); //避免重入 //先更新洞,因为洞可能会修复位置 for (let holeId of wall.RelevancyHoles) //更新关联的洞 { if (holeId.IsErase) continue; let hole = holeId.Object; if (wall.IsErase) hole.Erase(); await UpdateHole(hole); //这里如果只更新洞,那么有可能关联的其他的墙会逃逸 } UpdateWallHolesDataAndUpdateDraw(wall); }; //更新洞,顺便更新关联的墙? const UpdateHole = async (hole) => { if (updated.has(hole)) return; updated.add(hole); //避免重入 //先更新洞,因为洞可能会修复位置 UpdateHoleFakerWallsAndUpdateDraw(hole); for (let wallId of hole.RelevancyWalls) { if (wallId.IsErase) continue; let wall = wallId.Object; if (updated.has(wall)) continue; await UpdateWall(wall); } if (!isUndoRedo && hole.Template?.IsErase === false) { let tr = hole.Template.Object; await tr.UpdateTemplateTree(); } }; for (let en of ents) { if (en instanceof RoomWallBase) await UpdateWall(en); else if (en instanceof RoomHolePolyline) await UpdateHole(en); } } /** * 更新墙的洞的数据,并且更新绘制 */ function UpdateWallHolesDataAndUpdateDraw(wall) { wall.Holes = []; let getParam = CreateGetCurveParam(wall); let z = wall.OCSNoClone.elements[14]; for (let hole of wall.RelevancyHoles) { if (hole.IsErase) { wall.Holes.push({ StartParam: 0, EndParam: 0, Bottom: 0, Top: 0 }); continue; } let holeObj = hole.Object; let index = holeObj.RelevancyWalls.indexOf(wall.Id); if (index < 0) continue; let sp = holeObj.Points[index]; let ep = holeObj.Points[index + 1]; let startParam = getParam.GetParamAtPoint(sp); let endParam = getParam.GetParamAtPoint(ep); if (startParam > endParam) [startParam, endParam] = [endParam, startParam]; let bottom = holeObj.Position.z - z; let top = holeObj.Height + bottom; wall.Holes.push({ StartParam: startParam, EndParam: endParam, Bottom: bottom, Top: top }); } wall.Update(); return wall; } /** * 更新网洞的绘制 * 在墙移动的时候,或者网洞变化的时候 这个网洞的绘制就需要被更新 */ function UpdateHoleFakerWallsAndUpdateDraw(hole) { if (hole.IsErase) return; let fakerWalls = hole.RelevancyWalls.map(w => w.Object.Clone(false)); let pts = hole.Points; if (pts.length < 2 || fakerWalls.length !== pts.length - 1) { hole.Erase(); return; } if (pts.length === 2) { let p1 = pts[0]; let p2 = pts[1]; let wall = fakerWalls[0]; let orgWall = hole.RelevancyWalls[0].Object; let paramGet = CreateGetCurveParam(orgWall); let ranges = ParseWallRange(orgWall, paramGet); let [p1Param, p1Closeto] = paramGet.GetParamAtPoint2(p1); let [p2Param, p2Closeto] = paramGet.GetParamAtPoint2(p2); let range1 = FindBestRange(p1Param, ranges); let range2 = FindBestRange(p2Param, ranges); if (p1Closeto && p2Closeto) { if (range1 && range1 === range2) { wall.StartPoint = p1; wall.EndPoint = p2; if (wall instanceof RoomWallArc && p1Param > p2Param) wall.IsClockWise = !wall.IsClockWise; } else { let range = range1 ?? range2 ?? FindBestRange((p1Param + p2Param) * 0.5, ranges); if (!range) { hole.Erase(); return; } p1Param = MathUtils.clamp(p1Param, range[0], range[1]); p2Param = MathUtils.clamp(p2Param, range[0], range[1]); wall.StartPoint = paramGet.GetPointAtParam(p1Param); wall.EndPoint = paramGet.GetPointAtParam(p2Param); if (wall instanceof RoomWallArc && p1Param > p2Param) wall.IsClockWise = !wall.IsClockWise; } } else { let index = orgWall.RelevancyHoles.indexOf(hole.Id); let holeData = orgWall.Holes[index]; if (!holeData) { hole.Erase(); return; } if (p2Param > p1Param) { p1Param = holeData.StartParam; p2Param = holeData.EndParam; } else { p1Param = holeData.EndParam; p2Param = holeData.StartParam; } let range = FindBestRange(p1Param, ranges) ?? FindBestRange(p1Param, ranges) ?? FindBestRange((p1Param + p2Param) * 0.5, ranges); if (!range) { hole.Erase(); return; } p1Param = MathUtils.clamp(p1Param, range[0], range[1]); p2Param = MathUtils.clamp(p2Param, range[0], range[1]); wall.StartPoint = paramGet.GetPointAtParam(p1Param); wall.EndPoint = paramGet.GetPointAtParam(p2Param); if (wall instanceof RoomWallArc && p1Param > p2Param) wall.IsClockWise = !wall.IsClockWise; } new RoomWallParse(false, undefined, false).Parse(fakerWalls); hole.FakerWalls = fakerWalls; let newPts = hole.FakerPoints; if (pts.some((p, i) => !equalv2(p, newPts[i], 1e-4))) hole.UpdatePoints(); hole.Update(); return; } //pts.length >2 let curvemap = new CurveMap(); for (let w of fakerWalls) curvemap.AddCurveToMap(w, false, false, false); if (curvemap._Vertices.length !== pts.length) { hole.Erase(); return; } if (pts.length === 3) { let v = curvemap._Vertices.find(v => v.routes.length === 2); if (!v) //拐角点丢失 { hole.Erase(); return; } let r1 = v.routes.find(r => r.curve === fakerWalls[0]); let r2 = v.routes.find(r => r.curve === fakerWalls[1]); if (!r1.isReverse) fakerWalls[0].Reverse(); if (r2.isReverse) fakerWalls[1].Reverse(); // if (!equalv2(v.position, pts[1], 1e-4))//拐角点不再对齐 // { // let length1 = GetHoleLengthOfIndex(hole, 0); // let length2 = GetHoleLengthOfIndex(hole, 1); // fakerWalls[0].StartPoint = fakerWalls[0].GetPointAtDistance(fakerWalls[0].Length - length1); // fakerWalls[1].EndPoint = fakerWalls[1].GetPointAtDistance(length2); // } // else//拐角点仍然对齐 { { let length1 = GetHoleLengthOfIndex(hole, 0); let orgWall = hole.RelevancyWalls[0].Object; let ranges = ParseWallRange(orgWall); if (!r1.isReverse) length1 = Math.min(length1, orgWall.Length * ranges[0][1]); else length1 = Math.min(length1, orgWall.Length * (1 - arrayLast(ranges)[0])); fakerWalls[0].StartPoint = fakerWalls[0].GetPointAtDistance(fakerWalls[0].Length - length1); } { let length2 = GetHoleLengthOfIndex(hole, 1); let orgWall = hole.RelevancyWalls[1].Object; let ranges = ParseWallRange(orgWall); if (r2.isReverse) length2 = Math.min(length2, orgWall.Length * (1 - arrayLast(ranges)[0])); else length2 = Math.min(length2, orgWall.Length * ranges[0][1]); fakerWalls[1].EndPoint = fakerWalls[1].GetPointAtDistance(length2); } } } else if (pts.length === 4) { let v1 = curvemap._Vertices.find(v => v.routes.length === 2 && v.routes.some(r => r.curve === fakerWalls[0])); let v2 = curvemap._Vertices.find(v => v.routes.length === 2 && v.routes.some(r => r.curve === fakerWalls[2])); if (!v1 || !v2) { hole.Erase(); return; } let r1 = v1.routes.find(r => r.curve === fakerWalls[0]); let r2 = v2.routes.find(r => r.curve === fakerWalls[2]); let r3 = v1.routes.find(r => r.curve === fakerWalls[1]); if (r3.isReverse) fakerWalls[1].Reverse(); if (!r1.isReverse) fakerWalls[0].Reverse(); if (r2.isReverse) fakerWalls[2].Reverse(); { let length1 = GetHoleLengthOfIndex(hole, 0); let length2 = GetHoleLengthOfIndex(hole, 2); { let orgWall = hole.RelevancyWalls[0].Object; let ranges = ParseWallRange(orgWall); if (!r1.isReverse) length1 = Math.min(length1, orgWall.Length * ranges[0][1]); else length1 = Math.min(length1, orgWall.Length * (1 - arrayLast(ranges)[0])); fakerWalls[0].StartPoint = fakerWalls[0].GetPointAtDistance(fakerWalls[0].Length - length1); } { let orgWall = hole.RelevancyWalls[2].Object; let ranges = ParseWallRange(orgWall); if (r3.isReverse) length2 = Math.min(length2, orgWall.Length * (1 - arrayLast(ranges)[0])); else length2 = Math.min(length2, orgWall.Length * ranges[0][1]); fakerWalls[2].EndPoint = fakerWalls[2].GetPointAtDistance(length2); } } } new RoomWallParse(false, undefined, false).Parse(fakerWalls); hole.FakerWalls = fakerWalls; let newPts = hole.FakerPoints; if (pts.some((p, i) => !equalv2(p, newPts[i], 1e-4))) hole.UpdatePoints(); hole.Update(); } function GetHoleLengthOfIndex(hole, index) { if (hole.RelevancyWalls && hole.RelevancyWalls[index]?.Object) { let wall = hole.RelevancyWalls[index].Object; if (wall instanceof RoomWallLine) return hole.Points[index].distanceTo(hole.Points[index + 1]); else if (wall instanceof RoomWallArc) { let p1 = hole.Points[index]; let p2 = hole.Points[index + 1]; let param1 = wall.GetParamAtPoint(p1.clone().setZ(wall.Z)); let param2 = wall.GetParamAtPoint(p2.clone().setZ(wall.Z)); if (!isNaN(param1) && !isNaN(param2)) return Math.abs(wall.GetDistAtParam(param1) - wall.GetDistAtParam(param2)); let b = p1.distanceTo(p2); if (b < 1e-4) return 0; let r = wall.Radius; let l = 2 * Math.asin(b / (2 * r)) * r; //http://zhidao.baidu.com/question/553357442/answer/1393358387 return l; } } return 0; } const _LoadParamMaterialInjectFunctions = []; async function LoadParamMaterial(template) { for (let f of _LoadParamMaterialInjectFunctions) await f(template); } class ISpaceParse { /** * # 构造后请手动调用Parse()方法. * @param boards 板件列表 * @param [spaceOCS] 默认空间矩阵,如果不设置将使用第一块板作为空间矩阵 */ constructor(boards, spaceOCS) { /** * 解析空间成功 */ this.ParseOK = false; /** * 板件映射表 */ this.BoardMap = new Map(); /** * 动态中,禁止执行二次操作 */ this.IsDynamic = false; this.Rotation = { x: 0, y: 0, z: 0 }; this.Boards = boards; if (spaceOCS) this.SpaceOCS = spaceOCS; else if (boards && boards.length > 0) this.SpaceOCS = boards[0].SpaceOCS; else this.SpaceOCS = new Matrix4(); this.SpaceOCSInv = new Matrix4().getInverse(this.SpaceOCS); this.GeneralBoardMap(); } async Parse() { } get Size() { if (this.SpaceBox) return this.SpaceBox.getSize(new Vector3()); return new Vector3(); } get DrawCS() { if (!this.ParseOK) return new Matrix4(); let scs = this.SpaceOCS.clone(); let p = this.SpaceBox.min.clone().applyMatrix4(scs); scs.setPosition(p); return scs; } GetBoardInSpaceType(br) { //使用板件向量判断类型,而不是板件类型 let normal = br.Normal.transformDirection(this.SpaceOCSInv); let type; if (isParallelTo(XAxis, normal, 1e-3)) type = BoardType.Vertical; else if (isParallelTo(YAxis, normal, 1e-3)) type = BoardType.Behind; else if (isParallelTo(ZAxis, normal, 1e-3)) type = BoardType.Layer; return type; } /** * 构造板件类型Map */ GeneralBoardMap() { if (this.Boards && this.Boards.length > 0) { this.BoardMap.clear(); for (let br of this.Boards) { let type = this.GetBoardInSpaceType(br); if (type === undefined) continue; let brs = this.BoardMap.get(type); if (brs) brs.push(br); else this.BoardMap.set(type, [br]); } } } /** * 解析板件的盒子,并且(排序,归并) * @param boardCol * @param splitType */ ParseBoardBox(boardCol, splitType) { let boxCol = boardCol.map(b => b.GetBoundingBoxInMtx(this.SpaceOCSInv)); //查找最左的板和最右的板 if (splitType === SplitType.X) { let minX = Infinity; let leftIndex = 0; let maxX = -Infinity; let rightIndex = 0; for (let i = 0; i < boxCol.length; i++) { let box = boxCol[i]; if (box.min.x < minX) { minX = box.min.x; leftIndex = i; } if (box.max.x > maxX) { maxX = box.max.x; rightIndex = i; } } this.LeftBoard = boardCol[leftIndex]; this.RightBoard = boardCol[rightIndex]; } //根据分割类型排序 boxCol.sort((b1, b2) => { return b1.min.getComponent(splitType) - b2.min.getComponent(splitType); }); //归并盒子 arrayRemoveDuplicateBySort(boxCol, (b1, b2) => { if ( //对齐 equaln$1(b1.min.getComponent(splitType), b2.min.getComponent(splitType), 1e-2) && //厚度相等 equaln$1(b1.getSize(new Vector3()).getComponent(splitType), b2.getSize(new Vector3()).getComponent(splitType), 1e-2)) { b1.union(b2); return true; } return false; }); return boxCol; } } /** * 模版动作 */ let TemplateAction = class TemplateAction { constructor() { this.Name = "动作"; } WriteAllObjectRecord() { if (this.parent) this.parent.WriteAllObjectRecord(); } Update(paramDiff, newValue) { if (this.Expr) { let varDefines = {}; varDefines[this.parent.name] = newValue; newValue = safeEval(this.Expr, varDefines) || newValue; varDefines[this.parent.name] = paramDiff; paramDiff = safeEval(this.Expr, varDefines) || paramDiff; } this._Update(paramDiff, newValue); } /** * @重载 */ _Update(paramDiff, newValue) { } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); this.Name = file.Read(); if (ver > 2) { this.Expr = file.Read(); this.Description = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(3); file.Write(this.Name); file.Write(this.Expr); file.Write(this.Description); } }; __decorate([ AutoRecord ], TemplateAction.prototype, "Name", void 0); __decorate([ AutoRecord ], TemplateAction.prototype, "Expr", void 0); __decorate([ AutoRecord ], TemplateAction.prototype, "Description", void 0); TemplateAction = __decorate([ Factory ], TemplateAction); //当前仅在GetEntity 和Ssget上严格实现了 None 和 Other的情况 //其他的只有None 没有Other(其他的也不可能出现想选没选到的情况) var PromptStatus; (function (PromptStatus) { PromptStatus[PromptStatus["None"] = 0] = "None"; PromptStatus[PromptStatus["Cancel"] = -1] = "Cancel"; PromptStatus[PromptStatus["OK"] = 1] = "OK"; PromptStatus[PromptStatus["Keyword"] = 2] = "Keyword"; PromptStatus[PromptStatus["Other"] = 4] = "Other"; PromptStatus[PromptStatus["String"] = 8] = "String"; PromptStatus[PromptStatus["Error"] = -2] = "Error"; })(PromptStatus || (PromptStatus = {})); // export enum Errno 未来某一天我们可能需要这个东西 // { // Space = 0, // Enter = 1, // Esc = 2, // Left = 3, // Right = 4, // } class PromptResult { constructor() { this.Status = PromptStatus.None; //是否为子级菜单 this.isChild = false; //当用户选择失败的时候,提供当前选择失败的原因 // Errno?: Errno; } } class PromptDistendResult extends PromptResult { get Distance() { return this._value; } set Distance(v) { this._value = v; } } class PromptEntityResult extends PromptResult { constructor( //选择到的图形 Entity, //点取的点 Point, Object, IsCircle) { super(); this.Entity = Entity; this.Point = Point; this.Object = Object; this.IsCircle = IsCircle; } } function Encode(res, enMap) { if (res.Entity instanceof Line || res.Entity.constructor.name === "RoomWallLine") { enMap[0].push(res); return 1; } else if (res.Entity instanceof Arc || res.constructor.name === "RoomWallArc") { enMap[1].push(res); return 2; } else if (res.Entity instanceof Polyline) { enMap[2].push(res); return 4; } else if (res.Entity instanceof Spline) { enMap[3].push(res); return 8; } else if (res.Entity instanceof Ellipse) { enMap[4].push(res); return 16; } } //把圆转换成圆弧,避免圆参与计算. function CircleEnResToArc(enRes) { if (enRes.Entity instanceof Circle) { let an = angle(enRes.Point.clone().applyMatrix4(enRes.Entity.OCSInv)) + Math.PI; let arc = new Arc(new Vector3(), enRes.Entity.Radius, an, an + 0.1); arc.ApplyMatrix(enRes.Entity.OCS); arc.Center = enRes.Entity.Center; enRes.Entity = arc; enRes.IsCircle = true; } } function GetFilletCurve(enRes) { if (enRes.Entity instanceof Polyline) { let pl = enRes.Entity; let param = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes.Point, false)); let paramF = Math.floor(param); return [pl.GetCurveAtParam(param), paramF]; } else return [enRes.Entity, NaN]; } var ExtendType; (function (ExtendType) { ExtendType[ExtendType["Start"] = 1] = "Start"; ExtendType[ExtendType["End"] = 2] = "End"; })(ExtendType || (ExtendType = {})); class FilletUtils { Fillet(enRes1, enRes2) { CircleEnResToArc(enRes1); CircleEnResToArc(enRes2); let { enType, enMap } = this.EnCode(enRes1, enRes2); if (enType === 4 && enRes1.Entity === enRes2.Entity) return this.FilletPolyLineSelf(enRes1, enRes2); else if (enType >= 4 && enType < 8) return this.FilletPolylineAndCurve(enRes1, enRes2); let interPts = this.GetIntersectAndSort(enRes1, enRes2, enType, enMap); if (interPts.length === 0 || (interPts.length === 1 && (enType & 2))) //圆弧相切 { if (enType === 1) return this.FilletParallelLine(enRes1, enRes2); else if (enType === 3) return this.FilletLineAndArc(enMap, enRes1); else if (enType === 2) return this.FilletArcAndArc(enRes1, enRes2); return; } return this.FilletLineOrArc(enRes1, enRes2, interPts); } FilletLineOrArc(enRes1, enRes2, interPts) { let iPt = interPts[0]; //裁剪延伸,使两条线组成一个尖角 let splitedCu1 = this.SplitCurve(enRes1, iPt, interPts); let splitedCu2 = this.SplitCurve(enRes2, iPt, interPts); let fRadius = this.FilletRadius; let res = { cu1: splitedCu1.Curve, cu2: splitedCu2.Curve, arc: undefined }; if (fRadius > 0) { //尝试获取曲线所在的统一XY平面矩阵 let cuOCS = ComputerCurvesNormalOCS([splitedCu1.Curve, splitedCu2.Curve]); //角平分线向量. let bisectorVec = new Vector3(); let c1Derv = this.ComputerDerv(splitedCu1, bisectorVec); let c2Derv = this.ComputerDerv(splitedCu2, bisectorVec); //方向相反 if (equalv3(bisectorVec, new Vector3())) return; //相切 if (equalv3(c2Derv, c1Derv)) { bisectorVec.set(0, 0, 0); c1Derv = this.ComputerDerv2(splitedCu1, bisectorVec); c2Derv = this.ComputerDerv2(splitedCu2, bisectorVec); } let ocsInv = new Matrix4().getInverse(cuOCS); let cu1RoOcsInv = new Matrix4().extractRotation(ocsInv); //修改曲线矩阵至同一个平面 this.SetCurveOCS(splitedCu1.Curve, cuOCS); this.SetCurveOCS(splitedCu2.Curve, cuOCS); [c1Derv, c2Derv, bisectorVec].forEach(v => v.applyMatrix4(cu1RoOcsInv)); let offCu1 = splitedCu1.Curve.GetOffsetCurves(fRadius * -Math.sign(c1Derv.cross(bisectorVec).z))[0]; let offCu2 = splitedCu2.Curve.GetOffsetCurves(fRadius * -Math.sign(c2Derv.cross(bisectorVec).z))[0]; if (!offCu1 || !offCu2) return; // JigUtils.Draw(new Line(iPt, iPt.clone().add(c1Derv.clone().multiplyScalar(10)))).ColorIndex = 1; // JigUtils.Draw(new Line(iPt, iPt.clone().add(c2Derv.clone().multiplyScalar(10)))).ColorIndex = 2; // JigUtils.Draw(new Line(iPt, iPt.clone().add(bisectorVec))).ColorIndex = 3; // offCu1.ColorIndex = 6; // offCu2.ColorIndex = 6; // JigUtils.Draw(offCu1.Clone()); // JigUtils.Draw(offCu2.Clone()); let center = offCu1.IntersectWith(offCu2, IntersectOption.ExtendNone) .sort((p1, p2) => { return p1.distanceToSquared(iPt) - p2.distanceToSquared(iPt); })[0]; if (!center) return; let arcP1 = splitedCu1.Curve.GetClosestPointTo(center, true); let arcP2 = splitedCu2.Curve.GetClosestPointTo(center, true); if (!splitedCu1.Curve.PtOnCurve(arcP1) || !splitedCu2.Curve.PtOnCurve(arcP2)) return; //时针校验 let v1 = arcP1.clone().sub(center).applyMatrix4(cu1RoOcsInv); let v2 = arcP2.clone().sub(center).applyMatrix4(cu1RoOcsInv); //绘制圆弧 let arc = new Arc(new Vector3(), this.FilletRadius, angle(v1), angle(v2), v1.cross(v2).z < 0); arc.ApplyMatrix(cuOCS); arc.Center = center; res.arc = arc; //延伸或者裁剪到圆弧点 this.ExtendPt(splitedCu1, arcP1); this.ExtendPt(splitedCu2, arcP2); } res.cu1Extend = splitedCu1.ExtType; res.cu2Extend = splitedCu2.ExtType; return res; } /** * 设置直线的坐标系 以便可以正确的被偏移 * @param {Curve} cu * @param {Matrix4} newOCS */ SetCurveOCS(cu, newOCS) { if (cu instanceof Line) { let sp = cu.StartPoint; let ep = cu.EndPoint; cu.ApplyMatrix(cu.OCSInv).ApplyMatrix(newOCS); cu.StartPoint = sp; cu.EndPoint = ep; } } FilletPolyLineSelf(enRes1, enRes2) { let pl = enRes1.Entity; let param1 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes1.Point, false)); let param2 = pl.GetParamAtPoint(pl.GetClosestPointTo(enRes2.Point, false)); if (param1 > param2) { [param1, param2] = [param2, param1]; [enRes1, enRes2] = [enRes2, enRes1]; } let parF1 = Math.floor(param1); let parF2 = Math.floor(param2); //共线 if (parF1 === parF2) return; let c1 = pl.GetCurveAtParam(param1); let c2 = pl.GetCurveAtParam(param2); if (equalv3(c1.GetFirstDeriv(1).normalize(), c2.GetFirstDeriv(0).normalize())) return; let es1 = new PromptEntityResult(); es1.Entity = c1; es1.Point = enRes1.Point; let es2 = new PromptEntityResult(); es2.Entity = c2; es2.Point = enRes2.Point; let fres = this.Fillet(es1, es2); if (!fres) return; let pln = pl.Clone(); if (fres.cu1 instanceof Arc) pln.SetBulgeAt(parF1, fres.cu1.Bul); if (fres.cu2 instanceof Arc) pln.SetBulgeAt(parF2, fres.cu2.Bul); let splitType1 = fres.cu1Extend; let splitType2 = fres.cu2Extend; if (splitType1 === splitType2) return; if (!fres.arc) { if (splitType1 === ExtendType.End) { let ep = AsVector2(fres.cu1.EndPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF1 + 1, ep); let sp = AsVector2(fres.cu2.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF2, sp); //移除多余的点 pln.LineData.splice(parF1 + 1, parF2 - parF1 - 1); pln.Update(); return { cu1: pln }; } else { let ep = AsVector2(fres.cu1.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF1, ep); let sp = AsVector2(fres.cu2.EndPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF2 + 1, sp); pln.LineData.splice(parF2 + 2); pln.LineData.splice(0, parF1); pln.Update(); return { cu1: pln }; } } if (splitType1 === ExtendType.End) //没有经过起点 { let sp = AsVector2(fres.cu1.EndPoint.applyMatrix4(pln.OCSInv)); if (parF2 - parF1 === 1) { pln.AddVertexAt(parF1 + 1, sp); parF2++; } else pln.SetPointAt(parF1 + 1, sp); pln.SetBulgeAt(parF1 + 1, fres.arc.Bul); let ep = AsVector2(fres.cu2.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF2, ep); //移除多余的点 pln.LineData.splice(parF1 + 2, parF2 - parF1 - 2); pln.Update(); return { cu1: pln }; } else //经过起点 { let sp = AsVector2(fres.arc.EndPoint.applyMatrix4(pln.OCSInv)); let keepF1 = 0; if (parF2 + 1 <= pln.LineData.length) { if (parF1 === 0 || equaln$1(pln.GetBulgeAt(parF1 - 1), 0)) pln.AddVertexAt(parF2 + 1, sp); else { pln.SetPointAt(parF1 - 1, sp); keepF1 = -1; //保留圆弧位 } } else pln.SetPointAt(parF2 + 1, sp); if (keepF1 === 0) pln.SetBulgeAt(parF2 + 1, -fres.arc.Bul); else pln.SetBulgeAt(parF1 - 1, -fres.arc.Bul); let ep = AsVector2(fres.cu1.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(parF1, ep); pln.CloseMark = true; pln.LineData.splice(parF2 + 2 + keepF1); pln.LineData.splice(0, parF1 + keepF1); pln.Update(); return { cu1: pln }; } } FilletPolylineAndCurve(enRes1, enRes2) { let arr1 = GetFilletCurve(enRes1); let arr2 = GetFilletCurve(enRes2); let [cu1, paramF1] = arr1; let [cu2, paramF2] = arr2; let es1 = new PromptEntityResult(); es1.Entity = cu1; es1.Point = enRes1.Point; let es2 = new PromptEntityResult(); es2.Entity = cu2; es2.Point = enRes2.Point; let fres = this.Fillet(es1, es2); if (fres) { let cus = []; let isFirst = false; if (fres.cu1) { if (enRes1.Entity instanceof Polyline) { isFirst = true; let pln = enRes1.Entity.Clone(); pln.DigestionCloseMark(); if (fres.cu1 instanceof Arc) pln.SetBulgeAt(paramF1, fres.cu1.Bul); if (fres.cu1Extend === ExtendType.End) { pln.LineData.splice(paramF1 + 2); let ep = AsVector2(fres.cu1.EndPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(paramF1 + 1, ep); } else { pln.LineData.splice(0, paramF1); let sp = AsVector2(fres.cu1.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(0, sp); } cus.push(pln); } else if (!enRes1.IsCircle) cus.push(fres.cu1); } if (fres.arc) cus.push(fres.arc); if (fres.cu2) { if (enRes2.Entity instanceof Polyline) { let pln = enRes2.Entity.Clone(); pln.DigestionCloseMark(); if (fres.cu2 instanceof Arc) pln.SetBulgeAt(paramF2, fres.cu2.Bul); if (fres.cu2Extend === ExtendType.End) { pln.LineData.splice(paramF2 + 2); let ep = AsVector2(fres.cu2.EndPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(paramF2 + 1, ep); } else { pln.LineData.splice(0, paramF2); let sp = AsVector2(fres.cu2.StartPoint.applyMatrix4(pln.OCSInv)); pln.SetPointAt(0, sp); } cus.push(pln); cus.reverse(); } else if (!enRes2.IsCircle) cus.push(fres.cu2); } if (cus.length > 0) { let pl = cus[0]; if (!(pl instanceof Polyline)) return; for (let i = 1; i < cus.length; i++) pl.Join(cus[i]); if (isFirst) return { cu1: pl }; else return { cu2: pl }; } } return undefined; } FilletPolyLineAllAngular(enRes1) { let pl = enRes1.Entity; let cus = pl.Explode(); let count = cus.length; if (pl.IsClose) cus.push(cus[0]); let ncus = []; for (let i = 0; i < count; i++) { let c1 = cus[i]; let c2 = cus[i + 1]; ncus.push(c1); if (!c2) break; if (equalv3(c1.GetFirstDeriv(1).normalize(), c2.GetFirstDeriv(0).normalize())) continue; let es1 = new PromptEntityResult(); es1.Entity = c1; es1.Point = c1.EndPoint; let es2 = new PromptEntityResult(); es2.Entity = c2; es2.Point = c2.StartPoint; let fres = this.Fillet(es1, es2); if (fres) { if (fres.cu1) c1.CopyFrom(fres.cu1); if (fres.cu2) c2.CopyFrom(fres.cu2); if (fres.arc) ncus.push(fres.arc); } } let pln = pl.Clone(); pln.LineData = []; pln.ApplyMatrix(pln.OCSInv); pln.CloseMark = false; for (let cu of ncus) pln.Join(cu); pln.CloseMark = pl.CloseMark; return { cu1: pln, cu2: undefined, arc: undefined }; } FindNearestPt(pts, target) { let res = pts[0]; let dis = Infinity; for (let p of pts) { let d = p.distanceTo(target); if (d < dis) { res = p; dis = d; } } return res; } FilletBoard(brRes, ptRes) { let br = brRes.Entity; let brContour = br.ContourCurve.Clone(); //------1.求交 let brResPt = brRes.Point.clone().applyMatrix4(br.OCSInv).setZ(0); let ptResPt = ptRes.Point.clone().applyMatrix4(br.OCSInv).setZ(0); let l = new Line(brResPt, ptResPt); let ipts = l.IntersectWith(brContour, IntersectOption.ExtendThis); if (ipts.length > 2) //超过2个则有可能有多余交点 //找最近点 ipts = [this.FindNearestPt(ipts, brResPt), this.FindNearestPt(ipts, ptResPt)]; if (ipts.length !== 2) return "倒角失败!交点个数异常."; //------2.倒角 let es1 = new PromptEntityResult(brContour, ipts[0]); let es2 = new PromptEntityResult(brContour, ipts[1]); let res = this.FilletPolyLineSelf(es1, es2); if (res && res.cu1) return res.cu1; else return "倒角失败"; } /** * 平行线倒角 */ FilletParallelLine(enRes1, enRes2) { let l1 = enRes1.Entity; let l2 = enRes2.Entity; let l1Derv = l1.GetFirstDeriv(0); if (!isParallelTo(l1Derv, l2.GetFirstDeriv(0))) return; let vec = l2.StartPoint.sub(l1.StartPoint); if (isParallelTo(vec, l1Derv)) return; let par1 = l2.GetClosestAtPoint(l1.StartPoint, true).param; let par2 = l2.GetClosestAtPoint(l1.EndPoint, true).param; if (!isIntersect2(0, 1, par1, par2)) return; let lineClone1 = l1.Clone(); let lineClone2 = l2.Clone(); let par = l1.GetClosestAtPoint(enRes1.Point, true).param; let parFix = Math.round(par); let ptFix = lineClone1.GetPointAtParam(parFix); let ptL2Fix = lineClone2.GetClosestAtPoint(ptFix, true).closestPt; let cu1Extend = parFix === 0 ? ExtendType.Start : ExtendType.End; let cu2Extend; if ((par1 > par2) === (parFix === 1)) { lineClone2.StartPoint = ptL2Fix; cu2Extend = ExtendType.Start; } else { lineClone2.EndPoint = ptL2Fix; cu2Extend = ExtendType.End; } let radius = ptFix.distanceTo(ptL2Fix) / 2; if (radius < 1e-3) return; let arcCenter = midPoint(ptFix, ptL2Fix); let sv = ptFix.sub(arcCenter); let ev = ptL2Fix.sub(arcCenter); if (parFix === 0) l1Derv.negate(); //平面矩阵 let xVec = new Vector3(); let yVec = new Vector3(); let zVec = vec.cross(l1Derv).normalize(); let l1Normal = l1.Normal; if (isParallelTo(zVec, l1Normal)) zVec = l1Normal; Orbit.ComputUpDirection(zVec, yVec, xVec); let mtx = new Matrix4().makeBasis(xVec, yVec, zVec); let mtxInv = new Matrix4().getInverse(mtx); //变换 sv.applyMatrix4(mtxInv); ev.applyMatrix4(mtxInv); l1Derv.applyMatrix4(mtxInv); let sa = angle(sv); let ea = angle(ev); let clockwise = ev.cross(l1Derv).z > 0; let arc = new Arc(new Vector3(), radius, sa, ea, clockwise); arc.ApplyMatrix(mtx); arc.Center = arcCenter; return { cu1: lineClone1, cu1Extend, cu2: lineClone2, cu2Extend, arc, }; } /** * 计算圆弧与圆弧没有交点的情况下倒角结果. * @param enRes1 * @param enRes2 * @returns arc and arc */ FilletArcAndArc(enRes1, enRes2) { let a1 = enRes1.Entity; let a2 = enRes2.Entity; let arcO1 = a1.GetOffsetCurves(this.FilletRadius * (a1.IsClockWise ? -1 : 1))[0]; let arcO2 = a2.GetOffsetCurves(this.FilletRadius * (a2.IsClockWise ? -1 : 1))[0]; // arcO1.ColorIndex = 6; // arcO2.ColorIndex = 6; // JigUtils.Draw(arcO1); // JigUtils.Draw(arcO2); //求交 let intPts = arcO1.IntersectWith(arcO2, IntersectOption.ExtendBoth); if (intPts.length === 0) return; //无交点无法倒角 //两选择点的中点 let clickMidp = midPoint(enRes1.Point, enRes2.Point); //用来选择合适的交点 //选择合适的交点 intPts.sort((p1, p2) => { return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); }); //圆弧圆心 let narcCenter = intPts[0]; let narcP1 = a1.GetClosestPointTo(narcCenter, true); //两圆弧和相切弧的交点 let narcP2 = a2.GetClosestPointTo(narcCenter, true); let tempCircle = new Circle(narcCenter, this.FilletRadius); tempCircle.OCSNoClone.copy(a1.OCSNoClone).setPosition(narcCenter); let closestPt = a1.GetClosestPointTo(a2.Center, true); //两曲线距离对方圆心最近的点 let narcMP = tempCircle.GetClosestPointTo(closestPt, false); //相切圆距离closestPt最近的点 //构造圆弧 let narc = new Arc().ApplyMatrix(a1.OCS).FromThreePoint(narcP1, narcMP, narcP2); let a1Clone = a1.Clone(); let a2Clone = a2.Clone(); let a1Param = a1.GetParamAtPoint(narcP1); let a2Param = a2.GetParamAtPoint(narcP2); let a1Derv = a1.GetFirstDeriv(a1Param).normalize(); let a2Derv = a2.GetFirstDeriv(a2Param).normalize(); let narcDerv0 = narc.GetFirstDeriv(0).normalize(); let narcDerv1 = narc.GetFirstDeriv(1).normalize(); //裁剪圆弧 if (equalv3(a1Derv, narcDerv0)) a1Clone.EndPoint = narcP1; else a1Clone.StartPoint = narcP1; if (equalv3(a2Derv, narcDerv1)) a2Clone.StartPoint = narcP2; else a2Clone.EndPoint = narcP2; return { cu1: a1Clone, cu2: a2Clone, arc: narc }; } /** * 计算直线与圆弧没有交点(或相切)的情况下倒角结果 * @param enRes1 * @param enRes2 * @returns line and cir */ FilletLineAndArc(enMap, enRes1) { let lineRes = enMap[0][0]; let arcRes = enMap[1][0]; let line = lineRes.Entity; let arc = arcRes.Entity; let dir = GetPointAtCurveDir(line, arc.Center); let lineO = line.GetOffsetCurves(this.FilletRadius * dir)[0]; let arcO = arc.GetOffsetCurves(this.FilletRadius * (arc.IsClockWise ? -1 : 1))[0]; // tip:面积逆时针为正, 顺时针为负. // lineO.ColorIndex = 6; // arcO.ColorIndex = 6; // JigUtils.Draw(lineO); // JigUtils.Draw(arcO); //求交 let intPts = lineO.IntersectWith(arcO, IntersectOption.ExtendBoth); if (intPts.length === 0) return; //无交点无法倒角 //两选择点的中点 let clickMidp = midPoint(lineRes.Point, arcRes.Point); //选择适合的交点。 intPts.sort((p1, p2) => { return p1.distanceToSquared(clickMidp) - p2.distanceToSquared(clickMidp); }); //圆弧圆心 let arcCenter = intPts[0]; let arcP1 = line.GetClosestPointTo(arcCenter, true); //直线与相切圆的交点 let arcP2 = arc.GetClosestPointTo(arcCenter, true); //圆弧与相切圆的交点 let tempCircle = new Circle(arcCenter, this.FilletRadius); tempCircle.OCSNoClone.copy(arc.OCSNoClone).setPosition(arcCenter); let { closestPt, param } = line.GetClosestAtPoint(arc.Center, true); let arcMP = tempCircle.GetClosestPointTo(closestPt, false); //构造圆弧 let narc = new Arc().ApplyMatrix(arc.OCS).FromThreePoint(arcP1, arcMP, arcP2); //裁剪线 let lineClone = line.Clone(); let arcClone = arc.Clone(); let lineExtend; let p1Param = line.GetParamAtPoint(arcP1); if (p1Param > param) { lineClone.StartPoint = arcP1; lineExtend = ExtendType.Start; } else { lineClone.EndPoint = arcP1; lineExtend = ExtendType.End; } //裁剪圆弧 let arcParam = arc.GetParamAtPoint(arcP2); let arcDerv = arc.GetFirstDeriv(arcParam).normalize(); let narcDerv = narc.GetFirstDeriv(1).normalize(); let arcExtend; if (equalv3(arcDerv, narcDerv)) { arcClone.StartPoint = arcP2; arcExtend = ExtendType.Start; } else { arcClone.EndPoint = arcP2; arcExtend = ExtendType.End; } //先选直线为真 if (enRes1.Entity === line) return { cu1: lineClone, cu1Extend: lineExtend, cu2: arcClone, cu2Extend: arcExtend, arc: narc }; else return { cu1: arcClone, cu1Extend: arcExtend, cu2: lineClone, cu2Extend: lineExtend, arc: narc.Reverse() //#I3BWIA 避免起点和终点方向相反导致的多段线连接错误 }; } //获得两曲线的交点,并且排序交点. GetIntersectAndSort(enRes, enRes2, enType, enMap) { let interPts = enRes.Entity.IntersectWith(enRes2.Entity, IntersectOption.ExtendBoth); if (interPts.length > 1) { let baseP; if (enType & 1) //如果有直线,那么用直线 baseP = enMap[0][0].Point; else if (enType === 2) //如果都是圆弧,那么取中点 baseP = midPoint(enMap[1][0].Point, enMap[1][1].Point); interPts.sort((p1, p2) => p1.distanceToSquared(baseP) - p2.distanceToSquared(baseP)); } return interPts; } /** * 对图元列表进行按位编码,类型映射如下: * # 1:line 2:arc 4:polyline * @param enRes * @param enRes2 * @returns */ EnCode(enRes, enRes2) { let enMap = [[], [], [], [], []]; let enType = 0; enType |= Encode(enRes, enMap); enType |= Encode(enRes2, enMap); return { enType, enMap }; } //计算曲线在相交处的切线,取真实的切线 ComputerDerv(cuRes, dervSum) { let derv; let cu = cuRes.Curve; if (cuRes.ExtType === ExtendType.Start) { derv = cu.GetFirstDeriv(0).normalize(); dervSum.add(derv); } else { derv = cu.GetFirstDeriv(cu.EndParam).normalize(); dervSum.add(derv.clone().negate()); } return derv; } // 计算曲线在相交处的切线,取起点到终点的切线.(当曲线相切时调用此方法.) ComputerDerv2(cuRes, dervSum) { let cu = cuRes.Curve; let derv = cu.EndPoint.sub(cu.StartPoint); if (cuRes.ExtType === ExtendType.Start) dervSum.add(derv); else dervSum.add(derv.clone().negate()); return derv; } // 延伸或者裁剪到指定的圆弧的点. ExtendPt(cu, newP) { if (cu.ExtType === ExtendType.Start) cu.Curve.StartPoint = newP; else cu.Curve.EndPoint = newP; } /** * 切割或者延伸曲线,尖角化 * * @param cu 处理的曲线 * @param interPt 原先的相交点 * @param pickPoint 鼠标点击点 * @returns 返回新的曲线 */ SplitCurve(enRes, interPt, interPts) { let cu = enRes.Entity; let pickPoint = enRes.Point; let cp = cu.GetClosestPointTo(pickPoint, false); let cus = cu.GetSplitCurvesByPts([interPt]); if (cus.length === 0) cus.push(cu.Clone()); else if (cus.length === 2) cus.sort((c1, c2) => { return c1.GetClosestPointTo(cp, false).distanceTo(cp) < c2.GetClosestPointTo(cp, false).distanceTo(cp) ? -1 : 1; }); let exType = undefined; let newCu = cus[0]; if (newCu instanceof Line || newCu.constructor.name === "RoomWallLine") newCu.Extend(newCu.GetParamAtPoint(interPt)); //延伸到需要的长度 else if (newCu instanceof Arc || newCu.constructor.name === "RoomWallArc") { let arc = newCu; if (cus.length === 1) if (!cu.PtOnCurve(interPt)) { if (cu.PtOnCurve(interPts[1])) { //交点参数 let iparam = arc.GetParamAtPoint(interPts[1]); let pickParam = arc.GetParamAtAngle(arc.GetAngleAtPoint(pickPoint)); if (pickParam > iparam) { arc.EndAngle = arc.GetAngleAtPoint(interPt); exType = ExtendType.End; } else { arc.StartAngle = arc.GetAngleAtPoint(interPt); exType = ExtendType.Start; } } else { //终点,起点 interPts = interPts.sort((p1, p2) => { return arc.ComputeAnlge(arc.GetAngleAtPoint(p1)) - arc.ComputeAnlge(arc.GetAngleAtPoint(p2)); }); if (interPt === interPts[0]) { arc.EndAngle = arc.GetAngleAtPoint(interPt); exType = ExtendType.End; } else { arc.StartAngle = arc.GetAngleAtPoint(interPt); exType = ExtendType.Start; } } } } if (exType === undefined) { //使用equalv3时由于精度误差导致的判断错误 if (interPt.manhattanDistanceTo(newCu.StartPoint) < interPt.manhattanDistanceTo(newCu.EndPoint)) exType = ExtendType.Start; else exType = ExtendType.End; } return { Curve: newCu, ExtType: exType }; } } let TemplateFilletAction = class TemplateFilletAction extends TemplateAction { constructor() { super(); this.FilletDatas = []; } _Update(paramDiff, newValue) { for (let filletData of this.FilletDatas) { if (filletData.Entity?.IsErase !== false) continue; let br = filletData.Entity.Object; let update_bak = br.AutoUpdate; br.AutoUpdate = false; if (filletData.ArcParams.length > 0) this.Fillet(br, newValue, filletData); //内部造型 if (br.Grooves.length > 0 && filletData.Grooves?.length) { for (let data of filletData.Grooves) { const groove = br.Grooves[data.Index]; if (groove) { this.Fillet(groove, newValue, data); //槽中槽 if (data.Grooves) for (let sdata of data.Grooves) { let sGroove = groove.Grooves[sdata.Index]; if (sGroove) this.Fillet(sGroove, newValue, sdata); } } } } const path2d = br.Modeling2D; //二维刀路 if (path2d.length > 0 && filletData.Path2D?.length) { for (let data of filletData.Path2D) { let vm = path2d[data.Index]; if (vm) { this.Fillet(vm, newValue, data); br.Clear2DPathCache(); } } } //圆弧板放样路径倒角 if (filletData.ArcSweepPathParams?.length && br.IsArcBoard) { let faker = { path: br.GetSweepPath() }; if (Math.abs(newValue) < 0.1) newValue = 0.1; //如果此时我们不修正它,那么如果这个是反面,则会导致正面是18,继而导致正面是0,导致建模失败 for (let param of filletData.ArcSweepPathParams) { let radius = newValue; if (param < 0) //正面轮廓 { param = -param; let bul = faker.path.GetBulgeAt(Math.floor(param)); radius += br.Thickness * Math.sign(bul); } this.Fillet(faker, radius, { ArcParams: [param] }); } if (br.GetSweepPath() !== faker.path) { br.SetSweepPath(faker.path, br.SweepAngle); br.FixContourByArcSweepPath(); br.UpdateArcBoardOptions(false); } } br.AutoUpdate = update_bak; br.Update(UpdateDraw.Geometry); } } Fillet(br, newValue, d) { let cu = br instanceof ExtrudeSolid ? br.ContourCurve : br.path; if (cu instanceof Circle) return; let fillet = new FilletUtils(); fillet.FilletRadius = Math.max(Math.abs(newValue), 0.1); let cuOld = cu; for (let arcParam of d.ArcParams) { let param1 = FixIndex$1(Math.floor(arcParam) - 0.2, cu.EndParam); let param2 = FixIndex$1(Math.floor(arcParam) + 1.2, cu.EndParam); let p1 = cu.GetPointAtParam(param1); let p2 = cu.GetPointAtParam(param2); let res1 = new PromptEntityResult(cu, p1); let res2 = new PromptEntityResult(cu, p2); let fres = fillet.FilletPolyLineSelf(res1, res2); if (fres) { cu = fres.cu1; if (newValue < 0) cu.LineData[Math.floor(arcParam)].bul *= -1; } else { ToasterShowEntityMsg({ msg: `半径:${newValue} 倒角失败!请检查!`, timeout: 5000, intent: Intent.DANGER, ent: br }); } } if (br instanceof ExtrudeSolid) { if (cu !== cuOld) br.ContourCurve = cu; } else { if (cu !== cuOld) br.path = cu; } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.FilletDatas.length = 0; if (ver === 1) { let id = file.ReadObjectId(); let param1 = file.Read(); let param2 = file.Read(); let arcParam = param2 !== 0 ? param2 - 1 : param1 + 1; this.FilletDatas.push({ Entity: id, ArcParams: [arcParam] }); } else { let count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); let params = []; let parCount = file.Read(); for (let i = 0; i < parCount; i++) params.push(file.Read()); const groove = []; const path2d = []; if (ver >= 3) { const grooveCount = file.Read(); for (let i = 0; i < grooveCount; i++) { let data = { Index: undefined, ArcParams: [], }; data.Index = file.Read(); let parCount = file.Read(); for (let j = 0; j < parCount; j++) data.ArcParams.push(file.Read()); groove.push(data); if (ver > 3) { //槽中槽 data.Grooves = []; let subGrooveCount = file.Read(); for (let i = 0; i < subGrooveCount; i++) { let grooveIndex = file.Read(); let arcParamsCount = file.Read(); let arcParams = []; for (let j = 0; j < arcParamsCount; j++) arcParams.push(file.Read()); let subGrooveFilletData = { Index: grooveIndex, ArcParams: arcParams }; data.Grooves.push(subGrooveFilletData); } } } const path2DCount = file.Read(); for (let i = 0; i < path2DCount; i++) { let data = { Index: undefined, ArcParams: [], }; data.Index = file.Read(); let parCount = file.Read(); for (let j = 0; j < parCount; j++) data.ArcParams.push(file.Read()); path2d.push(data); } } const arcSweepPathParams = []; if (ver > 4) //ver 5 { let count = file.Read(); for (let i = 0; i < count; i++) arcSweepPathParams.push(file.Read()); } if (id) this.FilletDatas.push({ Entity: id, ArcParams: params, Grooves: groove, Path2D: path2d, ArcSweepPathParams: arcSweepPathParams }); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(5); super.WriteFile(file); file.Write(this.FilletDatas.length); for (let d of this.FilletDatas) { file.WriteObjectId(d.Entity); file.Write(d.ArcParams.length); for (let param of d.ArcParams) file.Write(param); //槽 file.Write(d.Grooves ? d.Grooves.length : 0); for (let data of (d.Grooves ?? [])) { file.Write(data.Index); file.Write(data.ArcParams.length); for (let par of data.ArcParams) file.Write(par); //槽中槽 file.Write(data.Grooves ? data.Grooves.length : 0); for (let sdata of (data.Grooves ?? [])) { file.Write(sdata.Index); file.Write(sdata.ArcParams.length); for (let par of sdata.ArcParams) file.Write(par); } //槽中槽end } //写入二维刀路 file.Write(d.Path2D ? d.Path2D.length : 0); for (let data of (d.Path2D ?? [])) { file.Write(data.Index); file.Write(data.ArcParams.length); for (let par of data.ArcParams) file.Write(par); } //写入圆弧板参数 file.Write(d.ArcSweepPathParams?.length ?? 0); if (d.ArcSweepPathParams) for (let param of d.ArcSweepPathParams) file.Write(param); } } }; TemplateFilletAction = __decorate([ Factory ], TemplateFilletAction); /** 精度的默认配置(用于避免切换到未配置过的账号时,配置被覆盖) */ const ARC_DRAW_CONFIG_DEFUALT = { ARC_SplitLength: 4, //圆的分段长度 ARC_RADIUS_MIN: 25, //大于半径25的自动优化成36等分圆 保证绘制光滑 Arc_MinSplitCount: 12, //圆的最小分段个数 ARC_MaxSplitCount: 180, //圆的最大分段个数 }; class UserConfig { constructor() { this._version = 53; //🌟🌟每次更新必须向上添加一次版本号🌟🌟 this.designer = ""; //一键布局的设计师 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.isNeedUpdataOption = false; //呼出标签命令时,强制切换配置 this._modeling2HoleRad = 20; //圆造型小于等于该值拆成孔数据 this.isAdmin = false; this.isMaster = false; this.rights = []; this.uese = ""; this.chat = {}; this.kjlConfig = { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "1", knifeRadius: "3", }; 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.dimensions = true; //显示板间尺寸 this.switchBackground = true; //切换背景 this.synchronousEnable = false; //同步模式状态 this.isLargeIcon = false; //右侧工具栏材质大图标模式 this.isModifyMaterial = true; //右侧工具栏应用时覆盖新的材质 this.isHighlightMaterial = true; //亮显材质球 this.cabinetSpacing = 1000; //柜子移动到0点配置 柜子间距 this.testModeSpacing = 200; //模拟走刀图形间距 this.ConceptualEdgeColor = 7; //概念线框的颜色 this.ConceptualOpacity = 1; //概念的透明度 this.ConceptualTransparentOpacity = 0.5; //概念(透明)的透明度 this.ConceptualOpacityUI = 0.5; // 概念(透明)程度 this.ConceptualColorUI = "原始颜色"; // 概念(透明)颜色 this.Physical2EdgeColor = 7; //真实视图带线框的线框颜色 默认白色 this.titleWidthMap = new Map(); //保存BBS输入框宽度,刷新后重置 this.autoSaveConfig = { enable: true, time: 1, }; this.showLines = false; this.showOpenDirLines = false; //显示开门方向纹路 this.doorLinesOption = { physicalShowLines: false, //真实视图显示开门方向纹路 physicalShowLines2: false, //真实视图带线框显示开门方向纹路 reverseOpenDirLines: false, //反转开门方向纹路 }; this.show2DPathLine = true; // 显示二维刀路路径线条 this.show2DPathObject = true; // 显示二维刀路 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, //统计双头排钻 drillRemark: false, //拆单获取排钻备注 checkSealType: "1", sealMaxValue: 0, //封边最大值(0不校验) sealValues: '', //封边值列表 hardwareExpressionFormattingAccuracy: 2, //复合实体表达式值格式化精度 partialSplitValueCanTakesEffect: false, //板件属性中填写局部拆单值可生效 xlineLength: 20000, //构造线长度默认使用20000 cancelHoleProcessing: false, //填写拆单尺寸板件取消孔槽加工 isCheckCustomBoardNumber: false, //是否开启自动板件编号校验 reservedEdgeCheckTip: true, //是否显示预留边检查提示 }; this.viewSize = { minViewHeight: 1e-3, maxViewHeight: 3e6, zoomSpeed: 0.6 }; this.splitParams = ARC_DRAW_CONFIG; this.autoLines = false; this.dimTextHeight = 60; this.boardCustomNumberTextHeight = 60; //自定义编号字体高度 this.performanceConfig = { fakersweep: false, disablerefcut: false, }; this.lineWidth = 2; //打印线框 this.forceFilterPxl = true; this.checkSealing = false; this.sealingColorMap = []; this.textStyleOption = { appointTextHight: false, noNeedAngle: false, }; this.showShareModule = false; this.openHistoryList = true; this.isOpenCabinet = false; //是否时开启门板、抽屉动作 this.lightHelper = false; //是否显示灯光助手 this.isShowSkyBox = false; //是否显示天空球,仅在透视相机下显示 this.openLightConfig = false; //画灯光前是否打开配置 this.oneKeyOpenLight = false; //一键开关灯光(通电/不通电) this.isShowLightShadow = false; //一键开关灯光阴影(除了太阳光) this.UCSGridConfig = { isShowUCSGrid: false, size: 2, divisions: 3, color1: '#C0C0C0', color2: '#808080', }; this.isOpenAxisSwitch = true; this.isOpenCameraSetting = true; this.dxfImportBlock = false; //DWG/DXF 导入时不导入块实体 this.dxfImportFrontView = false; //DWG/DXF 导入时插入前视图 this.printOption = { defaultPrint: true, //自动打开到打印界面 pageHeaderName: "", //页眉名称 pageFooterName: "", //页脚名称 printResolution: 1450, }; this.templateDisplayCount = 100; //插入模板时显示的最大个数 this.isShowAxesMatrix = true; //显示图元坐标轴线 this.commandInfoLongDisplay = false; //命令提示信息常显示 //铰链类型规则 this.hingeLidRule = { noLid: "5", allLid: "14" }; this.forceOpendirOrNameChange = false; //板名称与开门方向强制修改 this.throughModelSegmentedDrill = false; //挖穿造型分段排钻 this.brRelativePosChangeCount = true; //绘制层板时等分切换靠上靠下时数量变成1,立板背板同理 this.autoDeviation = false; //排钻自动偏移 this.autoDeviationMinDist = 200; //排钻自动偏移的最小排钻面 this.drillShortPrior = false; //排钻的碰撞面短的优先排钻 this.reservedEdgeCheckTip = true; //是否显示预留边检查提示 this.smallGrooveThreshold = 900; //全深槽影响阈值 this.isHardwareMaterial = false; //是否修改复合实体材质 this.setHardwareAttribute = false; // 是否修改复合实体属性 this.configName = "default"; this.configsNames = []; this.Init(); } Init() { let type = Number(globalThis.localStorage?.getItem(StoreageKeys.RenderType)); if (type && type !== RenderType.Edge && type !== RenderType.ModelGroove) this._renderType = type; globalThis.userconfig = this; } 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.openAutoCuttingReactor = true; Object.assign(this.maxSize, { height: 2440, width: 1220 }); Object.assign(this.kjlConfig, { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "1", knifeRadius: "3", }); Object.assign(this.chaidanOption, { changXiuBian: 6, duanXiuBian: 6, useDefaultRad: false, radius: 2.5, modeling2HoleRad: 20, noModeingData: "", hardwareExpressionFormattingAccuracy: 2, partialSplitValueCanTakesEffect: false, xlineLength: 20000, cancelHoleProcessing: false, isCheckCustomBoardNumber: false, reservedEdgeCheckTip: true, }); Object.assign(this.textStyleOption, { appointTextHight: false, needAngle: false, }); Object.assign(this.doorLinesOption, { physicalShowLines: false, //真实视图显示开门方向纹路 physicalShowLines2: false, //真实视图带线框显示开门方向纹路 reverseOpenDirLines: false, //反转开门方向纹路 }); this.dimTextHeight = 60; this.boardCustomNumberTextHeight = 60; this.smalliconmode = false; this.dimensions = true; this.switchBackground = false; this.synchronousEnable = false; this.isLargeIcon = false; this.isModifyMaterial = false; this.isHighlightMaterial = true; this.cabinetSpacing = 1000; this.ConceptualEdgeColor = 7; this.Physical2EdgeColor = 7; this.isShowSkyBox = false; Object.assign(this.UCSGridConfig, { isShowUCSGrid: true, size: 2, divisions: 3, color1: '#c0c0c0', color2: '#808080', }); this.isOpenAxisSwitch = true; this.isOpenCameraSetting = true; this.dxfImportBlock = false; this.titleWidthMap = new Map(); this.dxfImportFrontView = false; this.designer = ""; this.testModeSpacing = 50; Object.assign(this.printOption, { defaultPrint: true, pageHeaderName: "", pageFooterName: "", printResolution: 1450, }); this.templateDisplayCount = 100; Object.assign(this.hingeLidRule, { noLid: "5", allLid: "14" }); this.throughModelSegmentedDrill = false; this.brRelativePosChangeCount = true; this.autoDeviation = false; Object.assign(this.splitParams, ARC_DRAW_CONFIG_DEFUALT); ARC_DRAW_CONFIG.ARC_SplitLength = this.splitParams.ARC_SplitLength; ARC_DRAW_CONFIG.ARC_RADIUS_MIN = this.splitParams.ARC_RADIUS_MIN; ARC_DRAW_CONFIG.Arc_MinSplitCount = this.splitParams.Arc_MinSplitCount; ARC_DRAW_CONFIG.ARC_MaxSplitCount = this.splitParams.ARC_MaxSplitCount; this.drillShortPrior = false; this.autoDeviationMinDist = 200; this.isShowAxesMatrix = true; this.reservedEdgeCheckTip = true; this.smallGrooveThreshold = 900; this.commandInfoLongDisplay = false; this.ConceptualOpacityUI = 0.5; this.ConceptualColorUI = "原始颜色"; RenderState.ConceptualColor = ColorMap.get(this.ConceptualColorUI); if (this._renderType === RenderType.ConceptualTransparent) this.ConceptualTransparentOpacity = this.ConceptualOpacityUI; this.isHardwareMaterial = false; this.setHardwareAttribute = 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, dimensions: this.dimensions, switchBackground: this.switchBackground, openHistoryList: this.openHistoryList, synchronousEnable: this.synchronousEnable, isLargeIcon: this.isLargeIcon, isModifyMaterial: this.isModifyMaterial, isHighlightMaterial: this.isHighlightMaterial, cabinetSpacing: this.cabinetSpacing, ConceptualEdgeColor: this.ConceptualEdgeColor, Physical2EdgeColor: this.Physical2EdgeColor, isShowSkyBox: this.isShowSkyBox, openLightConfig: this.openLightConfig, UCSGridConfig: toJS(this.UCSGridConfig), isOpenAxisSwitch: this.isOpenAxisSwitch, isOpenCameraSetting: this.isOpenCameraSetting, dxfImportBlock: this.dxfImportBlock, titleWidthMap: Object.fromEntries(this.titleWidthMap.entries()), dxfImportFrontView: this.dxfImportFrontView, designer: this.designer, showOpenDirLines: this.showOpenDirLines, testModeSpacing: this.testModeSpacing, printOption: toJS(this.printOption), show2DPathLine: this.show2DPathLine, show2DPathObject: this.show2DPathObject, templateDisplayCount: this.templateDisplayCount, hingeLidRule: toJS(this.hingeLidRule), boardCustomNumberTextHeight: this.boardCustomNumberTextHeight, forceOpendirOrNameChange: this.forceOpendirOrNameChange, throughModelSegmentedDrill: this.throughModelSegmentedDrill, brRelativePosChangeCount: this.brRelativePosChangeCount, autoDeviation: this.autoDeviation, splitParams: toJS(this.splitParams), drillShortPrior: this.drillShortPrior, autoDeviationMinDist: this.autoDeviationMinDist, isShowAxesMatrix: this.isShowAxesMatrix, reservedEdgeCheckTip: this.reservedEdgeCheckTip, smallGrooveThreshold: this.smallGrooveThreshold, doorLinesOption: toJS(this.doorLinesOption), commandInfoLongDisplay: this.commandInfoLongDisplay, ConceptualOpacityUI: this.ConceptualOpacityUI, ConceptualColorUI: this.ConceptualColorUI, isHardwareMaterial: this.isHardwareMaterial, setHardwareAttribute: this.setHardwareAttribute, } }; } 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); } if (config.option.version > 16) { this.dimensions = config.option.dimensions; } if (config.option.version > 17) { this.switchBackground = config.option.switchBackground; } if (config.option.version > 18) { this.synchronousEnable = config.option.synchronousEnable; } if (config.option.version > 19) { this.isLargeIcon = config.option.isLargeIcon; } if (config.option.version > 20) { this.isModifyMaterial = config.option.isModifyMaterial; } if (config.option.version > 21) { this.openHistoryList = config.option.openHistoryList; } if (config.option.version > 22) { this.isShowSkyBox = config.option.isShowSkyBox; } if (config.option.version > 23) { this.openLightConfig = config.option.openLightConfig; } if (config.option.version > 24) { this.isHighlightMaterial = config.option.isHighlightMaterial; } if (config.option.version > 25) { Object.assign(this.UCSGridConfig, config.option.UCSGridConfig); this.isOpenAxisSwitch = config.option.isOpenAxisSwitch; this.isOpenCameraSetting = config.option.isOpenCameraSetting; } if (config.option.version > 26) { this.dxfImportBlock = config.option.dxfImportBlock; } if (config.option.version > 27) { this.cabinetSpacing = config.option.cabinetSpacing; } if (config.option.version > 28) { this.ConceptualEdgeColor = config.option.ConceptualEdgeColor; this.Physical2EdgeColor = config.option.Physical2EdgeColor; } if (config.option.version > 29) { this.titleWidthMap = new Map(Object.entries(config.option.titleWidthMap)); } if (config.option.version > 30) { this.dxfImportFrontView = config.option.dxfImportFrontView; } if (config.option.version > 31) { this.designer = config.option.designer; } if (config.option.version > 32) { this.showOpenDirLines = config.option.showOpenDirLines; } if (config.option.version > 33) { this.testModeSpacing = config.option.testModeSpacing; } if (config.option.version > 34) { Object.assign(this.printOption, config.option.printOption); } if (config.option.version > 35) { this.show2DPathLine = config.option.show2DPathLine; this.show2DPathObject = config.option.show2DPathObject; } if (config.option.version > 36) { this.templateDisplayCount = config.option.templateDisplayCount; } if (config.option.version > 37) { Object.assign(this.hingeLidRule, config.option.hingeLidRule); } if (config.option.version > 38) { this.boardCustomNumberTextHeight = config.option.boardCustomNumberTextHeight; } else this.boardCustomNumberTextHeight = 60; if (config.option.version > 39) { this.forceOpendirOrNameChange = config.option.forceOpendirOrNameChange; } if (config.option.version > 40) { this.throughModelSegmentedDrill = config.option.throughModelSegmentedDrill; } if (config.option.version > 41) { this.brRelativePosChangeCount = config.option.brRelativePosChangeCount; } if (config.option.version > 42) { this.autoDeviation = config.option.autoDeviation; } if (config.option.version > 43) { Object.assign(this.splitParams, config.option.splitParams); ARC_DRAW_CONFIG.ARC_SplitLength = this.splitParams.ARC_SplitLength; ARC_DRAW_CONFIG.ARC_RADIUS_MIN = this.splitParams.ARC_RADIUS_MIN; ARC_DRAW_CONFIG.Arc_MinSplitCount = this.splitParams.Arc_MinSplitCount; ARC_DRAW_CONFIG.ARC_MaxSplitCount = this.splitParams.ARC_MaxSplitCount; } if (config.option.version > 44) { this.drillShortPrior = config.option.drillShortPrior; } if (config.option.version > 45) { this.autoDeviationMinDist = config.option.autoDeviationMinDist; } if (config.option.version > 46) { this.isShowAxesMatrix = config.option.isShowAxesMatrix; } if (config.option.version > 47) { this.reservedEdgeCheckTip = config.option.reservedEdgeCheckTip; } if (config.option.version > 48) { this.smallGrooveThreshold = config.option.smallGrooveThreshold; } if (config.option.version > 49) { Object.assign(this.doorLinesOption, config.option.doorLinesOption); } if (config.option.version > 50) { this.commandInfoLongDisplay = config.option.commandInfoLongDisplay; } if (config.option.version > 51) { this.ConceptualOpacityUI = config.option.ConceptualOpacityUI; this.ConceptualColorUI = config.option.ConceptualColorUI; RenderState.ConceptualColor = ColorMap.get(this.ConceptualColorUI); if (this._renderType === RenderType.ConceptualTransparent) this.ConceptualTransparentOpacity = this.ConceptualOpacityUI; } if (config.option.version > 52) { this.isHardwareMaterial = config.option.isHardwareMaterial; this.setHardwareAttribute = config.option.setHardwareAttribute; } } } __decorate([ observable ], UserConfig.prototype, "designer", void 0); __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, "dimensions", void 0); __decorate([ observable ], UserConfig.prototype, "switchBackground", void 0); __decorate([ observable ], UserConfig.prototype, "synchronousEnable", void 0); __decorate([ observable ], UserConfig.prototype, "isLargeIcon", void 0); __decorate([ observable ], UserConfig.prototype, "isModifyMaterial", void 0); __decorate([ observable ], UserConfig.prototype, "isHighlightMaterial", void 0); __decorate([ observable ], UserConfig.prototype, "cabinetSpacing", void 0); __decorate([ observable ], UserConfig.prototype, "testModeSpacing", void 0); __decorate([ observable ], UserConfig.prototype, "ConceptualEdgeColor", void 0); __decorate([ observable ], UserConfig.prototype, "ConceptualOpacityUI", void 0); __decorate([ observable ], UserConfig.prototype, "ConceptualColorUI", void 0); __decorate([ observable ], UserConfig.prototype, "Physical2EdgeColor", void 0); __decorate([ observable ], UserConfig.prototype, "titleWidthMap", void 0); __decorate([ observable ], UserConfig.prototype, "autoSaveConfig", void 0); __decorate([ observable ], UserConfig.prototype, "showLines", void 0); __decorate([ observable ], UserConfig.prototype, "showOpenDirLines", void 0); __decorate([ observable ], UserConfig.prototype, "doorLinesOption", void 0); __decorate([ observable ], UserConfig.prototype, "show2DPathLine", void 0); __decorate([ observable ], UserConfig.prototype, "show2DPathObject", 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, "splitParams", void 0); __decorate([ observable ], UserConfig.prototype, "autoLines", void 0); __decorate([ observable ], UserConfig.prototype, "dimTextHeight", void 0); __decorate([ observable ], UserConfig.prototype, "boardCustomNumberTextHeight", void 0); __decorate([ observable ], UserConfig.prototype, "performanceConfig", void 0); __decorate([ observable ], UserConfig.prototype, "forceFilterPxl", void 0); __decorate([ observable ], UserConfig.prototype, "textStyleOption", void 0); __decorate([ observable ], UserConfig.prototype, "openHistoryList", void 0); __decorate([ observable ], UserConfig.prototype, "lightHelper", void 0); __decorate([ observable ], UserConfig.prototype, "isShowSkyBox", void 0); __decorate([ observable ], UserConfig.prototype, "openLightConfig", void 0); __decorate([ observable ], UserConfig.prototype, "oneKeyOpenLight", void 0); __decorate([ observable ], UserConfig.prototype, "isShowLightShadow", void 0); __decorate([ observable ], UserConfig.prototype, "UCSGridConfig", void 0); __decorate([ observable ], UserConfig.prototype, "isOpenAxisSwitch", void 0); __decorate([ observable ], UserConfig.prototype, "isOpenCameraSetting", void 0); __decorate([ observable ], UserConfig.prototype, "dxfImportBlock", void 0); __decorate([ observable ], UserConfig.prototype, "dxfImportFrontView", void 0); __decorate([ observable ], UserConfig.prototype, "printOption", void 0); __decorate([ observable ], UserConfig.prototype, "templateDisplayCount", void 0); __decorate([ observable ], UserConfig.prototype, "isShowAxesMatrix", void 0); __decorate([ observable ], UserConfig.prototype, "commandInfoLongDisplay", void 0); __decorate([ observable ], UserConfig.prototype, "hingeLidRule", void 0); __decorate([ observable ], UserConfig.prototype, "forceOpendirOrNameChange", void 0); __decorate([ observable ], UserConfig.prototype, "throughModelSegmentedDrill", void 0); __decorate([ observable ], UserConfig.prototype, "brRelativePosChangeCount", void 0); __decorate([ observable ], UserConfig.prototype, "autoDeviation", void 0); __decorate([ observable ], UserConfig.prototype, "autoDeviationMinDist", void 0); __decorate([ observable ], UserConfig.prototype, "drillShortPrior", void 0); __decorate([ observable ], UserConfig.prototype, "reservedEdgeCheckTip", void 0); __decorate([ observable ], UserConfig.prototype, "smallGrooveThreshold", void 0); __decorate([ observable ], UserConfig.prototype, "isHardwareMaterial", void 0); __decorate([ observable ], UserConfig.prototype, "setHardwareAttribute", void 0); new UserConfig(); function ApplyGoodInfo(en, material) { //切换材质时,材质锁定后,材料信息不跟随修改 if (en.Material != material.Id && en.Material?.Object?.IsMaterialLock) return; en.BoardProcessOption[EBoardKeyList.BrMat] = material.GoodsInfo.name; en.BoardProcessOption[EBoardKeyList.Color] = material.GoodsInfo.color; en.BoardProcessOption[EBoardKeyList.Mat] = material.GoodsInfo.material; } let TemplateMaterialAction = class TemplateMaterialAction extends TemplateAction { constructor(Entitys = [], CompositeEntitys = [], ApplyGoodInfo = true) { super(); this.Entitys = Entitys; this.CompositeEntitys = CompositeEntitys; this.ApplyGoodInfo = ApplyGoodInfo; } _Update(paramDiff) { if (!this.parent.MaterialValue) return; let hasMaterialLock = false; for (let id of this.Entitys) { if (!(id?.Object) || id.IsErase) continue; let en = id.Object; if (this.ApplyGoodInfo && en instanceof Board) ApplyGoodInfo(en, this.parent.MaterialValue); const lockStatus = en.GetMtlLockedStatus(); if (lockStatus.partMtlLocked || lockStatus.allMtlLocked || en.LockMaterial) hasMaterialLock = true; if (!(lockStatus.allMtlLocked || en.LockMaterial)) // 如果全部材质被锁定或者有实体材质锁,则不修改 en.SetAllMaterialAtSlot(this.parent.MaterialValue.Id); } for (let [id, indexs] of this.CompositeEntitys) { if (!(id?.Object) || id.IsErase) continue; let en = id.Object; if (en.LockMaterial) { hasMaterialLock = true; continue; } let allEntitys = []; const GetAllEntitys = (hard) => { for (let e of hard.Entitys) { if (e instanceof HardwareCompositeEntity) GetAllEntitys(e); else allEntitys.push(e); } }; GetAllEntitys(en); for (let index of indexs) { let subE = allEntitys[index]; if (!subE) continue; if (this.ApplyGoodInfo && subE instanceof Board) ApplyGoodInfo(subE, this.parent.MaterialValue); const lockStatus = subE.GetMtlLockedStatus(); if (lockStatus.partMtlLocked || lockStatus.allMtlLocked || subE.LockMaterial) hasMaterialLock = true; if (!(lockStatus.allMtlLocked || subE.LockMaterial)) // 如果全部材质被锁定或者有实体材质锁,则不修改 subE.SetAllMaterialAtSlot(this.parent.MaterialValue.Id); } } if (hasMaterialLock) { Toaster({ message: "注意:有被锁定的材质,无法修改", intent: Intent.WARNING, timeout: 3000 }); } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.ApplyGoodInfo = file.Read() === 1; this.Entitys.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Entitys.push(id); } if (ver > 1) { count = file.Read(); this.CompositeEntitys.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); let indexs = file.Read(); this.CompositeEntitys.push([id, indexs]); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); super.WriteFile(file); file.Write(this.ApplyGoodInfo ? 1 : 0); file.Write(this.Entitys.length); for (let ent of this.Entitys) file.WriteObjectId(ent); file.Write(this.CompositeEntitys.length); for (let [id, indexs] of this.CompositeEntitys) { file.WriteObjectId(id); file.Write(indexs.concat()); } } }; TemplateMaterialAction = __decorate([ Factory ], TemplateMaterialAction); /** * 模版参数类型 */ var TemplateParamType; (function (TemplateParamType) { TemplateParamType[TemplateParamType["String"] = 0] = "String"; TemplateParamType[TemplateParamType["Float"] = 1] = "Float"; TemplateParamType[TemplateParamType["Int"] = 2] = "Int"; TemplateParamType[TemplateParamType["Enum"] = 3] = "Enum"; TemplateParamType[TemplateParamType["Material"] = 4] = "Material"; })(TemplateParamType || (TemplateParamType = {})); /** * 模版参数 */ let TemplateParam = class TemplateParam { constructor() { /** 表达式 使用js引起求值(暂时) */ this.expr = ""; this.type = TemplateParamType.Float; this.isLock = false; //监听 this.actions = new Proxy([], { set: (target, key, value, receiver) => { if (Reflect.get(target, key, receiver) !== value) { this.WriteAllObjectRecord(); if (value instanceof TemplateAction) value.parent = this; } return Reflect.set(target, key, value, receiver); }, get: (target, key, receiver) => { if (key === ISPROXYKEY) return true; else return Reflect.get(target, key, receiver); } }); } WriteAllObjectRecord() { if (this.parent) this.parent.WriteAllObjectRecord(); } /** * private:仅供内部调用. * 更新参数值,并且触发动作. */ UpdateParam(value) { switch (this.type) { case TemplateParamType.String: break; case TemplateParamType.Float: let oldV = this.value; let newV = value; if (!equaln$1(oldV, newV)) { this.WriteAllObjectRecord(); this.value = newV; let diff = newV - oldV; for (let a of this.actions) a.Update(diff, newV); } else { for (let a of this.actions) { if (a instanceof TemplateFilletAction) a.Update(0, newV); } } break; case TemplateParamType.Int: break; case TemplateParamType.Enum: break; case TemplateParamType.Material: { if (this.MaterialValue) { for (let a of this.actions) a.Update(0, 0); } this.MaterialValue = undefined; break; } } } /** * 计算表达式的值并更新 * @param vardefines 变量定义列表 * @param paramMap 所有的参数列表.(可能我们需要依赖更新它) */ EvalUpdate(vardefines, paramMap, evaled, update = true) { if (this.type === TemplateParamType.Material) { if (update && this.MaterialValue) { for (let a of this.actions) { if (a instanceof TemplateMaterialAction) a.Update(0, this.value); } this.MaterialValue = undefined; } return 0; } if (this.expr === "") { if (update) { for (let a of this.actions) { if (a instanceof TemplateFilletAction) a.Update(0, this.value); } } return this.value; } if (evaled.has(this)) return this.value; if (update) evaled.add(this); let value = parseFloat(this.expr); if (isNaN(this.expr)) { //依赖收集 提前更新 let keywords = new Set(this.expr.split(/[\s(){}=+-/*/,%;<>&?:]/).filter(s => s.length > 0)); for (let key of keywords) { if (key !== this.name && paramMap.has(key)) vardefines[key] = paramMap.get(key).EvalUpdate(vardefines, paramMap, evaled, update); } try { value = eval2(this.expr, vardefines); if (typeof value === "boolean") value = value ? 1 : 0; else if (typeof value !== "number") throw "计算结果不是实数!"; } catch (error) { Toaster({ message: "有模块的参数表达式求值失败!请查看左下角信息!", intent: Intent.DANGER, timeout: 5000, key: "template_expr_error", }); InteractionLog([{ msg: `模块变量:${this.name}的表达式:${this.expr},求值失败!` + error }], LogType.Error); return this.value; } } else if (update) this.expr = ""; vardefines[this.name] = value; if (update) this.UpdateParam(value); return value; } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); this.name = file.Read(); this.expr = file.Read(); this.value = file.Read(); this.default = file.Read(); this.description = file.Read(); this.type = file.Read(); this.min = file.Read(); this.max = file.Read(); this.option = file.Read(); let count = file.Read(); this.actions.length = 0; for (let i = 0; i < count; i++) this.actions.push(file.ReadObject()); if (ver > 1) this.isLock = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); file.Write(this.name); file.Write(this.expr); file.Write(this.value); file.Write(this.default); file.Write(this.description); file.Write(this.type); file.Write(this.min); file.Write(this.max); file.Write(this.option); file.Write(this.actions.length); for (let action of this.actions) file.WriteObject(action); file.Write(this.isLock); } }; __decorate([ AutoRecord ], TemplateParam.prototype, "name", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "expr", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "value", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "default", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "description", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "type", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "min", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "max", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "option", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "actions", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "parent", void 0); __decorate([ AutoRecord ], TemplateParam.prototype, "isLock", void 0); TemplateParam = __decorate([ Factory ], TemplateParam); const BoardType2SplitType = [2, 0, 1]; //转换板件类型成为空间类型. 0x 1y 2z function ConverBoardTypeToSplitType(type) { return BoardType2SplitType[type]; } /** * 夹层空间分析 */ class ClampSpaceParse extends ISpaceParse { constructor() { super(...arguments); this.SelectBoxRecord = new Map(); } async Parse() { if (this.Boards.length === 1) { await this.ParseSignalBoard(); return; } //夹层空间 let clampBoxs = []; //单层空间(用于切割) let spliteBoxs = new Map(); for (let [boardType, boards] of this.BoardMap) { let splitType = ConverBoardTypeToSplitType(boardType); let boardBoxCol = this.ParseBoardBox(boards, splitType); //#IWFYY if (boardType === BoardType.Behind && this.BoardMap.size > 1 && boardBoxCol.length > 1) { let clampBox = boardBoxCol[0].clampSpace(boardBoxCol[1], splitType); let size = clampBox.getSize(new Vector3()); if (size.y > 2440) boardBoxCol = [arrayLast(boardBoxCol)]; } if (boardBoxCol.length > 1) { let clampBox = boardBoxCol[0].clampSpace(arrayLast(boardBoxCol), splitType); if (clampBox.isSolid()) clampBoxs.push(clampBox); } else if (boardBoxCol.length === 1) { spliteBoxs.set(splitType, boardBoxCol[0]); } } //归并盒子 let allSpaceBox; if (clampBoxs.length === 0) //如果不存在盒子,拿所有的盒子当空间 { allSpaceBox = new Box3Ext(); spliteBoxs.forEach((box) => { allSpaceBox.union(box); }); } else //夹层空间合并 { allSpaceBox = clampBoxs[0]; for (let i = 1, len = clampBoxs.length; i < len; i++) allSpaceBox.intersect(clampBoxs[i]); } //切割并选择合适的空间 await this.SpliteBoxsAndSelect(allSpaceBox, spliteBoxs); if (this.SpaceBox && this.SpaceBox.isSolid()) { //空间延伸到背板 let behindBox = spliteBoxs.get(SplitType.Y); if (behindBox && behindBox.min.y > this.SpaceBox.min.y) this.SpaceBox.max.setY(behindBox.min.y); this.ParseOK = true; } } /** * 单板延伸空间的时候的延伸距离 */ get SignalDist() { return this._signalDist; } async ParseSignalBoard() { let res = await this.GetSignalDist(); if (res.Status === PromptStatus.OK && res.Distance > 0) { let dist = res.Distance; this._signalDist = dist; let br = this.Boards[0]; let box = br.GetBoundingBoxInMtx(this.SpaceOCSInv); let type = this.GetBoardInSpaceType(br); if (type === undefined) //暂时不支持斜空间 { //暂时不支持 this.ParseOK = false; return; } let splitType = ConverBoardTypeToSplitType(type); let p1 = box.min.clone().setComponent(splitType, box.min.getComponent(splitType) - dist); let p2 = box.max.clone().setComponent(splitType, box.min.getComponent(splitType)); let p3 = box.min.clone().setComponent(splitType, box.max.getComponent(splitType)); let p4 = box.max.clone().setComponent(splitType, box.max.getComponent(splitType) + dist); let boxs = [new Box3Ext().setFromPoints([p1, p2]), new Box3Ext().setFromPoints([p3, p4])]; this.SpaceBox = await this.WrapSelectBox(boxs, splitType); if (this.SpaceBox) this.ParseOK = true; } else this.ParseOK = false; } //virtual (请重载) 指定用户选择单块板的延伸空间 async GetSignalDist() { let res = new PromptDistendResult(); res.Distance = 300; res.Status = PromptStatus.OK; return res; } SetRay(ray) { } async WrapSelectBox(splitBoxs, splitType) { let box = await this.SelectBox(splitBoxs, splitType); this.SelectBoxRecord.set(splitType, splitBoxs.indexOf(box)); return box; } /** * virtual (请重载) 当盒子空间被切割时,选择合适的空间 * @param splitBoxs 切割后的盒子(2个) * @param splitType 切割类型 * @returns box 盒子 */ async SelectBox(splitBoxs, splitType) { return splitBoxs[0]; } /** *用单块板包围盒切割空间 */ async SpliteBoxsAndSelect(orgBox, spliteBoxes) { this.SpaceBox = undefined; if (spliteBoxes.size === 0) { this.SpaceBox = orgBox; return; } for (let [splitType, spBox] of spliteBoxes) { let remBoxs = orgBox.substract(spBox, splitType); if (remBoxs[0] === orgBox) //如果切割失败,证明这个板没办法影响空间分析,所以移除它. ; if (remBoxs.length === 0) return undefined; else if (remBoxs.length === 1) { //#IZE2N if (splitType === SplitType.Y && remBoxs[0].min.y === orgBox.min.y) continue; orgBox = remBoxs[0]; } else orgBox = await this.WrapSelectBox(remBoxs, splitType); if (!orgBox) return; //Left Or Right Board if (splitType === SplitType.X) { if (spBox.min.x < orgBox.min.x) this.LeftBoard = this.BoardMap.get(BoardType.Vertical)[0]; else this.RightBoard = this.BoardMap.get(BoardType.Vertical)[0]; } } this.SpaceBox = orgBox; } } class ClampSpaceParseFix extends ClampSpaceParse { set SignalDist(v) { this._signalDist = v; } get SignalDist() { return this._signalDist; } //virtual (请重载) 指定用户选择单块板的延伸空间 async GetSignalDist() { let res = new PromptDistendResult(); res.Distance = this._signalDist; res.Status = PromptStatus.OK; return res; } async SelectBox(splitBoxs, splitType) { let index = this.SelectBoxRecord.get(splitType); return splitBoxs[index]; } } /** * 模版定位信息(基类) */ let Positioning = class Positioning { /** * 定位 (更新 SpaceCS SpaceBox SpaceSize) */ async Positioning(param) { } WriteAllObjectRecord() { if (this.parent) this.parent.WriteAllObjectRecord(); } ReadFile(file) { } WriteFile(file) { } }; Positioning = __decorate([ Factory ], Positioning); let PositioningClampSpace = class PositioningClampSpace extends Positioning { constructor() { super(...arguments); this.Objects = []; //按照 SplitType进行排序 0X 1Y 2Z this.SelectBoxIndex = [0, 0, 0]; //左右下 this.SignalDist = 100; //默认为100 防止空 //#endregion } FromSpaceParse(parse) { this.SignalDist = parse.SignalDist || 100; this.Objects = parse.Boards.map(br => br.Id); for (let [splitType, index] of parse.SelectBoxRecord) this.SelectBoxIndex[splitType] = index; } /** * 定位 */ async Positioning(param) { let brs = this.Objects.filter(id => !id.IsErase).map(id => id.Object); this._SpaceParse = new ClampSpaceParseFix(brs); for (let i = 0; i < 3; i++) this._SpaceParse.SelectBoxRecord.set(i, this.SelectBoxIndex[i]); this._SpaceParse.SignalDist = this.SignalDist; await this._SpaceParse.Parse(); if (this._SpaceParse.ParseOK) { this.SpaceCS = this._SpaceParse.DrawCS; this.SpaceSize = this._SpaceParse.Size; } else { this.SpaceCS = undefined; this.SpaceSize = undefined; // Log("模块定位错误!"); } } //#region File ReadFile(file) { file.Read(); let count = file.Read(); this.Objects.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Objects.push(id); } this.SelectBoxIndex = file.Read(); this.SignalDist = file.Read(); } WriteFile(file) { file.Write(1); file.Write(this.Objects.length); for (let id of this.Objects) file.WriteObjectId(id); file.Write(arrayClone(this.SelectBoxIndex)); file.Write(this.SignalDist); } }; __decorate([ AutoRecord ], PositioningClampSpace.prototype, "Objects", void 0); __decorate([ AutoRecord ], PositioningClampSpace.prototype, "SelectBoxIndex", void 0); __decorate([ AutoRecord ], PositioningClampSpace.prototype, "SignalDist", void 0); PositioningClampSpace = __decorate([ Factory ], PositioningClampSpace); /** * 临时定位 * 在首次绘制时使用2点3点空间时通常不能使用定位,使用临时定位设置给模块后,模块在第一次使用后清空定位. */ let PositioningTemporary = class PositioningTemporary extends Positioning { }; PositioningTemporary = __decorate([ Factory ], PositioningTemporary); /** * 模版类型 */ var TemplateType; (function (TemplateType) { //普通模板 TemplateType[TemplateType["Usual"] = 0] = "Usual"; //酒格 使用程序 TemplateType[TemplateType["Grid"] = 1] = "Grid"; //阵列模版 TemplateType[TemplateType["Array"] = 2] = "Array"; //没有实体的 TemplateType[TemplateType["Clear"] = 4] = "Clear"; //展开的 TemplateType[TemplateType["Expanded"] = 8] = "Expanded"; //隐藏的 TemplateType[TemplateType["Hidden"] = 16] = "Hidden"; TemplateType[TemplateType["HiddenChildren"] = 32] = "HiddenChildren"; //标记 TemplateType[TemplateType["Sign"] = 64] = "Sign"; //以下未启用 TemplateType[TemplateType["Door"] = 128] = "Door"; TemplateType[TemplateType["Drawer"] = 256] = "Drawer"; TemplateType[TemplateType["Handle"] = 512] = "Handle"; TemplateType[TemplateType["Hinge"] = 1024] = "Hinge"; })(TemplateType || (TemplateType = {})); //以下未启用 var TemplateType2; (function (TemplateType2) { TemplateType2[TemplateType2["Usual"] = 0] = "Usual"; TemplateType2[TemplateType2["Door"] = 1] = "Door"; TemplateType2[TemplateType2["Drawer"] = 2] = "Drawer"; TemplateType2[TemplateType2["Handle"] = 3] = "Handle"; TemplateType2[TemplateType2["Hinge"] = 4] = "Hinge"; TemplateType2[TemplateType2["WineRack"] = 5] = "WineRack"; TemplateType2[TemplateType2["Grid"] = 6] = "Grid"; })(TemplateType2 || (TemplateType2 = {})); var TemplateSplitType; (function (TemplateSplitType) { TemplateSplitType[TemplateSplitType["None"] = -1] = "None"; TemplateSplitType[TemplateSplitType["X"] = 0] = "X"; TemplateSplitType[TemplateSplitType["Y"] = 1] = "Y"; TemplateSplitType[TemplateSplitType["Z"] = 2] = "Z"; })(TemplateSplitType || (TemplateSplitType = {})); var TemplateRecord_1; const TemplateDefaultParams = ["L", "W", "H", "PX", "PY", "PZ", "RX", "RY", "RZ", "BH"]; const TempateDefaultParamCount = TemplateDefaultParams.length; /** * ### 模板记录 * 模版与实体总是互相关联的,所以添加实体进入模版的时候,应该保证这个记录已经加入到数据库 * * 保留参数名称列表: L W H RX RY RZ X Y Z * 保留参数前缀: _ $ * * #### 批量修改参数值. * 程序应该只传入expr的值,(禁止直接编辑value).传入后,交由模版进行计算更新. * 模版内部消化,决定是否保留expr.(当纯数字将被直接计算成value而不保存expr). * * 由于`DIV`变量的计算机制,`LWH`变量将不能依赖同层变量,(这可能会导致一些错误) * * 当模块节点处于切割空间内时,如果想重新绑定空间,那么空间树的位置将发生变更(实际上不管是不是在切割空间内,空间位置都可能发生变更) * */ let TemplateRecord = TemplateRecord_1 = class TemplateRecord extends SymbolTableRecord { constructor() { super(); this.Type = TemplateType.Usual; this.SplitType = TemplateSplitType.None; //展开的 这是UI数据我们暂时不序列化它(默认不展开有比较好的显示效果) this.isExpanded = false; //监听 this.Params = this.CreateProxyArray(value => { if (value instanceof TemplateParam) value.parent = this; }); this.Objects = this.CreateProxyArray(value => { if (value instanceof ObjectId && value.Object instanceof Entity) { if (!this.Id) console.warn("请先将模版添加到Database后在进行操作!"); value.Object.Template = this.Id; } }); this.Children = this.CreateProxyArray(value => { if (value instanceof ObjectId && value.Object instanceof TemplateRecord_1) { if (!this.Id) console.warn("请先将模版添加到Database后在进行操作!"); value.Object.Parent = this.Id; } }); this.EffectTemplates = this.CreateProxyArray((v => { })); } get Name() { return this.name; } set Name(name) { if (name !== this.name) { this.WriteAllObjectRecord(); this.name = name; } } get Parent() { return this._Parent; } set Parent(id) { if (id !== this._Parent) { this.WriteAllObjectRecord(); if (this?._Parent?.Object) arrayRemoveOnce((this.Parent.Object).Children, this.Id); this._Parent = id; } } get Root() { return this.Parent?.Object?.Root ?? this; } get IsRoot() { return this._Parent === undefined; } get Entitys() { return this.Objects.map(o => o.Object); } get AllEntitys() { let entitys = []; this.Traverse((t) => { for (let o of t.Objects) { if (!o.IsErase) entitys.push(o.Object); } }); return entitys; } GetProperty(p) { return (this.Type & p) !== 0; } SetProperty(p, v) { if (this.GetProperty(p) === v) return; if (v) this.Type |= p; else this.Type &= ~p; } get IsClear() { return this.GetProperty(TemplateType.Clear); } set IsClear(isClear) { this.SetProperty(TemplateType.Clear, isClear); } get IsSign() { return this.GetProperty(TemplateType.Sign); } set IsSign(isSign) { this.SetProperty(TemplateType.Sign, isSign); } get IsHidden() { return this.GetProperty(TemplateType.Hidden); } set IsHidden(isHidden) { this.SetProperty(TemplateType.Hidden, isHidden); } get IsHiddenChildren() { return this.GetProperty(TemplateType.HiddenChildren); } set IsHiddenChildren(isHidden) { this.SetProperty(TemplateType.HiddenChildren, isHidden); } Purge() { this.Children = this.Children.filter(rc => rc && !rc.IsErase && rc.Object instanceof TemplateRecord_1); this.Objects = this.Objects.filter(id => id?.IsErase === false); } Traverse(callback) { callback(this); for (let c of this.Children) { if (c && c.Object) { if (c.Object instanceof TemplateRecord_1) { let template = c.Object; template.Traverse(callback); } else { Toaster({ message: "模块子实体有个错误,程序已经暂时先忽略了这个错误!", timeout: 8000, intent: Intent.DANGER, }); } } } } async TraverseAsync(callback) { await callback(this); for (let c of this.Children) { if (c && c.Object) { let template = c.Object; await template.TraverseAsync(callback); } } } /** 节点深度,根节点=0 */ get NodeDepth() { if (this._NodeDepthCache !== undefined) return this._NodeDepthCache; if (!this.Parent?.Object) return 0; let parentTemplate = this.Parent.Object; this._NodeDepthCache = parentTemplate.NodeDepth + 1; return this._NodeDepthCache; } get NodeIndex() { if (!this.Parent?.Object) return 0; let parentTemplate = this.Parent.Object; return parentTemplate.Children.indexOf(this.objectId); } /** 模版定位 */ get Positioning() { if (this._Positioning) return this._Positioning; let spaceCS = this.GetTemplateRealitySpaceCS(); let positioning = new PositioningTemporary(); positioning.SpaceCS = spaceCS; positioning.SpaceSize = new Vector3(this.LParam.value, this.WParam.value, this.HParam.value); return positioning; } /** * 当存在夹层空间定位时,辅助定位表达式将使用夹层空间作为相对空间. */ set Positioning(p) { this.WriteAllObjectRecord(); if (p) p.parent = this; this._Positioning = p; } //#region param /** 初始化基础参数 */ InitBaseParams() { for (let paramName of TemplateDefaultParams) { let value = 0; let param = new TemplateParam(); param.name = paramName; param.type = TemplateParamType.Float; param.value = value; this.Params.push(param); } this.LParam.description = "宽"; this.WParam.description = "深"; this.HParam.description = "高"; this.Params[9].description = "板厚"; this.Params[9].value = 18; return this; } get LParam() { return this.Params[0]; } get WParam() { return this.Params[1]; } get HParam() { return this.Params[2]; } get PXParam() { return this.Params[3]; } get PYParam() { return this.Params[4]; } get PZParam() { return this.Params[5]; } get RXParam() { return this.Params[6]; } get RYParam() { return this.Params[7]; } get RZParam() { return this.Params[8]; } GetParam(paramName) { return this.Params.find(param => param.name === paramName); } SetParamExpr(paramName, expr) { let param = this.GetParam(paramName); if (param) param.expr = expr; } DeleteParam(paramName) { let index = this.Params.findIndex(p => p.name === paramName); if (index !== -1 && index >= TempateDefaultParamCount) //LWH P R 禁止删除 this.Params.splice(index, 1); return this; } //#endregion param /** * 通常UI操作的时候,都需要更新整个树,所以隐藏这个API. * see `UpdateTemplateTree` * * 更新当前节点 * * ### 定位 (大小,方位) * * - (放弃)如果当前节点是更新树的最高层(但当前节点不是根节点) * 那么当存在夹层空间定位的时候,可以不重复进行夹层空间定位,因为此时该空间不会发生变化. * 补充:如果夹层空间的板件都在上层,那么可以做这个优化,如果定位的板件没在模块中,那么不能进行这个优化. * * - [更新优先]在没有实现变量依赖收集(类似mobx)时,我们认为`positioning`的优先级最高. * 所以`positioning`会被优先更新. `LWH`,`XYZ`. * * - 在使用变量定位时,需要传入上层的坐标系,以便进行相对定位. * 使用空间分析时,已经不需要上层坐标系. * * - 辅助定位:帮助空间坐标系进行旋转 * 辅助定位的参数变量将暴露出来.{RX RY RZ} * 辅助定位作为参数变量时,用户使用值时很难正确的计算值,应~加入交互选择.(辅助计算) * * ### 变量表达式计算 * * - `LWH`将被`positioning`替代,但变量定义仍然正常存在. * * - 变量大部分时候都是被批量更新,(同时传入许多参数). * */ async Update() { this._CacheParamVars = this.GetParameterDefinition(false); let ens = this.Objects.filter(id => !id.IsErase).map(id => id.Object); let evaled = new Set(); this._CacheSpaceCS = this.GetTemplateSpaceCS(false); let paramMap = new Map(); for (let param of this.Params) paramMap.set(param.name, param); if (this._Positioning) { await this._Positioning.Positioning(); if (!this._Positioning.SpaceCS) { //退化成个体坐标系 if (ens.length) { this._Positioning.SpaceCS = ens[0].SpaceOCS; this._Positioning.SpaceSize = new Vector3(this.LParam.value, this.WParam.value, this.HParam.value); } else { Log(`模块:(${this.name})定位错误!`, LogType.Error); return; //出事故 } } } for (let en of ens) { en.ApplyMatrix(en.SpaceOCSInv); if (en instanceof Board) en.IsLazyGrooveCheck = true; } //#region 1.定位(坐标系和大小) /** * LWH在存在定位空间和继承空间时的表达式行为不一致. * - 在存在定位空间的时候,LWH是修改定位空间旋转后的值. * - 不存在定位空间的时候,修改的是空间旋转前的值,因为此时已经没有空间尺寸可供旋转了, * 只能先提供空间尺寸,然后才进行旋转. */ if (this._Positioning) { this._CacheSpaceCS = this._Positioning.SpaceCS; this._CacheSpaceSize = this._Positioning.SpaceSize; this.RotateSpaceCS(paramMap, evaled); if (this.LParam.expr) this._CacheSpaceSize.x = this.LParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); if (this.WParam.expr) this._CacheSpaceSize.y = this.WParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); if (this.HParam.expr) this._CacheSpaceSize.z = this.HParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); if (this._Positioning instanceof PositioningTemporary) this._Positioning = undefined; } else { let l = this.LParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); let w = this.WParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); let h = this.HParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); this._CacheSpaceSize = new Vector3(l, w, h); this.RotateSpaceCS(paramMap, evaled); if (!this.Parent) { this.PXParam.value = 0; this.PYParam.value = 0; this.PZParam.value = 0; this.PXParam.expr = ""; this.PYParam.expr = ""; this.PZParam.expr = ""; this.RXParam.value = 0; this.RYParam.value = 0; this.RZParam.value = 0; this.RXParam.expr = ""; this.RYParam.expr = ""; this.RZParam.expr = ""; } } //更新LWH(通过定位空间) this.LParam.UpdateParam(this._CacheSpaceSize.x); this.WParam.UpdateParam(this._CacheSpaceSize.y); this.HParam.UpdateParam(this._CacheSpaceSize.z); evaled.add(this.LParam); evaled.add(this.WParam); evaled.add(this.HParam); //#endregion //更新其他参数变量 Eval local params for (const param of this.Params) { if (param.type === TemplateParamType.Material) await LoadParamMaterial(param); param.EvalUpdate(this._CacheParamVars, paramMap, evaled); } //删除材质变量(材质变量仅在KJL导入中使用,重新出现在右侧列表中是不明智的?) (但是用户可能编辑更新了它?) // arrayRemoveIf(this.Params, p => p.type === TemplateParamType.Material); this.UpdateEntitys(); //相对定位. use PX PY PZ(这个代码在这里,保证所有的变量都是最新的. 这样我们就可以使用H/2自己定位到自己空间的一半位置)(这个只会修改定位,所以放在这里并不会影响其他的代码) this.UpdatePosition(paramMap, evaled); //变换到新的模版空间 for (let en of ens) { en.ApplyMatrix(this._CacheSpaceCS); if (en instanceof Board) en.LazyGrooveCheckAll(); } //更新顶层变量值 if (!this.Parent) { for (const param of this.Params) this._CacheParamVars[`$${param.name}`] = param.value; } else { for (let param of this._CatchRootParam) this._CacheParamVars[`$${param.name}`] = param.value; } //保持SpaceCS for (let ent of ens) { ent.SpaceOCS = this._CacheSpaceCS; } } //在模块更新回模块空间之前更新实体(请参考左右侧板模块) UpdateEntitys() { } /** * 使用PXPYPZ更新空间位置 */ UpdatePosition(paramMap, evaled) { let x = this.PXParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); let y = this.PYParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); let z = this.PZParam.EvalUpdate(this._CacheParamVars, paramMap, evaled, false); if (x !== 0 || y !== 0 || z !== 0) { this.PXParam.value = x; this.PYParam.value = y; this.PZParam.value = z; let baseP = new Vector3(x, y, z); baseP.applyMatrix4(this._CacheSpaceCS); this._CacheSpaceCS.setPosition(baseP); } } /** * 旋转空间定位,如果旋转成功,那么SpaceSize和SpaceCS都可能被更新 */ RotateSpaceCS(paramMap, evaled) { this.RXParam.EvalUpdate(this._CacheParamVars, paramMap, evaled); this.RYParam.EvalUpdate(this._CacheParamVars, paramMap, evaled); this.RZParam.EvalUpdate(this._CacheParamVars, paramMap, evaled); //use RX RY RZ let rx = MathUtils.degToRad(this.RXParam.value); let ry = MathUtils.degToRad(this.RYParam.value); let rz = MathUtils.degToRad(this.RZParam.value); if (rx !== 0 || ry !== 0 || rz !== 0) { let mrx = new Matrix4().makeRotationX(rx); let mry = new Matrix4().makeRotationY(ry); let mrz = new Matrix4().makeRotationZ(rz); let mro = mrz.multiply(mry.multiply(mrx)); let roSpace = mro.multiplyMatrices(this._CacheSpaceCS, mro); let roSpaceInv = mrx.getInverse(roSpace); //变量复用 let transfromToRoSpace = roSpaceInv.multiply(this._CacheSpaceCS); let box = new Box3(new Vector3(), this._CacheSpaceSize.clone()); box.applyMatrix4(transfromToRoSpace); box.getSize(this._CacheSpaceSize); let baseP = box.min.clone().applyMatrix4(roSpace); roSpace.setPosition(baseP); //更新LWH(通过定位空间) this.LParam.UpdateParam(this._CacheSpaceSize.x); this.WParam.UpdateParam(this._CacheSpaceSize.y); this.HParam.UpdateParam(this._CacheSpaceSize.z); this._CacheSpaceCS = roSpace; } this._CacheParamVars["L"] = this._CacheSpaceSize.x; this._CacheParamVars["W"] = this._CacheSpaceSize.y; this._CacheParamVars["H"] = this._CacheSpaceSize.z; } /** 以广度搜索优先更新节点树 */ async UpdateTemplateTree() { if (this.Parent && !this.Parent.IsErase) { let parent = this.Parent.Object; if (parent.SplitType !== TemplateSplitType.None || this.NeedUpdateParent) return await parent.UpdateTemplateTree(); } let stack = [this]; let evaledTemplates = new Set(stack); let effects = this.EffectTemplates.filter(t => t?.IsErase === false).map(t => t.Object); await this.Update(); while (stack.length > 0 || effects.length) { let template = stack.shift(); if (!template) //如果堆栈内更新完了,就更新影响的模块 { //最早画的index let index = Max(effects, (a, b) => a.Id.Index > b.Id.Index); template = effects.splice(index, 1)[0]; if (evaledTemplates.has(template)) continue; evaledTemplates.add(template); await template.Update(); } //清理历史记录时,子对象会被清理,为了防止被清理掉,清除不需要的id template.Children = template.Children.filter(id => id && !id.IsErase); //计算DIV(给子空间使用) if (template.Children.length > 0 && template.SplitType !== TemplateSplitType.None) { let sum = 0; //除去div时,总共占用的空间 for (let c of template.Children) { let ctemplate = c.Object; let vardefines = ctemplate.GetParameterDefinition(false); vardefines._DIV = 0; let param = ctemplate.Params[template.SplitType]; sum += param.EvalUpdate(vardefines, new Map(), new Set(), false); } let sumDiv = 0; //div=1时占用的空间 for (let c of template.Children) { let ctemplate = c.Object; let vardefines = ctemplate.GetParameterDefinition(false); vardefines._DIV = 1; let param = ctemplate.Params[template.SplitType]; sumDiv += param.EvalUpdate(vardefines, new Map(), new Set(), false); } let divCount = sumDiv - sum; if (divCount > 0) { //div可用总空间 let divSum = template.Params[template.SplitType].value; if (template.SplitType === TemplateSplitType.Z) //因为历史原因保留了这个代码 其实这个是不需要的 { //上下开门高度总DIV用MBG参数 let mbg = template.Params.find(p => p.name === "MBG"); if (mbg) divSum = mbg.value; } divSum -= sum; if (divSum > 0) template._CacheParamVars.DIV = divSum / divCount; else template._CacheParamVars.DIV = 0; } template._CacheParamVars.POS = 0; } //更新子层模块 for (let c of template.Children) { let ctemplate = c.Object; stack.push(ctemplate); evaledTemplates.add(ctemplate); await ctemplate.Update(); if (template._CacheParamVars.POS !== undefined) //更新POS { let paramValue = ctemplate.Params[template.SplitType].value; if (typeof paramValue === "number") template._CacheParamVars.POS += ctemplate.Params[template.SplitType].value; else template._CacheParamVars.POS += parseFloat(ctemplate.Params[template.SplitType].value); } } //收集影响的模块 for (let cId of template.EffectTemplates) { if (cId.IsErase !== false) continue; if (evaledTemplates.has(cId.Object)) continue; effects.push(cId.Object); } } } /** * 本节点可用的所有变量定义.(包括变量继承) * @param [useCache=true] 当更新当前节点的时候,我们不希望使用缓存,(因为父节点的参数可能已经被更新) * @returns */ GetParameterDefinition(useCache = true) { if (this._CacheParamVars && useCache) return this._CacheParamVars; let vars = this.GetParentParams(); this._CatchRootParam = new Set(); for (const param of this.Params) { if (param.type === TemplateParamType.Material) continue; //材质参数暂时不参与计算 vars[param.name] = param.value; let rootParamName = "$" + param.name; if (!this.Parent) vars[rootParamName] = param.value; //root else if (!(rootParamName in vars)) { this._CatchRootParam.add(param); vars[rootParamName] = param.value; //顶层变量捕获 root } } this._CacheParamVars = vars; return vars; } //清空变量缓存(当增加参数时,如果不清空缓存,下层的变量会无法取得当前层的变量值) ClearParameterDefinitionCache() { this.Traverse(t => { t._CacheParamVars = undefined; t._CatchRootParam = undefined; }); } /** * 变量继承 * - 每继承一层,前缀增加一个`_` * - 顶层前缀`$` * * @returns 继承于父空间的变量定义列表 */ GetParentParams() { if (!this.Parent?.Object) return {}; let parent = this.Parent.Object; let params = parent.GetParameterDefinition(); let newParams = {}; for (let key in params) { if (key[0] !== "$") newParams["_" + key] = params[key]; else newParams[key] = params[key]; } if (newParams._DIV === undefined && parent.SplitType !== TemplateSplitType.None) newParams._DIV = 1; if (newParams._POS === undefined) newParams._POS = 0; return newParams; } get SpaceParse() { let spaceParse; if (this._Positioning && this._Positioning instanceof PositioningClampSpace) spaceParse = this._Positioning._SpaceParse; else spaceParse = new ISpaceParse(); spaceParse.SpaceOCS = this._CacheSpaceCS; spaceParse.ParseOK = true; spaceParse.SpaceBox = new Box3Ext(new Vector3(), this._CacheSpaceSize); return spaceParse; } /** * 获得当前的模版空间的相对坐标系 * - 存在父节点的时候使用父节点的模版空间坐标系 * - 不存在父节点的时候使用自身的模版空间坐标系 * * - 空间坐标系将被定位更新 * * @param [useCache=true] 当更新当前节点的时候,我们不希望使用缓存,(因为父节点的参数可能已经被更新) */ GetTemplateSpaceCS(useCache = true) { if (useCache && this._CacheSpaceCS) return this._CacheSpaceCS.clone(); if (this.Parent?.Object) { let template = this.Parent.Object; return template.GetTemplateSpaceCS(); } for (let brId of this.Objects) { if (brId.Object && !brId.IsErase) { let br = brId.Object; return br.SpaceOCS; } } return new Matrix4(); } /** * 获得当前模块的实际位置坐标系. * 使用 GetTemplateSpaceCS 可能会得到不准确的位置.(得到Parent或者已经缓存的位置) */ GetTemplateRealitySpaceCS() { for (let brId of this.Objects) { if (brId.Object && !brId.IsErase) { let br = brId.Object; return br.SpaceOCS; } } return this.GetTemplateSpaceCS(true); //此时已经可能不准确 } get NeedUpdateParent() { if (this._Positioning) return false; //存在自我定位时,不需要更新父层 if (this.Parent) { return true; //不管父层是不是被更新,都需要更新父层,因为父层可能被移动 } return false; //没有父层时,不需要更新父层 } get PositioningSupportBoards() { let brs = []; if (this._Positioning && this._Positioning instanceof PositioningClampSpace) { for (let id of this._Positioning.Objects) if (!id.IsErase && id.Object instanceof Board) brs.push(id.Object); } if (brs.length === 0) { //可视化空间使用周围板件数据 let parentTemplate = this.Parent?.Object; while (parentTemplate) { for (let e of parentTemplate.AllEntitys) if (e instanceof Board) { brs.push(e); break; } if (brs.length) break; parentTemplate = parentTemplate.Parent?.Object; } if (brs.length === 0) brs.push(new Board()); } return brs; } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { this._Version = file.Read(); super.ReadFile(file); this.Type = file.Read(); this._Parent = file.ReadHardObjectId(); let count = file.Read(); this.Children.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadHardObjectId(); if (id) this.Children.push(id); } count = file.Read(); this.Params.length = 0; for (let i = 0; i < count; i++) this.Params.push(file.ReadObject()); count = file.Read(); this.Objects.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Objects.push(id); } this._Positioning = file.ReadObject(); if (this._Version > 1) this.SplitType = file.Read() ?? this.SplitType; this.EffectTemplates.length = 0; if (this._Version > 3) { count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.EffectTemplates.push(id); } } //清空缓存,因为我们回滚了模块(BUG:修改参数->撤销->替换模块(此时读取了缓存的数据) this._CacheParamVars = undefined; this._CacheSpaceCS = undefined; this._CacheSpaceSize = undefined; this._CatchRootParam = undefined; this._NodeDepthCache = undefined; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); let type = this.Type; //我们在保存文件的时候,才会去序列化它,否则我们不会自动添加历史记录(被动式) if (this.isExpanded) type |= TemplateType.Expanded; else type &= ~TemplateType.Expanded; file.Write(type); file.WriteHardObjectId(this._Parent); file.Write(this.Children.length); for (let id of this.Children) file.WriteHardObjectId(id); file.Write(this.Params.length); for (let param of this.Params) file.WriteObject(param); file.Write(this.Objects.length); for (let id of this.Objects) file.WriteHardObjectId(id); file.WriteObject(this._Positioning); file.Write(this.SplitType); file.Write(this.EffectTemplates.length); for (let id of this.EffectTemplates) file.WriteObjectId(id); } }; __decorate([ AutoRecord ], TemplateRecord.prototype, "Type", void 0); __decorate([ AutoRecord ], TemplateRecord.prototype, "SplitType", void 0); __decorate([ AutoRecord ], TemplateRecord.prototype, "Children", void 0); __decorate([ AutoRecord ], TemplateRecord.prototype, "EffectTemplates", void 0); __decorate([ AutoRecord ], TemplateRecord.prototype, "Params", void 0); __decorate([ AutoRecord ], TemplateRecord.prototype, "Objects", void 0); TemplateRecord = TemplateRecord_1 = __decorate([ Factory ], TemplateRecord); function Purge(db) { db.hm.Clear(); let bakEnable = db.hm.Enable; db.hm.Enable = false; //应该避免占位空间被PU(占位空间内没有实体,也没有子层) arrayRemoveIf(db.TemplateTable.Objects, t => { if (!t || t.IsErase) return true; let allEnts = []; t.Traverse(ct => { for (let id of ct.Objects) { if (id.Object) allEnts.push(id.Object); } }); if (allEnts.length && allEnts.every(e => e.IsErase)) { if (!t.Id || !(t instanceof TemplateRecord)) throw "PU正在删除无id的实体"; t.Erase(); t.Traverse(ct => { if (!ct.Id || !(ct instanceof TemplateRecord)) throw "PU正在删除无id的实体"; ct.Erase(); }); //PU所有子层 return true; } //否则PU本层 t.Purge(); }); //再次清理,避免子模块没了之后,父模块还引用它 for (let t of db.TemplateTable.Objects) t.Children = t.Children.filter(c => !c.IsErase); arrayRemoveIf(db.GroupTable.Objects, g => { if (!g || g.IsErase) return true; g.Purge(); return g.Entitys.length === 0; }); //由于我们现在Goodbye会将Object清空,所以为了模块能正确的pu,我们把这个延后了 arrayRemoveIf(db.ModelSpace.Entitys, e => { let isErase = e.IsErase; if (isErase) e.GoodBye(); else if (e instanceof Text) { //清理空文本文字实体 if (!e.TextString?.trim()) { e.GoodBye(); return true; } } return isErase; }); arrayRemoveIf(db.Lights.Entitys, e => { let isErase = e.IsErase; if (isErase) e.GoodBye(); return isErase; }); arrayRemoveIf(db.LayoutSpace.Entitys, e => { let isErase = e.IsErase; if (isErase) e.GoodBye(); return isErase; }); arrayRemoveIf(db.ProcessingGroupTable.Objects, e => { if (!e || e.IsErase) return true; e.Purge(); return e.Objects.length === 0; }); arrayRemoveIf(db.AlignLineGroupTable.Objects, e => { if (!e || e.IsErase) return true; e.Purge(); return e.Objects.length === 0; }); db.hm.Enable = bakEnable; } //裁剪树 有时候会有不好的行为,仅在copy.ts中使用 function PurgeTemplateTreeRoot(db) { for (let t of db.TemplateTable.Objects) { if (t.IsRoot && t.Objects.length === 0) { if (!t.Id) throw "错误!正在删除无id的实体!"; t.Erase(); let children = t.Children.concat(); //必须备份,否则子实体设置Parent=undefined时错误 t.Children = []; for (let ctId of children) { let ct = ctId.Object; ct.Parent = undefined; } } } arrayRemoveIf(db.TemplateTable.Objects, t => t.IsErase); } var DimStyleKeyCode; (function (DimStyleKeyCode) { DimStyleKeyCode[DimStyleKeyCode["DIMEXO"] = 20] = "DIMEXO"; /**如果设置为 1,延长线具有固定长度 */ DimStyleKeyCode[DimStyleKeyCode["DIMFXL"] = 49] = "DIMFXL"; /**延长线长度 */ DimStyleKeyCode[DimStyleKeyCode["DIMFXLON"] = 290] = "DIMFXLON"; /**控制尺寸中换算单位的显示 */ DimStyleKeyCode[DimStyleKeyCode["DIMALT"] = 170] = "DIMALT"; /**控制换算单位的小数位数。 */ DimStyleKeyCode[DimStyleKeyCode["DIMALTD"] = 171] = "DIMALTD"; DimStyleKeyCode[DimStyleKeyCode["DIMADEC"] = 179] = "DIMADEC"; /**箭头尺寸 */ DimStyleKeyCode[DimStyleKeyCode["DIMASZ"] = 41] = "DIMASZ"; DimStyleKeyCode[DimStyleKeyCode["DIMEXE"] = 44] = "DIMEXE"; DimStyleKeyCode[DimStyleKeyCode["DIMTAD"] = 77] = "DIMTAD"; DimStyleKeyCode[DimStyleKeyCode["DIMTXT"] = 140] = "DIMTXT"; /*147 尺寸线距离文字的距离(从尺寸线偏移) */ DimStyleKeyCode[DimStyleKeyCode["DIMGAP"] = 147] = "DIMGAP"; })(DimStyleKeyCode || (DimStyleKeyCode = {})); //enum to string // let code = DimStyleKeyCode[DimStyleKeyCode.DIMALT]; //enum keys // type keys = keyof typeof DimStyleKeyCode //ref https://ezdxf.readthedocs.io/en/stable/dxfinternals/tables/dimstyle_table.html var DimTextPosDir; (function (DimTextPosDir) { DimTextPosDir[DimTextPosDir["Top"] = 1] = "Top"; DimTextPosDir[DimTextPosDir["Out"] = 2] = "Out"; })(DimTextPosDir || (DimTextPosDir = {})); /** * 标注样式 */ let DimStyleRecord = class DimStyleRecord extends SymbolTableRecord { constructor() { super(...arguments); /** 如果设置为 1,延长线(脚线)具有固定长度 DIMFXLON 290 */ this.DIMFXLON = true; //1 /** DIMFXL 49 尺寸线下方的延长线长度如果固定(DIMFXLON 为 1),DIMEXE 定义尺寸线上方的长度 */ this.DIMFXL = 100; /** 控制尺寸中换算单位的显示 DIMALT 170 */ this.DIMALT = true; //2 // /** 控制换算单位的小数位数。如果打开 DIMALT,DIMALTD 会设置交替测量中小数点右侧显示的位数。 DIMALTD 171 */ this.DIMALTD = 2; //控制对齐标注的小数位精度 this.DIMADEC = 2; //控制在角度尺寸中显示的精度位置的数量。 /** DIMASZ 41 控制尺寸线和引线箭头的大小。还控制钩线的大小。箭头大小的倍数决定尺寸线和文本是否应适合尺寸界线。如果由 DIMBLK 设置,DIMASZ 也用于缩放箭头块。当 DIMTSZ 不为零时,DIMASZ 无效。 */ this.DIMASZ = 10; /**147 尺寸线距离文字的距离(从尺寸线偏移) */ this.DIMGAP = 10; /**42 指定尺寸界线与原点的偏移距离。对于固定长度的延长线,此值确定最小偏移量。 */ this.DIMEXO = 20; /**43 */ this.DIMDLI = 20; /**44 肩膀上面的延伸线长度 */ this.DIMEXE = 20; /** 140 文字高度 */ this.DIMTXT = 60; /**77 控制文本相对于尺寸线的垂直位置。 */ this.DIMTAD = DimTextPosDir.Out; //#endregion } // DIMTMOVE 279 设置标注文字移动规则。0 = 使用尺寸文本移动尺寸线 1 = 在移动尺寸文本时添加引线 2 = 允许文本在没有引线的情况下自由移动 //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); let bitV = file.Read(); this.DIMFXLON = (bitV & 1) > 0; this.DIMALT = (bitV & 2) > 0; this.DIMFXL = file.Read(); this.DIMALTD = file.Read(); this.DIMASZ = file.Read(); this.DIMGAP = file.Read(); this.DIMEXO = file.Read(); this.DIMDLI = file.Read(); this.DIMEXE = file.Read(); this.DIMTXT = file.Read(); this.DIMTAD = file.Read(); if (ver > 1) { this.DIMADEC = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); super.WriteFile(file); let bitV = 0; //压缩布尔值到里面 if (this.DIMFXLON) bitV += 1; if (this.DIMALT) bitV += 2; file.Write(bitV); file.Write(this.DIMFXL); file.Write(this.DIMALTD); file.Write(this.DIMASZ); file.Write(this.DIMGAP); file.Write(this.DIMEXO); file.Write(this.DIMDLI); file.Write(this.DIMEXE); file.Write(this.DIMTXT); file.Write(this.DIMTAD); file.Write(this.DIMADEC); } }; __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMFXLON", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMFXL", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMALT", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMALTD", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMADEC", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMASZ", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMGAP", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMEXO", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMDLI", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMEXE", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMTXT", void 0); __decorate([ AutoRecord ], DimStyleRecord.prototype, "DIMTAD", void 0); DimStyleRecord = __decorate([ Factory ], DimStyleRecord); let Dimension = class Dimension extends Entity { constructor() { super(...arguments); this.OnlyRenderType = true; this._Text = new Text(undefined, "", 0, FontType.YaHei, 1); this._DimStyleOverride = new Map; //标注样式替代 this._RoomName = ""; this._CabinetName = ""; } //避免标注实体使用材质 get Material() { return this._MaterialId; } set Material(materialId) { } //获取覆盖的标注样式(来自Key) GetDimStyleOverrideValue(key) { return this._DimStyleOverride.get(key); } //设置覆盖的标注样式 SetDimStyleOverrideValue(key, value) { if (this.GetDimStyleOverrideValue(key) === value) return; this.WriteAllObjectRecord(); let oldV = this.GetDimStyleValue(key); this._DimStyleOverride.set(key, value); if (oldV !== value) this.Update(); } //设置多个覆盖的标注样式 SetDimStyleOverrideValues(valueMap) { this.WriteAllObjectRecord(); for (let [key, value] of valueMap) { this._DimStyleOverride.set(key, value); } this.Update(); } DeleteDimStyleOverrideValue(key) { if (!this._DimStyleOverride.has(key)) return; this.WriteAllObjectRecord(); this._DimStyleOverride.delete(key); this.Update(); } ClearDimStyleOverride() { if (this._DimStyleOverride.size === 0) return; this.WriteAllObjectRecord(); this._DimStyleOverride.clear(); this.Update(); } //获取当前的标注样式值 GetDimStyleValue(key) { let value = this.GetDimStyleOverrideValue(key); if (value !== undefined) return value; if (this._DimStyle?.Object) return this._DimStyle.Object[DimStyleKeyCode[key]]; if (HostApplicationServices.CurrentDimStyle && HostApplicationServices.CurrentDimStyle.Object instanceof DimStyleRecord) return HostApplicationServices.CurrentDimStyle.Object[DimStyleKeyCode[key]]; } get DimStyle() { return this._DimStyle; } /**设置标注样式 */ set DimStyle(styleId) { if (styleId === this._DimStyle) return; this.WriteAllObjectRecord(); this._DimStyle = styleId; this.Update(); } //#endregion set TextString(txt) { let str = this.GetPrimitiveString(); let tstr = this._TextString ? this._TextString.replace("<>", str) : str; if (txt !== tstr) { this.WriteAllObjectRecord(); if (txt.trim() === "") this._TextString = null; else this._TextString = txt.replace(str, "<>"); this.Update(); } } get TextString() { return this._TextString ? this._TextString.replace("<>", this.GetPrimitiveString()) : this.GetPrimitiveString(); } /**计算文字盒子占用的宽度 */ get TextBoxWidth() { return this.TextString.length * Math.abs(this.TextSize / 2) * 1.35; } get TextSize() { return this.GetDimStyleValue(DimStyleKeyCode.DIMTXT); } set TextSize(size) { this.SetDimStyleOverrideValue(DimStyleKeyCode.DIMTXT, size); } get FractionDigits() { return this.GetDimStyleValue(DimStyleKeyCode.DIMALTD); } set FractionDigits(length) { this.SetDimStyleOverrideValue(DimStyleKeyCode.DIMALTD, length); } get RoomName() { return this._RoomName; } set RoomName(value) { if (value === this._RoomName) return; this.WriteAllObjectRecord(); this._RoomName = value; } get CabinetName() { return this._CabinetName; } set CabinetName(value) { if (value === this._CabinetName) return; this.WriteAllObjectRecord(); this._CabinetName = value; } ReadDimStyle(file) { this._DimStyleOverride.clear(); let size = file.Read(); for (let i = 0; i < size; i++) { let k = file.Read(); let v = file.Read(); this._DimStyleOverride.set(k, v); } } WriteDimStyle(file) { file.Write(this._DimStyleOverride.size); for (let [k, v] of this._DimStyleOverride) { file.Write(k); file.Write(v); } } ReadRoomAndCabinetName(file) { this._RoomName = file.Read(); this._CabinetName = file.Read(); } WriteRoomAndCabinetName(file) { file.Write(this._RoomName); file.Write(this._CabinetName); } }; Dimension = __decorate([ Factory ], Dimension); let CreateObjectData = class CreateObjectData extends CADObject { /** * 如果是实体新建,那么不需要调用`Save`,历史记录会在命令结束的时候调用. * 如果是实体删除,那么请自行调用`Save`. */ constructor(Object) { super(); this.Object = Object; this.CADFiler = new CADFiler(); } //记录数据,在命令结束的时候记录 Save() { if (this.Object && this.CADFiler.Data.length === 0) { this.CADFiler.Data.length = 0; this.CADFiler.WriteObject(this.Object); } return this; } GetObject(db) { this.CADFiler.Reset(); this.CADFiler.database = db; this.Object = this.CADFiler.ReadObject(); this.CADFiler.Reset(); return this.Object; } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); //ver; let data = file.Read(); this.CADFiler.Data = data; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); //ver file.Write(this.CADFiler.Data); } }; CreateObjectData = __decorate([ Factory ], CreateObjectData); let RemoveObjectData = class RemoveObjectData extends CADObject { constructor(index) { super(); this.index = index; } get Index() { return this.index; } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); //ver this.index = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); file.Write(this.index); } //局部撤销 ApplyPartialUndo(file) { } }; RemoveObjectData = __decorate([ Factory ], RemoveObjectData); let ObjectCollection = class ObjectCollection extends CADObject { constructor() { super(...arguments); this.Objects = []; //#endregion -----------------------------File End----------------------------- } Destroy() { super.Destroy(); this.Objects.length = 0; } /** * @param object * @param isCheckObjectCleanly 检查对象是否清白,如果对象不清白,那么将会被拒绝加入到集合. */ Append(object, isCheckObjectCleanly = true) { if (isCheckObjectCleanly && object.Id) { console.warn("尝试加入已经分配id的对象!"); return; } if (this._db && !object.Id) object.SetOwnerDatabase(this._db); else object.SetDatabase(this._db); this.Objects.push(object); this.AppendEvent(object); let undoRec = this.UndoRecord(); if (undoRec) { let hisRec = new HistorycRecord(); hisRec.redoData = new CreateObjectData(object); hisRec.undoData = new RemoveObjectData(this.Objects.length - 1); undoRec.WriteObjectHistoryPath(this, hisRec); } return object.Id; } AppendEvent(obj) { } Remove(obj) { let index = this.Objects.indexOf(obj); this.RemoveIndex(index); } RemoveIndex(index) { if (index === -1) return; let obj = this.Objects[index]; this.Objects.splice(index, 1); let undoRec = this.UndoRecord(); if (undoRec) { let hisRec = new HistorycRecord(); hisRec.undoData = new CreateObjectData(obj).Save(); hisRec.redoData = new RemoveObjectData(index); undoRec.WriteObjectHistoryPath(this, hisRec); } if (obj) obj.GoodBye(); return obj; } //#region -----------------------------File----------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { this.Objects.length = 0; super.ReadFile(file); let cout = file.Read(); for (let i = 0; i < cout; i++) { let obj = file.ReadObject(); if (obj) { this.Objects.push(obj); //由于旧的图纸可能没有这个Owner 可能造成一些错误,这里修复它 // if (!obj.Owner) //因为对纹组导致的 必须修复Id obj.Owner = this.SpecialOwner ?? this.objectId; } } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(this.Objects.length); for (let obj of this.Objects) file.WriteObject(obj); } //局部撤销 ApplyPartialUndo(undoData) { if (undoData instanceof CreateObjectData) { let obj = undoData.GetObject(this._db); this.Objects.push(obj); this.AppendEvent(obj); undoData.CreateObject = obj; } else if (undoData instanceof RemoveObjectData) { let obj = this.RemoveIndex(undoData.Index); undoData.RemoveObject = obj; } } }; ObjectCollection = __decorate([ Factory ], ObjectCollection); let BlockTableRecord = class BlockTableRecord extends SymbolTableRecord { constructor() { super(); this.EntityCol = new ObjectCollection(); end(this.EntityCol, this.EntityCol.AppendEvent, (e) => { this.AppendEvent(e); }); } SetOwnerDatabase(db) { super.SetOwnerDatabase(db); this.EntityCol.SetOwnerDatabase(db); return this; } get Entitys() { return this.EntityCol.Objects; } /** 提供通用的Add方法(以便在WblockClone时能统一调用.Add) */ Add(obj, isCheckObjectCleanly = true) { this.Append(obj, isCheckObjectCleanly); return Status.True; } Append(entity, isCheckObjectCleanly = true) { if (this._db) { if (entity instanceof Dimension) { if (!entity.DimStyle) entity.DimStyle = this._db.DimStyleTable.Current; //设置默认的标注样式 } else if (entity instanceof RoomWallBase || entity instanceof RoomFlatTop || entity instanceof RoomHolePolyline) { if (!entity.Material) entity.Material = this._db.MaterialTable.CurWallMtl; } else if (entity instanceof RoomFlatFloor) { if (!entity.Material) entity.Material = this._db.MaterialTable.CurFloorMtl; } else if (entity instanceof Board) { if (!entity.Material) entity.Material = this._db.MaterialTable.CurBoardMtl; } if (!entity.HasLayer) entity.Layer = this._db.LayerTable.Current; else if (entity instanceof Text) { if (!entity.FontStyle) entity.FontStyle = this._db.FontStyleTable.Current; //设置默认的字体样式 } } this.EntityCol.Append(entity, isCheckObjectCleanly); entity.Owner = this.objectId; } /**添加实体实现(通过转发) */ AppendEvent(entity) { } Remove(entity) { this.EntityCol.Remove(entity); } Destroy() { super.Destroy(); this.EntityCol.Destroy(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.EntityCol.SpecialOwner = this.objectId; this.EntityCol.ReadFile(file); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); this.EntityCol.WriteFile(file); } }; BlockTableRecord = __decorate([ Factory ], BlockTableRecord); let ObjectAllDataHistoryRecord = class ObjectAllDataHistoryRecord extends HistorycRecord { constructor(RecordEntityId) { super(); this.RecordEntityId = RecordEntityId; if (RecordEntityId) this.undoData = new AllObjectData(this.RecordEntityId.Object); } WriteRedo() { this.redoData = new AllObjectData(this.RecordEntityId.Object); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.RecordEntityId = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.WriteObjectId(this.RecordEntityId); } }; ObjectAllDataHistoryRecord = __decorate([ Factory ], ObjectAllDataHistoryRecord); /** * 命令的历史记录 */ let CommandHistoryRecord = class CommandHistoryRecord extends CADObject { constructor(CommandName = "") { super(); this.CommandName = CommandName; //历史记录表 this._HistoryList = new Map(); this._CreateObjects = new Map(); } get HistoryList() { return this._HistoryList; } GetObjectHistoryList(id) { if (!this._HistoryList.has(id)) this._HistoryList.set(id, []); return this._HistoryList.get(id); } EndCommand() { for (let [id, hrs] of this._HistoryList) { let hr = this.GetObjectAllDataRecord(hrs); if (hr) hr.WriteRedo(); } for (let [, hr] of this._CreateObjects) { let h = hr.redoData; h.Save(); } } //获取对象快照记录(如果有的话) GetObjectAllDataRecord(historyList) { if (historyList.length > 0) { let hr = historyList[historyList.length - 1]; if (hr instanceof ObjectAllDataHistoryRecord) return hr; } } //对象写入历史记录 WriteObjectHistoryPath(obj, history) { if (!obj || this._CreateObjects.has(obj)) //某些时候obj可能为空 return; let hrs = this.GetObjectHistoryList(obj.Id); if (this.GetObjectAllDataRecord(hrs)) return; if (history.redoData instanceof CreateObjectData) this._CreateObjects.set(history.redoData.Object, history); hrs.push(history); } WriteObjectSnapshoot(obj) { if (!obj.Id) { console.warn("错误!CreateObjectHistory"); return; } if (this._CreateObjects.has(obj)) return; let hrs = this.GetObjectHistoryList(obj.Id); if (this.GetObjectAllDataRecord(hrs)) return; let hr = new ObjectAllDataHistoryRecord(obj.Id); hrs.push(hr); } CreateEraseHistory(obj, isErase) { let hr = new HistorycRecord(); hr.undoData = new EraseEntityData(!isErase); hr.redoData = new EraseEntityData(isErase); this.WriteObjectHistoryPath(obj, hr); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); this.CommandName = file.Read(); let cout = file.Read(); this._HistoryList.clear(); for (let i = 0; i < cout; i++) { let id = file.ReadObjectId(); let length = file.Read(); let hrs = []; this._HistoryList.set(id, hrs); for (let j = 0; j < length; j++) { let hr = file.ReadObject(); hrs.push(hr); } } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); file.Write(this.CommandName); file.Write(this._HistoryList.size); for (let [id, hrs] of this._HistoryList) { file.WriteObjectId(id); file.Write(hrs.length); for (let hr of hrs) { file.WriteObject(hr); } } } }; CommandHistoryRecord = __decorate([ Factory ], CommandHistoryRecord); let AlignLineGroupTable = class AlignLineGroupTable extends ObjectCollection { Append(object, isCheckObjectCleanly = true) { let id = super.Append(object, isCheckObjectCleanly); if (id) object.Owner = this.Id; return id; } Add(record, isCheckObjectCleanly = true) { let id = this.Append(record, isCheckObjectCleanly); if (id) return Status.True; else return Status.False; } Remove(record) { return super.Remove(record); } }; AlignLineGroupTable = __decorate([ Factory ], AlignLineGroupTable); //相机近端面最小值 const ViewNearMinSize = 1e-2; const ViewScopeSize = 4e6; //相机活动范围 const ViewScopeMin = new Vector3(-ViewScopeSize, -ViewScopeSize * 0.7, -ViewScopeSize); const ViewScopeMax = ViewScopeMin.clone().negate(); const ViewBoxMin = new Vector3(1, 1, 1); const viewBoxMax = new Vector3(1e7, 1e7, 1e7); var CameraType; (function (CameraType) { CameraType[CameraType["OrthographicCamera"] = 1] = "OrthographicCamera"; CameraType[CameraType["PerspectiveCamera"] = 2] = "PerspectiveCamera"; })(CameraType || (CameraType = {})); /** * * 相机的控制. * ->切换相机 * ->设置视口大小 * ->旋转和移动相机. */ class CameraUpdate { constructor() { this._CameraArray = new Map(); //视口显示的高度 this._ViewHeight = 1000; //观察的位置 this._Target = new Vector3(); //观察向量 this._Direction = new Vector3(0, 0, -1); //观察的轨道. this._Orbit = new Orbit(); //近端面 this._Near = ViewNearMinSize; this.DisableRotate = false; this._CameraArray.set(OrthographicCamera, new OrthographicCamera(-2, 2, 2, -2, -ViewScopeSize, ViewScopeSize)); this._CameraArray.set(PerspectiveCamera, new PerspectiveCamera(60, 1, this._Near, ViewScopeSize)); this._CurCamera = this._CameraArray.get(OrthographicCamera); this._Orbit.SetFromDirection(this._Direction); this.UpdateUp(); this.Update(); } get Orbit() { return this._Orbit; } get Aspect() { return this._Width / this._Height; } get Target() { return this._Target; } set Target(value) { this._Target = value; } get Camera() { return this._CurCamera; } get ViewHeight() { return this._ViewHeight; } set ViewHeight(height) { this._ViewHeight = MathUtils.clamp(height, HostApplicationServices.viewSize.minViewHeight, HostApplicationServices.viewSize.maxViewHeight); this.ZoomEvent(); } get Direction() { return this._Direction.clone(); } SetSize(width, height) { this._Width = width; this._Height = height; } get Width() { return this._Width; } get Height() { return this._Height; } get Fov() { return this._CameraArray.get(PerspectiveCamera).fov; } set Fov(fov) { let camera = this._CameraArray.get(PerspectiveCamera); camera.fov = fov; this.UpdateCameraMatrix(); } get Near() { return this._Near; } set Near(value) { this._Near = value; let camera = this._CameraArray.get(PerspectiveCamera); camera.near = this._Near; this.UpdateCameraMatrix(); } /** * 平移相机. * @param {Vector3} mouseMove */ Pan(mouseMove) { mouseMove.y *= -1; mouseMove.multiplyScalar(-this._ViewHeight / this._Height); mouseMove.applyQuaternion(this.Camera.quaternion); this._Target.add(mouseMove); this._Target.clamp(ViewScopeMin, ViewScopeMax); this.Update(); } Rotate(mouseMove, target) { let scale = this.CameraType === CameraType.PerspectiveCamera ? 0.002 : 0.003; this._Orbit.RoX -= mouseMove.y * scale; this._Orbit.theta -= mouseMove.x * scale; if (this.CameraType === CameraType.PerspectiveCamera && !target) //转头 { if (this.DisableRotate) return; this._Orbit.UpdateDirection(this._Direction); this.UpdateUp(); let camera = this.Camera; camera.aspect = this.Aspect; let distens = (this._ViewHeight / 2) / (Math.tan(MathUtils.degToRad(camera.fov) / 2)); this._Target.copy(this._Direction).multiplyScalar(distens).add(this.Camera.position); //重新计算观察点位置 this.UpdateCameraMatrix(); return; } //缓存观察点 let oldTargetFormCameraSpace = target.clone().applyMatrix4(this.Camera.matrixWorldInverse); this._Orbit.UpdateDirection(this._Direction); this.UpdateUp(); this.Update(); //-----还原观察点 //得到新的观察点相对于相机的位置 let newTargetFormCameraSpace = target.clone().applyMatrix4(this.Camera.matrixWorldInverse); //减去原先的位置. 得到观测点在相机内移动的向量 newTargetFormCameraSpace.sub(oldTargetFormCameraSpace); //乘以相机的矩阵. 得到向量在世界坐标系的位置 newTargetFormCameraSpace.applyMatrix4(this.Camera.matrix); //因为使用的是点变换,所以减去基点,得到向量 newTargetFormCameraSpace.sub(this.Camera.position); //加上移动的向量. 使得观察点时钟在相机的某个位置 this._Target.add(newTargetFormCameraSpace); this.Update(); } UpdateCameraMatrix() { this.Camera.matrixAutoUpdate = true; // this.Camera.updateMatrix(); //如果不使用autoUpdate,那么应该还原这句 this.Camera.lookAt(this._Target); this.Camera.updateProjectionMatrix(); // this.Camera.updateMatrix(); //如果不使用autoUpdate,那么应该还原这句 this.Camera.updateMatrixWorld(false); this.Camera.matrixAutoUpdate = false; } Zoom(scale, scaleCenter) { if (this.Camera instanceof OrthographicCamera) { this.ViewHeight *= scale; if (scaleCenter && this._ViewHeight < HostApplicationServices.viewSize.maxViewHeight) { this._Target.sub(scaleCenter); this._Target.multiplyScalar(scale); this._Target.add(scaleCenter); } } else if (this.Camera instanceof PerspectiveCamera) { let add = scale > 1 ? 1 : -1; add *= this.Camera.position.distanceTo(this._Target) / 10; this._Target.add(this._Direction.clone().multiplyScalar(-add)); } this.Update(); } ZoomExtentsBox3(box3) { if (!box3 || box3.isEmpty()) return; this.Camera.updateMatrixWorld(false); box3.getCenter(this._Target); //变换到相机坐标系 box3.applyMatrix4(this.Camera.matrixWorldInverse); let size = box3.getSize(new Vector3()); size.clamp(ViewBoxMin, viewBoxMax); // this._Target.z = box3.max.z; //使用这个代码 在变换矩阵可以使得观察点在前面,但是会导致观察距离过长 //宽高比 let aspectRatio = size.x / size.y; let viewAspectRatio = this.Aspect; if (aspectRatio > viewAspectRatio) this.ViewHeight = size.x / viewAspectRatio; else this.ViewHeight = size.y; this.Update(); } /** * 设置相机的观察向量. * @param {Vector3} dir 方向向量,这个向量传入后会被更改为单位向量 */ LookAt(dir) { this.LookAtEvent(dir); this._Orbit.SetFromDirection(dir); this._Direction.copy(dir); this.UpdateUp(); this.Update(); } LookAtEvent(dir) { } UpdateUp() { Orbit.ComputUpDirection(this._Direction, this.Camera.up); } /** * 根据视口大小,设置相机视口范围. */ Update() { this.Camera.position.copy(this._Target); if (this.Camera instanceof OrthographicCamera) { this.Camera.left = this.Aspect * this._ViewHeight / -2; this.Camera.right = this.Aspect * this._ViewHeight / 2; this.Camera.bottom = this._ViewHeight / -2; this.Camera.top = this._ViewHeight / 2; this.Camera.position.sub(this._Direction); } else if (this.Camera instanceof PerspectiveCamera) { this.Camera.aspect = this.Aspect; let distens = (this._ViewHeight / 2) / (Math.tan(MathUtils.degToRad(this.Camera.fov) / 2)); this.Camera.position.sub(this._Direction.clone().multiplyScalar(distens)); } else return; this.UpdateCameraMatrix(); } ZoomEvent() { } set CameraType(type) { if (type !== this.CameraType) this.SwitchCamera(); } get CameraType() { if (this.Camera instanceof OrthographicCamera) return CameraType.OrthographicCamera; else return CameraType.PerspectiveCamera; } SwitchCamera() { if (this.Camera instanceof OrthographicCamera) this._CurCamera = this._CameraArray.get(PerspectiveCamera); else this._CurCamera = this._CameraArray.get(OrthographicCamera); this.UpdateUp(); this.Update(); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); this._ViewHeight = file.Read(); if (ver < 4) { this._Target.fromArray(file.Read()); this._Direction.fromArray(file.Read()); } else { this._Target.set(file.Read(), file.Read(), file.Read()); this._Direction.set(file.Read(), file.Read(), file.Read()); } this._Orbit.SetFromDirection(this._Direction); this.UpdateUp(); this.Update(); if (ver > 1) this.CameraType = file.Read(); if (ver > 2) this.Fov = file.Read(); if (ver > 4) this.Near = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(5); file.Write(this._ViewHeight); file.WriteVec3(this._Target); file.WriteVec3(this._Direction); file.Write(this.CameraType); file.Write(this.Fov); file.Write(this.Near); } CopyFrom(camer) { let f = new CADFiler; camer.WriteFile(f); f.Reset(); this.ReadFile(f); } } let CameraSnapshootRecord = class CameraSnapshootRecord extends CADObject { constructor() { super(...arguments); this.Name = ""; this.CameraData = new CameraUpdate; this.UCS = new Matrix4; this.RenderType = RenderType.Wireframe; //#endregion } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); if (ver === 1) { let datas = file.Read(); let f = new CADFiler(datas); f.Read(); //old ver this.CameraData.ReadFile(f); this.UCS.elements = f.Read(); this.RenderType = f.Read(); } else { this.CameraData.ReadFile(file); for (let i = 0; i < 16; i++) this.UCS.elements[i] = file.Read(); this.RenderType = file.Read(); } this.Name = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(2); this.CameraData.WriteFile(file); for (let e of this.UCS.elements) file.Write(e); file.Write(this.RenderType); file.Write(this.Name); } }; CameraSnapshootRecord = __decorate([ Factory ], CameraSnapshootRecord); class DeepCloneFiler extends CADFiler { constructor(idMaping = new Map()) { super(); this.idMaping = idMaping; this.hardObjectIds = new Set(); this.cloned = new Set(); } ReadObjectId() { let index = this.Read(); if (index <= 0) return; let id = this.idMaping.get(index); if (id) return id; id = new ObjectId(); this.idMaping.set(index, id); return id; } ReadSoftObjectId() { let id = this.ReadObjectId(); if (id) id._RelevancyType = RelevancyType.Soft; return id; } ReadHardObjectId() { let id = this.ReadObjectId(); if (id) id._RelevancyType = RelevancyType.Hard; return id; } WriteHardObjectId(id) { if (id && id.Index >= 100 && !this.cloned.has(id.Index)) //当存在id时,表示对象正在被拷贝,或者已经拷贝完成 this.hardObjectIds.add(id.Index); return this.WriteObjectId(id); } } let SymbolTable = class SymbolTable extends CADObject { constructor() { super(...arguments); /** * 符号列表,支持迭代,请勿直接修改数据 */ this.Symbols = new Map(); this._FileVer = 0; } Add(record, isCheckObjectCleanly = true) { if (this.Symbols.has(record.Name)) return Status.DuplicateRecordName; if (!isCheckObjectCleanly) record.SetDefaultDb(this._db); else if (this._db) record.SetOwnerDatabase(this._db); record.Owner = this.objectId; this.Symbols.set(record.Name, record); return Status.True; } Remove(record) { let selfRecord = this.Symbols.get(record.Name); if (selfRecord === record) { let undoRec = this.UndoRecord(); if (undoRec) { let hisRec = new HistorycRecord(); hisRec.undoData = new CreateObjectData(selfRecord).Save(); hisRec.redoData = new RemoveObjectData(selfRecord.Name); undoRec.WriteObjectHistoryPath(this, hisRec); } this.Symbols.delete(record.Name); } } Destroy() { super.Destroy(); this.Symbols.clear(); } GetAt(name) { return this.Symbols.get(name); } Has(name) { return this.Symbols.has(name); } AllocateName(name) { for (let i = 1; i < this.Symbols.size + 2; i++) { let newName = `${name}${i}`; if (!this.Has(newName)) return newName; } } ChangeRecordName(record, newName) { if (this.Has(newName)) return false; this.Symbols.delete(record.Name); this.Symbols.set(newName, record); return true; } //#region -------------------------File------------------------- ReadFile(file) { super.ReadFile(file); let ver = file.Read(); this._FileVer = ver; let count = file.Read(); this.Symbols.clear(); for (let i = 0; i < count; i++) { if (ver === 1) file.Read(); let record = file.ReadObject(); if (record) { this.Symbols.set(record.Name, record); // if (!record.Owner) record.Owner = this.objectId; } } } WriteFile(file) { super.WriteFile(file); file.Write(5); file.Write(this.Symbols.size); for (let [key, record] of this.Symbols) { file.WriteObject(record); } } //局部撤销 ApplyPartialUndo(undoData) { if (undoData instanceof CreateObjectData) { let record = undoData.GetObject(this._db); this.Symbols.set(record.Name, record); undoData.CreateObject = record; } else if (undoData instanceof RemoveObjectData) { let obj = this.Symbols.get(undoData.Index); this.Symbols.delete(undoData.Index); undoData.RemoveObject = obj; } else super.ApplyPartialUndo(undoData); // else if (undoData instanceof RenameObjectData) // { // let record = this.GetAt(undoData._oldName); // this.Symbols.delete(undoData._oldName); // this.Symbols.set(undoData._newName, record); // } } }; __decorate([ observable ], SymbolTable.prototype, "Symbols", void 0); SymbolTable = __decorate([ Factory ], SymbolTable); let DimStyleTable = class DimStyleTable extends SymbolTable { get DimStyleNames() { let dimStyleNames = []; for (let dimStyle of this.Symbols) dimStyleNames.push(dimStyle[0]); return dimStyleNames; } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.Current = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.WriteObjectId(this.Current); } }; DimStyleTable = __decorate([ Factory ], DimStyleTable); let FontStyleTable = class FontStyleTable extends SymbolTable { get FontStyleNames() { let fontStyleNames = []; for (let fontStyle of this.Symbols) fontStyleNames.push(fontStyle[0]); return fontStyleNames; } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.Current = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.WriteObjectId(this.Current); } }; FontStyleTable = __decorate([ Factory ], FontStyleTable); let GroupTable = class GroupTable extends ObjectCollection { Append(object, isCheckObjectCleanly = true) { let id = super.Append(object, isCheckObjectCleanly); if (id) object.Owner = this.Id; return id; } Add(record, isCheckObjectCleanly = true) { let id = this.Append(record, isCheckObjectCleanly); if (id) return Status.True; else return Status.False; } //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); } }; GroupTable = __decorate([ Factory ], GroupTable); let CommandState = { CommandIng: false, }; var HistoricManage_1; /** * 历史记录管理 * * 关于子命令标记: * 当子命令标记出现的时候,才允许出现局部撤销,否则命令执行中不允许撤销,触发命令重写了撤销和重做的事件. */ let HistoricManage = HistoricManage_1 = class HistoricManage extends CADObject { constructor(useSubCommand = true) { super(); this.useSubCommand = useSubCommand; this.curIndex = -1; //当前执行位置,也就是当前的状态, undo时,撤销当前状态,redo时,应用下一个状态 this.lockIndex = -1; //锁定极限撤销索引(将无法在往前撤销) this.historyRecord = []; //历史记录 this.doing = false; //正在执行工作 例如: 文件读取中 撤销中 重做中 this.Enable = true; } /** * 清理历史记录 * @param [remCount=0] 剩余的记录记录个数 */ Clear(remCount = 0) { let delCount = (this.curIndex + 1) - remCount; if (delCount <= 0) return; this.historyRecord.splice(0, delCount); this.curIndex -= delCount; } //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); this.curIndex = file.Read(); let cout = file.Read(); this.historyRecord = []; for (let i = 0; i < cout; i++) { this.historyRecord.push(file.ReadObject()); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); file.Write(this.curIndex); file.Write(this.historyRecord.length); for (let rec of this.historyRecord) { file.WriteObject(rec); } } //命令正在当前状态 get IsNow() { return this.historyRecord.length !== 0 && this.curIndex === this.historyRecord.length - 1; } get UndoData() { if (this.doing || this.Enable === false) return undefined; if (!this.IsNow) { this.StartCmd(""); } if (this._SignalCommandHistory && CommandState.CommandIng) return this._SignalCommandHistory.UndoData; else return this.historyRecord[this.historyRecord.length - 1]; } //开始子命令标记 StartMark(subCommandName = "") { if (!this.useSubCommand) return; if (!this._SignalCommandHistory) this._SignalCommandHistory = new HistoricManage_1(false); this._SignalCommandHistory.EndCmd(); this._SignalCommandHistory.StartCmd(subCommandName); } /** * 不要在命令中直接调用这个函数! * 请调用 commandMachine.CommandStart */ StartCmd(cmdName) { //删除当前状态以后的所有状态 this.historyRecord.splice(this.curIndex + 1, this.historyRecord.length - (this.curIndex + 1)); this.historyRecord.push(new CommandHistoryRecord(cmdName)); this.curIndex = this.historyRecord.length - 1; } CurrentHasHistory() { let lastRec = this.historyRecord[this.curIndex]; if (!lastRec) return false; this.MergeSubCommmand(lastRec); return lastRec.HistoryList.size > 0; } //结束当前的命令,返回是否写入历史记录 EndCmd() { if (!this.IsNow) return false; let lastRec = this.historyRecord[this.curIndex]; if (lastRec) { this.MergeSubCommmand(lastRec); lastRec.EndCommand(); if (lastRec.HistoryList.size === 0) { this.historyRecord.pop(); this.curIndex--; return false; } // else if (this.historyRecord.length > 20)//最大历史记录个数为20 (如果在这边直接删除记录,会导致临时编辑器错误) // { // this.historyRecord.shift(); // this.curIndex--; // } else if (this.historyRecord.length !== 0 && this.historyRecord.length % 50 === 0) Toaster({ message: `警告:已经有${this.historyRecord.length}个命令历史记录了,建议您使用PU命令进行清理,避免图纸卡顿和崩溃!`, timeout: 15000, intent: Intent.WARNING, }); return true; } return false; } /** * 获得这个命令修改的对象列表 */ get ChangeObjects() { let lastRec = this.historyRecord[this.curIndex]; let objects = new Set(); if (lastRec) { this.MergeSubCommmand(lastRec); for (let [id] of lastRec.HistoryList) { objects.add(id.Object); } } return objects; } GetRangeChangeObject(startIndex, endIndex) { let objects = new Set(); for (let i = startIndex; i < endIndex; i++) for (let [id] of this.historyRecord[i].HistoryList) objects.add(id.Object); return objects; } /** * 合并命令历史记录,因为命令记录器允许存在子记录,所以当命令结束时,应该把子记录合并到父记录中,子记录将被摧毁. */ MergeSubCommmand(lastRec) { if (this._SignalCommandHistory) { for (let rc of this._SignalCommandHistory.historyRecord) { for (let [id, hrs] of rc.HistoryList) { if (id?.Object) //某些情况下可能为空 for (let hr of hrs) lastRec.WriteObjectHistoryPath(id.Object, hr); } } this._SignalCommandHistory = undefined; } } Undo() { if (this.curIndex === this.lockIndex) return; //被锁定,无法撤销 this.EndCmd(); let historyRec = this.historyRecord[this.curIndex]; if (!historyRec) return false; this.doing = true; for (let [id, recList] of historyRec.HistoryList) { for (let i = recList.length; i--;) { id?.Object?.ApplyPartialUndo(recList[i].undoData); } } this.UndoEvent(historyRec.CommandName, historyRec); this.curIndex--; this.doing = false; return true; } Redo() { let historyRec = this.historyRecord[this.curIndex + 1]; if (!historyRec) return false; this.doing = true; for (let [id, recList] of historyRec.HistoryList) { for (let rec of recList) { id.Object.ApplyPartialUndo(rec.redoData); } } this.RedoEvent(historyRec.CommandName, historyRec); this.curIndex++; this.doing = false; return true; } UndoEvent(cmdName, historyRec) { } RedoEvent(cmdName, historyRec) { } }; HistoricManage = HistoricManage_1 = __decorate([ Factory ], HistoricManage); let LayerTableRecord = class LayerTableRecord extends SymbolTableRecord { // @AutoRecord IsFrozen = false;//冻结 // @AutoRecord IsHidden = false;//隐藏 // @AutoRecord Description = "";//描述 constructor() { super(); this.ColorIndex = 7; // @AutoRecord LineWeight: number = 0; this.IsOff = false; //关 this.IsLocked = false; //锁定 } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.ColorIndex = file.Read(); this.IsOff = file.Read(); this.IsLocked = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.ColorIndex); file.Write(this.IsOff); file.Write(this.IsLocked); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; __decorate([ AutoRecord ], LayerTableRecord.prototype, "ColorIndex", void 0); __decorate([ AutoRecord ], LayerTableRecord.prototype, "IsOff", void 0); __decorate([ AutoRecord ], LayerTableRecord.prototype, "IsLocked", void 0); LayerTableRecord = __decorate([ Factory ], LayerTableRecord); var Light_1; /** * 灯光实体基类 */ let Light = Light_1 = class Light extends Entity { constructor() { super(...arguments); this.OnlyRenderType = true; this._Intensity = 2; //强度 this._LightColor = new Color(); //光源颜色 this.Temperature = 6500; //色温 this.IndirectLightingIntensity = 3; //0-200 间接光照强度 //LightComponent extends LightComponentBase this.SpecularScale = 1; //高光度范围 默认1 (物理) this._ShowHelper = true; // Light.DefaultShowHelpr; 默认绘制的灯光总是有助手,避免让用户困惑 this._CaseShadow = true; //投射阴影 this._OpenLight = Light_1.DefaultOpenLight; //开灯 } Clone() { return CADObject.prototype.Clone.call(this); } get CaseShadow() { return this._CaseShadow; } set CaseShadow(v) { if (v === this._CaseShadow) return; this.WriteAllObjectRecord(); this._CaseShadow = v; this.Update(); } get OpenLight() { return this._OpenLight; } set OpenLight(v) { if (v === this._OpenLight) return; this._OpenLight = v; this.Update(); } //因为有set 所以必须11对应 get Position() { return super.Position; } set Position(v) { super.Position = v; this.Update(UpdateDraw.Geometry); } get IsVisible() { return !this._isErase && this._Visible; } get Color() { return this._LightColor; } set Color(color) { this.WriteAllObjectRecord(); this._LightColor.set(color); this.Update(); } get ShowHelper() { return this._ShowHelper; } set ShowHelper(v) { if (v === this._ShowHelper) return; this.WriteAllObjectRecord(); this._ShowHelper = v; this.Update(); } get BoundingBoxInOCS() { return new Box3Ext(new Vector3(-0.05, -0.05, -0.05), new Vector3(0.05, 0.05, 0.05)); } get BoundingBox() { return new Box3().setFromCenterAndSize(this.Position, new Vector3(0.1, 0.1, 0.1)); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint) { switch (snapMode) { case ObjectSnapMode.End: return this.GetGripPoints(); } return []; } GetGripPoints() { return [this.Position]; } MoveGripPoints(indexList, vec) { this.ApplyMatrix(MoveMatrix(vec)); this.Update(); } UpdateDrawObject(type, en) { en.intensity = this.WebIntensity; en.color = this._LightColor; en.visible = this._OpenLight; } get Intensity() { return this._Intensity; } set Intensity(v) { if (equaln(v, this._Intensity, 0.01)) return; this.WriteAllObjectRecord(); this._Intensity = v; this.Update(); } get WebIntensity() { return this._Intensity; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._LightColor = new Color(file.Read()); this._Intensity = file.Read(); if (typeof this._Intensity === "string") this._Intensity = parseFloat(this._Intensity); this._ShowHelper = file.Read(); if (ver > 1) { this._LightColor.r = file.Read(); this._LightColor.g = file.Read(); this._LightColor.b = file.Read(); this.Temperature = file.Read(); this.IndirectLightingIntensity = file.Read(); this.SpecularScale = file.Read(); this._CaseShadow = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._LightColor.getStyle()); file.Write(this._Intensity); file.Write(this._ShowHelper); //ver2 { file.Write(this._LightColor.r); file.Write(this._LightColor.g); file.Write(this._LightColor.b); file.Write(this.Temperature); file.Write(this.IndirectLightingIntensity); file.Write(this.SpecularScale); file.Write(this._CaseShadow); } } }; Light.DefaultOpenLight = false; Light.DefaultCaseShadow = false; Light.DefaultShowHelpr = false; __decorate([ AutoRecord ], Light.prototype, "Temperature", void 0); __decorate([ AutoRecord ], Light.prototype, "IndirectLightingIntensity", void 0); __decorate([ AutoRecord ], Light.prototype, "SpecularScale", void 0); Light = Light_1 = __decorate([ Factory ], Light); let AmbientLight = class AmbientLight extends Light { constructor() { super(...arguments); this._Intensity = 0.9; this._OpenLight = true; //开灯 this._CaseShadow = false; } InitDrawObject(renderType = RenderType.Wireframe) { let light = new AmbientLight$1(this.Color, this._Intensity); return light; } }; AmbientLight = __decorate([ Factory ], AmbientLight); class SunLightHelper extends DirectionalLightHelper { constructor(light, size, color) { const geometry = new SphereBufferGeometry(1, 16, 16); const material = new LineBasicMaterial({ fog: false }); material.color = light.color; super(light, size, color); this.children[0].geometry = geometry.scale(400, 400, 400); this.children[0].material = material; } } /** * 平行光源 */ let DirectionalLight = class DirectionalLight extends Light { constructor() { super(); this._Intensity = 50; //强度 //光源源角度 0-50 this.LightSourceAngle = 0.5; //源软角度角度 this.LightSourceSoftAngle = 0; this.OnlyRenderType = true; this._Target = new Vector3(); this._ShowHelper = false; this._OpenLight = true; //开灯 this.SpecularScale = 0; //高光度范围 默认0 关闭太阳光反射 } //避免被删除 Erase(isErase = true) { this.ShowHelper = !isErase; } /** * @param theta 身体旋转 角度(deg) * @param phi 头部旋转 角度(deg) */ SetRotate(thetaDeg, phiDeg) { let phi = (180 - phiDeg) * MathUtils.DEG2RAD; let v = new Vector3(Math.cos(phi), 0, Math.sin(phi)); let r = new Matrix3().rotate(MathUtils.DEG2RAD * thetaDeg); v.applyMatrix3(r); this.WriteAllObjectRecord(); this._Target.setFromMatrixPosition(this._Matrix).sub(v); } get Target() { return this._Target.clone(); } set Target(p) { this.WriteAllObjectRecord(); if (!equalv3(p, this.Position)) { this._Target.copy(p); this.Update(); } } get SunPosition() { return super.Position; } set SunPosition(p) { if (equaln$1(p.x, this._Matrix.elements[12]) && equaln$1(p.y, this._Matrix.elements[13]) && equaln$1(p.z, this._Matrix.elements[14])) return; this.WriteAllObjectRecord(); this._Matrix.setPosition(p); this.Update(UpdateDraw.Geometry); } get Position() { return super.Position; } set Position(p) { console.error("不支持的用法! 错误的设计"); let bak = this._Target.toArray(); super.Position = p; this._Target.fromArray(bak); this.Update(UpdateDraw.Geometry); } get WebIntensity() { let x = this._Intensity / 150; x = Math.pow(x, 0.4); return x * 1.4; // 7/5=1.25 } ApplyMatrix(m) { super.ApplyMatrix(m); this._Target.applyMatrix4(m); this.Update(UpdateDraw.Geometry); return this; } GetGripPoints() { if (this.ShowHelper) return [this.Position, this._Target]; else return []; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); if (indexList[0] === 0) this.Position = this.Position.add(vec); else this._Target.add((vec)); } InitDrawObject(renderType = RenderType.Wireframe) { let lightGroup = new Group(); let light = new DirectionalLight$1(this._LightColor, this.WebIntensity); Object.defineProperty(light, "castShadow", { get: () => this.CaseShadow //HostApplicationServices.isShowLightShadow //太阳光无视这个配置! }); light.shadow.camera.matrixAutoUpdate = true; light.shadow.camera.near = 1; light.shadow.camera.far = 100000; light.shadow.mapSize = new Vector2$1(2048, 2048); let helper = new SunLightHelper(light, 500); helper.lightPlane.matrixAutoUpdate = true; lightGroup.add(light, helper); lightGroup.matrixAutoUpdate = false; lightGroup.matrix.copy(this._Matrix); lightGroup.updateMatrixWorld(true); this.UpdateDrawObject(renderType, lightGroup); return lightGroup; } UpdateDrawObject(type, en) { let light = en.children[0]; super.UpdateDrawObject(type, light); light.target.position.copy(this._Target); light.target.updateMatrix(); light.target.updateMatrixWorld(true); en.updateMatrixWorld(true); let helper = en.children[1]; helper.visible = this._ShowHelper; if (this._ShowHelper) { helper.update(); helper.matrix = light.matrix; helper.targetLine.updateMatrix(); } } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._Target.fromArray(file.Read()); if (ver > 1) { this.LightSourceAngle = file.Read(); this.LightSourceSoftAngle = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.Write(this._Target.toArray()); //ver2 file.Write(this.LightSourceAngle); file.Write(this.LightSourceSoftAngle); } }; __decorate([ AutoRecord ], DirectionalLight.prototype, "LightSourceAngle", void 0); __decorate([ AutoRecord ], DirectionalLight.prototype, "LightSourceSoftAngle", void 0); DirectionalLight = __decorate([ Factory ], DirectionalLight); let HemisphereLight = class HemisphereLight extends Light { constructor() { super(...arguments); this._GroundColor = new Color(); //UE有这个属性 但是默认是黑的 this._Intensity = 1; this.AutoExposure = false; //自动曝光 this.ExposureCompensation = 1; //默认为1 this._OpenLight = true; //开灯 } get GroundColor() { return this._GroundColor; } set GroundColor(color) { this.WriteAllObjectRecord(); this._GroundColor = color; this.Update(); } get WebIntensity() { return Math.min(this._Intensity, 4) / 5; } InitDrawObject(renderType = RenderType.Wireframe) { let light = new HemisphereLight$1(this.Color, this._LightColor, this.WebIntensity); return light; } UpdateDrawObject(type, en) { super.UpdateDrawObject(type, en); let lg = en; lg.groundColor = this._GroundColor; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._GroundColor.fromArray(file.Read()); if (ver > 1) { this.AutoExposure = file.Read(); this.ExposureCompensation = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); //ver file.Write(this._GroundColor.toArray()); //ver2 file.Write(this.AutoExposure); file.Write(this.ExposureCompensation); } }; __decorate([ AutoRecord ], HemisphereLight.prototype, "AutoExposure", void 0); __decorate([ AutoRecord ], HemisphereLight.prototype, "ExposureCompensation", void 0); HemisphereLight = __decorate([ Factory ], HemisphereLight); let ProcessingGroupTable = class ProcessingGroupTable extends ObjectCollection { Append(object, isCheckObjectCleanly = true) { let id = super.Append(object, isCheckObjectCleanly); if (id) object.Owner = this.Id; return id; } Add(record, isCheckObjectCleanly = true) { let id = this.Append(record, isCheckObjectCleanly); if (id) return Status.True; else return Status.False; } Remove(record) { return super.Remove(record); } }; ProcessingGroupTable = __decorate([ Factory ], ProcessingGroupTable); var LayerNode_1; let LayerTable = class LayerTable extends SymbolTable { constructor() { super(...arguments); /** 用于描述图层结构的根节点 */ this._Root = new LayerNode("根节点", true); //#endregion } get Current() { return this._Current; } set Current(id) { if (this._Current === id) return; let undoData = this.UndoRecord(); if (undoData) { let hr = new HistorycRecord(); hr.undoData = new ObjectIdData(this._Current); hr.redoData = new ObjectIdData(id); undoData.WriteObjectHistoryPath(this, hr); } this._Current = id; HostApplicationServices.CurrentLayer = id; } get Root() { return this._Root; } set Root(root) { if (this._Root === root) return; let undoData = this.UndoRecord(); if (undoData) { let hr = new HistorycRecord(); hr.undoData = new ObjectTreeData(this._Root); hr.redoData = new ObjectTreeData(root); undoData.WriteObjectHistoryPath(this, hr); } this._Root = root; } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this._Current = file.ReadObjectId(); this._Root = file.ReadObject(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.WriteObjectId(this._Current); file.WriteObject(this._Root); } //局部撤销 ApplyPartialUndo(undoData) { if (undoData instanceof ObjectIdData) { this._Current = undoData.objectId; HostApplicationServices.CurrentLayer = this._Current; } else if (undoData instanceof ObjectTreeData) this._Root = undoData.data; else super.ApplyPartialUndo(undoData); } }; LayerTable = __decorate([ Factory ], LayerTable); /** 用于描述图层结构的节点 */ let LayerNode = LayerNode_1 = class LayerNode { constructor(name, isHasChildren = false) { this.name = name; if (isHasChildren) this.children = []; } ReadFile(f) { this.name = f.ReadString(); const count = f.Read(); if (count !== -1) { this.children = []; for (let i = 0; i < count; i++) { const child = new LayerNode_1(""); child.ReadFile(f); this.children.push(child); } } } WriteFile(f) { f.WriteString(this.name); if (!this.children) f.Write(-1); else { f.Write(this.children.length); for (const child of this.children) child.WriteFile(f); } } }; LayerNode = LayerNode_1 = __decorate([ Factory ], LayerNode); /** * 记录当前id的序列化数据 */ let ObjectIdData = class ObjectIdData { ReadFile(file) { this.objectId = file.ReadObjectId(); return this; } WriteFile(file) { file.WriteObjectId(this.objectId); return this; } constructor(objectId) { this.objectId = objectId; } }; ObjectIdData = __decorate([ Factory ], ObjectIdData); /** 记录树状结构的序列化数据*/ let ObjectTreeData = class ObjectTreeData { ReadFile(file) { this.data = file.ReadObject(); return this; } WriteFile(file) { file.WriteObject(this.data); return this; } constructor(data) { this.data = data; } }; ObjectTreeData = __decorate([ Factory ], ObjectTreeData); class MaterialTable extends SymbolTable { get CurFloorMtl() { return this._CurFloorMtl; } get CurWallMtl() { return this._CurWallMtl; } get CurBoardMtl() { return this._CurDrawMtl; } get CurBulkCeilingFaceMaterial() { return this._CurBulkCeilingFaceMaterial; } set CurFloorMtl(id) { if (id === this._CurFloorMtl) return; this.WriteAllObjectRecord(); this._CurFloorMtl = id; if (this._db && this._db.IsDefaultDatabase) HostApplicationServices.DefaultFloorMaterial = id.Object.Material; } set CurWallMtl(id) { if (id === this._CurWallMtl) return; this.WriteAllObjectRecord(); this._CurWallMtl = id; if (this._db && this._db.IsDefaultDatabase) HostApplicationServices.DefaultWallMaterial = id.Object.Material; } set CurBoardMtl(id) { if (id === this._CurDrawMtl) return; this.WriteAllObjectRecord(); this._CurDrawMtl = id; if (this._db && this._db.IsDefaultDatabase) HostApplicationServices.DefaultMeshMaterial = id.Object.Material; } set CurBulkCeilingFaceMaterial(id) { if (id === this._CurBulkCeilingFaceMaterial) return; this.WriteAllObjectRecord(); this._CurBulkCeilingFaceMaterial = id; if (this._db && this._db.IsDefaultDatabase) HostApplicationServices.DefaultBulkheadCeilingMaterial = id.Object.Material; } get Materials() { return this.Symbols; } Remove(record) { this.WriteAllObjectRecord(); record.Erase(); return super.Remove(record); } GetAt(name) { return super.GetAt(name); } Has(name) { return super.Has(name); } AllocateName(name = "材质") { return super.AllocateName(name); } Destroy() { super.Destroy(); this._CurDrawMtl = undefined; this._CurFloorMtl = undefined; this._CurWallMtl = undefined; } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { super.ReadFile(file); if (this._FileVer > 2) { this._CurFloorMtl = file.ReadObjectId(); this._CurWallMtl = file.ReadObjectId(); } if (this._FileVer > 3) this._CurDrawMtl = file.ReadObjectId(); if (this._FileVer > 4) this._CurBulkCeilingFaceMaterial = file.ReadObjectId(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.WriteObjectId(this._CurFloorMtl); file.WriteObjectId(this._CurWallMtl); file.WriteObjectId(this._CurDrawMtl); file.WriteObjectId(this._CurBulkCeilingFaceMaterial); } } __decorate([ observable ], MaterialTable.prototype, "_CurFloorMtl", void 0); __decorate([ observable ], MaterialTable.prototype, "_CurWallMtl", void 0); __decorate([ observable ], MaterialTable.prototype, "_CurDrawMtl", void 0); __decorate([ observable ], MaterialTable.prototype, "_CurBulkCeilingFaceMaterial", void 0); let TextureTable = class TextureTable extends SymbolTable { AllocateName() { do { let name = MathUtils.generateUUID(); if (!this.Has(name)) return name; } while (true); } get Textures() { return this.Symbols; } }; TextureTable = __decorate([ Factory ], TextureTable); let TemplateTable = class TemplateTable extends ObjectCollection { Append(object, isCheckObjectCleanly = true) { let id = super.Append(object, isCheckObjectCleanly); if (id) object.Owner = this.Id; return id; } Add(record, isCheckObjectCleanly = true) { let id = this.Append(record, isCheckObjectCleanly); if (id) return Status.True; else return Status.False; } }; TemplateTable = __decorate([ Factory ], TemplateTable); class TextureTableEventBus { static GetInstance() { if (this._SingleInstance) return this._SingleInstance; this._SingleInstance = new TextureTableEventBus; return this._SingleInstance; } UpdateEvent(texture) { } } /** * 数据化的贴图类.实现了序列化. */ let TextureTableRecord = class TextureTableRecord extends SymbolTableRecord { constructor() { super(...arguments); this.wrapS = MirroredRepeatWrapping; //横向(水平)平铺 this.wrapT = MirroredRepeatWrapping; //竖向(垂直)平铺 this.repeatX = 1; //这里已经变成了贴图的尺寸了 this.repeatY = 1; this.rotation = 0; //旋转 弧度deg this.imageUrl = ""; this.moveX = 0; //材质位移 this.moveY = 0; this.imgUrl = ""; this.texture = new Texture(); this.waits = []; //#endregion } set WrapS(wrap) { if (wrap !== this.wrapS) { this.WriteAllObjectRecord(); this.wrapS = wrap; } } get WrapS() { return this.wrapS; } ; get WrapT() { return this.wrapT; } ; set WrapT(wrap) { if (wrap !== this.wrapT) { this.WriteAllObjectRecord(); this.wrapT = wrap; } } async Update() { return ""; } async TextureUpdate() { } async WaitUpdate() { if (this.texture.image) return; let p = new Promise((res, rej) => { this.waits.push(res); }); return p; } GetThreeTexture() { return this.texture; } //#region -------------------------File------------------------- ReadFile(file) { super.ReadFile(file); let ver = file.Read(); this.wrapS = file.Read(); this.wrapT = file.Read(); this.repeatX = file.Read(); this.repeatY = file.Read(); this.rotation = file.Read(); this.imageUrl = file.Read(); if (ver > 1) { this.moveX = file.Read(); this.moveY = file.Read(); } this.Update(); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this.wrapS); file.Write(this.wrapT); file.Write(this.repeatX); file.Write(this.repeatY); file.Write(this.rotation); file.Write(this.imageUrl); file.Write(this.moveX); file.Write(this.moveY); } }; __decorate([ AutoRecord ], TextureTableRecord.prototype, "repeatX", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "repeatY", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "rotation", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "imageUrl", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "moveX", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "moveY", void 0); __decorate([ AutoRecord ], TextureTableRecord.prototype, "imgUrl", void 0); TextureTableRecord = __decorate([ Factory ], TextureTableRecord); class WblockCloneFiler extends DeepCloneFiler { } let Database = class Database { /** * * @param [buildDefaultDrawing=false] 构建默认对象(标注 图层 材质 灯光) * @param [IsDefaultDatabase=false] app图纸 * @param [disableHistoric=false] 禁止命令记录(撤销重做) */ constructor(buildDefaultDrawing = false, IsDefaultDatabase = false, disableHistoric = false) { this.IsDefaultDatabase = IsDefaultDatabase; //相机快照记录 this.CameraSnapshoots = []; /** 这是为UE设计器提供的拓展数据,WebCAD只负责保存,不使用它. * 在存储吊顶模块时,存储了材质槽信息 */ this.ExtendedData = null; this.idIndex = 1; this.idMap = new Map(); //注意:下面的注释是严格排序的,请不要随意插入某个表 this.ModelSpace = new BlockTableRecord().SetOwnerDatabase(this); this.MaterialTable = new MaterialTable().SetOwnerDatabase(this); this.TextureTable = new TextureTable().SetOwnerDatabase(this); this.TemplateTable = new TemplateTable().SetOwnerDatabase(this); this.GroupTable = new GroupTable().SetOwnerDatabase(this); this.Lights = new BlockTableRecord().SetOwnerDatabase(this); this.ProcessingGroupTable = new ProcessingGroupTable().SetOwnerDatabase(this); this.hm = new HistoricManage().SetDefaultDb(this); this.hm.Enable = false; this.LayoutSpace = new BlockTableRecord().SetOwnerDatabase(this); this.DimStyleTable = new DimStyleTable().SetOwnerDatabase(this); this.AlignLineGroupTable = new AlignLineGroupTable().SetOwnerDatabase(this); //注意:新对象在这里添加 this.LayerTable = new LayerTable().SetOwnerDatabase(this); this.FontStyleTable = new FontStyleTable().SetOwnerDatabase(this); if (IsDefaultDatabase) { if (HostApplicationServices.Database) console.error("重复初始化默认图纸"); HostApplicationServices.Database = this; } if (buildDefaultDrawing) { this.InitDimStyle(); //60 this.InitLayer(); //61; this.InitMaterial(); //70 this.InitLight(); this.InitFontStyle(); this.SettingDefaultStyleAndMaterial(); } this.hm.Enable = !disableHistoric; //100以内的id是系统保留id,默认初始化内部对象. this.idIndex = 100; } InitDimStyle() { this.idIndex = 60; let styleRd = new DimStyleRecord; styleRd.Name = "默认"; this.DimStyleTable.Add(styleRd); this.DimStyleTable.Current = styleRd.Id; } InitLayer() { this.idIndex = 61; let layerRd = new LayerTableRecord; layerRd.Name = "默认"; this.LayerTable.Add(layerRd); this.LayerTable.Current = layerRd.Id; HostApplicationServices.CurrentLayer = layerRd.Id; const root = new LayerNode("根节点"); root.children = [new LayerNode(layerRd.Name)]; this.LayerTable.Root = root; } InitMaterial() { this.idIndex = 70; this.DefaultMaterial = new PhysicalMaterialRecord(); this.DefaultMaterial.Name = "默认"; let texture = new TextureTableRecord(); texture.WrapS = MirroredRepeatWrapping; texture.WrapT = MirroredRepeatWrapping; texture.repeatX = 1; texture.repeatY = 1; this.TextureTable.Add(texture); this.DefaultMaterial.map = texture.Id; this.DefaultMaterial.roughnessMap = texture.Id; this.DefaultMaterial.bumpMap = texture.Id; this.MaterialTable.Add(this.DefaultMaterial); this.MaterialTable.CurBoardMtl = this.DefaultMaterial.Id; texture.Update(); } InitLight() { this.idIndex = 80; this.AmbientLight = new AmbientLight(); this.SunLight = new DirectionalLight(); this.SunLight.OCSNoClone.setPosition(4000, -4000, 4000); this.HemisphereLight = new HemisphereLight(); this.SunLight.Visible = true; this.HemisphereLight.Visible = true; this.Lights.Add(this.AmbientLight); this.Lights.Add(this.SunLight); this.Lights.Add(this.HemisphereLight); } InitFontStyle() { this.idIndex = 90; let styleRd = new FontStyleRecord; styleRd.Name = "默认"; this.FontStyleTable.Add(styleRd); this.FontStyleTable.Current = styleRd.Id; let styleRd1 = new FontStyleRecord; styleRd1.Name = "样式1"; styleRd1.FontType = FontType.SongTi; this.FontStyleTable.Add(styleRd1); } SettingDefaultStyleAndMaterial() { if (!this.IsDefaultDatabase) return; this.DefaultMaterial = this.GetObjectId(71)?.Object ?? this.DefaultMaterial; this.DefaultMaterial.Update(); this.DefaultLayer = this.GetObjectId(61)?.Object ?? this.DefaultLayer; HostApplicationServices.DefaultMeshMaterial = this.MaterialTable.CurBoardMtl?.Object?.Material ?? this.DefaultMaterial.Material; HostApplicationServices.DefaultWallMaterial = this.MaterialTable.CurWallMtl?.Object?.Material ?? this.DefaultMaterial.Material; HostApplicationServices.DefaultFloorMaterial = this.MaterialTable.CurFloorMtl?.Object?.Material ?? this.DefaultMaterial.Material; HostApplicationServices.DefaultBulkheadCeilingMaterial = this.MaterialTable.CurBulkCeilingFaceMaterial?.Object?.Material ?? this.DefaultMaterial.Material; HostApplicationServices.CurrentDimStyle = this.DimStyleTable.Current; HostApplicationServices.CurrentLayer = this.LayerTable.Current; HostApplicationServices.CurrentFontStyle = this.FontStyleTable.Current; } Destroy() { this.ExtendedData = null; this.idMap.clear(); this.ModelSpace.Destroy(); this.LayoutSpace.Destroy(); this.MaterialTable.Destroy(); this.TextureTable.Destroy(); this.TemplateTable.Destroy(); this.GroupTable.Destroy(); this.Lights.Destroy(); this.ProcessingGroupTable.Destroy(); this.AlignLineGroupTable.Destroy(); this.DimStyleTable.Destroy(); this.LayerTable.Destroy(); this.FontStyleTable.Destroy(); this.hm.Destroy(); this.CameraSnapshoots.length = 0; this.hm.historyRecord.length = 0; //注意:下面的注释是严格排序的,请不要随意插入某个表 this.idIndex = 1; this.ModelSpace.SetOwnerDatabase(this); this.MaterialTable.SetOwnerDatabase(this); this.TextureTable.SetOwnerDatabase(this); this.TemplateTable.SetOwnerDatabase(this); this.GroupTable.SetOwnerDatabase(this); this.Lights.SetOwnerDatabase(this); this.ProcessingGroupTable.SetOwnerDatabase(this); this.hm.SetDefaultDb(this); this.LayoutSpace.SetOwnerDatabase(this); this.DimStyleTable.SetOwnerDatabase(this); this.AlignLineGroupTable.SetOwnerDatabase(this); //注意:新对象在这里添加 this.LayerTable.SetOwnerDatabase(this); this.FontStyleTable.SetOwnerDatabase(this); this.idIndex = 100; } //#region Serialize FileWrite(file = new CADFiler) { file.Write(14); //ver; file.Write(this.idIndex); this.LayerTable.WriteFile(file); this.ModelSpace.WriteFile(file); this.TextureTable.WriteFile(file); this.MaterialTable.WriteFile(file); this.hm.WriteFile(file); this.TemplateTable.WriteFile(file); this.GroupTable.WriteFile(file); this.Lights.WriteFile(file); this.ProcessingGroupTable.WriteFile(file); this.LayoutSpace.WriteFile(file); file.Write(this.CameraSnapshoots.length); for (let r of this.CameraSnapshoots) r.WriteFile(file); this.DimStyleTable.WriteFile(file); file.Write(this.ExtendedData); this.AlignLineGroupTable.WriteFile(file); this.FontStyleTable.WriteFile(file); return file; } FileRead(file) { Entity.__ReadFileIng__ = true; this.hm.doing = true; this.Destroy(); file.database = this; let ver = file.Read(); this.idIndex = file.Read(); //图层 if (ver > 12) this.LayerTable.ReadFile(file); else { let indexBak = this.idIndex; this.InitLayer(); this.idIndex = indexBak; } this.ModelSpace.ReadFile(file); this.TextureTable.ReadFile(file); this.MaterialTable.ReadFile(file); this.hm.ReadFile(file); if (ver > 1) this.TemplateTable.ReadFile(file); if (ver > 2) this.GroupTable.ReadFile(file); if (ver > 3) { this.Lights.ReadFile(file); this.AmbientLight = this.Lights.Entitys[0] ?? this.AmbientLight; this.SunLight = this.Lights.Entitys[1] ?? this.SunLight; this.HemisphereLight = this.Lights.Entitys[2] ?? this.HemisphereLight; } else { let index = this.idIndex; this.InitLight(); this.idIndex = index; } if (ver > 4) this.ProcessingGroupTable.ReadFile(file); if (ver > 5) this.LayoutSpace.ReadFile(file); if (ver < 8) //旧版本数据错误 修复它 { for (let e of this.LayoutSpace.Entitys) e.Owner = this.LayoutSpace.Id; } if (ver > 6) { let count = file.Read(); this.CameraSnapshoots.length = 0; for (let i = 0; i < count; i++) { let r = new CameraSnapshootRecord; r.ReadFile(file); this.CameraSnapshoots.push(r); } } if (ver > 8) this.DimStyleTable.ReadFile(file); else { let indexBak = this.idIndex; this.InitDimStyle(); this.idIndex = indexBak; } if (ver === 9) { for (let e of this.LayoutSpace.EntityCol.Objects) e.Owner = this.LayoutSpace.objectId; } if (ver > 10) this.ExtendedData = file.Read(); if (ver > 11) this.AlignLineGroupTable.ReadFile(file); if (ver > 13) this.FontStyleTable.ReadFile(file); else { let indexBak = this.idIndex; this.InitFontStyle(); this.idIndex = indexBak; } this.SettingDefaultStyleAndMaterial(); this.hm.doing = false; Entity.__ReadFileIng__ = false; return this; } //#endregion //#region Clone /** * 注意:跨图纸拷贝(WblockCloneObjects) 同图纸拷贝(DeppCloneObjects) * * 单个数据库内克隆对象(objects),并将他们附加到指定的容器对象(owner). * @param objects 被克隆的对象 * @param owner 克隆对象的容器 * @param idMap id映射 oldid->newid * @param deferXlation 指示是否应该进行ID转换 * @returns 新克隆的对象列表 */ DeepCloneObjects(objects, owner, idMap = new Map(), deferXlation = false) { let f = new DeepCloneFiler(); let newObjects = []; for (let e of objects) { let newE = e instanceof Light ? this.DeepCloneObject(f, e, this.Lights, idMap) : this.DeepCloneObject(f, e, owner, idMap); //灯光拷贝到Lights if (newE) newObjects.push(newE); } //对于无法新拷贝的实体,指向原先的对象(需要是软拷贝才行) for (let [index, objectId] of f.idMaping) { if (!objectId.Object && objectId._RelevancyType === RelevancyType.Soft) { let oldId = this.GetObjectId(index); if (oldId) { objectId.Index = index; objectId.Object = oldId.Object; } } } this.ClearEmptyAssoc(f); return newObjects; } /** * 清理拷贝后的实体空引用 */ ClearEmptyAssoc(f) { let tempF = new CADFiler(); tempF.database = this; for (let [index, objectId] of f.idMaping) { if (objectId.Object && objectId.Index !== index) { tempF.Clear(); objectId.Object.WriteFile(tempF); tempF.Reset(); if (objectId.Object instanceof Entity) objectId.Object.AutoUpdate = false; objectId.Object.ReadFile(tempF); if (objectId.Object instanceof Entity) objectId.Object.AutoUpdate = true; } } } DeepCloneObject(filer, object, owner, idMap = new Map()) { if (idMap.has(object.Id)) return idMap.get(object.Id).Object; if (owner instanceof SymbolTable) //应该不能直接拷贝这类型的实体,因为会直接名称重复 return; filer.Data.length = 0; filer.Reset(); filer.WriteObject(object); let newObject = filer.ReadObject(); if (!(newObject instanceof Light) && newObject instanceof Entity) //Light类的对象不能拷贝绘制 否则出错 newObject.CloneDrawObject(object); this.AllocationObjectId(newObject); idMap.set(object.Id, newObject.Id); for (let [index, id] of filer.idMaping) { if (!id.Object && index < 100) { let oldId = this.GetObjectId(index, false); if (oldId) { id.Index = index; id.Object = oldId.Object; } } } //拷贝硬绑定对象(提前拷贝) while (filer.hardObjectIds.size > 0) { let hardObjectIds = filer.hardObjectIds; filer.hardObjectIds = new Set(); for (let idIndex of hardObjectIds) { let objectId = this.GetObjectId(idIndex, false); let object = objectId?.Object; if (object === undefined) continue; //对象已经被删除 if (!object.Owner) console.error("无主?"); if (object.Owner.Object instanceof SymbolTable) { // idMap.set(objectId, newId); //无用 因为每次都会进这里 //当我们拷贝样式(图层,材质,标注样式,文字样式(这种以Name-Value对应的记录)时,由于名称不能重复,所以拷贝会失败,这时我们把它转换为软引用,我们就可以避免拷贝,并且保持引用正常) //newId._RelevancyType = RelevancyType.Soft;//这句已经没用了 let newId = filer.idMaping.get(idIndex); newId.Index = objectId.Index; newId.Object = objectId.Object; } else this.DeepCloneObject(filer, object, object.Owner.Object, idMap); //指向新对象 } } owner.Add(newObject, false); return newObject; } /** * 注意:跨图纸拷贝(WblockCloneObjects) 同图纸拷贝(DeppCloneObjects) * * 将来自不同数据库的对象列表拷贝到本数据库中. * 当前支持使用HardId模式来硬关联某个对象,使该对象能够在WblockClone时一起被带过来. * 当前不支持硬关联对象的Owner不是默认的容器. * 如果需要这么做,请将该对象的Owner设置为Hard关联 * @param objects 对象不能属于本数据库 * @param owner 克隆对象的新容器 * @param idMap id映射 */ WblockCloneObejcts(objects, owner, idMap, drc, filer = new WblockCloneFiler) { Entity.__ReadFileIng__ = true; for (let obj of objects) filer.cloned.add(obj.objectId.Index); for (let obj of objects) if (obj instanceof Light) this.WblockCloneObject(obj, this.Lights, idMap, drc, filer); else this.WblockCloneObject(obj, owner, idMap, drc, filer); this.ClearEmptyAssoc(filer); Entity.__ReadFileIng__ = false; return objects.map(o => idMap.get(o.Id).Object); } /** * 克隆引用对象 WriteHardObjectId */ WblockCloneReferenceObject(object, f, idMap, drc) { let oldData = f.Data; f.Data = []; let oldDb = object.Db; let hardObjectIds = f.hardObjectIds; f.hardObjectIds = new Set(); for (let idIndex of hardObjectIds) { let oldId = oldDb.GetObjectId(idIndex); if (!(oldId?.Object) || oldId.IsErase) continue; //对已经被删除的对象不进行拷贝 //使用旧的OwnerId得到新的OwnerId,假设所有者都是数据库默认存在的. //TODO: 当OwnerId>100时,表示这个所有者不是数据库里面默认存在的,那么应该将Owner拷贝过来. let newOwnerId = this.GetObjectId(oldId.Object.Owner.Index); //owner.Db === this let newOwner = newOwnerId.Object; this.WblockCloneObject(oldId.Object, newOwner, idMap, drc, f); } f.Data = oldData; } WblockCloneObject(object, owner, idMap, drc, filer) { //克隆的对象有可能被其他的对象依赖并且克隆完毕了. let cloneId = filer.idMaping.get(object.Id.Index); if (cloneId && cloneId.Object) return; filer.Data.length = 0; //表示该对象已经被拷贝了 filer.cloned.add(object.Id.Index); if (owner instanceof SymbolTable) { let record = object; let name = record.Name; if (owner.Has(name)) //名称重复 { let status = drc; if (status === DuplicateRecordCloning.Rename) { //new name for (let i = 1;; i++) { let nname = `${name}(${i})`; if (!owner.Has(nname)) { name = nname; break; } } filer.WriteObject(record); this.WblockCloneReferenceObject(record, filer, idMap, drc); filer.Reset(); let newRecord = filer.ReadObject(); newRecord.Owner = undefined; newRecord.Name = name; this.AllocationObjectId(newRecord); owner.Add(newRecord, false); idMap.set(object.Id, newRecord.Id); } else if (status === DuplicateRecordCloning.Replace) { let oldRecord = owner.GetAt(name); //将f的id映射设置为旧的id filer.idMaping.set(object.Id.Index, oldRecord.Id); record.WriteFile(filer); this.WblockCloneReferenceObject(record, filer, idMap, drc); filer.Reset(); //此时重新读取的话,将会得到原先的id,实现id不变 oldRecord.ReadFile(filer); oldRecord.Owner = owner.Id; idMap.set(object.Id, oldRecord.Id); } else if (status === DuplicateRecordCloning.Ignore) { let oldRecord = owner.GetAt(name); filer.idMaping.set(object.Id.Index, oldRecord.Id); idMap.set(object.Id, oldRecord.Id); } return; } } filer.WriteObject(object); this.WblockCloneReferenceObject(object, filer, idMap, drc); filer.Reset(); let newObject = filer.ReadObject(); this.AllocationObjectId(newObject); //先给予Id,避免历史记录里面没有id if (owner === this.ModelSpace && newObject instanceof Entity) newObject.CloneDrawObject(object); owner.Add(newObject, false); idMap.set(object.Id, newObject.Id); } /** * 为拷贝出来的对象分配id索引,并在数据库中注册 */ AllocationObjectId(object) { object.Id.Index = this.idIndex++; this.idMap.set(object.Id.Index, object.Id); } Insert() { } /** * 写块 * @param outputDatabase 输出到指定的数据库 * @param objects * @param basePoint * @param cloning */ Wblock(outputDataBase, objects, basePoint, cloning) { } //#endregion //#region IdManager //创建一个id,自动递增它的索引号,并且会自动加入到db的id列表中. AllocateId() { return this.GetObjectId(this.idIndex++, true); } GetObjectId(index, create = false) { //id 从1开始,0被保留作为空id if (index === 0) return undefined; let id = this.idMap.get(index); if (!create || id) return id; id = new ObjectId(index); this.idMap.set(index, id); return id; } DeleteId(idIndex) { this.idMap.delete(idIndex); } }; Database = __decorate([ Factory ], Database); let FaceEntity = class FaceEntity extends Entity { constructor(p1 = new Vector3(), p2 = new Vector3(), p3 = new Vector3(), normal = new Vector3()) { super(); this.p1 = p1; this.p2 = p2; this.p3 = p3; this.normal = normal; } InitDrawObject(renderType = RenderType.Wireframe) { let g = new Geometry(); g.vertices.push(this.p1, this.p2, this.p3); g.faces.push(new Face3(0, 1, 2)); return new Line$1(g); } //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this.p1.fromArray(file.Read()); this.p2.fromArray(file.Read()); this.p3.fromArray(file.Read()); this.normal.fromArray(file.Read()); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.p1.toArray()); file.Write(this.p2.toArray()); file.Write(this.p3.toArray()); file.Write(this.normal.toArray()); } }; FaceEntity = __decorate([ Factory ], FaceEntity); /** * 外部引用的实体,比如glTF */ let EntityFbx = class EntityFbx extends Entity { constructor(_url) { super(); this._url = _url; this.OnlyRenderType = true; this._Size = new Vector3; //原始尺寸 this._ScaleSize = new Vector3; //缩放后的尺寸 this._Center = new Vector3; //盒子中心 this._OverWriteMaterial = new Map(); //section index -> materialId } get IsVisible() { return HostApplicationServices.IsRoomEntityVisible && super.IsVisible; } get Url() { return this._url; } set Url(url) { if (this._url !== url) { this.WriteAllObjectRecord(); this._url = url; } } get CurSize() { return this.ScaleSize.x ? this.ScaleSize.clone() : this._Size.clone(); } get ScaleSize() { return this._ScaleSize; } set ScaleSize(size) { if (!equalv3(size, this.ScaleSize.x ? this.ScaleSize : this._Size)) { this.WriteAllObjectRecord(); if (this._ScaleSize.x && equalv3(size, this._Size)) this._ScaleSize.set(0, 0, 0); else this._ScaleSize.copy(size); this.Update(); } } get Scale() { if (this._ScaleSize.x && this._Size.x) return this._ScaleSize.clone().divide(this._Size); return new Vector3(1, 1, 1); } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } get BoundingBoxInOCS() { let box = new Box3Ext(this._Size.clone().multiplyScalar(-0.5).add(this._Center), this._Size.clone().multiplyScalar(0.5).add(this._Center)); if (this._ScaleSize.x) { box.applyMatrix4(new Matrix4().makeScale(this._ScaleSize.x / this._Size.x, this._ScaleSize.y / this._Size.y, this._ScaleSize.z / this._Size.z)); } return box; } get OverWriteMaterial() { return this._OverWriteMaterial; } IsMtlLockAtSlot(slotIndex) { if (this.LockMaterial) return true; const curMtl = this._OverWriteMaterial.get(slotIndex); if (curMtl?.Object?.IsMaterialLock) return true; return false; } SetMaterialAtSlot(mtl, slotIndex) { if (this.IsMtlLockAtSlot(slotIndex)) return; if (this._OverWriteMaterial.get(slotIndex) !== mtl) { this.WriteAllObjectRecord(); this._OverWriteMaterial.set(slotIndex, mtl); this.Update(UpdateDraw.Material); } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._OverWriteMaterial.get(i); if (curMtl?.Object?.IsMaterialLock) continue; this.WriteAllObjectRecord(); this._OverWriteMaterial.set(i, mtl); } } else { const curMtl = this._OverWriteMaterial.get(0); if (!curMtl?.Object?.IsMaterialLock) { this.WriteAllObjectRecord(); this._OverWriteMaterial.set(0, mtl); } } } }); } this.Update(UpdateDraw.Material); } GetMtlLockedStatus() { let partMtlLocked = false; let allMtlLocked = true; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._OverWriteMaterial.get(i); if (curMtl?.Object?.IsMaterialLock) { partMtlLocked = true; continue; } allMtlLocked = false; } } else { allMtlLocked = false; const curMtl = this._OverWriteMaterial.get(0); if (curMtl?.Object?.IsMaterialLock) partMtlLocked = allMtlLocked = true; } } }); } return { allMtlLocked, partMtlLocked, }; } _GetValidOWMtlRecord(index, containErased = false) { const obj = this._OverWriteMaterial.get(index)?.Object; if (obj) if (containErased || !obj.IsErase) return obj; return undefined; } // 注意:不含默认的fbx材质record GetPhyMtlRecords(containErased = false) { const materials = []; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._GetValidOWMtlRecord(i, containErased); if (curMtl) materials.push(curMtl); } } else { const curMtl = this._GetValidOWMtlRecord(0, containErased); if (curMtl) materials.push(curMtl); } } }); } return materials; } //通过二进制数组生成fbx模型 LoadFBXModelFromArrayBuffer(fbxArray) { return; } CloneDrawObject(from) { } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let p = this.Position; p.applyMatrix4(m); m.extractBasis(Entity._xa, Entity._ya, Entity._za); let scaleX = Entity._xa.length(); let scaleY = Entity._ya.length(); let scaleZ = Entity._za.length(); if (!this._ScaleSize.x) this._ScaleSize.copy(this._Size); this._ScaleSize.x *= scaleX; this._ScaleSize.y *= scaleY; this._ScaleSize.z *= scaleZ; Entity._xa.normalize(); Entity._ya.normalize(); Entity._za.normalize(); m = new Matrix4().makeBasis(Entity._xa, Entity._ya, Entity._za); this.ApplyMatrix(m); this.Position = p; this.Update(); return this; } _GetValidOWMtl(index) { const obj = this._OverWriteMaterial.get(index)?.Object; const owMtl = !obj?.IsErase && obj?.Material; if (!owMtl) return; return owMtl; } MoveGripPoints(indexList, vec) { if (indexList.length) { this.WriteAllObjectRecord(); this.Position = this.Position.add(vec); } } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._url = file.Read(); if (ver > 1) { this._Size.x = file.Read(); this._Size.y = file.Read(); this._Size.z = file.Read(); this._Center.x = file.Read(); this._Center.y = file.Read(); this._Center.z = file.Read(); this._ScaleSize.x = file.Read(); this._ScaleSize.y = file.Read(); this._ScaleSize.z = file.Read(); } this._OverWriteMaterial.clear(); if (ver > 2) { let size = file.Read(); for (let i = 0; i < size; i++) { let index = file.Read(); let id = file.ReadHardObjectId(); if (id) this._OverWriteMaterial.set(index, id); } } if (ver > 3) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this._url); //2 file.Write(this._Size.x); file.Write(this._Size.y); file.Write(this._Size.z); file.Write(this._Center.x); file.Write(this._Center.y); file.Write(this._Center.z); file.Write(this._ScaleSize.x); file.Write(this._ScaleSize.y); file.Write(this._ScaleSize.z); //ver3 file.Write(this._OverWriteMaterial.size); for (let [index, id] of this._OverWriteMaterial) { file.Write(index); file.WriteHardObjectId(id); } // ver4 file.WriteBool(this._LockMaterial); } }; EntityFbx = __decorate([ Factory ], EntityFbx); new Box3; /** * 外部引用的实体,比如glTF */ let EntityRef = class EntityRef extends Entity { // `/Data/ASSETS/DXAA_0001` constructor(_url) { super(); this._url = _url; this.OnlyRenderType = true; this._Size = new Vector3; //原始尺寸 this._ScaleSize = new Vector3; //缩放后的尺寸 this._Center = new Vector3; //盒子中心 this._OverWriteMaterial = new Map(); //section index -> materialId } get IsVisible() { return HostApplicationServices.IsRoomEntityVisible && super.IsVisible; } get Url() { return this._url; } set Url(url) { if (this._url !== url) { this.WriteAllObjectRecord(); this._url = url; } } get CurSize() { return this.ScaleSize.x ? this.ScaleSize.clone() : this._Size.clone(); } get ScaleSize() { return this._ScaleSize; } set ScaleSize(size) { if (!equalv3(size, this.ScaleSize.x ? this.ScaleSize : this._Size)) { this.WriteAllObjectRecord(); if (this._ScaleSize.x && equalv3(size, this._Size)) this._ScaleSize.set(0, 0, 0); else this._ScaleSize.copy(size); this.Update(); } } get Scale() { if (this._ScaleSize.x && this._Size.x) return this._ScaleSize.clone().divide(this._Size); return new Vector3(1, 1, 1); } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } get BoundingBoxInOCS() { let box = new Box3Ext(this._Size.clone().multiplyScalar(-0.5).add(this._Center), this._Size.clone().multiplyScalar(0.5).add(this._Center)); if (this._ScaleSize.x) { box.applyMatrix4(new Matrix4().makeScale(this._ScaleSize.x / this._Size.x, this._ScaleSize.y / this._Size.y, this._ScaleSize.z / this._Size.z)); } return box; } CloneDrawObject(from) { for (let [type, obj] of from._CacheDrawObject) { let oldUserDaata = obj.userData; obj.traverse(o => o.userData = {}); let newObj = obj.clone(); let mesh1 = []; let mesh2 = []; obj.traverse(o => { if (o instanceof Mesh) mesh1.push(o); }); newObj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) o.material = [...o.material]; mesh2.push(o); } }); for (let i = 0; i < mesh1.length; i++) { mesh2[i]['__old_material__'] = mesh1[i]['__old_material__']; } obj.userData = oldUserDaata; // obj.userData.IsClone = true;//因为这个实体不需要修改内部的geom 所以我们可以复用她 newObj.matrix = this._Matrix; newObj.userData = { Entity: this }; // newObj.userData.IsClone = true; //因为这个实体不需要修改内部的geom 所以我们可以复用她 this._CacheDrawObject.set(type, newObj); } this.NeedUpdateFlag = UpdateDraw.None; } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let p = this.Position; p.applyMatrix4(m); m.extractBasis(Entity._xa, Entity._ya, Entity._za); let scaleX = Entity._xa.length(); let scaleY = Entity._ya.length(); let scaleZ = Entity._za.length(); if (!this._ScaleSize.x) this._ScaleSize.copy(this._Size); this._ScaleSize.x *= scaleX; this._ScaleSize.y *= scaleY; this._ScaleSize.z *= scaleZ; Entity._xa.normalize(); Entity._ya.normalize(); Entity._za.normalize(); m = new Matrix4().makeBasis(Entity._xa, Entity._ya, Entity._za); this.ApplyMatrix(m); this.Position = p; this.Update(); return this; } get OverWriteMaterial() { return this._OverWriteMaterial; } IsMtlLockAtSlot(slotIndex) { if (this.LockMaterial) return true; const curMtl = this._OverWriteMaterial.get(slotIndex); if (curMtl?.Object?.IsMaterialLock) return true; return false; } SetMaterialAtSlot(mtl, slotIndex) { if (this.IsMtlLockAtSlot(slotIndex)) return; if (this._OverWriteMaterial.get(slotIndex) !== mtl) { this.WriteAllObjectRecord(); this._OverWriteMaterial.set(slotIndex, mtl); this.Update(UpdateDraw.Material); } } SetAllMaterialAtSlot(mtl) { if (this.LockMaterial) return; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._OverWriteMaterial.get(i); if (curMtl?.Object?.IsMaterialLock) continue; this.WriteAllObjectRecord(); this._OverWriteMaterial.set(i, mtl); } } else { const curMtl = this._OverWriteMaterial.get(0); if (!curMtl?.Object?.IsMaterialLock) { this.WriteAllObjectRecord(); this._OverWriteMaterial.set(0, mtl); } } } }); } this.Update(UpdateDraw.Material); } GetMtlLockedStatus() { let partMtlLocked = false; let allMtlLocked = true; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._OverWriteMaterial.get(i); if (curMtl?.Object?.IsMaterialLock) { partMtlLocked = true; continue; } allMtlLocked = false; } } else { allMtlLocked = false; const curMtl = this._OverWriteMaterial.get(0); if (curMtl?.Object?.IsMaterialLock) partMtlLocked = allMtlLocked = true; } } }); } return { partMtlLocked, allMtlLocked, }; } _GetValidOWMtlRecord(index, containErased = false) { const obj = this._OverWriteMaterial.get(index)?.Object; if (obj) if (containErased || !obj.IsErase) return obj; return undefined; } GetPhyMtlRecords(containErased = false) { const materials = []; for (let [, obj] of this._CacheDrawObject) { obj.traverse(o => { if (o instanceof Mesh) { if (Array.isArray(o.material)) { for (let i = 0; i < o.material.length; i++) { const curMtl = this._GetValidOWMtlRecord(i, containErased); if (curMtl) materials.push(curMtl); } } else { const curMtl = this._GetValidOWMtlRecord(0, containErased); if (curMtl) materials.push(curMtl); } } }); } return materials; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let box = this.BoundingBox; let [x1, y1, z1] = [box.min.x, box.min.y, box.min.z]; let [x2, y2, z2] = [box.max.x, box.max.y, box.max.z]; switch (snapMode) { case ObjectSnapMode.End: return this.GetGripPoints(); case ObjectSnapMode.Mid: let mid = [ new Vector3(x1, y1, (z1 + z2) / 2), new Vector3(x1, y2, (z1 + z2) / 2), new Vector3(x2, y2, (z1 + z2) / 2), new Vector3(x2, y1, (z1 + z2) / 2), ]; let midline = [ new Vector3(x1, (y1 + y2) / 2, z1), new Vector3(x2, (y1 + y2) / 2, z1), new Vector3((x1 + x2) / 2, y2, z1), new Vector3((x1 + x2) / 2, y1, z1), ]; let v = new Vector3(0, 0, z2); let mids = []; mids.push(...mid, ...midline, ...midline.map(p => p.clone().add(v))); return mids; case ObjectSnapMode.Nea: let lines = BoxLine(this.BoundingBox); let neas = []; for (let l of lines) neas.push(...l.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); return neas; } return []; } GetGripPoints() { return []; //这些点必须按照boxInOcs矩阵显示 并且这个点太多了 太烦了 暂时关闭 } MoveGripPoints(indexList, vec) { if (indexList.length) { this.WriteAllObjectRecord(); this.Position = this.Position.add(vec); } } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._url = file.Read(); if (ver > 1) { this._Size.x = file.Read(); this._Size.y = file.Read(); this._Size.z = file.Read(); this._Center.x = file.Read(); this._Center.y = file.Read(); this._Center.z = file.Read(); this._ScaleSize.x = file.Read(); this._ScaleSize.y = file.Read(); this._ScaleSize.z = file.Read(); } this._OverWriteMaterial.clear(); if (ver > 2) { let size = file.Read(); for (let i = 0; i < size; i++) { let index = file.Read(); let id = file.ReadHardObjectId(); if (id) this._OverWriteMaterial.set(index, id); } } if (ver > 3) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this._url); //2 file.Write(this._Size.x); file.Write(this._Size.y); file.Write(this._Size.z); file.Write(this._Center.x); file.Write(this._Center.y); file.Write(this._Center.z); file.Write(this._ScaleSize.x); file.Write(this._ScaleSize.y); file.Write(this._ScaleSize.z); //ver3 file.Write(this._OverWriteMaterial.size); for (let [index, id] of this._OverWriteMaterial) { file.Write(index); file.WriteHardObjectId(id); } // ver4 file.WriteBool(this._LockMaterial); } }; EntityRef = __decorate([ Factory ], EntityRef); // export enum MaterialType // { // 木纹 = "木纹", // 乳胶漆 = "乳胶漆", // 烤漆 = "烤漆", // 金属 = "金属", // 亚光金属 = "亚光金属", // 自发光 = "自发光", // 大理石 = "大理石", // 混凝土 = "混凝土", // 粗糙石材 = "粗糙石材", // 玻璃 = "玻璃", // 皮革 = "皮革", // 布料 = "布料", // 竹藤编织 = "竹藤编织", // } //默认参数 const DefaultParam = Object.freeze({ //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(0.5, 0.5, 0.5), //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion //透明 transparent: false, //透明度 0-, opacity: 1, //不透明度, //#region 透明度附加 默认折叠 opacityContrast: 1, //不透明度对比 默认1 opacityBorder: 1, //不透明度边界 默认1 opacityMaximum: 1, //不透明度最大值 默认1 opacityMinimum: 0.3, // 不透明度最小值 默认0.3 //#endregion matalness: 0, //金属性 0-, bumpScale: 0.1, //凹凸比(法线强度) roughness: 0.2, //粗糙, specular: 1, //高, selfLuminous: 0, //自发光强度 //#region 菲涅尔 fresnelPO: 1, //菲涅尔对比度 默认1 范围-1至10 fresnelST: 1, //菲涅尔强度 默认1 范围0至20 fresnelLuminance: 1, //菲涅尔亮度 默认1 范围0至20 fresnelLightColor: new Color(1, 1, 1), //菲涅尔亮部颜色 默认白色 范围RGB fresnelDarkColor: new Color(1, 1, 1), //菲涅尔暗部颜色 默认白色 范围RGB //#endregion UVType: 0, //坐标 世界坐标或UV坐标 sharpen: 1, //锐化 默认1 范围0-20 默认折叠 }); //默认材质参数映射 const DefaultParamMap = Object.freeze({ 木纹: Object.freeze(Object.assign({ ...DefaultParam }, { //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光, matalness: 0, //金属性 0-, roughness: 0.5, //粗糙, bumpScale: 0.5, //凹凸比(法线强度) sharpen: 1, //锐化 默认1 范围0-20 默认折叠 selfLuminous: 0, //自发光强度 UVType: 0, //默认UV坐标 })), 乳胶漆: Object.freeze(Object.assign({ ...DefaultParam }, { //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(0.5, 0.5, 0.5), //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光, matalness: 0, //金属性 0-, roughness: 0.5, //粗糙, bumpScale: 0, //凹凸比(法线强度) sharpen: 1, //锐化 默认1 范围0-20 默认折叠 selfLuminous: 0, //自发光强度 UVType: 1, //默认世界坐标 })), 烤漆: Object.freeze(Object.assign({ ...DefaultParam }, { //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(0.04, 0.04, 0.04), //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光, matalness: 0, //金属性 0-, roughness: 0.1, //粗糙, bumpScale: 0, //凹凸比(法线强度) sharpen: 1, //锐化 默认1 范围0-20 默认折叠 selfLuminous: 0, //自发光强度 UVType: 1, //默认世界坐标 })), 金属: Object.freeze(Object.assign({ ...DefaultParam }, { //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光, matalness: 1, //金属性 0-, roughness: 0, //粗糙, bumpScale: 0, //凹凸比(法线强度) sharpen: 1, //锐化 默认1 范围0-20 默认折叠 selfLuminous: 0, //自发光强度 UVType: 1, //默认世界坐标 })), 亚光金属: Object.freeze(Object.assign({ ...DefaultParam }, { //基础色 color: "#ffffff", //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认0.5灰色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光, matalness: 1, //金属性 0-, roughness: 0.2, //粗糙, bumpScale: 0, //凹凸比(法线强度) sharpen: 1, //锐化 默认1 范围0-20 默认折叠 selfLuminous: 0, //自发光强度 UVType: 1, //默认世界坐标 })), 自发光: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 默认1 范围0-1 matalness: 0, //金属性 默认0 范围0-1 roughness: 0, //粗糙度 默认0 范围0-1 bumpScale: 0, //法线强度 默认0 范围0-20 sharpen: 1, //锐化强度 默认1 范围0-20 selfLuminous: 10, //自发光亮度 默认10 范围0-200 UVType: 1, //默认世界坐标 })), 大理石: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 默认1 范围0-1 matalness: 0, //金属性 默认0 范围0-1 roughness: 0, //粗糙度 默认0 范围0-1 bumpScale: 0, //法线强度 默认0 范围0-20 sharpen: 1, //锐化强度 默认1 范围0-20 selfLuminous: 0, //自发光亮度 默认0 范围0-200 UVType: 1, //默认世界坐标 })), 混凝土: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 0.5, //高光 默认0.5 范围0-1 matalness: 0, //金属性 默认0 范围0-1 roughness: 1, //粗糙度 默认1 范围0-1 bumpScale: 10, //法线强度 默认10 范围0-20 sharpen: 1, //锐化强度 默认1 范围0-20 selfLuminous: 0, //自发光亮度 默认0 范围0-200 UVType: 1, //默认世界坐标 })), 粗糙石材: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 0.5, //高光 默认0.5 范围0-1 matalness: 0, //金属性 默认0 范围0-1 roughness: 1, //粗糙度 默认1 范围0-1 bumpScale: 5, //法线强度 默认5 范围0-20 sharpen: 1, //锐化强度 默认1 范围0-20 selfLuminous: 0, //自发光亮度 默认0 范围0-200 UVType: 1, //默认世界坐标 })), 玻璃: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认 1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 范围0-1 matalness: 1, //金属性 范围0-1 roughness: 0, //粗糙 范围0-1 bumpScale: 0, //凹凸比(法线强度) 范围0-20 sharpen: 1, //锐化 默认1 范围0-20 selfLuminous: 0, //自发光强度 范围0-200 //#region 透明度附加 默认折叠 opacity: 0.6, opacityContrast: 1, //不透明度对比 默认1 opacityBorder: 1, //不透明度边界 默认1 opacityMaximum: 1, //不透明度最大值 默认1 opacityMinimum: 0.3, // 不透明度最小值 默认0.3 //#endregion UVType: 1, //默认世界坐标 })), 皮革: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认 1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 范围0-1 matalness: 0.1, //金属性 范围0-1 roughness: 0.5, //粗糙 范围0-1 bumpScale: 1, //凹凸比(法线强度) 范围0-20 sharpen: 1, //锐化 默认1 范围0-20 selfLuminous: 0, //自发光强度 范围0-200 //#region 菲涅尔 fresnelPO: 1, //菲涅尔对比度 默认1 范围-1至10 fresnelST: 1, //菲涅尔强度 默认1 范围0至20 fresnelLuminance: 1, //菲涅尔亮度 默认1 范围0至20 fresnelLightColor: new Color(1, 1, 1), //菲涅尔亮部颜色 默认白色 范围RGB fresnelDarkColor: new Color(1, 1, 1), //菲涅尔暗部颜色 默认白色 范围RGB //#endregion UVType: 1, //默认世界坐标 })), 布料: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认 1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 范围0-1 matalness: 0, //金属性 范围0-1 roughness: 0.5, //粗糙 范围0-1 bumpScale: 1, //凹凸比(法线强度) 范围0-20 sharpen: 1, //锐化 默认1 范围0-20 selfLuminous: 0, //自发光强度 范围0-200 //#region 菲涅尔 fresnelPO: 1, //菲涅尔对比度 默认1 范围-1至10 fresnelST: 1, //菲涅尔强度 默认1 范围0至20 fresnelLuminance: 1, //菲涅尔亮度 默认1 范围0至20 fresnelLightColor: new Color(1, 1, 1), //菲涅尔亮部颜色 默认白色 范围RGB fresnelDarkColor: new Color(0.5, 0.5, 0.5), //菲涅尔暗部颜色 默认灰色 范围RGB //#endregion UVType: 1, //默认世界坐标 })), 竹藤编织: Object.freeze(Object.assign({ ...DefaultParam }, { //#region 基础色附加 baseColorluminance: 0, //基础色亮度 默认0 范围±1 baseColorLightColor: new Color(1, 1, 1), //基础色_亮部颜色 默认 1白色 范围RGB颜色 baseColorDarkColor: new Color(0, 0, 0), //基础色_暗部颜色 默认 0黑色 范围RGB颜色 baseColorSaturability: 1, //基础色饱和度 默认1 范围0-10; //#endregion specular: 1, //高光 范围0-1 matalness: 0, //金属性 范围0-1 roughness: 0.2, //粗糙 范围0-1 bumpScale: 2, //凹凸比(法线强度) 范围0-20 sharpen: 1, //锐化 默认1 范围0-20 selfLuminous: 0, //自发光强度 范围0-200 UVType: 1, //默认世界坐标 })), // 自定义: Object.freeze(Object.assign({ ...DefaultParam }, { // })) as MaterialParam, }); function SetMaterialParams(mtl, param) { for (let key in param) { let v = param[key]; if (v instanceof Color) { let c = mtl[key]; c.copy(v); } else mtl[key] = param[key]; } } /** * 弧形窗 */ let TemplateArcWindowRecord = class TemplateArcWindowRecord extends TemplateRecord { constructor() { super(); this.name = "弧形窗(自动)"; } InitWindowFrame(hole, arcWallIndex) { if (!hole.objectId) return; this.HoleObjectId = hole.objectId; this.ArcWallIndex = arcWallIndex; //下边框 let bottomFrame = Board.CreateBoard(50, 50, 50, BoardType.Layer); bottomFrame.ApplyMatrix(bottomFrame.OCSInv); bottomFrame.ColorIndex = 8; //玻璃 let glass = bottomFrame.Clone(); glass.ColorIndex = 7; //其余边框 let leftFrame = bottomFrame.Clone(); let rightFrame = bottomFrame.Clone(); let topFrame = bottomFrame.Clone(); let windowModel = new HardwareCompositeEntity(); windowModel.Entitys.push(leftFrame, rightFrame, topFrame, bottomFrame, glass); this._db.ModelSpace.Append(windowModel); this.Objects.push(windowModel.Id); //边框材质 let frameMaterial = this._db.MaterialTable.GetAt("弧形窗边框"); if (!frameMaterial) { frameMaterial = new PhysicalMaterialRecord(); SetMaterialParams(frameMaterial, DefaultParamMap.金属); frameMaterial.Name = "弧形窗边框"; frameMaterial.type = "金属"; frameMaterial.color = "#333333"; frameMaterial.roughness = 0.4; //粗糙度 frameMaterial.specular = 0.5; //高光 frameMaterial.Update(); this._db.MaterialTable.Add(frameMaterial); } windowModel.Material = frameMaterial.Id; //玻璃材质 let glassMaterial = this._db.MaterialTable.GetAt("弧形窗玻璃"); if (!glassMaterial) { glassMaterial = new PhysicalMaterialRecord(); SetMaterialParams(glassMaterial, DefaultParamMap.玻璃); glassMaterial.Name = "弧形窗玻璃"; glassMaterial.type = "玻璃"; glassMaterial.transparent = true; glassMaterial.opacity = 0.4; glassMaterial.Update(); this._db.MaterialTable.Add(glassMaterial); } glass.Material = glassMaterial.Id; } async Update() { await super.Update(); let [windowModelId] = this.Objects; if (!windowModelId || windowModelId.IsErase) return; let hole = this.HoleObjectId?.Object; let arcWall = hole?.FakerWalls[this.ArcWallIndex]; if (!hole || !arcWall) return; let windowModel = windowModelId.Object; let leftFrame = windowModel.Entitys[0]; let rightFrame = windowModel.Entitys[1]; let topFrame = windowModel.Entitys[2]; let bottomFrame = windowModel.Entitys[3]; let glass = windowModel.Entitys[4]; bottomFrame.ApplyMatrix(bottomFrame.OCSInv); let wpdist = hole.WpDist[0] !== 0 ? hole.WpDist[0] + arcWall.Thickness / 2 + 25 : 0; //外飘时考虑 墙厚 + 边框厚 + 外飘距离 const radius = arcWall.Radius; let leftArc = arcWall.LeftCurves[0]; let rightArc = arcWall.RightCurves[0]; //弧形窗角度 let allAngle = arcWall.AllAngle; let midAngle = allAngle / 2; let midDistance = (leftArc.EndPoint.distanceTo(leftArc.StartPoint) + rightArc.EndPoint.distanceTo(rightArc.StartPoint)) / 4; let maxThanPI = allAngle > Math.PI; //弧形圆点 let center; if (equaln$1(allAngle, Math.PI, 1e-7)) center = new Vector3(radius); else if (allAngle < Math.PI) center = new Vector3(midDistance, -Math.sqrt(radius * radius - midDistance * midDistance)); else if (maxThanPI) //弧形大于Π时,会翻转 { let radian = 2 * Math.PI / 360 * MathUtils.radToDeg(Math.PI - midAngle); center = new Vector3(radius + wpdist, Math.cos(radian) * (radius + wpdist)); } // let reduceAngle = MathUtils.degToRad(25 * 180 / (Math.PI * radius)); let startAngle = Math.PI / 2 + midAngle; let endAngle = Math.PI / 2 - midAngle; //内缩 // if (hole.FakerWalls.length === 2) // { // if (equalv3(arcWall.StartPoint.clone().applyMatrix4(hole.OCSInv), new Vector3(0))) // startAngle = startAngle + reduceAngle * (maxThanPI ? 1 : -1); // else // endAngle = endAngle + reduceAngle * (maxThanPI ? -1 : 1);; // } //下边框弧形轮廓 let bottomArc1 = new Arc(center, radius - 25 + wpdist, startAngle, endAngle); let bottomArc2 = new Arc(center, radius + 25 + wpdist, startAngle, endAngle); let bottomCloseLine1 = new Line(bottomArc1.StartPoint, bottomArc2.StartPoint); let bottomCloseLine2 = new Line(bottomArc1.EndPoint, bottomArc2.EndPoint); let bottomContourCurve = Polyline.Combine([bottomArc1, bottomCloseLine1, bottomArc2, bottomCloseLine2]); bottomFrame.ContourCurve = bottomContourCurve; //玻璃弧长50 的角度 let glassAngle1 = MathUtils.degToRad(50 * 180 / (Math.PI * (radius - 2))); let glassAngle2 = MathUtils.degToRad(50 * 180 / (Math.PI * (radius + 2))); //玻璃 let glassArc1 = new Arc(center, radius - 2 + wpdist, startAngle - glassAngle1, endAngle + glassAngle1); let glassArc2 = new Arc(center, radius + 2 + wpdist, startAngle - glassAngle2, endAngle + glassAngle2); let glassCloseLine1 = new Line(glassArc1.StartPoint, glassArc2.StartPoint); let glassCloseLine2 = new Line(glassArc1.EndPoint, glassArc2.EndPoint); let glassContourCurve = Polyline.Combine([glassArc1, glassCloseLine1, glassArc2, glassCloseLine2]); glass.Thickness = hole.Height - 100; glass.ApplyMatrix(glass.OCSInv); glass.ContourCurve = glassContourCurve; glass.Position = glass.Position.add(new Vector3(0, 0, 50)); //上边框 topFrame.CopyFrom(bottomFrame); topFrame.Position = bottomFrame.Position.add(new Vector3(0, 0, hole.Height - 50)); //边框弧长50 的角度 let angle1 = MathUtils.degToRad(50 * 180 / (Math.PI * (radius - 25))); let angle2 = MathUtils.degToRad(50 * 180 / (Math.PI * (radius + 25))); //左边框弧形轮廓 let leftArc1 = bottomArc1.Clone(); let leftArc2 = bottomArc2.Clone(); if (maxThanPI) { leftArc1.StartAngle = leftArc1.EndAngle + angle1; leftArc2.StartAngle = leftArc2.EndAngle + angle2; } else { leftArc1.EndAngle = leftArc1.StartAngle - angle1; leftArc2.EndAngle = leftArc2.StartAngle - angle2; } let leftCloseLine1 = new Line(leftArc1.StartPoint, leftArc2.StartPoint); let leftCloseLine2 = new Line(leftArc1.EndPoint, leftArc2.EndPoint); let leftFrameContourCurve = Polyline.Combine([leftArc1, leftCloseLine1, leftArc2, leftCloseLine2]); leftFrame.Thickness = hole.Height - 100; leftFrame.ApplyMatrix(leftFrame.OCSInv); leftFrame.ContourCurve = leftFrameContourCurve; leftFrame.Position = leftFrame.Position.add(new Vector3(0, 0, 50)); //右边框弧形轮廓 let rightArc1 = bottomArc1.Clone(); let rightArc2 = bottomArc2.Clone(); if (maxThanPI) { rightArc1.EndAngle = rightArc1.StartAngle - angle1; rightArc2.EndAngle = rightArc2.StartAngle - angle2; } else { rightArc1.StartAngle = rightArc1.EndAngle + angle1; rightArc2.StartAngle = rightArc2.EndAngle + angle2; } let rightCloseLine1 = new Line(rightArc1.StartPoint, rightArc2.StartPoint); let rightCloseLine2 = new Line(rightArc1.EndPoint, rightArc2.EndPoint); let rightFrameContourCurve = Polyline.Combine([rightArc1, rightCloseLine1, rightArc2, rightCloseLine2]); rightFrame.Thickness = hole.Height - 100; rightFrame.ApplyMatrix(rightFrame.OCSInv); rightFrame.ContourCurve = rightFrameContourCurve; rightFrame.Position = rightFrame.Position.add(new Vector3(0, 0, 50)); //定位 let pos = arcWall.IsClockWise ? arcWall.StartPoint.clone() : arcWall.EndPoint.clone(); let x = arcWall.EndPoint.sub(arcWall.StartPoint).normalize().multiplyScalar(arcWall.IsClockWise ? 1 : -1); let z = ZAxis; let y = new Vector3().crossVectors(ZAxis, x); if (maxThanPI) { //外飘时基点外移 let v = arcWall.Center.clone().sub(pos).normalize(); pos.subVectors(pos, v.multiplyScalar(wpdist)); //边框处在第一象限 let scalar = (rightArc1.StartPoint.x + rightArc2.StartPoint.x) / 2; pos.subVectors(pos, x.clone().multiplyScalar(scalar)); } windowModel.OCS = new Matrix4().makeBasis(x, y, z).setPosition(pos); windowModel.Update(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.HoleObjectId = file.ReadObjectId(); this.ArcWallIndex = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.WriteObjectId(this.HoleObjectId); file.Write(this.ArcWallIndex); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; __decorate([ AutoRecord ], TemplateArcWindowRecord.prototype, "HoleObjectId", void 0); __decorate([ AutoRecord ], TemplateArcWindowRecord.prototype, "ArcWallIndex", void 0); TemplateArcWindowRecord = __decorate([ Factory ], TemplateArcWindowRecord); async function BuildLayerBoards(opt, space, grooveOption) { let spaceBox = space.SpaceBox; let spaceOCS = space.SpaceOCS; let size = spaceBox.getSize(new Vector3()); //使用模块 添加参数表达式 计算值 let template = new TemplateRecord().InitBaseParams(); CreateTemplateParam(template, size, opt, grooveOption); //独立参数 { AppendTemplateParam(template, "QS", 0, opt.calcFrontShrink, "前缩"); //前缩 AppendTemplateParam(template, "ZS", 0, opt.calcLeftShrink, "左缩"); //左缩 AppendTemplateParam(template, "YS", 0, opt.calcRightShrink, "右缩"); //右缩 AppendTemplateParam(template, "BS", 0, opt.calcHeight, "板深"); //板宽 } await template.UpdateTemplateTree(); let width; if (opt.isTotalLength) width = size.y; else width = template.GetParam("BS").value; let count = template.GetParam("C").value; count = count < 1 ? 1 : count; opt.count = count; let type = opt.boardRelative; let spaceSize = template.GetParam("S").value; let frontShrink = template.GetParam("QS").value; width -= frontShrink; if (width <= 0) { Log("宽度无效,可能前缩过大,请修正", LogType.Error); return []; } let leftShrink = template.GetParam("ZS").value; let rightShrink = template.GetParam("YS").value; let thickness = template.GetParam("BH").value; let len = size.x - leftShrink - rightShrink; if (len <= 0) { Log("长度无效,可能左缩右缩过大,请修正", LogType.Error); return []; } let board = Board.CreateBoard(len, width, thickness, BoardType.Layer); if (grooveOption) { board.KnifeRadius = template.GetParam("R").value; board.GroovesAddDepth = template.GetParam("AD").value; board.GroovesAddWidth = template.GetParam("AW").value; board.GroovesAddLength = template.GetParam("AL").value; } opt.height = len; opt.width = width; //等分单层空间大小 let singleSize = (size.z - (thickness * count)) / (count + 1); let brs = []; for (let i = 1; i <= count; i++) { let b = board.Clone(); b.Name = opt.name; if (type === BrRelativePos.Top) b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(size.x - rightShrink, frontShrink, size.z - (spaceSize + thickness) * i)))); else if (type === BrRelativePos.Bottom) b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(size.x - rightShrink, frontShrink, spaceSize * i + (i - 1) * thickness)))); else { b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(size.x - rightShrink, frontShrink, singleSize * i + (i - 1) * thickness)))); } b.ApplyMatrix(spaceOCS); brs.push(b); } return brs; } async function BuildVerticalBoards(opt, space, grooveOption) { const spaceBox = space.SpaceBox; const spaceOCS = space.SpaceOCS; let size = spaceBox.getSize(new Vector3()); //使用模块 添加参数表达式 计算值 let template = new TemplateRecord().InitBaseParams(); CreateTemplateParam(template, size, opt, grooveOption); //独立参数 { AppendTemplateParam(template, "QS", 0, opt.calcFrontShrink, "前缩"); //前缩 AppendTemplateParam(template, "HS", 0, opt.calcBottomShrink, "后缩"); //后缩 AppendTemplateParam(template, "BS", 0, opt.calcWidth, "板深"); //板深 AppendTemplateParam(template, "BG", 0, opt.calcHeight, "板高"); //板高 } await template.UpdateTemplateTree(); let frontShrink = template.GetParam("QS").value; let bottomShink = template.GetParam("HS").value; let width; if (opt.isTotalWidth) width = size.y - frontShrink; else { width = template.GetParam("BS").value; } if (width <= 0) { Log("宽度无效,可能前缩过大,请修正", LogType.Error); return []; } let length; if (opt.isTotalLength) length = size.z - bottomShink; else length = template.GetParam("BG").value; let count = template.GetParam("C").value; count = count < 1 ? 1 : count; opt.count = count; let type = opt.boardRelative; let spaceSize = template.GetParam("S").value; let thickness = template.GetParam("BH").value; thickness = thickness < 1 ? 1 : thickness; let board = Board.CreateBoard(length, width, thickness, BoardType.Vertical); if (grooveOption) { board.KnifeRadius = template.GetParam("R").value; board.GroovesAddDepth = template.GetParam("AD").value; board.GroovesAddWidth = template.GetParam("AW").value; board.GroovesAddLength = template.GetParam("AL").value; } opt.height = length; opt.width = width; //等分单层空间大小 let singleSize = (size.x - (thickness * count)) / (count + 1); let brs = []; for (let i = 1; i <= count; i++) { let b = board.Clone(); b.Name = opt.name; if (type === BrRelativePos.Left) b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(spaceSize * i + (i - 1) * thickness, frontShrink, bottomShink)))); else if (type === BrRelativePos.Right) b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(size.x - (spaceSize + thickness) * i, frontShrink, bottomShink)))); else b.ApplyMatrix(MoveMatrix(spaceBox.min.clone().add(new Vector3(singleSize * i + (i - 1) * thickness, frontShrink, bottomShink)))); b.ApplyMatrix(spaceOCS); brs.push(b); } return brs; } async function BuildBehindBoards(opt, space, grooveOption) { let newBox = space.SpaceBox.clone(); let spaceOcs = space.SpaceOCS; //判断延伸 let leftExt = opt.leftExt; let rightExt = opt.rightExt; let topExt = opt.topExt; let bottomExt = opt.bottomExt; newBox.max.add(new Vector3(leftExt + rightExt, 0, topExt + bottomExt)); newBox.translate(new Vector3(-leftExt, 0, -bottomExt)); //获取背板高度 let size = newBox.getSize(new Vector3()); //使用模块 添加参数表达式 计算值 let template = new TemplateRecord().InitBaseParams(); CreateTemplateParam(template, size, opt, grooveOption); //独立参数 { AppendTemplateParam(template, "BG", 0, opt.calcHeight, "板高"); //板高 AppendTemplateParam(template, "MD", 0, opt.calcMoveDist, "移动距离"); //移动距离 } await template.UpdateTemplateTree(); let height; if (opt.boardPosition === BehindHeightPositon.AllHeight) height = size.z; else height = template.GetParam("BG").value; let moveDist = template.GetParam("MD").value; //判断背板位置,更新背板高度 switch (opt.boardPosition) { case BehindHeightPositon.ForTop: newBox.min.add(new Vector3(0, 0, size.z - height)); newBox.translate(new Vector3(0, 0, moveDist)); break; case BehindHeightPositon.ForBottom: newBox.max.add(new Vector3(0, 0, height)); newBox.translate(new Vector3(0, 0, -moveDist)); break; } let count = template.GetParam("C").value; count = count < 1 ? 1 : count; opt.count = count; //相对位置 let relPos = opt.boardRelative; //单层空间宽度 let spaceSize = template.GetParam("S").value; let thickness = template.GetParam("BH").value; thickness = thickness < 1 ? 1 : thickness; let board = Board.CreateBoard(height, size.x, thickness, BoardType.Behind); if (grooveOption) { board.KnifeRadius = template.GetParam("R").value; board.GroovesAddDepth = template.GetParam("AD").value; board.GroovesAddWidth = template.GetParam("AW").value; board.GroovesAddLength = template.GetParam("AL").value; } opt.height = height; opt.width = size.x; //等分单层空间大小 let singleSize = (size.y - (thickness * count)) / (count + 1); let brs = []; //构建板件 for (let i = 1; i <= count; i++) { let b = board.Clone(); b.Name = opt.name; if (relPos === BrRelativePos.Front) b.ApplyMatrix(MoveMatrix(newBox.min.clone().add(new Vector3(0, spaceSize * i + thickness * i, 0)))); else if (relPos === BrRelativePos.Back) b.ApplyMatrix(MoveMatrix(newBox.min.clone().add(new Vector3(0, size.y - spaceSize * i - (i - 1) * thickness, 0)))); else b.ApplyMatrix(MoveMatrix(newBox.min.clone().add(new Vector3(0, (singleSize + thickness) * i, 0)))); b.ApplyMatrix(spaceOcs); brs.push(b); } return brs; } function ExtendsBoardThickness(temp, thickness) { let bhPar = temp.GetParam("BH"); if (bhPar) { bhPar.expr = thickness; if (temp.Parent) { let rootBh = safeEval(temp.Root.GetParam("BH")?.value); bhPar.expr = rootBh === thickness ? "$BH" : thickness; } } } function AppendTemplateParam(template, name, value, expr, description) { let param = new TemplateParam(); param.name = name; param.value = value; param.expr = expr; param.description = description; template.Params.push(param); } function CreateTemplateParam(template, size, opt, grooveOption) { template.GetParam("L").value = size.x; //空间长 template.GetParam("W").value = size.y; //空间深 template.GetParam("H").value = size.z; //空间高 let BHParam = template.GetParam("BH"); //板厚 BHParam.expr = opt.exprThickness ?? 18; AppendTemplateParam(template, "C", 1, opt.exprCount, "板件数量"); //板件数量 AppendTemplateParam(template, "S", 0, opt.calcSpaceSize, "空间距离"); //板件空间距离 AppendTemplateParam(template, "R", 3, grooveOption?.exprKnifeRadius ?? "3", "刀半径"); //刀半径 AppendTemplateParam(template, "AL", 0, grooveOption?.exprGrooveAddLength ?? "0", "槽加长"); //槽加长 AppendTemplateParam(template, "AD", 0, grooveOption?.exprGrooveAddDepth ?? "0", "槽加深"); //槽加深 AppendTemplateParam(template, "AW", 0, grooveOption?.exprGrooveAddWidth ?? "0", "槽加宽"); //槽加宽 } class DrawLatticeDrawerTool extends Singleton { constructor() { super(...arguments); this.haveTopBr = false; } get Config() { return this._config; } Draw(space, config) { this._config = config; this.space = space; this.ParseSpaceBrs(); this.HandleSpace(); let size = space.Size; if (config.arrayType === ELatticeArrayType.ByWidth) { config.widthCount = Math.floor((size.x + config.thickness) / (config.gripWidth + config.thickness)); config.depthCount = Math.floor((size.y + config.thickness) / (config.gripDepth + config.thickness)); } if (config.widthCount <= 0 || config.depthCount <= 0) { this.End(); return []; } let gripWidth = (size.x - ((config.widthCount - 1) * config.thickness)) / config.widthCount; let gripDepth = (size.y - (config.depthCount - 1) * config.thickness) / config.depthCount; config.gripWidth = gripWidth; config.gripDepth = gripDepth; let position = space.SpaceBox.min.clone(); let verBr = Board.CreateBoard(size.z, size.y, config.thickness, BoardType.Vertical); //分析切角圆弧的结果 let res = this.ParseArcLenOrObliuqeAng(verBr); if (!res) { this.End(); return []; } //左右侧板跟随 this.ChangeLeftRightBr(verBr); this.ParseBrTooth(verBr); this.ParseHighSealing(verBr, config.leftSealed, config.rightSealed, config.upSealed, config.downSealed, false); this.WriteBoardProcessOption(verBr); let lattices = []; for (let i = 1; i < config.widthCount; i++) { let br = verBr.Clone(); br.Name = "竖板" + i; let pos = position.clone(); pos.add(new Vector3(gripWidth * i + (i - 1) * config.thickness)); br.Position = pos; br.ApplyMatrix(space.SpaceOCS); lattices.push(br); } let beBr = Board.CreateBoard(size.z, size.x, config.thickness, BoardType.Behind); this.ParseBrTooth(beBr); this.ParseHighSealing(beBr, config.leftSealed, config.rightSealed, config.upSealed, config.downSealed, true); this.WriteBoardProcessOption(beBr); for (let i = 1; i < config.depthCount; i++) { let br = beBr.Clone(); br.Name = "横板" + i; let pos = position.clone(); pos.add(new Vector3(0, (gripDepth + config.thickness) * i)); br.Position = pos; br.ApplyMatrix(space.SpaceOCS); lattices.push(br); } this.End(); return lattices; } HandleSpace() { const config = this.Config; let box = this.space.SpaceBox; box.max.add(new Vector3(0, 0, -this.Config.downDist)); //处理间隙 box.min.add(new Vector3(config.space, config.space)); box.max.add(new Vector3(-config.space, -config.space)); if (this.frontBr && this.backBr) { let backPos = this.backBr.Position.applyMatrix4(this.frontBr.OCSInv); let addH = backPos.y + this.backBr.Height - this.frontBr.Height; if (addH > 0) { this.space.SpaceBox.max.add(new Vector3(0, 0, addH)); if (config.isAuto) config.arcLen = addH; } else Log("挡板高度大于等于格子抽板高度,无法自动识别弧度!", LogType.Error); } if (this.haveTopBr) { Log("顶板不为空,绘制格子抽可能错误!", LogType.Error); } } ChangeLeftRightBr(refBr) { const config = this.Config; if (this.leftBr && this.rightBr) { if (config.isChange) { let lWidth = this.leftBr.Width; let lHeight = this.leftBr.Height; let lgrooves = this.leftBr.Grooves.slice(); let rgrooves = this.rightBr.Grooves.slice(); let rWidth = this.rightBr.Width; let rHeight = this.rightBr.Height; this.leftBr.ContourCurve = refBr.ContourCurve.Clone(); this.rightBr.ContourCurve = refBr.ContourCurve.Clone(); this.leftBr.Width = lWidth; this.leftBr.Height = lHeight; this.rightBr.Width = rWidth; this.rightBr.Height = rHeight; if (this.leftBr.Grooves.length !== lgrooves.length) this.leftBr.AppendGrooves(lgrooves); if (this.rightBr.Grooves.length !== rgrooves.length) this.rightBr.AppendGrooves(rgrooves); } } } ParseBrTooth(br) { const config = this.Config; let grooveLenAdd = config.knifeRad; let initPts = []; let addWidth = config.grooveAddWidth / 2; //槽加长 if (br.BoardType === BoardType.Behind) { let p1 = new Vector3(br.Width - config.gripWidth + addWidth, br.Height); let p2 = new Vector3(br.Width - config.gripWidth + addWidth, br.Height / 2 - grooveLenAdd); let p3 = new Vector3(br.Width - config.gripWidth - br.Thickness - addWidth, br.Height / 2 - grooveLenAdd); let p4 = new Vector3(br.Width - config.gripWidth - br.Thickness - addWidth, br.Height); initPts.push(p1, p2, p3, p4); for (let i = 2; i < config.widthCount; i++) { initPts.push(...[p1, p2, p3, p4].map(p => p.clone().add(new Vector3(-(config.gripWidth + br.Thickness) * (i - 1))))); } let cu = br.ContourCurve; cu.AddVertexAt(cu.EndParam - 1, initPts.map(p => AsVector2(p))); br.ContourCurve = cu; } else { let p1 = new Vector3(config.gripDepth - addWidth, 0); let p2 = new Vector3(config.gripDepth - addWidth, br.Height / 2 + grooveLenAdd); let p3 = new Vector3(config.gripDepth + br.Thickness + addWidth, br.Height / 2 + grooveLenAdd); let p4 = new Vector3(config.gripDepth + br.Thickness + addWidth, 0); initPts.push(p1, p2, p3, p4); for (let i = 2; i < config.depthCount; i++) { initPts.push(...[p1, p2, p3, p4].map(p => p.clone().add(new Vector3((config.gripDepth + br.Thickness) * (i - 1))))); } let cu = br.ContourCurve; cu.AddVertexAt(1, initPts.map(p => AsVector2(p))); br.ContourCurve = cu; } } ParseSpaceBrs() { let vertBrs = this.space.BoardMap.get(BoardType.Vertical); if (vertBrs && vertBrs.length > 1) { vertBrs.sort((br1, br2) => { return br1.Position.applyMatrix4(this.space.SpaceOCSInv).x - br2.Position.applyMatrix4(this.space.SpaceOCSInv).x; }); this.leftBr = vertBrs[0]; this.rightBr = arrayLast(vertBrs); } let behindBrs = this.space.BoardMap.get(BoardType.Behind); if (behindBrs && behindBrs.length > 1) { behindBrs.sort((br1, br2) => { return br1.Position.applyMatrix4(this.space.SpaceOCSInv).y - br2.Position.applyMatrix4(this.space.SpaceOCSInv).y; }); this.backBr = arrayLast(behindBrs); this.frontBr = behindBrs[0]; } let lyBrs = this.space.BoardMap.get(BoardType.Layer); this.haveTopBr = lyBrs && lyBrs.length > 1; } ParseArcLenOrObliuqeAng(br) { const config = this.Config; const size = this.space.Size; if (config.isOpenCut) { if (config.upCut > size.z || config.downCut > size.z) return true; if (config.upCut < 1 || config.downCut < 1) return true; let cu = br.ContourCurve; cu.AddVertexAt(3, new Vector2$1(config.upCut, br.Height)); cu.SetPointAt(4, new Vector2$1(0, br.Height - config.downCut)); } else { if (config.arcLen > size.z || config.arcLen > size.y) { Log("圆弧角过大", LogType.Error); return false; } let cu = br.ContourCurve; if (config.arcLen === 0) { Log("圆弧角为0"); return true; } cu.AddVertexAt(3, new Vector2$1(config.arcLen, br.Height)); cu.SetBulgeAt(3, Math.tan(Math.PI / 8)); cu.SetPointAt(4, new Vector2$1(0, br.Height - config.arcLen)); } return true; } WriteBoardProcessOption(br) { const config = this.Config; br.KnifeRadius = config.knifeRad; br.BoardProcessOption.sealedUp = config.upSealed.toString(); br.BoardProcessOption.sealedDown = config.downSealed.toString(); br.BoardProcessOption.sealedLeft = config.leftSealed.toString(); br.BoardProcessOption.sealedRight = config.rightSealed.toString(); br.BoardProcessOption.drillType = DrillType.None; br.BoardProcessOption.highDrill.fill(DrillType.None); br.BoardProcessOption[EBoardKeyList.Lines] = config.linesType; //纹路 } ParseHighSealing(br, leftSealed, rightSealed, topSealed, downSealed, isHor) { let cu = br.ContourCurve; br.BoardProcessOption.sealedLeft = leftSealed.toString(); br.BoardProcessOption.sealedRight = rightSealed.toString(); br.BoardProcessOption.sealedUp = topSealed.toString(); br.BoardProcessOption.sealedDown = downSealed.toString(); let highSeals = []; let sizes = [...new Set([downSealed, rightSealed, topSealed, leftSealed])]; let downSeal = { size: downSealed, color: sizes.indexOf(downSealed) + 1, sealColor: "" }; let rigthSeal = { size: rightSealed, color: sizes.indexOf(rightSealed) + 1, sealColor: "" }; let topSeal = { size: topSealed, color: sizes.indexOf(topSealed) + 1, sealColor: "" }; let leftSeal = { size: leftSealed, color: sizes.indexOf(leftSealed) + 1, sealColor: "" }; if (isHor) { let topSealNoSize = { size: 0, color: sizes.indexOf(topSealed) + 1, sealColor: "" }; highSeals.push(downSeal, rigthSeal); for (let i = 1; i <= cu.EndParam - 3; i++) { if ((i - 1) % 4 === 0) highSeals.push(topSeal); else highSeals.push(topSealNoSize); } highSeals.push(leftSeal); } else { let count = (this._config.depthCount - 1) * 3 + this._config.depthCount; let downSealNoSize = { size: 0, color: sizes.indexOf(downSealed) + 1, sealColor: "" }; for (let i = 0; i < count; i++) { if (i % 4 === 0) highSeals.push(downSeal); else highSeals.push(downSealNoSize); } for (let i = count; i < cu.EndParam; i++) { let c = cu.GetCurveAtIndex(i); if (c instanceof Arc) break; let derv = c.GetFirstDeriv(0).normalize(); if (equalv3(derv, YAxis)) highSeals.push(rigthSeal); else if (isParallelTo(derv, XAxis)) highSeals.push(topSeal); else if (equalv3(derv, YAxis.clone().negate())) highSeals.push(leftSeal); else highSeals.push(topSeal); } } br.BoardProcessOption.highSealed = highSeals; } End() { this._config = null; this.leftBr = null; this.rightBr = null; this.backBr = null; this.frontBr = null; this.haveTopBr = false; } } let TemplateLatticeRecord = class TemplateLatticeRecord extends TemplateRecord { constructor() { super(); this.option = { ...DefaultLatticeConfig }; this.name = "格子抽(自动)"; } get Option() { return Object.assign({}, this.option); } set Option(option) { this.WriteAllObjectRecord(); Object.assign(this.option, option); ExtendsBoardThickness(this, option.thickness); } // InitBaseParams() // { // super.InitBaseParams(); // return this; // } async Update() { await super.Update(); let thickness = this.GetParam("BH")?.value; if (thickness) this.option.thickness = thickness; let tool = DrawLatticeDrawerTool.GetInstance(); let sbrs = this.PositioningSupportBoards; let space = new ISpaceParse(sbrs, this._CacheSpaceCS); space.ParseOK = true; space.SpaceBox = new Box3Ext(new Vector3(), this._CacheSpaceSize); let nbrs = tool.Draw(space, this.Option); for (let br of nbrs) { if (this.option.useBoardProcessOption) { if (sbrs.length) { br.BoardProcessOption[EBoardKeyList.CabinetName] = sbrs[0].BoardProcessOption[EBoardKeyList.CabinetName]; br.BoardProcessOption[EBoardKeyList.RoomName] = sbrs[0].BoardProcessOption[EBoardKeyList.RoomName]; br.BoardProcessOption[EBoardKeyList.Mat] = sbrs[0].BoardProcessOption[EBoardKeyList.Mat]; br.BoardProcessOption[EBoardKeyList.BrMat] = sbrs[0].BoardProcessOption[EBoardKeyList.BrMat]; br.BoardProcessOption[EBoardKeyList.Color] = sbrs[0].BoardProcessOption[EBoardKeyList.Color]; } } else { const { roomName, cabinetName, boardMatName, material, color } = this.option; br.BoardProcessOption[EBoardKeyList.RoomName] = roomName; br.BoardProcessOption[EBoardKeyList.CabinetName] = cabinetName; br.BoardProcessOption[EBoardKeyList.Mat] = material; br.BoardProcessOption[EBoardKeyList.BrMat] = boardMatName; br.BoardProcessOption[EBoardKeyList.Color] = color; } } for (let i = nbrs.length; i < this.Objects.length; i++) this.Objects[i].Object.Erase(); for (let i = 0; i < nbrs.length; i++) { if (i < this.Objects.length) { let br = this.Objects[i].Object; br.Erase(false); br.CopyFrom(nbrs[i]); br.SpaceOCS = this._CacheSpaceCS; } else { nbrs[i].SpaceOCS = this._CacheSpaceCS; this._db.ModelSpace.Append(nbrs[i]); this.Objects.push(nbrs[i].Id); } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.option.arrayType = file.Read(); this.option.gripWidth = file.Read(); this.option.gripDepth = file.Read(); this.option.widthCount = file.Read(); this.option.depthCount = file.Read(); this.option.knifeRad = file.Read(); this.option.thickness = file.Read(); this.option.arcLen = file.Read(); this.option.downDist = file.Read(); this.option.space = file.Read(); this.option.grooveAddWidth = file.Read(); this.option.upSealed = file.Read(); this.option.downSealed = file.Read(); this.option.leftSealed = file.Read(); this.option.rightSealed = file.Read(); this.option.isAuto = file.Read(); this.option.isChange = file.Read(); this.option.isOpenCut = file.Read(); this.option.upCut = file.Read(); this.option.downCut = file.Read(); if (ver > 1) { this.option.linesType = file.Read(); } if (ver > 2) { this.option.useBoardProcessOption = file.Read(); } if (ver > 3) { this.option.boardMatName = file.Read(); this.option.material = file.Read(); this.option.color = file.Read(); this.option.roomName = file.Read(); this.option.cabinetName = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this.option.arrayType); file.Write(this.option.gripWidth); file.Write(this.option.gripDepth); file.Write(this.option.widthCount); file.Write(this.option.depthCount); file.Write(this.option.knifeRad); file.Write(this.option.thickness); file.Write(this.option.arcLen); file.Write(this.option.downDist); file.Write(this.option.space); file.Write(this.option.grooveAddWidth); file.Write(this.option.upSealed); file.Write(this.option.downSealed); file.Write(this.option.leftSealed); file.Write(this.option.rightSealed); file.Write(this.option.isAuto); file.Write(this.option.isChange); file.Write(this.option.isOpenCut); file.Write(this.option.upCut); file.Write(this.option.downCut); file.Write(this.option.linesType); file.Write(this.option.useBoardProcessOption); file.Write(this.option.boardMatName); file.Write(this.option.material); file.Write(this.option.color); file.Write(this.option.roomName); file.Write(this.option.cabinetName); } }; TemplateLatticeRecord = __decorate([ Factory ], TemplateLatticeRecord); let PositioningFixed = class PositioningFixed extends Positioning { async Positioning() { } //#region File ReadFile(file) { file.Read(); if (!this.SpaceCS) this.SpaceCS = new Matrix4; if (!this.SpaceSize) this.SpaceSize = new Vector3; for (let i = 0; i < 16; i++) this.SpaceCS.elements[i] = file.Read(); this.SpaceSize.set(file.Read(), file.Read(), file.Read()); } WriteFile(file) { file.Write(1); for (let e of this.SpaceCS.elements) file.Write(e); file.Write(this.SpaceSize.x); file.Write(this.SpaceSize.y); file.Write(this.SpaceSize.z); } }; PositioningFixed = __decorate([ Factory ], PositioningFixed); /** * 房门模块建模 * 禁止使用板来建模(因为板会被倒角) * 默认左侧为门铰链位置 右边为门把手位置 * 需要提供门开启旋转轴(门铰链所在的位置) * 移动门外开时门位置的移动动作 所需的移动值?(用于旋转轴的切换?) * */ let TemplateRoomDoorRecord = class TemplateRoomDoorRecord extends TemplateRecord { constructor() { super(); this.IsMirror = false; this.IsReverse = false; this.name = "户型门根节点"; } InitHoleParams() { let hole = this.HoleObjectId?.Object; if (!hole) return; return this; } async Update() { super.Update(); let hole = this.HoleObjectId?.Object; if (!hole) return; if (!this.Children[0]?.Object) return; let tr = this.Children[0].Object; let pos = new PositioningFixed; pos.SpaceCS = hole.FakerWalls[0].OCS; let wall = hole.FakerWalls[0]; let length = wall.Length; if (this.IsMirror) { pos.SpaceCS.elements[0] *= -1; pos.SpaceCS.elements[1] *= -1; pos.SpaceCS.elements[2] *= -1; pos.SpaceCS.elements[12] -= pos.SpaceCS.elements[0] * length; pos.SpaceCS.elements[13] -= pos.SpaceCS.elements[1] * length; pos.SpaceCS.elements[14] -= pos.SpaceCS.elements[2] * length; } if (this.IsReverse) { pos.SpaceCS.elements[4] *= -1; pos.SpaceCS.elements[5] *= -1; pos.SpaceCS.elements[6] *= -1; } let yDir = new Vector3().setFromMatrixColumn(pos.SpaceCS, 1); pos.SpaceCS.elements[12] -= yDir.x * hole.FakerWalls[0].Thickness * 0.5; pos.SpaceCS.elements[13] -= yDir.y * hole.FakerWalls[0].Thickness * 0.5; pos.SpaceCS.elements[14] -= yDir.z * hole.FakerWalls[0].Thickness * 0.5; pos.SpaceSize = new Vector3(length, wall.Thickness, wall.Height); tr.Positioning = pos; } Reverse() { let hole = this.HoleObjectId?.Object; if (!hole) return; if (!this.Children[0]?.Object) return; this.Children[0].Object; let pos = new PositioningFixed; pos.SpaceCS = hole.FakerWalls[0].OCS; let xAxis = new Vector3(); let yAxis = new Vector3(); let zAxis = new Vector3(); pos.SpaceCS.extractBasis(xAxis, yAxis, zAxis); yAxis.multiplyScalar(-1); pos.SpaceCS = new Matrix4().makeBasis(xAxis, yAxis, zAxis); this.Update(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.HoleObjectId = file.ReadObjectId(); if (ver > 1) this.DoorLogo = file.Read(); if (ver > 2) this.IsMirror = file.Read(); else this.IsMirror = false; if (ver > 3) this.IsReverse = file.Read(); else this.IsReverse = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.WriteObjectId(this.HoleObjectId); file.Write(this.DoorLogo); file.Write(this.IsMirror); file.Write(this.IsReverse); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; __decorate([ AutoRecord ], TemplateRoomDoorRecord.prototype, "HoleObjectId", void 0); __decorate([ AutoRecord ], TemplateRoomDoorRecord.prototype, "DoorLogo", void 0); __decorate([ AutoRecord ], TemplateRoomDoorRecord.prototype, "IsMirror", void 0); __decorate([ AutoRecord ], TemplateRoomDoorRecord.prototype, "IsReverse", void 0); TemplateRoomDoorRecord = __decorate([ Factory ], TemplateRoomDoorRecord); function BuildTopBottomBoards(topOpt, bottomOpt, space) { let brs = []; if (topOpt.isDraw) { let basePt = GetTopBoardBasePt(topOpt, space); brs.push(GetTopOrDownBoard(space, topOpt, basePt, true)); } if (bottomOpt.isDraw) { let basePt = GetBottomBoardBasePt(bottomOpt, space); brs.push(GetTopOrDownBoard(space, bottomOpt, basePt, false)); let size = space.Size; let refSize = size.y; const thickness = bottomOpt.footThickness; if (bottomOpt.offset > 0 && !bottomOpt.isWrapSide) { //绘制前地脚 if (bottomOpt.isDrawFooter) { brs.push(GetFootBoard(bottomOpt, space, false)); refSize -= thickness + bottomOpt.footBehindShrink; } //绘制后地脚 if (bottomOpt.isDrawBackFooter) { brs.push(GetFootBoard(bottomOpt, space, true)); refSize -= thickness + bottomOpt.footerOffset; } //绘制加强条 if (bottomOpt.isDrawStrengthenStrip) brs.push(...GetStrengthenStrips(bottomOpt, space, refSize - bottomOpt.frontDist - bottomOpt.behindDistance)); } } return brs; } function GetTopOrDownBoard(spaceParse, opt, basePt, isTop = true) { //前后距 let frontDist = -opt.frontDist; let backDist = -opt.behindDistance; //左右延伸 let leftExt = opt.leftExt; let rightExt = opt.rightExt; //大小 let size = spaceParse.Size; let length = size.x; let width = size.y + frontDist + backDist; let thickness = opt.thickness; if (opt.isWrapSide) { let leftBoardThickness = spaceParse.LeftBoard ? spaceParse.LeftBoard.Thickness : opt.thickness; let rightBoardThickness = spaceParse.RightBoard ? spaceParse.RightBoard.Thickness : opt.thickness; length += leftBoardThickness + rightBoardThickness + leftExt + rightExt; } else { leftExt = 0; rightExt = 0; } let board = Board.CreateBoard(length, width, thickness, BoardType.Layer); board.Name = "顶板"; if (!isTop) { board.Name = "底板"; } //移动右缩和前距的距离 basePt.add(new Vector3(rightExt, -frontDist)); board.ApplyMatrix(MoveMatrix(basePt)); board.ApplyMatrix(spaceParse.SpaceOCS); return board; } function GetTopBoardBasePt(opt, spc) { let box = spc.SpaceBox; let min = box.min; let max = box.max; let basePoint = new Vector3(); if (opt.isWrapSide) { let rightBoardThickness = spc.RightBoard ? spc.RightBoard.Thickness : opt.thickness; basePoint.set(max.x + rightBoardThickness, min.y, max.z); } else { basePoint.set(max.x, min.y, max.z - (opt.thickness + parseFloat(opt.offset))); } return basePoint; } function GetBottomBoardBasePt(opt, spc) { let box = spc.SpaceBox; let min = box.min; let max = box.max; let basePoint = new Vector3(); if (opt.isWrapSide) { let rightBoardThickness = spc.RightBoard ? spc.RightBoard.Thickness : opt.thickness; basePoint.set(max.x + rightBoardThickness, min.y, min.z - opt.thickness); } else { basePoint.set(max.x, min.y, min.z + opt.offset); } return basePoint; } function GetFootBoard(opt, spaceParse, isBack) { let offset = opt.offset; let thickness = opt.footThickness; let footBoard = Board.CreateBoard(offset, spaceParse.Size.x, opt.footThickness, BoardType.Behind); footBoard.Name = isBack ? "后地脚" : "地脚线"; let moveDist = isBack ? spaceParse.Size.y - opt.footerOffset - opt.behindDistance : thickness + opt.footBehindShrink + opt.frontDist; footBoard.ApplyMatrix(MoveMatrix(spaceParse.SpaceBox.min.clone().add(new Vector3(0, moveDist)))) .ApplyMatrix(spaceParse.SpaceOCS); return footBoard; } function GetStrengthenStrips(opt, spaceParse, width) { let brs = []; const thickness = opt.footThickness; let count = opt.divCount; if (count === 0) return brs; let spaceSize = (spaceParse.Size.x - count * thickness) / (count + 1); let br = Board.CreateBoard(opt.offset, width, thickness, BoardType.Vertical); let yDist = opt.frontDist; if (opt.isDrawFooter) yDist += (opt.footBehindShrink + thickness); for (let i = 1; i <= count; i++) { let b = br.Clone(); b.Name = "加强条" + i; b.ApplyMatrix(MoveMatrix(spaceParse.SpaceBox.min.clone().add(new Vector3(spaceSize * i + (i - 1) * thickness, yDist)))) .ApplyMatrix(spaceParse.SpaceOCS); brs.push(b); } return brs; } /**顶底板模板 */ let TemplateTopBottomBoard = class TemplateTopBottomBoard extends TemplateRecord { constructor() { super(); this._topOption = { ...DefaultTopBoardOption }; this._bottomOption = { ...DefaultBottomBoardOption }; this.UseBoardProcessOption = false; this.isChangeBigHole = false; this.DrawCounts = [1, 1, 1]; this.name = "顶底板"; } get TopOption() { return Object.assign({}, this._topOption); } set TopOption(option) { this.WriteAllObjectRecord(); Object.assign(this._topOption, option); ExtendsBoardThickness(this, option.thickness); } get BottomOption() { return Object.assign({}, this._bottomOption); } set BottomOption(option) { this.WriteAllObjectRecord(); Object.assign(this._bottomOption, option); } // InitBaseParams() // { // super.InitBaseParams(); // return this; // } async Update() { await super.Update(); let thickness = this.GetParam("BH")?.value; if (thickness) { this._topOption.thickness = thickness; this._bottomOption.thickness = thickness; this._bottomOption.footThickness = thickness; } let spaceParse = this.SpaceParse; let nbrs = BuildTopBottomBoards(this._topOption, this._bottomOption, spaceParse); let sbrs = this.PositioningSupportBoards; if (this.BoardProcessOption) { let minSealed = GetMinSealed(this.BoardProcessOption); for (let br of nbrs) { br.BoardProcessOption = this.BoardProcessOption; this.SetBoardProcess(br, minSealed); } this.BoardProcessOption = undefined; } if (this.UseBoardProcessOption && sbrs.length > 0) { let cname = sbrs[0].BoardProcessOption[EBoardKeyList.CabinetName]; let rname = sbrs[0].BoardProcessOption[EBoardKeyList.RoomName]; for (let br of nbrs) { br.BoardProcessOption[EBoardKeyList.CabinetName] = cname; br.BoardProcessOption[EBoardKeyList.RoomName] = rname; br.BoardProcessOption[EBoardKeyList.Mat] = sbrs[0].BoardProcessOption[EBoardKeyList.Mat]; br.BoardProcessOption[EBoardKeyList.BrMat] = sbrs[0].BoardProcessOption[EBoardKeyList.BrMat]; br.BoardProcessOption[EBoardKeyList.Color] = sbrs[0].BoardProcessOption[EBoardKeyList.Color]; } this.UseBoardProcessOption = false; } let oldBrss = [[], [], []]; let refBr; let minSealed; for (let id of this.Objects) { let b = id.Object; if (!refBr) refBr = b; oldBrss[b.BoardType].push(b); } if (refBr) minSealed = GetMinSealed(refBr.BoardProcessOption); let newBrss = [[], [], []]; for (let b of nbrs) { newBrss[b.BoardType].push(b); } for (let i = 0; i < oldBrss.length; i++) { let oldBrs = oldBrss[i]; let newBrs = newBrss[i]; let oldLen = oldBrs.length; for (let j = newBrs.length; j < oldLen; j++) oldBrs[j].Erase(); for (let j = 0; j < newBrs.length; j++) { if (j < oldLen) { if (j >= this.DrawCounts[i]) { oldBrs[j].Erase(false); } oldBrs[j].Name = newBrs[j].Name; oldBrs[j].Position = newBrs[j].Position; oldBrs[j].Width = newBrs[j].Width; oldBrs[j].Height = newBrs[j].Height; oldBrs[j].Thickness = newBrs[j].Thickness; if (this.isChangeBigHole) { if (oldBrs[j].Name === "底板") { if (this._bottomOption.bigHoleDir === FaceDirection.Inside) oldBrs[j].BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Front; else oldBrs[j].BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Back; } else if (oldBrs[j].Name === "顶板") { if (this._bottomOption.bigHoleDir === FaceDirection.Front) oldBrs[j].BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Front; else oldBrs[j].BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Back; } } } else { if (refBr) { newBrs[j].BoardProcessOption = refBr.BoardProcessOption; this.SetBoardProcess(newBrs[j], minSealed); } this._db.ModelSpace.Append(newBrs[j]); this.Objects.push(newBrs[j].Id); } } this.DrawCounts[i] = newBrs.length; } //保持SpaceCS for (let id of this.Objects) { if (id && !id.IsErase) id.Object.SpaceOCS = this._CacheSpaceCS; } } SetBoardProcess(br, minSealed) { if (br.Name === "底板") { if (this._bottomOption.bigHoleDir === FaceDirection.Inside) br.BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Front; else br.BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Back; } else if (br.Name === "顶板") { if (this._bottomOption.bigHoleDir === FaceDirection.Front) br.BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Front; else br.BoardProcessOption[EBoardKeyList.BigHole] = FaceDirection.Back; } else if (br.Name.includes("地脚") || br.Name.includes("加强条")) { // br.BoardProcessOption.composingFace = ComposingType.Reverse; br.BoardProcessOption.bigHoleDir = FaceDirection.Back; br.BoardProcessOption.lines = LinesType.Reverse; //用薄封边 br.BoardProcessOption.sealedUp = minSealed; br.BoardProcessOption.sealedDown = minSealed; br.BoardProcessOption.sealedLeft = minSealed; br.BoardProcessOption.sealedRight = minSealed; } } ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.DrawCounts[0] = file.Read(); this.DrawCounts[1] = file.Read(); this.DrawCounts[2] = file.Read(); this._topOption.type = file.Read(); this._topOption.name = file.Read(); this._topOption.isDraw = file.Read(); this._topOption.thickness = file.Read(); this._topOption.frontDist = file.Read(); this._topOption.behindDistance = file.Read(); this._topOption.isWrapSide = file.Read(); this._topOption.useLFData = file.Read(); this._topOption.leftExt = file.Read(); this._topOption.rightExt = file.Read(); this._topOption.offset = file.Read(); if (ver === 1 && typeof this._topOption.offset === "string") this._topOption.offset = parseFloat(this._topOption.offset) ?? 0; this._bottomOption.type = file.Read(); this._bottomOption.name = file.Read(); this._bottomOption.isDraw = file.Read(); this._bottomOption.thickness = file.Read(); this._bottomOption.frontDist = file.Read(); this._bottomOption.behindDistance = file.Read(); this._bottomOption.isWrapSide = file.Read(); this._bottomOption.useLFData = file.Read(); this._bottomOption.leftExt = file.Read(); this._bottomOption.rightExt = file.Read(); this._bottomOption.offset = file.Read(); this._bottomOption.footThickness = file.Read(); this._bottomOption.isDrawFooter = file.Read(); this._bottomOption.footBehindShrink = file.Read(); this._bottomOption.isDrawBackFooter = file.Read(); this._bottomOption.isDrawStrengthenStrip = file.Read(); this._bottomOption.footerOffset = file.Read(); this._bottomOption.divCount = file.Read(); if (ver > 2) this._bottomOption.bigHoleDir = file.Read(); } WriteFile(file) { file.Write(3); super.WriteFile(file); file.Write(this.DrawCounts[0]); file.Write(this.DrawCounts[1]); file.Write(this.DrawCounts[2]); file.Write(this._topOption.type); file.Write(this._topOption.name); file.Write(this._topOption.isDraw); file.Write(this._topOption.thickness); file.Write(this._topOption.frontDist); file.Write(this._topOption.behindDistance); file.Write(this._topOption.isWrapSide); file.Write(this._topOption.useLFData); file.Write(this._topOption.leftExt); file.Write(this._topOption.rightExt); file.Write(this._topOption.offset); file.Write(this._bottomOption.type); file.Write(this._bottomOption.name); file.Write(this._bottomOption.isDraw); file.Write(this._bottomOption.thickness); file.Write(this._bottomOption.frontDist); file.Write(this._bottomOption.behindDistance); file.Write(this._bottomOption.isWrapSide); file.Write(this._bottomOption.useLFData); file.Write(this._bottomOption.leftExt); file.Write(this._bottomOption.rightExt); file.Write(this._bottomOption.offset); file.Write(this._bottomOption.footThickness); file.Write(this._bottomOption.isDrawFooter); file.Write(this._bottomOption.footBehindShrink); file.Write(this._bottomOption.isDrawBackFooter); file.Write(this._bottomOption.isDrawStrengthenStrip); file.Write(this._bottomOption.footerOffset); file.Write(this._bottomOption.divCount); file.Write(this._bottomOption.bigHoleDir); } }; __decorate([ AutoRecord ], TemplateTopBottomBoard.prototype, "DrawCounts", void 0); TemplateTopBottomBoard = __decorate([ Factory ], TemplateTopBottomBoard); //获得最薄的封边 function GetMinSealed(opt) { return Math.max(0, Math.min(parseFloat(opt.sealedDown) || 0, parseFloat(opt.sealedLeft) || 0, parseFloat(opt.sealedRight) || 0, parseFloat(opt.sealedUp) || 0)).toString(); } /** * 窗: * I(窗) * L(转角窗,2面墙,4面飘窗) (左边墙?) (右边墙?) (左边窗?) * U(转角窗,2面墙,6面飘窗) (左边墙?) (右边墙?) * * 对于这种转角窗 需要生成一个V型的实体来补一下 * * 圆弧窗? * * 参数: * * 窗框材质 * * 窗台石? 台面厚度? 台面凸出? 台面左右凸出 * * I 外飘 = W/2 居中 * L 左外飘 右外飘 * U 左外飘 中外飘 右外飘? */ //窗台石默认参数 const DefaultCTSarams = [ ["CTS", "窗台石?", "1"], ["CTSH", "窗石厚", "30"], ["CTSTC", "窗石凸", "30"], ["CTSZYTC", "左右凸出", "30"], ["WZ", "窗位置", "25"], //在外飘未启用之前 ["HD", "窗厚度", "50"], //在外飘未启用之前 ]; const DefaultIWindowParams = [ ["WP", "外飘", "500"], ]; const DefaultLWindowParams = [ ["ZWP", "左外飘", "500"], ["YWP", "右外飘", "500"], ]; const DefaultUWindowParams = [ ["ZWP", "左外飘", "500"], ["MWP", "中外飘", "500"], ["YWP", "右外飘", "500"], ]; function InitParam(tr, paramD) { let [name, description, expr] = paramD; let param = tr.GetParam(name); if (param) return; param = new TemplateParam(); param.name = name; param.expr = expr; param.value = 0; param.description = description; tr.Params.push(param); } let TemplateWindowRecord = class TemplateWindowRecord extends TemplateRecord { constructor() { super(); this.LeftIsWall = false; this.RightIsWall = false; this.name = "户型窗根节点"; } InitHoleParams() { let hole = this.HoleObjectId?.Object; if (!hole) return; //初始化窗台石参数 for (let p of DefaultCTSarams) InitParam(this, p); let holePtsCount = hole.PointsCount; if (holePtsCount === 2) { for (let p of DefaultIWindowParams) InitParam(this, p); } else if (holePtsCount === 3) { for (let p of DefaultLWindowParams) InitParam(this, p); } else if (holePtsCount === 4) { for (let p of DefaultUWindowParams) InitParam(this, p); } return this; } set WpDist(v) { let count = this.HoleObjectId?.Object?.PointsCount; if (!count) return; if (count === 2) this.GetParam("WP").expr = v[0]; else if (count === 3) { this.GetParam("ZWP").expr = v[0]; this.GetParam("YWP").expr = v[1]; } else { this.GetParam("ZWP").expr = v[0]; this.GetParam("MWP").expr = v[1]; this.GetParam("YWP").expr = v[2]; } } get WpDists() { let hole = this.HoleObjectId?.Object; if (!hole) return; let wpdists = []; let walls = hole.FakerWalls; if (walls.length === 1) { let wp = this.GetParam("WP").value; wpdists.push(wp); } else if (walls.length === 2) { let zwp = this.GetParam("ZWP").value; let ywp = this.GetParam("YWP").value; wpdists.push(zwp, ywp); } else if (walls.length === 3) { let zwp = this.GetParam("ZWP").value; let mwp = this.GetParam("MWP").value; let ywp = this.GetParam("YWP").value; wpdists.push(zwp, mwp, ywp); } return wpdists; } async Update() { super.Update(); let hole = this.HoleObjectId?.Object; if (!hole) return; let ctsParam = this.GetParam("CTS"); //#region 计算窗台石轮廓 let ctstc = this.GetParam("CTSTC").value; //窗台石凸出 let ctszytc = this.GetParam("CTSZYTC").value; //窗台石左右凸出 let wpdists = this.WpDists; hole.GetWpRegion(wpdists, ctstc, ctszytc); if (ctsParam.value) //1 使用窗台石? ; EntityUpdateWrap(hole, () => { hole.WpDist = wpdists; hole.WpLeftWall = this.LeftIsWall; hole.WpRightWall = this.RightIsWall; }); let windowWidth = this.GetParam("HD").value ?? 50; if (wpdists.every(d => d === 0)) //没有外飘 { let wzParam = this.GetParam("WZ"); let fakerWalls = hole.FakerWalls; if (wzParam.value !== 0) { fakerWalls = fakerWalls.map(w => w.GetOffsetCurves(wzParam.value)[0]); //窗户相交最近点 const getMinDistPoint = (pts, curve) => { let pt = pts[0]; let length = curve.distanceTo(pt); for (let y = 1; y < pts.length; y++) { if (curve.distanceTo(pts[y]) < length) pt = pts[y]; } return pt; }; for (let i = 0; i < fakerWalls.length - 1; i++) { let pre = fakerWalls[i]; let next = fakerWalls[i + 1]; //直线墙分裂后画L型墙导致无交点,这里修复它. let ipts = pre.IntersectWith(next, IntersectOption.ExtendBoth); if (ipts.length === 0) ipts.push(pre.EndPoint); let pt = getMinDistPoint(ipts, pre.EndPoint); pre.EndPoint = pt; next.StartPoint = pt; } for (let w of fakerWalls) { if (w instanceof RoomWallLine) w.UpdateOCSToMinBox(); } } for (let i = 0; i < this.Children.length; i++) { if (i > hole.PointsCount) break; let wall = fakerWalls[i]; let ctemplate = this.Children[i].Object; if (!ctemplate) continue; let pos = new PositioningFixed; pos.SpaceCS = wall.OCS; pos.SpaceSize = new Vector3(wall.Length, windowWidth, wall.Height); ctemplate.Positioning = pos; } } else { //根据外飘偏移 let wpCurves = []; for (let i = 0; i < hole.FakerWalls.length; i++) { let w = hole.FakerWalls[i]; let d = wpdists[i]; //外飘距离 if (w instanceof RoomWallArc) wpCurves.push(w.GetOffsetCurves((w.Thickness * 0.5 + d) * (w.IsClockWise ? -1 : 1))[0]); else wpCurves.push(w.GetOffsetCurves(-(w.Thickness * 0.5 + d))[0]); } //求交连接 for (let i = 1; i < wpCurves.length; i++) { let pre = wpCurves[i - 1]; let now = wpCurves[i]; let iPt = pre.IntersectWith(now, IntersectOption.ExtendBoth)[0]; if (!iPt) continue; //直线墙打断后支持L飘窗绘制 pre.EndPoint = iPt; now.StartPoint = iPt; } //左侧非墙 let templateIndex = 0; if (!this.LeftIsWall) { templateIndex++; let sp = hole.LidCurves[0].StartPoint.setZ(hole.Z); let x = hole.LidCurves[0].GetFirstDeriv(0).normalize().negate(); let z = ZAxis; let y = new Vector3().crossVectors(ZAxis, x); let tr = this.Children[0].Object; if (!tr) return; let pos = new PositioningFixed; pos.SpaceCS = new Matrix4().makeBasis(x, y, z).setPosition(sp); pos.SpaceSize = new Vector3(wpdists[0], windowWidth, hole.Height); tr.Positioning = pos; } for (let i = 0; i < wpCurves.length; i++) { let c = wpCurves[i]; let tr = this.Children[templateIndex]?.Object; if (!tr) return; let sp = c.StartPoint; let x = c.GetFirstDeriv(0).normalize(); let z = ZAxis; let y = new Vector3().crossVectors(ZAxis, x); let pos = new PositioningFixed; pos.SpaceCS = new Matrix4().makeBasis(x, y, z).setPosition(sp); pos.SpaceSize = new Vector3(c.Length, windowWidth, hole.Height); tr.Positioning = pos; templateIndex++; } if (!this.RightIsWall && arrayLast(wpdists) > 0) { let tr = this.Children[templateIndex]?.Object; if (!tr) return; let sp = arrayLast(wpCurves).EndPoint; let w = arrayLast(hole.FakerWalls); let x = w.EndPoint.sub(sp).normalize(); let z = ZAxis; let y = new Vector3().crossVectors(ZAxis, x); let pos = new PositioningFixed; pos.SpaceCS = new Matrix4().makeBasis(x, y, z).setPosition(sp); pos.SpaceSize = new Vector3(arrayLast(wpdists), windowWidth, hole.Height); tr.Positioning = pos; } } } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); if (ver > 1) { let wallFlag = file.Read(); this.LeftIsWall = (wallFlag & 1) === 1; this.RightIsWall = (wallFlag & 2) === 2; } if (ver > 2) this.HoleObjectId = file.ReadObjectId(); if (ver > 3) { this.WindowLogo = file.Read(); this.LeftWindowLogo = file.Read(); this.RightWindowLogo = file.Read(); this.LeftWindowTemplateId = file.Read(); this.RightWindowTemplateId = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); let wallFlag = 0; if (this.LeftIsWall) wallFlag = 1; if (this.RightIsWall) wallFlag += 2; file.Write(wallFlag); file.WriteObjectId(this.HoleObjectId); file.Write(this.WindowLogo); file.Write(this.LeftWindowLogo); file.Write(this.RightWindowLogo); file.Write(this.LeftWindowTemplateId); file.Write(this.RightWindowTemplateId); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "HoleObjectId", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "CTSId", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "LeftIsWall", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "RightIsWall", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "WindowLogo", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "LeftWindowLogo", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "RightWindowLogo", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "LeftWindowTemplateId", void 0); __decorate([ AutoRecord ], TemplateWindowRecord.prototype, "RightWindowTemplateId", void 0); TemplateWindowRecord = __decorate([ Factory ], TemplateWindowRecord); const SIN45 = Math.sin(Math.PI / 4); class DrawWineRackTool extends Singleton { constructor() { super(...arguments); this.boardlist = []; } get Config() { return this._config; } Parse(space, config) { this._config = config; this.boardlist.length = 0; this.space = space; //处理格子深 if (!config.isTotalDepth) { let depth = safeEval(config.calcDepth, { L: space.Size.x, W: space.Size.y, H: space.Size.z }); if (!isNaN(depth)) { space.SpaceBox.max.setY(space.SpaceBox.min.y + depth); } } //处理前缩 space.SpaceBox.min.add(new Vector3(0, config.frontCut)); //左右缩 space.SpaceBox.min.add(new Vector3(config.leftCut)); space.SpaceBox.max.add(new Vector3(-config.rightCut)); //上缩 space.SpaceBox.max.add(new Vector3(0, 0, -config.topCut)); } /** 拾取空间周围的板件柜名房名等信息*/ GetBoardProcessOption(br) { if (!this._config) return; //在矩形变酒格时,因为没有配置 所以导致错误 //获取空间周围的板件数据 if (this.space.Boards.length > 0 && this._config.useBoardProcessOption) { let refProcessData = this.space.Boards[0].BoardProcessOption; br.BoardProcessOption[EBoardKeyList.RoomName] = refProcessData[EBoardKeyList.RoomName]; br.BoardProcessOption[EBoardKeyList.CabinetName] = refProcessData[EBoardKeyList.CabinetName]; br.BoardProcessOption[EBoardKeyList.Mat] = refProcessData[EBoardKeyList.Mat]; br.BoardProcessOption[EBoardKeyList.BrMat] = refProcessData[EBoardKeyList.BrMat]; br.BoardProcessOption[EBoardKeyList.Color] = refProcessData[EBoardKeyList.Color]; } else { const { roomName, cabinetName, boardMatName, material, color } = this._config; br.BoardProcessOption[EBoardKeyList.RoomName] = roomName; br.BoardProcessOption[EBoardKeyList.CabinetName] = cabinetName; br.BoardProcessOption[EBoardKeyList.Mat] = material; br.BoardProcessOption[EBoardKeyList.BrMat] = boardMatName; br.BoardProcessOption[EBoardKeyList.Color] = color; } } /**分析高级封边数据 */ ParseHighSealing(br, leftSealed, rightSealed, topSealed, downSealed, isLeft) { this.GetBoardProcessOption(br); let cu = br.ContourCurve; br.BoardProcessOption.sealedLeft = leftSealed.toString(); br.BoardProcessOption.sealedRight = rightSealed.toString(); br.BoardProcessOption.sealedUp = topSealed.toString(); br.BoardProcessOption.sealedDown = downSealed.toString(); let sizes = [...new Set([downSealed, rightSealed, topSealed, leftSealed])]; let downSeal = { size: downSealed, color: sizes.indexOf(downSealed) + 1, sealColor: "" }; let rigthSeal = { size: rightSealed, color: sizes.indexOf(rightSealed) + 1, sealColor: "" }; let topSeal = { size: topSealed, color: sizes.indexOf(topSealed) + 1, sealColor: "" }; let leftSeal = { size: leftSealed, color: sizes.indexOf(leftSealed) + 1, sealColor: "" }; let highSeals = [downSeal]; if (isLeft) { let leftSealNoSize = { size: 0, color: sizes.indexOf(leftSealed) + 1, sealColor: "" }; highSeals.push(rigthSeal, topSeal); for (let i = 3; i <= cu.EndParam - 1; i++) { if ((i - 3) % 4 === 0) highSeals.push(leftSeal); else highSeals.push(leftSealNoSize); } } else { let rightSealNoSize = { size: 0, color: sizes.indexOf(rightSealed) + 1, sealColor: "" }; for (let i = 1; i <= cu.EndParam - 3; i++) { if ((i - 1) % 4 === 0) highSeals.push(rigthSeal); else highSeals.push(rightSealNoSize); } highSeals.push(topSeal, leftSeal); } br.BoardProcessOption.highSealed = highSeals; br.BoardProcessOption.drillType = DrillType.None; br.BoardProcessOption.highDrill.fill(DrillType.None); } /**补板 */ AddLayerOrVerticalBoard(actWidth, actHeight) { const config = this.Config; const size = this.space.Size; let isDrawLy = false; //是否补层板 if (config.isDrawLy && (config.arrayType === EWRackArrayType.Fixed || config.fullType === EFullType.ByWidth)) { isDrawLy = true; let length = actWidth; let width = size.y + config.frontCut; let position = this.space.SpaceBox.min.clone(); if (config.arrayType === EWRackArrayType.Fixed) { if (config.fullDir === EFullDir.Right) { length += config.rightCut; position.add(new Vector3(size.x + config.rightCut, -config.frontCut, actHeight + config.topCut)); } else { length += config.leftCut; position.add(new Vector3(actWidth, -config.frontCut, actHeight + config.topCut)); } } else { length += (config.leftCut + config.rightCut); position.add(new Vector3(actWidth + config.rightCut, -config.frontCut, actHeight + config.topCut)); } //补板跟随 if (config.followNarrow) { //处理前缩 position.add(new Vector3(0, config.frontCut)); width -= config.frontCut; } let br = Board.CreateBoard(length, width, config.brThick2); this.GetBoardProcessOption(br); br.BoardProcessOption.bigHoleDir = FaceDirection.Back; br.Position = position; br.ApplyMatrix(this.space.SpaceOCS); this.boardlist.push(br); } if (config.isDrawVer && (config.arrayType === EWRackArrayType.Fixed || config.fullType === EFullType.ByHeight)) { let len = actHeight + config.topCut; if (isDrawLy) len += config.brThick2; let br = Board.CreateBoard(len, size.y + config.frontCut, config.brThick2, BoardType.Vertical); let position = this.space.SpaceBox.min.clone(); if (config.fullDir === EFullDir.Right) position.x += size.x - actWidth - config.brThick2; else { br.BoardProcessOption.bigHoleDir = FaceDirection.Back; position.x += actWidth; } //补板跟随 if (config.followNarrow) { br.WriteAllObjectRecord(); position.y += config.frontCut; br.Width -= config.frontCut; } //这里要跟外面平 position.y -= config.frontCut; this.GetBoardProcessOption(br); br.Position = position; br.ApplyMatrix(this.space.SpaceOCS); this.boardlist.push(br); } } } const R2WRTolerance = 1e-3; /** * 斜酒格 */ class DrawObliqueWineRackTool extends DrawWineRackTool { Parse(space, config) { super.Parse(space, config); const size = space.Size; const spaceHeight = size.z; const spaceWidth = size.x; const ptMid = new Vector3(0.5 * size.x, size.z / 2, 0); let mirrorMtx; if (config.fullType === EFullType.ByWidth) mirrorMtx = MakeMirrorMtx(YAxis, ptMid); else mirrorMtx = MakeMirrorMtx(XAxis, ptMid); ptMid.set(0.5 * size.x, size.z / 2, 0); polar(ptMid, Math.PI * 0.75, -config.boardThick); if (config.isFull && config.arrayType !== EWRackArrayType.Fixed) { switch (config.fullType) { case EFullType.ByHeight: this.CalGridWidth(spaceHeight); break; case EFullType.ByWidth: this.CalGridWidth(spaceWidth); break; case EFullType.Symmetry: this.CalGridWidth(spaceHeight); this.CalGridWidth(spaceWidth); } let rectPl = new Polyline().Rectangle(spaceWidth, spaceHeight); let gripWidth = config.boardThick + config.gripWidth; let retPls = []; let pls1 = this.CalculationRectPolylineArr(ptMid, rectPl, gripWidth); polar(ptMid, Math.PI * 0.75, -gripWidth); let pls2 = this.CalculationRectPolylineArr(ptMid, rectPl, -gripWidth); retPls.push(...pls1, ...pls2); for (let pl of [...pls1, ...pls2]) { let plClone = pl.Clone(); plClone.ApplyMatrix(mirrorMtx); retPls.push(plClone); } this.DrawBoardFormPolyLine(retPls); } else { let lWRData = []; let rWRData = []; let res; let wineRackStyle = config.wineRackStyle; switch (config.arrayType) { case EWRackArrayType.ByWidth: res = this.CalcWineRackDataByWidth(lWRData, rWRData, wineRackStyle); break; case EWRackArrayType.ByCount: res = this.CalcWineRackDataByCount(lWRData, rWRData, wineRackStyle); break; case EWRackArrayType.Fixed: res = this.CalcWineRackDataByFixed(lWRData, rWRData, wineRackStyle); } let pls = []; for (let data of lWRData) { pls.push(this.CreatePolylineByWineData(data)); } rWRData.sort((d1, d2) => { if (equaln$1(d1.basePt.x, d2.basePt.x)) { return d1.basePt.y - d2.basePt.y; } else return d2.basePt.x - d1.basePt.x; }); for (let data of rWRData) { pls.push(this.CreatePolylineByWineData(data, false)); } //补板 this.AddLayerOrVerticalBoard(res.width, res.height); this.DrawBoardFormPolyLine(pls, res.width); } } CalGridWidth(length) { const count = (length - this.Config.boardThick * 2 / SIN45) / ((this.Config.boardThick + this.Config.gripWidth) * Math.sqrt(2)); this.Config.gripWidth = ((length - this.Config.boardThick / SIN45) / Math.floor(count)) / Math.sqrt(2) - this.Config.boardThick; } /**获取矩形多段线的4个对角点并判断是否有效 */ GetRect4Pts(xline, rectPl, p1, p2, p3, p4) { let res1 = this.GetRect2Pts(xline, rectPl, p1, p2); let vec = polar(new Vector3(), Math.PI * 0.75, this.Config.boardThick); xline.ApplyMatrix(MoveMatrix(vec)); let res2 = this.GetRect2Pts(xline, rectPl, p3, p4); return res1 && res2 && p1.distanceTo(p2) > this.Config.gripWidth; } /**获取矩形一边的点 */ GetRect2Pts(xline, rectPl, p1, p2) { let intPts = xline.IntersectWith(rectPl, IntersectOption.ExtendBoth); if (intPts.length === 2) { p1.copy(intPts[0]); p2.copy(intPts[1]); if (p1.x > p2.x) { let tmpPt = p2.clone(); p2.copy(p1); p1.copy(tmpPt); } return true; } return false; } /**按格子宽获取酒格数据 */ CalcWineRackDataByWidth(lWRDataList, rWRDataList, wineRackStyle) { let size = this.space.Size; let Config = this.Config; let widthCount = 0, heightCount = 0, gripWidth = 0; let width = size.x - 2 * Config.boardThick * SIN45; let height = size.z - 2 * Config.boardThick * SIN45; if (Config.fullType === EFullType.ByWidth) { widthCount = Math.floor(width / ((Config.gripWidth + Config.boardThick) * SIN45)); gripWidth = width / widthCount; //处理锁定个数 if (!Config.isLock) { heightCount = Math.floor(height / gripWidth); } else { heightCount = Math.floor(Config.heightCount * 2); } height = heightCount * gripWidth + 2 * Config.boardThick * SIN45; width = size.x; } else { heightCount = Math.floor(height / ((Config.gripWidth + Config.boardThick) * SIN45)); gripWidth = height / heightCount; //处理锁定个数 if (!Config.isLock) { widthCount = Math.floor(width / gripWidth); } else { widthCount = Math.floor(Config.widthCount * 2); } width = widthCount * gripWidth + 2 * Config.boardThick * SIN45; height = size.z; } this.GetWineRackData({ width, height, gripWidth, brThick: Config.boardThick, widthCount, heightCount, lWRDataList, rWRDataList }, wineRackStyle); return { width: width, height: height }; } CalcWineRackDataByCount(lWRDataList, rWRDataList, wineRackStyle) { const config = this.Config; const size = this.space.Size; let widthCount = Math.floor(config.widthCount * 2); let heightCount = Math.floor(config.heightCount * 2); let gripWidth = (size.x - 2 * config.boardThick * SIN45) / widthCount; let width = 0; let height = 0; if (config.fullType === EFullType.ByWidth) { gripWidth = (size.x - 2 * config.boardThick * SIN45) / widthCount; width = size.x; if (!config.isLock) //如果没锁定个数 { heightCount = Math.floor((size.z - 2 * config.boardThick * SIN45) / gripWidth + 1e-3); } height = heightCount * gripWidth + 2 * config.boardThick * SIN45; } else { gripWidth = (size.z - 2 * config.boardThick * SIN45) / heightCount; if (!config.isLock) //如果没有锁定个数 { widthCount = Math.floor((size.x - 2 * config.boardThick * SIN45) / gripWidth); } width = widthCount * gripWidth + 2 * config.boardThick * SIN45; height = size.z; } this.GetWineRackData({ width, height, gripWidth, brThick: config.boardThick, widthCount, heightCount, lWRDataList, rWRDataList }, wineRackStyle); return { width: width, height: height }; } CalcWineRackDataByFixed(lWRDataList, rWRDataList, wineRackStyle) { const config = this.Config; let widthCount = Math.floor(config.widthCount * 2); let heightCount = Math.floor(config.heightCount * 2); let gripWidth = config.gripWidth * SIN45; let width = widthCount * gripWidth + 2 * config.boardThick * SIN45; let height = heightCount * gripWidth + 2 * config.boardThick * SIN45; this.GetWineRackData({ width, height, gripWidth, brThick: config.boardThick, widthCount, heightCount, lWRDataList, rWRDataList }, wineRackStyle); return { width: width, height: height }; } /**构建酒格正面多段线组*/ CalculationRectPolylineArr(ptMid, rectPl, gripWidth) { let pls = []; let pt = ptMid.clone(); while (true) { let pl = this.CalculationRectPolyline(pt, rectPl); if (!pl) break; polar(pt, Math.PI * 0.75, gripWidth); pls.push(pl); } return pls; } /**构建酒格正面多段线*/ CalculationRectPolyline(pt, rectPl) { const xline = new Line(pt, pt.clone().add(new Vector3(1, 1))); const p1 = new Vector3(); const p2 = new Vector3(); const p3 = new Vector3(); const p4 = new Vector3(); if (!this.GetRect4Pts(xline, rectPl, p1, p2, p3, p4)) { return null; } let minDis = this.Config.boardThick / Math.sqrt(2); //如果左边停靠 if (p1.x < minDis) { p1.copy(polar(p3.clone(), Math.PI * -0.25, this.Config.boardThick)); } else { p3.copy(polar(p1.clone(), Math.PI * 0.75, this.Config.boardThick)); } //右边点 上面停靠 let size = this.space.Size; if (p2.y > size.z - minDis) { p2.copy(polar(p4.clone(), -Math.PI * 0.25, this.Config.boardThick)); } else { p4.copy(polar(p2.clone(), Math.PI * 0.75, this.Config.boardThick)); } if (p1.distanceTo(p2) < (this.Config.gripWidth + this.Config.boardThick * 1.8) && p1.y > this.Config.boardThick * 2) return null; let lineData = [p1, p2, p4, p3].map(p => { return { pt: AsVector2(p), bul: 0 }; }); let pl = new Polyline(lineData); pl.CloseMark = true; return pl; } GetWineRackData(option, wineRackStyle) { switch (wineRackStyle) { case EWineRackStyle.Semilattice: this.GetWineRackDataPreferentialSemilattice(option); break; case EWineRackStyle.WholeLattice: this.GetWineRackDataPreferentialWholeLattice(option); break; } } /** * 获取酒格数据半格优先 * @param {{width: number 总宽 * height: number 总高 * gripWidth: number 半格宽 * brThick: number 板厚 * widthCount: number 半格宽数量 * heightCount: number 半格高数量 * lWRDataList: IWineRackData[], 往左倒 * rWRDataList: IWineRackData[], 往右倒 * }} option * @memberof DrawObliqueWineRackTool */ GetWineRackDataPreferentialSemilattice(option) { const { width, height, gripWidth, brThick, widthCount, heightCount, lWRDataList, rWRDataList } = option; /** 板厚对角的一半 */ const brThickDiagonal = brThick * SIN45; for (let i = 0, Lenght = Math.floor((widthCount + 1) / 2); i < Lenght; i++) { let p1 = brThickDiagonal + gripWidth * i * 2; let data = { basePt: new Vector3(p1, 0, 0), brLength: 0 }; if (width - p1 >= height - brThickDiagonal) { data.brLength = (height - brThickDiagonal) / SIN45; } else { data.brLength = (width - p1) / SIN45; } rWRDataList.push(data); } for (let i = 0, Lenght = Math.floor((heightCount - 1) / 2); i < Lenght; i++) { let p1 = (i + 1) * gripWidth * 2; let data = { basePt: new Vector3(brThickDiagonal, p1, 0), brLength: 0 }; if (height - p1 > width - brThickDiagonal) { data.brLength = (width - brThickDiagonal) / SIN45; } else { data.brLength = (height - p1 - brThickDiagonal) / SIN45; } rWRDataList.push(data); } for (let i = 0, flag = true, Lenght = Math.floor((widthCount - 1) / 2); i < Lenght; i++) { let p1 = brThickDiagonal + gripWidth * (i + 1) * 2; let data = { basePt: new Vector3(p1, brThickDiagonal * 2, 0), brLength: 0 }; if (p1 >= height - brThickDiagonal) { if (flag && heightCount % 2 === 0) data.brLength = (height - brThickDiagonal * 2) / SIN45; else data.brLength = (height - brThickDiagonal * 3) / SIN45; flag = false; } else { data.brLength = (p1 - brThickDiagonal * 2) / SIN45; } lWRDataList.push(data); } if (widthCount % 2 === 0) { for (let i = 0, flag = true, Lenght = Math.ceil(heightCount / 2); i < Lenght; i++) { let p1 = height - gripWidth * 2 * (i - 1) - gripWidth * (heightCount % 2 === 0 ? 2 : 1); let data = { basePt: new Vector3(width - brThickDiagonal, p1, 0), brLength: 0 }; if (i === 0) { data.basePt = new Vector3(width, brThickDiagonal, 0); if (height === width) { data.brLength = (width - brThickDiagonal) / SIN45; flag = false; } else { data.brLength = (Math.min(width, height) - brThickDiagonal * 2) / SIN45; } } else { if (height - p1 + brThickDiagonal * 2 >= width) { if (flag) data.brLength = (width - brThickDiagonal * 2) / SIN45; else data.brLength = (width - brThickDiagonal * 3) / SIN45; flag = false; } else { data.brLength = (height - p1 - brThickDiagonal) / SIN45; } } lWRDataList.push(data); } } else { for (let i = 0, flag = true, Lenght = Math.floor(heightCount / 2); i < Lenght; i++) { let p1 = height - gripWidth * 2 * i - gripWidth * (heightCount % 2 === 0 ? 1 : 2); let data = { basePt: new Vector3(width - brThickDiagonal, p1, 0), brLength: 0 }; if (height - p1 + brThickDiagonal * 2 >= width) { if (flag) data.brLength = (width - brThickDiagonal * 2) / SIN45; else data.brLength = (width - brThickDiagonal * 3) / SIN45; flag = false; } else { data.brLength = (height - p1 - brThickDiagonal) / SIN45; } lWRDataList.push(data); } } } /** * 获取酒格数据整格优先 * @param {{width: number 总宽 * height: number 总高 * gripWidth: number 半格宽 * brThick: number 板厚 * widthCount: number 半格宽数量 * heightCount: number 半格高数量 * lWRDataList: IWineRackData[], //往左倒 * rWRDataList: IWineRackData[], //往右倒 * }} option * @memberof DrawObliqueWineRackTool */ GetWineRackDataPreferentialWholeLattice(option) { const { width, height, gripWidth, brThick, widthCount, heightCount, lWRDataList, rWRDataList } = option; let data; for (let i = 0; i < Math.floor(widthCount / 2); i++) { let p1 = gripWidth + brThick * SIN45 + gripWidth * i * 2; data = { basePt: new Vector3(p1, 0, 0), brLength: 0 }; if (width - p1 > height - brThick * SIN45) { data.brLength = (height - brThick * SIN45) / SIN45; } else { data.brLength = (width - p1) / SIN45; } rWRDataList.push(data); } for (let i = 0; i < Math.floor(heightCount / 2); i++) { let p1 = gripWidth + brThick * SIN45 + i * gripWidth * 2; data = { basePt: new Vector3(brThick * SIN45, p1 - brThick * SIN45, 0), brLength: 0 }; if (height - p1 > width - brThick * SIN45) { data.brLength = (width - brThick * SIN45) / SIN45; } else { data.brLength = (height - p1) / SIN45; } rWRDataList.push(data); } for (let i = 0; i < Math.floor(widthCount / 2); i++) { let p1 = gripWidth + i * gripWidth * 2; data = { basePt: new Vector3(p1 + brThick * SIN45, 2 * brThick * SIN45, 0), brLength: 0 }; if (equaln$1(p1, height - brThick * SIN45 * 2)) { data.brLength = (height - brThick * SIN45 * 2) / SIN45; } else if (p1 > height - brThick * SIN45 * 3) { data.brLength = (height - brThick * SIN45 * 3) / SIN45; } else { data.brLength = (p1 - brThick * SIN45) / SIN45; } lWRDataList.push(data); } if (widthCount % 2 === 0) { for (let i = 0; i < Math.floor(heightCount / 2); i++) { let p1 = gripWidth + 2 * brThick * SIN45 + i * gripWidth * 2; data = { basePt: new Vector3(width - brThick * SIN45, p1, 0), brLength: 0 }; if (equaln$1(height - p1, width - 2 * brThick * SIN45)) { data.brLength = (height - p1) / SIN45; } else if (height - p1 - brThick * SIN45 > width - 2 * brThick * SIN45) { data.brLength = (width - brThick * SIN45 * 3) / SIN45; } else { data.brLength = (height - p1 - brThick * SIN45) / SIN45; } lWRDataList.push(data); } } else { for (let i = 0; i < Math.floor(heightCount / 2) + 1; i++) { let p1 = 0; if (i == 0) { p1 = brThick * SIN45; data = { basePt: new Vector3(width, p1, 0), brLength: 0 }; if (equaln$1(height, width, 1e-3)) { data.brLength = (width - brThick * SIN45) / SIN45; } else if (height - p1 > width - 2 * brThick * SIN45) { data.brLength = (width - brThick * SIN45 * 2) / SIN45; } else { data.brLength = (height - p1 - brThick * SIN45) / SIN45; } } else { p1 = 2 * brThick * SIN45 + i * gripWidth * 2; data = { basePt: new Vector3(width - brThick * SIN45, p1, 0), brLength: 0 }; if (height - p1 > brThick) { if (equaln$1(height - p1, width - 2 * brThick * SIN45, 1e-3)) { data.brLength = (height - p1) / SIN45; } else if (height - p1 > width - brThick * SIN45) { data.brLength = (width - brThick * SIN45 * 3) / SIN45; } else { data.brLength = (height - p1 - brThick * SIN45) / SIN45; } } else { continue; //这里需要跳出. } } lWRDataList.push(data); } } } /**根据酒格数据获取多段线 */ CreatePolylineByWineData(data, isLeft = true) { let thick = this.Config.boardThick; let lineData = [ { pt: new Vector2$1(), bul: 0 }, { pt: new Vector2$1(thick, 0), bul: 0 }, { pt: new Vector2$1(thick, data.brLength), bul: 0 }, { pt: new Vector2$1(0, data.brLength), bul: 0 }, ]; let pl = new Polyline(lineData); pl.CloseMark = true; let mat = MoveMatrix(new Vector3(thick)) .multiply(new Matrix4().makeRotationZ(0.25 * Math.PI * (isLeft ? 1 : -1))) .multiply(MoveMatrix(new Vector3(-thick))); pl.ApplyMatrix(mat) .ApplyMatrix(MoveMatrix(data.basePt.add(new Vector3(-thick)))); return pl; } /**根据多段线组构建酒格 */ DrawBoardFormPolyLine(pls, actualWidth, config) { const leftData = []; const rightData = []; for (let pl of pls) { let data = this.ParsePolyLine(pl); if (!data.isOk) continue; if (data.isLeft) leftData.push(data); else rightData.push(data); } this.CreateBoard(leftData, rightData, actualWidth, config); this.CreateBoard(rightData, leftData, actualWidth, config); } ParsePolyLine(pl) { if (pl.IsClose) { if (pl.EndParam !== 4) { return { isOk: false }; } let pts = pl.GetStretchPoints(); if (equalv3(pts[0], arrayLast(pts))) pts.pop(); /*****统一初始形状 * **** h * * * * 0 **** t */ pts.sort((a, b) => { if (equaln$1(a.y, b.y)) return a.x - b.x; else return a.y - b.y; }); let pts1 = pts.splice(0, 2); pts1.sort((a, b) => a.x - b.x); pts.sort((a, b) => a.distanceTo(pts1[1]) - b.distanceTo(pts1[1])); pts.unshift(...pts1); const area = pl.Area; let v1 = pts[1].clone().clone().sub(pts[0]); let v2 = arrayLast(pts).clone().sub(pts[0]); let [vecY, vecX] = v1.length() > v2.length() ? [v1, v2] : [v2, v1]; let length = vecY.length(); let thick = vecX.length(); let mat = new Matrix4().makeBasis(vecX.normalize(), vecY.normalize(), ZAxis).setPosition(pts[0]); let matInv = new Matrix4().getInverse(mat); let bp = pts[0].clone().negate(); if (equaln$1(length * thick, area, 5)) { let an = vecY.angleTo(XAxis); if (equaln$1(an, Math.PI / 4, R2WRTolerance)) { pts.sort((p1, p2) => p2.x - p1.x); return { isOk: true, isLeft: true, pl, length, matInv, basePt: new Vector3(pts[0].x, 0, pts[0].y), thickness: thick, isRo: true, }; } else if (equaln$1(an, 3 * Math.PI / 4, R2WRTolerance)) { pts.sort((p1, p2) => p1.y - p2.y); return { isOk: true, isLeft: false, pl, length, matInv, basePt: new Vector3(pts[0].x, 0, pts[0].y), thickness: thick, isRo: true, }; } else if (isParallelTo(vecX, XAxis, R2WRTolerance)) { return { isOk: true, isLeft: false, pl, length, matInv: new Matrix4().setPosition(bp), basePt: new Vector3(pts[0].x, 0, pts[0].y), thickness: thick, isVer: true, isRo: false, }; } else if (isParallelTo(vecX, YAxis, R2WRTolerance)) { return { isOk: true, isLeft: true, pl, length, matInv: new Matrix4().setPosition(bp), basePt: new Vector3(pts[1].x, 0, pts[1].y), thickness: thick, isRo: false, }; } else { return { isOk: false }; } } } return { isOk: false }; } /**绘制酒格 */ CreateBoard(lData, rData, actualWidth, cof) { if (!lData.length) return; let leftSeal, rightSeal, upSeal, downSeal, knifeRad; let config; if (cof) { leftSeal = cof.sealedLeft; rightSeal = cof.sealedRight; upSeal = cof.sealedUp; downSeal = cof.sealedDown; knifeRad = cof.knifeRadius; } else { config = this.Config; leftSeal = config.leftEdge; rightSeal = config.rightEdge; upSeal = config.topEdge; downSeal = config.bottomEdge; knifeRad = config.grooveLengthAdd; } for (let i = 0; i < lData.length; i++) { let d = lData[i]; let dists = []; for (let d2 of rData) { let intPts = d.pl.IntersectWith(d2.pl, IntersectOption.ExtendNone); if (intPts.length === 4) { dists.push(...intPts.map(p => { let p1 = p.applyMatrix4(d.matInv); if (!d.isRo && !d.isVer) return Math.abs(p1.x); return Math.abs(p1.y); })); } } arraySortByNumber(dists); arrayRemoveDuplicateBySort(dists, (n1, n2) => equaln$1(n1, n2, R2WRTolerance)); let pl = this.GetPolyline(d, dists, cof); if (pl) { let br = Board.CreateBoard(1, 1, d.thickness ?? config.boardThick, d.isVer ? BoardType.Vertical : BoardType.Layer); if (d.isLeft) br.Name = "右板" + (lData.length - i); else br.Name = "左板" + (i + 1); br.ContourCurve = pl; this.ParseHighSealing(br, leftSeal, rightSeal, upSeal, downSeal, d.isLeft); br.KnifeRadius = knifeRad; if (d.isRo) br.RotateBoard(0, Math.PI / 4 * (d.isLeft ? -1 : 1), 0); br.ApplyMatrix(MoveMatrix(this.space.SpaceBox.min)); br.ApplyMatrix(MoveMatrix(d.basePt)); if (config && (config.fullType === EFullType.ByHeight || config.arrayType === EWRackArrayType.Fixed) && config.fullDir === EFullDir.Right) { br.ApplyMatrix(MoveMatrix(new Vector3(this.space.Size.x - (actualWidth ?? 0)))); } br.ApplyMatrix(this.space.SpaceOCS); this.boardlist.push(br); } } } /**构建酒格形状,加入齿 */ GetPolyline(data, dists, cof) { let len = data.length; let isLeft = data.isLeft; const size = this.space.Size; let pl = new Polyline(); pl.Rectangle(size.y, len); let addWidth; //槽加宽 let knifeRad; if (cof) { addWidth = cof.addLen; knifeRad = cof.knifeRadius; } else { const config = this.Config; addWidth = config.grooveWidthAdd; knifeRad = config.grooveLengthAdd; } if (isLeft) { if (!data.isVer) { let newDist = dists.map(d => len - d); dists.length = 0; dists.push(...newDist); } } addWidth = addWidth / 2; if (dists.length % 2 === 0 && dists.length > 1) { let pts = []; for (let i = 0; i < dists.length; i++) { if (i % 2 === 0) { if (isLeft) { pts.push(new Vector2$1(0, dists[i] + addWidth)); pts.push(new Vector2$1(size.y / 2 + knifeRad, dists[i] + addWidth)); //因为圆形刀的限制,必须加大一个刀,保证生产出来的板能够正确的卡进去 } else { pts.push(new Vector2$1(size.y, dists[i] - addWidth)); pts.push(new Vector2$1(size.y / 2 - knifeRad, dists[i] - addWidth)); } } else { if (isLeft) { pts.push(new Vector2$1(size.y / 2 + knifeRad, dists[i] - addWidth)); pts.push(new Vector2$1(0, dists[i] - addWidth)); } else { pts.push(new Vector2$1(size.y / 2 - knifeRad, dists[i] + addWidth)); pts.push(new Vector2$1(size.y, dists[i] + addWidth)); } } } pl.AddVertexAt(isLeft ? pl.NumberOfVertices : 2, pts); } return pl; } } /** * 正酒格 */ class DrawUprightWineRackTool extends DrawWineRackTool { Parse(space, config) { super.Parse(space, config); let size = space.Size; let gripWidth = config.gripWidth; let brThick = config.boardThick; let spaceHeight = size.z; let spaceWidth = size.x; let gripHeight = gripWidth; let actualHeight = spaceHeight; let actualWidth = spaceWidth; switch (config.arrayType) { case EWRackArrayType.ByWidth: // let tempWidthCount = 0; // let tempHeightCount = 0; // if (widCount > 0 || heightCount > 0) // { // tempWidthCount = Math.floor(spaceWidth / (gripWidth + brThick)); // tempHeightCount = Math.floor(spaceHeight / (gripWidth + brThick)); // gripWidth = (spaceWidth - (tempWidthCount - 1) * brThick) / tempWidthCount; // gripHeight = (spaceHeight - (tempHeightCount - 1) * brThick) / tempHeightCount; // if (widCount > 0) // { // heightCount = tempHeightCount; // actualWidth = gripWidth * widCount + brThick * (widCount - 1); // } // else if (heightCount > 0) // { // widCount = tempWidthCount; // actualWidth = gripWidth * widCount + brThick * (widCount - 1); // } // } config.widthCount = Math.floor(spaceWidth / (gripWidth + brThick)); config.heightCount = Math.floor(spaceHeight / (gripWidth + brThick)); config.gripWidth = (spaceWidth - (config.widthCount - 1) * brThick) / config.widthCount; gripHeight = (spaceHeight - (config.heightCount - 1) * brThick) / config.heightCount; break; case EWRackArrayType.ByCount: if (config.widthCount > 0) { config.widthCount = Math.floor(config.widthCount); } else { let gk = (spaceHeight - 2 * brThick) / config.heightCount; config.widthCount = Math.floor((spaceWidth - 2 * brThick) / gk + 1e-6); } if (config.heightCount > 0) { config.heightCount = Math.floor(config.heightCount); } else { let gk = (spaceWidth - 2 * config.heightCount) / config.heightCount; config.heightCount = Math.floor((spaceHeight - 2 * brThick) / gk + 1e-6); } config.gripWidth = (spaceWidth - (config.widthCount - 1) * brThick) / config.widthCount; gripHeight = (spaceHeight - (config.heightCount - 1) * brThick) / config.heightCount; break; case EWRackArrayType.Fixed: config.widthCount = Math.floor(config.widthCount); config.heightCount = Math.floor(config.heightCount); actualWidth = gripWidth * config.widthCount + brThick * (config.widthCount - 1); actualHeight = gripWidth * config.heightCount + brThick * (config.heightCount - 1); } if (config.widthCount <= 0 || config.heightCount <= 0) throw `绘制酒格失败,酒格个数异常! 宽个数:${config.widthCount} 高个数:${config.heightCount}`; config.widthCount--; config.heightCount--; this.BuildBoard(gripHeight, actualWidth, actualHeight, space); // this.AddLayerOrVerticalBoard(actualWidth, actualHeight); } BuildBoard(gripHeight, spaceWidth, spaceHeight, space) { const config = this.Config; let min = space.SpaceBox.min; let size = space.Size; let brThick = config.boardThick; let verBr = Board.CreateBoard(spaceHeight, size.y, brThick, BoardType.Vertical); this.ParseBrTooth(verBr, gripHeight); for (let i = 1; i <= config.widthCount; i++) { let br = verBr.Clone(); br.Name = "右板" + i; br.ApplyMatrix(MoveMatrix(min.clone().add(new Vector3(config.gripWidth * i + (i - 1) * brThick)))).ApplyMatrix(space.SpaceOCS); this.boardlist.push(br); } let lyBr = Board.CreateBoard(spaceWidth, size.y, brThick); this.ParseBrTooth(lyBr); for (let i = 1; i <= config.heightCount; i++) { let br = lyBr.Clone(); br.Name = "左板" + i; br.ApplyMatrix(MoveMatrix(min.clone().add(new Vector3(spaceWidth, 0, gripHeight * i + (i - 1) * brThick)))) .ApplyMatrix(space.SpaceOCS); this.boardlist.push(br); } } ParseBrTooth(br, gripHeight) { const config = this.Config; let initPts = []; let addWidth = config.grooveWidthAdd / 2; //槽加长 let topEdge = config.topEdge; let bottomEdge = config.bottomEdge; let leftEdge = config.leftEdge; let rightEdge = config.rightEdge; let grooveLenAdd = config.grooveLengthAdd; br.BoardProcessOption.sealedUp = topEdge.toString(); br.BoardProcessOption.sealedDown = bottomEdge.toString(); br.BoardProcessOption.sealedLeft = leftEdge.toString(); br.BoardProcessOption.sealedRight = rightEdge.toString(); if (br.BoardType === BoardType.Layer) { let p1 = new Vector3(0, config.gripWidth - addWidth); let p2 = new Vector3(br.Width / 2 + grooveLenAdd, config.gripWidth - addWidth); let p3 = new Vector3(br.Width / 2 + grooveLenAdd, config.gripWidth + br.Thickness + addWidth); let p4 = new Vector3(0, config.gripWidth + br.Thickness + addWidth); initPts.push(p1, p2, p3, p4); for (let i = 2; i <= config.widthCount; i++) { initPts.push(...[p1, p2, p3, p4].map(p => p.clone().add(new Vector3(0, (config.gripWidth + br.Thickness) * (i - 1))))); } initPts.reverse(); let cu = br.ContourCurve; cu.AddVertexAt(cu.EndParam, initPts.map(p => AsVector2(p))); br.ContourCurve = cu; } else { let p1 = new Vector3(br.Width, gripHeight - addWidth); let p2 = new Vector3(br.Width / 2 - grooveLenAdd, gripHeight - addWidth); let p3 = new Vector3(br.Width / 2 - grooveLenAdd, gripHeight + br.Thickness + addWidth); let p4 = new Vector3(br.Width, gripHeight + br.Thickness + addWidth); initPts.push(p1, p2, p3, p4); for (let i = 2; i <= config.heightCount; i++) { initPts.push(...[p1, p2, p3, p4].map(p => p.clone().add(new Vector3(0, (gripHeight + br.Thickness) * (i - 1))))); } let cu = br.ContourCurve; cu.AddVertexAt(2, initPts.map(p => AsVector2(p))); br.ContourCurve = cu; } this.ParseHighSealing(br, config.leftEdge, config.rightEdge, config.topEdge, config.bottomEdge, br.BoardType === BoardType.Layer); } } let TemplateWineRackRecord = class TemplateWineRackRecord extends TemplateRecord { constructor() { super(); this.option = { ...DefaultWineRackConfig }; this.name = "酒格(自动)"; } get Option() { return Object.assign({}, this.option); } set Option(option) { this.WriteAllObjectRecord(); Object.assign(this.option, option); ExtendsBoardThickness(this, option.boardThick); } async Update() { await super.Update(); let bh = this.GetParam("BH")?.value; if (bh) this.option.boardThick = bh; let bh2 = this.GetParam("BH2")?.value; if (bh2) this.option.brThick2 = bh2; let wineRack; if (this.option.type === EWineRackType.Oblique) wineRack = DrawObliqueWineRackTool.GetInstance(); else wineRack = DrawUprightWineRackTool.GetInstance(); let sbrs = this.PositioningSupportBoards; let space = new ISpaceParse(sbrs, this._CacheSpaceCS); space.ParseOK = true; space.SpaceBox = new Box3Ext(new Vector3(), this._CacheSpaceSize); wineRack.Parse(space, this.Option); let nbrs = wineRack.boardlist; for (let i = nbrs.length; i < this.Objects.length; i++) this.Objects[i].Object.Erase(); for (let i = 0; i < nbrs.length; i++) { if (i < this.Objects.length) { let br = this.Objects[i].Object; br.Erase(false); br.CopyFrom(nbrs[i]); br.SpaceOCS = this._CacheSpaceCS; } else { this._db.ModelSpace.Append(nbrs[i]); this.Objects.push(nbrs[i].Id); nbrs[i].SpaceOCS = this._CacheSpaceCS; } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.option.type = file.Read(); this.option.arrayType = file.Read(); this.option.fullType = file.Read(); this.option.isFull = file.Read(); this.option.fullDir = file.Read(); this.option.isLock = file.Read(); this.option.heightCount = file.Read(); this.option.widthCount = file.Read(); this.option.isTotalDepth = file.Read(); this.option.depth = file.Read(); this.option.calcDepth = file.Read(); this.option.gripWidth = file.Read(); this.option.boardThick = file.Read(); this.option.grooveWidthAdd = file.Read(); this.option.leftEdge = file.Read(); this.option.rightEdge = file.Read(); this.option.topEdge = file.Read(); this.option.bottomEdge = file.Read(); this.option.frontCut = file.Read(); this.option.leftCut = file.Read(); this.option.rightCut = file.Read(); this.option.topCut = file.Read(); this.option.grooveLengthAdd = file.Read(); this.option.isDrawLy = file.Read(); this.option.isDrawVer = file.Read(); this.option.brThick2 = file.Read(); if (ver > 1) this.option.followNarrow = file.Read(); if (ver > 2) this.option.wineRackStyle = file.Read(); if (ver > 3) { this.option.boardMatName = file.Read(); this.option.material = file.Read(); this.option.color = file.Read(); this.option.roomName = file.Read(); this.option.cabinetName = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this.option.type); file.Write(this.option.arrayType); file.Write(this.option.fullType); file.Write(this.option.isFull); file.Write(this.option.fullDir); file.Write(this.option.isLock); file.Write(this.option.heightCount); file.Write(this.option.widthCount); file.Write(this.option.isTotalDepth); file.Write(this.option.depth); file.Write(this.option.calcDepth); file.Write(this.option.gripWidth); file.Write(this.option.boardThick); file.Write(this.option.grooveWidthAdd); file.Write(this.option.leftEdge); file.Write(this.option.rightEdge); file.Write(this.option.topEdge); file.Write(this.option.bottomEdge); file.Write(this.option.frontCut); file.Write(this.option.leftCut); file.Write(this.option.rightCut); file.Write(this.option.topCut); file.Write(this.option.grooveLengthAdd); file.Write(this.option.isDrawLy); file.Write(this.option.isDrawVer); file.Write(this.option.brThick2); file.Write(this.option.followNarrow); file.Write(this.option.wineRackStyle); file.Write(this.option.boardMatName); file.Write(this.option.material); file.Write(this.option.color); file.Write(this.option.roomName); file.Write(this.option.cabinetName); } }; TemplateWineRackRecord = __decorate([ Factory ], TemplateWineRackRecord); //铰链 function IsHinge(en) { if (en instanceof HardwareCompositeEntity) { if (en.Template) { let temp = en.Template.Object; if (temp?.Parent?.Object?.Name === "铰链空间") return true; } // else //有时候 模块会被破坏,所以模块检测不是唯一标准 { return en.HardwareOption.name.includes("铰链"); } } } // 户型 和 模型 function IsHouse(en) { return IsRoomBase(en) || IsModel(en); } //模型 function IsModel(en) { return en instanceof EntityRef || en instanceof EntityFbx; } // 户型 function IsRoomBase(en) { if (en instanceof RoomBase || (en instanceof HardwareTopline && (en.HardwareOption.name === "地脚线" || en.HardwareOption.name === "吊顶")) || en.Template?.Object instanceof TemplateArcWindowRecord || en.Template?.Object?.Parent?.Object instanceof TemplateWindowRecord || en.Template?.Object?.Parent?.Object instanceof TemplateRoomDoorRecord) return true; else return false; } const EmptyArray = []; let VisualSpaceBox = class VisualSpaceBox extends Entity { constructor(_Length = 1, _Width = 1, _Height = 1) { super(); this._Length = _Length; this._Width = _Width; this._Height = _Height; this.OnlyRenderType = true; this._LText = new Text; this._WText = new Text; this._HText = new Text; this._IsRoot = false; this._DisplayLength = true; this._DisplayWidth = true; this._DisplayHeight = true; this._LText.IsEmbedEntity = true; this._WText.IsEmbedEntity = true; this._HText.IsEmbedEntity = true; this._LText.TextAligen = TextAligen.Down; this._WText.TextAligen = TextAligen.Top; this._HText.TextAligen = TextAligen.Top; this._LText._Matrix.makeBasis(XAxis, ZAxis, YAxis); this._WText._Matrix.makeBasis(YAxis, XAxisN, ZAxis); this._HText._Matrix.makeBasis(ZAxis, XAxisN, YAxisN); } get Length() { return this._Length; } get Width() { return this._Width; } get Height() { return this._Height; } set IsRoot(b) { if (this._IsRoot !== b) { this._IsRoot = b; for (let [, obj] of this._CacheDrawObject) { let [box, edge, lobj, wobj, hobj] = obj.children; if (this._IsRoot) box.visible = false; else box.visible = true; if (this._IsRoot) { this._LText.TextAligen = TextAligen.Top; this._WText.TextAligen = TextAligen.Down; this._HText.TextAligen = TextAligen.Down; } else { this._LText.TextAligen = TextAligen.Down; this._WText.TextAligen = TextAligen.Top; this._HText.TextAligen = TextAligen.Top; } } } } set DisplayWidth(b) { for (let [, obj] of this._CacheDrawObject) { let [box, edge, lobj, wobj, hobj] = obj.children; wobj.visible = b; } this._DisplayWidth = b; } set DisplayLength(b) { for (let [, obj] of this._CacheDrawObject) { let [box, edge, lobj, wobj, hobj] = obj.children; lobj.visible = b; } this._DisplayLength = b; } set DisplayHeight(b) { for (let [, obj] of this._CacheDrawObject) { let [box, edge, lobj, wobj, hobj] = obj.children; hobj.visible = b; } this._DisplayHeight = b; } SetSize(l, w, h) { if (l !== this._Length || w !== this._Width || h !== this._Height) { this.WriteAllObjectRecord(); this._Length = l; this._Width = w; this._Height = h; this.Update(); } } get BoundingBoxInOCS() { return new Box3Ext(new Vector3, new Vector3(this._Length, this._Width, this._Height)); } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } //#region 捕捉 /** * * @param snapMode 捕捉模式(单一) * @param pickPoint const * @param lastPoint const * @param viewXform const 最近点捕捉需要这个变量 * @returns object snap points */ GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: { let pts = [ new Vector3(), new Vector3(this._Length), new Vector3(this._Length, this._Width), new Vector3(0, this._Width), new Vector3(0, 0, this._Height), new Vector3(this._Length, 0, this._Height), new Vector3(this._Length, this._Width, this._Height), new Vector3(0, this._Width, this._Height), ]; for (let p of pts) p.applyMatrix4(this._Matrix); return pts; } } return EmptyArray; } //#endregion //#region Draw InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); //box obj.add(new Mesh(backGeo, ColorMaterial.GetBasicMaterialTransparent2(this.DrawColorIndex, 0.5))); //edge obj.add(new LineSegments(edgeGeo, ColorMaterial.GetLineMaterial(this.DrawColorIndex))); obj.add(this._LText.DrawObject); obj.add(this._WText.DrawObject); obj.add(this._HText.DrawObject); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(type, obj) { let [box, edge, lobj, wobj, hobj] = obj.children; box.scale.set(this._Length, this._Width, this._Height); box.updateMatrix(); edge.scale.set(this._Length, this._Width, this._Height); edge.updateMatrix(); box.visible = !this._IsRoot; if (this._IsRoot) { this._LText.TextAligen = TextAligen.Top; this._WText.TextAligen = TextAligen.Down; this._HText.TextAligen = TextAligen.Down; } else { this._LText.TextAligen = TextAligen.Down; this._WText.TextAligen = TextAligen.Top; this._HText.TextAligen = TextAligen.Top; } this._LText.TextString = FixedNotZero(this._Length, 2); this._WText.TextString = FixedNotZero(this._Width, 2); this._HText.TextString = FixedNotZero(this._Height, 2); //实体被错误的Dispose,导致需要这样的更新 this._LText.Update(); this._WText.Update(); this._HText.Update(); lobj.visible = this._DisplayLength; wobj.visible = this._DisplayWidth; hobj.visible = this._DisplayHeight; lobj.position.set(this._Length / 2, 0, 0); lobj.updateMatrix(); wobj.position.set(0, this._Width / 2, 0); wobj.updateMatrix(); hobj.position.set(0, 0, this._Height / 2); hobj.updateMatrix(); } UpdateDrawObjectMaterial(type, obj, material) { let [mesh, line] = obj.children; mesh.material = ColorMaterial.GetBasicMaterialTransparent2(this.DrawColorIndex, 0.2); line.material = ColorMaterial.GetLineMaterial(this.DrawColorIndex); } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this._Length = file.Read(); this._Width = file.Read(); this._Height = file.Read(); this._IsRoot = file.Read(); this._DisplayLength = file.Read(); this._DisplayWidth = file.Read(); this._DisplayHeight = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this._Length); file.Write(this._Width); file.Write(this._Height); file.Write(this._IsRoot); file.Write(this._DisplayLength); file.Write(this._DisplayWidth); file.Write(this._DisplayHeight); } // //局部撤销 // ApplyPartialUndo(undoData: CADObject) // { // super.ApplyPartialUndo(undoData); // } //#endregion //#region GetGripPoints() { // return EmptyArray;//因为更新模块树是异步的,没办法在拽拖夹点时顺带更新,所以移除这个特性. let x = new Vector3; let y = new Vector3; let z = new Vector3; this._Matrix.extractBasis(x, y, z); x.multiplyScalar(this._Length); y.multiplyScalar(this._Width); z.multiplyScalar(this._Height); let p = this.Position; return [p, x.add(p), y.add(p), z.add(p)]; } MoveGripPoints(indexList, vec) { let template = this.Template?.Object; if (template && template !== template.Root) return; for (let i of indexList) { if (i === 0) { this.WriteAllObjectRecord(); this.Position = this.Position.add(vec); return; } else { continue; } } } GetStretchPoints() { return []; } /** * 拉伸夹点,用于Stretch命令 * * @param {Array} indexList 拉伸点索引列表. * @param {Vector3} vec 移动向量 * @memberof Entity */ MoveStretchPoints(indexList, vec) { } }; __decorate([ AutoRecord ], VisualSpaceBox.prototype, "_IsRoot", void 0); __decorate([ AutoRecord ], VisualSpaceBox.prototype, "_DisplayLength", void 0); __decorate([ AutoRecord ], VisualSpaceBox.prototype, "_DisplayWidth", void 0); __decorate([ AutoRecord ], VisualSpaceBox.prototype, "_DisplayHeight", void 0); VisualSpaceBox = __decorate([ Factory ], VisualSpaceBox); class BackFaceBoxBufferGeometry extends BufferGeometry { constructor(length = 1, width = 1, height = 1) { super(); let pts = [ 0, width, 0, length, width, 0, length, width, height, 0, width, height, ]; let uvs = [ 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, ]; let indices = [0, 1, 2, 0, 2, 3]; this.setIndex(indices); this.setAttribute('position', new Float32BufferAttribute(pts, 3)); // this.setAttribute('normal', new Float32BufferAttribute(normals, 3)); this.setAttribute('uv', new Float32BufferAttribute(uvs, 2)); } } const backGeo = new BackFaceBoxBufferGeometry(); const edgeGeo = GenerateBoxEdgeGeometry(1, 1, 1); var ViewportEntity_1; const ProjScreenMatrix = new Matrix4(); let ViewportEntity = ViewportEntity_1 = class ViewportEntity extends Entity { constructor(_width = 1, _height = 1) { super(); this._width = _width; this._height = _height; this.scene = new Scene(); this.camera = new CameraUpdate(); this._renderType = RenderType.Print; this._HideObjectIds = new Set(); this._ShowObjectIds = new Set(); this._FreezeLayers = new Set(); this._RenderTarget = new WebGLRenderTarget(0, 0); this.ViewData = { left: 0, bottom: 0, }; this._Color = 0; this._Frustum = new Frustum(); this.OnlyRenderType = true; this.NeedUpdateTexture = true; this.IsFirstVersion = false; } get Left() { return this.Position.x; } get Bottom() { return this.Position.y; } get Width() { return this._width; } set Width(v) { if (v === this._height || v < 0) return; this.WriteAllObjectRecord(); this._width = v; this.Update(UpdateDraw.Geometry); } get ViewWidth() { return this.camera.Width ?? 1; } get ViewHeight() { return this.camera.Height ?? 1; } get Height() { return this._height; } set Height(v) { if (v === this._height || v < 0) return; this.WriteAllObjectRecord(); this._height = v; this.Update(UpdateDraw.Geometry); } get EntitysBoundingBox() { if (!this._EntitysBoundingBox) { this.UpdateEntitysBoundingBox(); } return this._EntitysBoundingBox; } UpdateEntitysBoundingBox() { this._EntitysBoundingBox = new Box3Ext(); for (let en of this.Entitys) { if (!en.IsErase && en instanceof Board) this._EntitysBoundingBox.union(en.BoundingBox); } } IsHide(id) { return this._HideObjectIds.has(id); } IsShow(id) { return this._ShowObjectIds.has(id); } get HideObjects() { return [...this._HideObjectIds]; } get ShowObjects() { return [...this._ShowObjectIds]; } HasFreezeLayer(id) { return this._FreezeLayers.has(id); } DeleteFreezeLayer(id) { if (!this._FreezeLayers.has(id)) return; this.WriteAllObjectRecord(); this._FreezeLayers.delete(id); } AddFreezeLayer(id) { if (this._FreezeLayers.has(id)) return; this.WriteAllObjectRecord(); this._FreezeLayers.add(id); } //#region 显示隐藏实体控制 //应显尽显 ShowAll() { if (this._HideObjectIds.size === 0) return; this.WriteAllObjectRecord(); //这个实现建立在一切数据都是准确的情况下 for (let id of this._HideObjectIds) this._ShowObjectIds.add(id); //这里应该直接绘制的 this._HideObjectIds.clear(); this.UpdateScene(); //优化 应该在上面直接绘制的 } //添加显示实体 AppendShowObjects(ids) { this.WriteAllObjectRecord(); if (!Array.isArray(ids)) ids = [ids]; for (let id of ids) { let ent = id?.Object; if (ent) { this._ShowObjectIds.add(id); if (ent instanceof Entity) this.EntitysBoundingBox.union(ent.BoundingBox); } this._HideObjectIds.delete(id); } } //(未被使用)移除显示实体 RemoveShowObjects(ids) { this.WriteAllObjectRecord(); if (!Array.isArray(ids)) ids = [ids]; ids.forEach(id => this._ShowObjectIds.delete(id)); } //添加隐藏实体 AppendHideObjects(ids) { this.WriteAllObjectRecord(); if (!Array.isArray(ids)) ids = [ids]; for (let id of ids) { this._HideObjectIds.add(id); this._ShowObjectIds.delete(id); } } //(未被使用)移除隐藏实体 RemoveHideObjects(ids) { this.WriteAllObjectRecord(); if (!Array.isArray(ids)) ids = [ids]; for (let id of ids) { this._HideObjectIds.delete(id); this._ShowObjectIds.add(id); } } //#endregion GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { switch (snapMode) { case ObjectSnapMode.End: return this.GetGripPoints(); case ObjectSnapMode.Mid: case ObjectSnapMode.Nea: case ObjectSnapMode.Ext: case ObjectSnapMode.Per: } return []; } SetUVs(geo) { let uvArray = [ 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0 ]; geo.setAttribute("uv", new Float32BufferAttribute(uvArray, 2)); } get Entitys() { let ens = []; for (let id of this._ShowObjectIds) if (id?.Object) ens.push(id.Object); for (let id of this._HideObjectIds) if (id?.Object) ens.push(id.Object); return ens; } get Points() { let z = -1; return [ new Vector3(0, 0, z), new Vector3(this._width, 0, z), new Vector3(this._width, this._height, z), new Vector3(0, this._height, z), ]; } UpdateByPts(p1, p2) { let left = Math.min(p1.x, p2.x); let bottom = Math.min(p1.y, p2.y); this._width = Math.abs(p1.x - p2.x); this._height = Math.abs(p1.y - p2.y); this.Position = new Vector3(left, bottom); } GetGripPoints() { return this.Points.map(p => p.applyMatrix4(this.OCS).setZ(0)); } MoveGripPoints(indexList, moveVec) { this.WriteAllObjectRecord(); let pts = this.GetGripPoints(); for (let index of indexList) { pts[index].add(moveVec); this.UpdateByPts(pts[index], pts[FixIndex(index + 2, 4)]); } this.Update(); } ZoomAll() { let box = new Box3; for (let obj of this.scene.children) { let ent = GetEntity(obj); if (ent) box.union(ent.BoundingBox); else box.union(GetBox(obj)); } if (box.isEmpty()) box.set(new Vector3(), new Vector3(1000 * (this.Width / this.Height), 1000, 1000)); this.camera.ZoomExtentsBox3(box); this.camera.Zoom(1.2); } ZoomtoEntitys(ens) { let box = new Box3(); ens.reduce((b, e) => b.union(e.BoundingBox), box); if (box.isEmpty()) return; this.camera.ZoomExtentsBox3(box); this.camera.Zoom(1.2); } get RenderType() { return this._renderType; } set RenderType(v) { if (v === this._renderType) return; this.WriteAllObjectRecord(); this._renderType = v; let currentRenderType = this._renderType; for (let o of this.scene.children) { if (o instanceof Group) { o.children.length = 0; let e = o.userData.Entity; if (this._renderType === RenderType.Physical2 && IsHouse(e)) //真实带线框视口 显示户型(使用真实类型) currentRenderType = RenderType.Physical; else currentRenderType = this._renderType; let obj = e.GetDrawObjectFromRenderType(e.IsOnlyRender ? currentRenderType + 100 : currentRenderType); if (obj) o.children.push(obj); else console.warn(e, `视口:该实体渲染类型${this._renderType}类型不存在`); } } } get BoundingBox() { return new Box3().setFromPoints(this.GetGripPoints()); } CanRennder(en) { if (en?.Id && !en.IsErase && (en instanceof Entity) && !(en instanceof Hole) && !(en instanceof ViewportEntity_1) && !(en instanceof VisualSpaceBox)) { //首个版本布局由隐藏列表控制 if (this.IsFirstVersion) { if (this._HideObjectIds.has(en.Id)) return false; } else { if (!this._ShowObjectIds.has(en.Id) || this._HideObjectIds.has(en.Id)) return false; } // if (en instanceof HardwareCompositeEntity && en.HardwareOption.name.includes("铰链")) if (IsHinge(en)) return false; return true; } return false; } DrawEntityToScene(en) { if (!this.CanRennder(en)) return; //在真实线框视图 添加户型失败问题 ( 显示户型(使用真实视图类型)) let currentRenderType = this._renderType; if (currentRenderType === RenderType.Physical2 && IsHouse(en)) currentRenderType = RenderType.Physical; else currentRenderType = this._renderType; let cloneObject = new Group(); let renderType = en.IsOnlyRender ? currentRenderType + 100 : currentRenderType; let o = en.GetDrawObjectFromRenderType(renderType); if (!o) { console.warn(en, `视口:该实体渲染类型${renderType}类型不存在`); return; } cloneObject.children.push(o); cloneObject.name = en.Id.Index.toString(); cloneObject.userData = { Entity: en }; cloneObject.updateMatrixWorld(); //因为LayoutTool里面有更新实体的操作 所以这个代码暂时是多余的 // //实体被删除的时候 这里自然的隐藏 // Object.defineProperty(cloneObject, "visible", { // get: () => !en.IsErase // }); this.scene.add(cloneObject); this.NeedUpdateTexture = true; } GoodBye() { super.GoodBye(); DisposeThreeObj(this.scene); Object3DRemoveAll(this.scene); this.ViewportMaterial?.dispose(); this.ViewportMaterial = undefined; this._RenderTarget.dispose(); } UpdateScene() { } CloneDrawObject(from) { super.CloneDrawObject(from); for (let [type, obj] of this._CacheDrawObject) { obj.children[1].material = this.ViewportMaterial; } } IsInFrustum(obj) { let camera = this.camera.Camera; ProjScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); this._Frustum.setFromProjectionMatrix(ProjScreenMatrix); let isInt = false; obj.traverse((o) => { if (!isInt && o.geometry) isInt = this._Frustum.intersectsObject(o); }); return isInt; } //TODO:有个bug,在实体列表更新的时候 这个box没有更新,会导致一些错误. IsIntersectsEntitysBox(box) { return this.EntitysBoundingBox.intersectsBox(box, 100); } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this.IsFirstVersion = ver === 1; this.camera.ReadFile(file); this._width = file.Read(); this._height = file.Read(); this._renderType = file.Read(); if (ver === 2 && this._renderType === RenderType.Jig) this._renderType = RenderType.Wireframe; let count = file.Read(); this._HideObjectIds.clear(); for (let i = 0; i < count; i++) { let id = file.ReadSoftObjectId(); if (id) this._HideObjectIds.add(id); } if (ver > 1) { let count = file.Read(); this._ShowObjectIds.clear(); for (let i = 0; i < count; i++) { let id = file.ReadSoftObjectId(); if (id) this._ShowObjectIds.add(id); } } if (ver > 3) { this.camera.LookAt(new Vector3(file.Read(), file.Read(), file.Read())); this.camera.Camera.up = new Vector3(file.Read(), file.Read(), file.Read()); } this._FreezeLayers.clear(); if (ver > 4) { let count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadSoftObjectId(); if (id) this._FreezeLayers.add(id); } } if (!this._isErase) this.UpdateScene(); } WriteFile(file) { super.WriteFile(file); file.Write(5); this.camera.WriteFile(file); file.Write(this._width); file.Write(this._height); file.Write(this._renderType); file.Write(this._HideObjectIds.size); this._HideObjectIds.forEach(id => file.WriteSoftObjectId(id)); file.Write(this._ShowObjectIds.size); this._ShowObjectIds.forEach(id => file.WriteSoftObjectId(id)); file.WriteVec3(this.camera.Direction); file.WriteVec3(this.camera.Camera.up); // 图层的视口冻结 file.Write(this._FreezeLayers.size); this._FreezeLayers.forEach(id => file.WriteSoftObjectId(id)); } }; ViewportEntity = ViewportEntity_1 = __decorate([ Factory ], ViewportEntity); let Cylineder = class Cylineder extends Entity { constructor(rad, height) { super(); this.m_Center = new Vector3(); this.m_Radius = rad || 1; this.m_Height = height || 1; } get Radius() { return this.m_Radius; } set Radius(v) { this.WriteAllObjectRecord(); this.m_Radius = v; this.Update(); } get Height() { return this.m_Height; } set Height(v) { this.WriteAllObjectRecord(); this.m_Height = v; this.Update(); } get Center() { return new Vector3().setFromMatrixPosition(this.OCS); } set Center(v) { this.WriteAllObjectRecord(); this._Matrix.setPosition(v); this.Update(); } CreateGeometry() { let cir = Contour.CreateContour([new Circle(this.m_Center, this.m_Radius)]).Shape; let extrudeSettings = { bevelEnabled: false, depth: this.m_Height }; return new ExtrudeGeometry(cir, extrudeSettings); } InitDrawObject(renderType) { return new Mesh(this.CreateGeometry(), new MeshNormalMaterial()); } UpdateDrawObject(type, en) { let obj = en; obj.geometry = this.CreateGeometry(); obj.geometry.verticesNeedUpdate = true; } _ReadFile(file) { super._ReadFile(file); file.Read(); //1 this.m_Radius = file.Read(); this.m_Height = file.Read(); this.m_Center.fromArray(file.Read()); } WriteFile(file) { super.WriteFile(file); file.Write(1); //ver file.Write(this.m_Radius); file.Write(this.m_Height); file.Write(this.m_Center.toArray()); } }; Cylineder = __decorate([ Factory ], Cylineder); var RevolveSolid_1; let r = new Matrix2; let v = new Vector3; function AxisCS(n) { let x = new Vector3; let y = new Vector3; Orbit.ComputUpDirection(n, y, x); return new Matrix4().makeBasis(x.normalize(), y.normalize(), n); } let _Arc = new Arc(new Vector3, 1, 0, Math.PI / 2, false); let RevolveSolid = RevolveSolid_1 = class RevolveSolid extends Entity { constructor(axisDirection, axisPos, contour, _StartAngle = 0, _EndAngle = Math.PI / 2) { super(); this._StartAngle = _StartAngle; this._EndAngle = _EndAngle; this._DisplayAccuracy = 0; this._CacheContourPoints3DQ2 = []; //放样点集合缓存(只是避免了重复构造) if (axisDirection) { axisDirection.normalize(); let x = new Vector3; let y = new Vector3; Orbit.ComputUpDirection(axisDirection, y, x); this._Matrix.makeBasis(x.normalize(), y.normalize(), axisDirection); } else axisDirection = new Vector3(0, 0, 1); if (axisPos) this._Matrix.setPosition(axisPos); else axisPos = new Vector3; if (contour) { if (contour.Id) this._Contour = contour.Clone(); else this._Contour = contour; this._Contour.IsEmbedEntity = true; if (this._Contour.IsClockWise) this._Contour.Reverse(); let ocs = this.OCS.setPosition(0, 0, 0); let ocsInv = this.OCSInv; let endParam = this._Contour.EndParam; let p = this._Contour.StartPoint.applyMatrix4(ocsInv); axisPos.add(axisDirection.clone().multiplyScalar(p.z)); //对齐X轴 for (let i = 0; i < endParam; i += 0.5) { let p = this._Contour.GetPointAtParam(i); p.applyMatrix4(ocsInv).setZ(0); if (!equalv3(p, ZeroVec)) { p.applyMatrix4(ocs); p.normalize(); //X轴 let y = axisDirection.clone().cross(p).normalize(); this._Matrix.makeBasis(p, y, axisDirection).setPosition(axisPos); break; } } this._Contour.ApplyMatrix(this.OCSInv); } } get DisplayAccuracy() { return this._DisplayAccuracy; } set DisplayAccuracy(v) { if (!equaln$1(v, this._DisplayAccuracy)) { this.WriteAllObjectRecord(); this._DisplayAccuracy = v; this.Update(); } } get Contour() { return this._Contour; } GetGripPoints() { let box = this._Contour.BoundingBox; let size = box.getSize(new Vector3); let p1 = new Vector3(size.x); let p2 = p1.clone(); r.setRotate(this._StartAngle); r.applyVector(p1); r.setRotate(this._EndAngle); r.applyVector(p2); p1.applyMatrix4(this._Matrix); p2.applyMatrix4(this._Matrix); return [p1, p2]; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint) { switch (snapMode) { case ObjectSnapMode.Nea: break; case ObjectSnapMode.Cen: break; case ObjectSnapMode.Per: break; case ObjectSnapMode.Tan: break; case ObjectSnapMode.End: return this.GetGripPoints(); } return []; } MoveGripPoints(indexs, vec) { this.WriteAllObjectRecord(); for (let i of indexs) { let p = this.GetGripPoints()[i]; let np = p.add(vec).applyMatrix4(this.OCSInv); let a = angle(np); if (i === 0) this._StartAngle = a; else this._EndAngle = a; this.Update(); return; } } get BoundingBox() { let geom = this.MeshGeometry; if (!geom) { console.error("RevolveSolid无法建模"); return new Box3; } if (!geom.boundingBox) geom.computeBoundingBox(); return geom.boundingBox.clone().applyMatrix4(this._Matrix); } get MeshGeometry() { if (this._MeshGeometry) return this._MeshGeometry; _Arc.StartAngle = this._StartAngle; _Arc.EndAngle = this._EndAngle; if (this._CacheIsRev === undefined) { let n = this._Contour.Normal; this._CacheIsRev = n.y > 0; } if (this._Contour.DisplayAccuracy !== this._DisplayAccuracy) { this._CacheContourPoints = undefined; this._CacheContourPoints3DQ2 = []; } if (this._CacheContourPoints === undefined) { this._CacheContourPoints = []; this._Contour.DisplayAccuracy = this._DisplayAccuracy; for (let p of this._Contour.Shape.getPoints(6)) { this._CacheContourPoints.push(p); if (p["_mask_"]) this._CacheContourPoints.push(p); } this._CacheContourPoints3D = this._CacheContourPoints.map(p => AsVector3(p).applyMatrix4(this._Contour.OCSNoClone)); this._CacheContourFaces = ShapeUtils.triangulateShape(this._CacheContourPoints, []); this._CacheContourPoints3DQ = []; if (isParallelTo(this._Contour.Normal, YAxis)) this._CacheContourPoints3DQ.push(...this._CacheContourPoints3D); else for (let i = 0; i < this._CacheContourPoints3D.length; i++) { let p1 = this._CacheContourPoints3D[i]; let p2 = this._CacheContourPoints3D[FixIndex$1(i + 1, this._CacheContourPoints3D)]; let length = p1.distanceTo(p2); v.subVectors(p2, p1); let count = clamp(Math.floor(length / 50), 2, 16); //合理的控制采样精度是性能的关键 v.divideScalar(count); this._CacheContourPoints3DQ.push(p1); if (equaln$1(p1.x, 0) && equaln$1(p1.y, 0) && equaln$1(p2.x, 0) && equaln$1(p2.y, 0)) continue; for (let j = 1; j < count; j++) { this._CacheContourPoints3DQ.push(v.clone().multiplyScalar(j).add(p1)); } } if (!this._CacheIsRev) this._CacheContourPoints3DQ.reverse(); } let us = [0]; for (let i = 1; i < this._CacheContourPoints.length; i++) us.push(us[i - 1] + this._CacheContourPoints[i].distanceTo(this._CacheContourPoints[i - 1]) * 0.001); //性能:此处用BufferGeometry可以提高性能 let geo = new Geometry; if (this._StartAngle !== this._EndAngle) { //起点盖子 r.setRotate(this._StartAngle); let contoursStart = this._CacheContourPoints3D.map(p => { p = p.clone(); r.applyVector(p); return p; }); geo.vertices.push(...contoursStart); for (let face of this._CacheContourFaces) { if (this._CacheIsRev) geo.faces.push(new Face3(face[2], face[1], face[0])); else geo.faces.push(new Face3(face[0], face[1], face[2])); } //终点盖子 r.setRotate(this._EndAngle); let contoursEnd = this._CacheContourPoints3D.map(p => { p = p.clone(); r.applyVector(p); return p; }); let count = geo.vertices.length; geo.vertices.push(...contoursEnd); for (let face of this._CacheContourFaces) { if (!this._CacheIsRev) geo.faces.push(new Face3(count + face[2], count + face[1], count + face[0])); else geo.faces.push(new Face3(count + face[0], count + face[1], count + face[2])); } } let allAngle = this._StartAngle === this._EndAngle ? Math.PI * 2 : _Arc.AllAngle; RevolveLine(geo, this._CacheContourPoints3DQ, this._CacheContourPoints3DQ2, this._StartAngle, allAngle, us); geo.computeFaceNormals(); geo.computeVertexNormals(); return this._MeshGeometry; } //#region Draw InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawGeometry() { if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } UpdateDrawObject(type, obj) { Object3DRemoveAll(obj); if (type === RenderType.Wireframe) { let g = this.MeshGeometry; obj.add(new Mesh(g, RevolveSolid_1.MeshNromal)); } else if (type === RenderType.Physical || type === RenderType.Physical2) { let g = this.MeshGeometry; obj.add(new Mesh(g, this.MeshMaterial)); } // 概念透明 else if (type === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; obj.add(new Mesh(this.MeshGeometry, ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide))); } else // if (type === RenderType.Conceptual || type===RenderType.Physical || type === RenderType.Jig) { let g = this.MeshGeometry; obj.add(new Mesh(g, ColorMaterial.GetConceptualMaterial(this.DrawColorIndex))); } return obj; } /** * 更新实体Jig状态时的材质 */ UpdateJigMaterial(color = 8) { for (let [type, obj] of this._CacheDrawObject) { if (obj.children.length) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetBasicMaterialTransparent(8, 0.1); } } } RestoreJigMaterial() { for (let [type, obj] of this._CacheDrawObject) { if (obj.children.length) { let mesh = obj.children[0]; if (type === RenderType.Wireframe) mesh.material = RevolveSolid_1.MeshNromal; // 概念透明 else if (type === RenderType.ConceptualTransparent) { let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; mesh.material = ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide); } else if (type === RenderType.Physical2 || type === RenderType.Physical) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } else mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } } } /** * 当实体需要被更新时,更新实体材质 */ UpdateDrawObjectMaterial(renderType, obj) { if (renderType === RenderType.Conceptual) { let mesh = obj.children[0]; mesh.material = ColorMaterial.GetConceptualMaterial(this.DrawColorIndex); } // 概念透明 else if (renderType === RenderType.ConceptualTransparent) { let mesh = obj.children[0]; let color = this.DrawColorIndex; if (RenderState.ConceptualColor === ColorMap.get("灰度单色")) color = 8; mesh.material = ColorMaterial.GetConceptualTransparentMaterial(color, FrontSide); } else if (renderType === RenderType.Physical2 || renderType === RenderType.Physical) { let mesh = obj.children[0]; mesh.material = this.MeshMaterial; } } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._Contour = file.ReadObject(); if (ver > 1) { this._StartAngle = file.Read(); this._EndAngle = file.Read(); } if (ver > 2) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; if (ver > 3) this._DisplayAccuracy = file.Read(); return this; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.WriteObject(this._Contour); file.Write(this._StartAngle); file.Write(this._EndAngle); // ver3 file.WriteBool(this._LockMaterial); // ver4 file.Write(this._DisplayAccuracy); return this; } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; RevolveSolid.MeshNromal = new MeshNormalMaterial({ wireframe: true }); RevolveSolid = RevolveSolid_1 = __decorate([ Factory ], RevolveSolid); function RevolveLine(geo, contourPoints, cachePoints, startAngle, allAngle, us) { //计算圆的半径 let rotateCircleRadius = 0; for (let p of contourPoints) { let r = p.x * p.x + p.y * p.y; if (r > rotateCircleRadius) rotateCircleRadius = r; } rotateCircleRadius = Math.sqrt(rotateCircleRadius); let minCount = Math.max(2, Math.ceil((allAngle) / Math.PI) * 4); let angleCount = clamp(Math.floor(rotateCircleRadius / 3.2), minCount, 30); // let angleCount = Math.max(Math.floor(allAngle / 0.2), 3);//合理的控制采样精度是性能的关键 let anDiv = allAngle / angleCount; let startVerticesCount = geo.vertices.length; let contourVerticesCount = contourPoints.length; let count2 = (equaln$1(allAngle, Math.PI * 2) ? angleCount : angleCount + 1); //TODO:如果有必要的话,此处可以缓存0-360度的轮廓点,用空间换时间 for (let i = 0; i < count2; i++) { r.setRotate(startAngle + anDiv * i); let curPoints; if (i < cachePoints.length) //使用已经生成的点,避免重复的生成新的Vector3造成的过多GC { curPoints = cachePoints[i]; for (let j = 0; j < curPoints.length; j++) { let p = curPoints[j]; p.copy(contourPoints[j]); r.applyVector(p); } } else { curPoints = contourPoints.map(p => { p = p.clone(); r.applyVector(p); return p; }); cachePoints.push(curPoints); } geo.vertices.push(...curPoints); } for (let i = 0; i < angleCount; i++) { let nextI = FixIndex$1(i + 1, count2); let s1 = startVerticesCount + i * contourVerticesCount; let s2 = startVerticesCount + nextI * contourVerticesCount; for (let j = 0; j < contourVerticesCount; j++) { let jn = FixIndex$1(j + 1, contourVerticesCount); if (!IsAxis(contourPoints[j], contourPoints[jn])) { geo.faces.push(new Face3(j + s1, jn + s1, s2 + j)); geo.faces.push(new Face3(j + s2, jn + s1, s2 + jn)); geo.faceVertexUvs[0].push([ new Vector2$1(i / (angleCount / 2), us[j]), new Vector2$1(i / (angleCount / 2), us[jn]), new Vector2$1(nextI / (angleCount / 2) || 2, us[j]), ], [ new Vector2$1(nextI / (angleCount / 2) || 2, us[j]), new Vector2$1(i / (angleCount / 2), us[jn]), new Vector2$1(nextI / (angleCount / 2) || 2, us[jn]), ]); } } } } function IsAxis(p1, p2) { return equaln$1(p1.x, 0) && equaln$1(p1.y, 0) && equaln$1(p2.x, 0) && equaln$1(p2.y, 0); } /** * 对纹组信息设置. */ let AlignLineGroupRecord = class AlignLineGroupRecord extends SymbolTableRecord { constructor() { super(); this.Objects = []; } get Name() { return this.name; } set Name(name) { if (name !== this.name) { this.WriteAllObjectRecord(); this.name = name; } } Purge() { arrayRemoveIf(this.Objects, id => !id || id.IsErase); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); let count = file.Read(); this.Objects.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Objects.push(id); } } //对象将自身数据写入到文件 WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.Objects.length); for (let id of this.Objects) file.WriteObjectId(id); } }; __decorate([ AutoRecord ], AlignLineGroupRecord.prototype, "Objects", void 0); AlignLineGroupRecord = __decorate([ Factory ], AlignLineGroupRecord); function GetDimLineMaterial(dim, renderType) { if (renderType === RenderType.Wireframe) return ColorMaterial.GetLineMaterial(dim.DrawColorIndex); else if (renderType > 100) return ColorMaterial.GetLineMaterial(0); else return ColorMaterial.GetLineMaterial(6); } /** * 两条直线的角度标注 */ let LineAngularDimension = class LineAngularDimension extends Dimension { constructor(_L1StartPoint = new Vector3(), //第一条直线的起点 _L1EndPoint = new Vector3(), //第一条直线的终点 _L2StartPoint = new Vector3(), //第二条直线的起点 _L2EndPoint = new Vector3(), _DimPoint = new Vector3()) { super(); this._L1StartPoint = _L1StartPoint; this._L1EndPoint = _L1EndPoint; this._L2StartPoint = _L2StartPoint; this._L2EndPoint = _L2EndPoint; this._DimPoint = _DimPoint; this._Arc = new Arc(); // this._Arc.ColorIndex = 3; this._Text.TextAligen = TextAligen.Down; this._Text.Height = HostApplicationServices.dimTextHeight; } get FractionDigits() { return this.GetDimStyleValue(DimStyleKeyCode.DIMADEC); } set FractionDigits(length) { this.SetDimStyleOverrideValue(DimStyleKeyCode.DIMADEC, length); } GetPrimitiveString() { return FixedNotZero(MathUtils.radToDeg(this._Arc.AllAngle), this.FractionDigits) + "°"; } UpdateDimData(l1sp, l1ep, l2sp, l2ep, dimp) { this.WriteAllObjectRecord(); let inv = this.OCSInv; for (let [p, pn] of [ [this._L1StartPoint, l1sp], [this._L1EndPoint, l1ep], [this._L2StartPoint, l2sp], [this._L2EndPoint, l2ep], [this._DimPoint, dimp], ]) { if (pn) p.copy(pn).applyMatrix4(inv); } this.Update(); } get Text() { return this._Text; } get L1StartPoint() { return this._L1StartPoint.clone().applyMatrix4(this._Matrix); } get L2StartPoint() { return this._L2StartPoint.clone().applyMatrix4(this._Matrix); } get L1EndPoint() { return this._L1EndPoint.clone().applyMatrix4(this._Matrix); } get L2EndPoint() { return this._L2EndPoint.clone().applyMatrix4(this._Matrix); } get DimPoint() { return this._DimPoint.clone().applyMatrix4(this._Matrix); } get Arc() { return this._Arc.Clone().ApplyMatrix(this._Matrix); } //#region 动态拽拖 GetGripPoints() { return [ this._L1StartPoint, this._L1EndPoint, this._L2StartPoint, this._L2EndPoint, this._DimPoint, ].map(p => { return p.clone().applyMatrix4(this._Matrix); }); } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); let arr = [ this._L1StartPoint, this._L1EndPoint, this._L2StartPoint, this._L2EndPoint, this._DimPoint, ]; vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); for (let i of indexList) arr[i].add(vec); this.Update(); } GetStretchPoints() { return this.GetGripPoints(); } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); this.MoveGripPoints(indexList, vec); } //#endregion //#region 绘制 Clone() { let ent = super.Clone(); ent._Text.CopyFrom(this._Text); ent._Arc.CopyFrom(this._Arc); for (let [type, obj] of ent._CacheDrawObject) { for (let o of obj.children) if (o instanceof Line$1) o.geometry = o.geometry.clone(); } return ent; } Explode() { //为了避免Text对象没有被更新. this.GetDrawObjectFromRenderType(); return [ this._Arc.Clone().ApplyMatrix(this._Matrix), this._Text.Clone().ApplyMatrix(this._Matrix) ]; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let arc = this._Arc.Clone().ApplyMatrix(this._Matrix); switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints().concat([arc.StartPoint, arc.EndPoint]); default: return arc.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); } } InitDrawObject(renderType = RenderType.Wireframe) { this._Text.ColorIndex = this.ColorIndex; let colorMaterial = GetDimLineMaterial(this, renderType); let arrow1 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrow2 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrowSize = 10; let l, l2; if (renderType === RenderType.WireframePrint) { l = new Line2(undefined, ColorMaterial.PrintLineMatrial); l2 = new Line2(undefined, ColorMaterial.PrintLineMatrial); arrowSize *= HostApplicationServices.lineWidth * 0.5; } else { l = new Line$1(BufferGeometryUtils.CreateFromPts([new Vector3(), new Vector3()]), colorMaterial); l2 = new Line$1(BufferGeometryUtils.CreateFromPts([new Vector3(), new Vector3()]), colorMaterial); } arrow1.scale.set(arrowSize, arrowSize, arrowSize); arrow2.scale.set(arrowSize, arrowSize, arrowSize); let obj = new Object3D(); obj.add(arrow1, arrow2, l, l2); this.UpdateDrawObject(renderType, obj); return obj; } Update2Line(l1, l2, intPt, li1, li2) { let intPar1 = l1.GetParamAtPoint(intPt); if (intPar1 >= 1) l1.Reverse(); let intPar2 = l2.GetParamAtPoint(intPt); if (intPar2 >= 1) l2.Reverse(); const updateLine = (l, li, refPt, refPt2) => { let par = l.GetParamAtPoint(refPt); let refPar = l.GetParamAtPoint(intPt); if (par > 1) { li.visible = true; if (li instanceof Line$1) { let geo = li.geometry; geo.copy(BufferGeometryUtils.CreateFromPts([l.EndPoint, refPt])); } else { let geo = li.geometry; geo.setPositions([...l.EndPoint.toArray(), ...refPt.toArray()]); } } else if (par < 0) { li.visible = true; if (li instanceof Line$1) { let geo = li.geometry; geo.copy(BufferGeometryUtils.CreateFromPts([par < refPar ? intPt : l.StartPoint, refPt])); } else { let geo = li.geometry; let p = par < refPar ? intPt : l.StartPoint; geo.setPositions([...p.toArray(), ...refPt.toArray()]); } } else if (isNaN(par) && refPt2) { updateLine(l, li, refPt2); } else { li.visible = false; } }; updateLine(l1, li1, this._Arc.StartPoint, this._Arc.EndPoint); updateLine(l2, li2, this._Arc.EndPoint, this._Arc.StartPoint); } UpdateDrawObject(renderType, obj) { let [arrow1, arrow2, li1, li2] = obj.children; obj.remove(...obj.children.slice(4)); let l1 = new Line(this._L1StartPoint, this._L1EndPoint); let l2 = new Line(this._L2StartPoint, this._L2EndPoint); let insP = l1.IntersectWith(l2, IntersectOption.ExtendBoth)[0]; if (insP) { this._Arc.AutoUpdate = false; //如果没有在这边关闭这个,那么Arc的盒子将错误 this._Arc.Center = insP; this._Arc.Radius = insP.distanceTo(this._DimPoint); let ans = [this._L1StartPoint, this._L1EndPoint, this._L2StartPoint, this._L2EndPoint] .map(p => { if (equalv3(p, insP)) return NaN; return angle(p.clone().sub(insP)); }).filter(a => !isNaN(a)); ans = ans.concat(ans.map(a => (a + Math.PI) % (Math.PI * 2))); arraySortByNumber(ans); arrayRemoveDuplicateBySort(ans, (a1, a2) => equaln$1(a1, a2)); let dimAn = angle(this._DimPoint.clone().sub(insP)); for (let i = 0; i < ans.length; i++) { let ni = FixIndex$1(i + 1, ans.length); this._Arc.StartAngle = ans[ni]; this._Arc.EndAngle = ans[i]; this._Arc.ColorIndex = this._Color; if (this._Arc.ParamOnCurve(this._Arc.GetParamAtAngle(dimAn))) { this._Arc.AutoUpdate = true; this._Arc.DeferUpdate(); AddEntityDrawObject(obj, this._Arc, renderType); arrow1.position.copy(this._Arc.StartPoint); arrow1.rotation.z = this._Arc.GetFirstDerivAngle(0) + Math.PI / 2; arrow1.updateMatrix(); arrow2.position.copy(this._Arc.EndPoint); arrow2.rotation.z = this._Arc.GetFirstDerivAngle(1) - Math.PI / 2; arrow2.updateMatrix(); this._Text.AutoUpdate = false; //更新标记 this._Text.Height = this.TextSize; this._Text.TextString = this.TextString; let midAngle = this._Arc.GetAngleAtParam(0.5); this._Text.Position = polar(this._Arc.GetPointAtParam(0.5), midAngle, 10); this._Text.TextRotation = midAngle % (Math.PI) - Math.PI / 2; this._Text.TextAligen = midAngle > Math.PI ? TextAligen.Top : TextAligen.Down; this._Text.AutoUpdate = true; //更新标记 this._Text.DeferUpdate(); AddEntityDrawObject(obj, this._Text, renderType); break; } } this.Update2Line(l1, l2, insP, li1, li2); } } UpdateDrawObjectMaterial(type, obj, material) { let colorMaterial = GetDimLineMaterial(this, type); let count = Math.min(4, obj.children.length); for (let i = 0; i < count; i++) { let l = obj.children[i]; l.material = colorMaterial; } this._Arc.ColorIndex = this._Color; this._Text.ColorIndex = this._Color; } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let p1 = this.L1StartPoint; let p2 = this.L1EndPoint; let p3 = this.L2StartPoint; let p4 = this.L2EndPoint; let p5 = this.DimPoint; reviseMirrorMatrix(this._Matrix, 1); this.UpdateDimData(p2, p1, p4, p3, p5); return this; } //#endregion //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._L1StartPoint.fromArray(file.Read()); this._L1EndPoint.fromArray(file.Read()); this._L2StartPoint.fromArray(file.Read()); this._L2EndPoint.fromArray(file.Read()); this._DimPoint.fromArray(file.Read()); if (ver > 1) this._TextString = file.Read(); if (ver > 2 && ver < 4) this.TextSize = file.Read(); if (ver > 3) this.ReadDimStyle(file); if (ver > 4) this._DimStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentDimStyle; else this._DimStyle = HostApplicationServices.CurrentDimStyle; if (ver > 5) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(6); file.Write(this._L1StartPoint.toArray()); file.Write(this._L1EndPoint.toArray()); file.Write(this._L2StartPoint.toArray()); file.Write(this._L2EndPoint.toArray()); file.Write(this._DimPoint.toArray()); file.Write(this._TextString); this.WriteDimStyle(file); file.WriteHardObjectId(this._DimStyle); this.WriteRoomAndCabinetName(file); } }; LineAngularDimension = __decorate([ Factory ], LineAngularDimension); /**用于提供捕捉的多段线 */ let snapPolyline = new Polyline([{ pt: new Vector2$1, bul: 0 }, { pt: new Vector2$1, bul: 0 }, { pt: new Vector2$1, bul: 0 }, { pt: new Vector2$1, bul: 0 }]); /** * 对齐标注 * 存在子类重载(线性标注 LinearDimension) */ let AlignedDimension = class AlignedDimension extends Dimension { /**构造函数提供的点在对象坐标系内部 */ constructor( //针脚 _FootP1 = new Vector3(), _FootP2 = new Vector3(), //肩膀 _ArmP1 = new Vector3(), _ArmP2 = new Vector3(), _TextRotation = null, //当尺寸线放不下文字时,使用引线 _UseLead = true, _RoomName = "", _CabinetName = "") { super(); this._FootP1 = _FootP1; this._FootP2 = _FootP2; this._ArmP1 = _ArmP1; this._ArmP2 = _ArmP2; this._TextRotation = _TextRotation; this._UseLead = _UseLead; this._RoomName = _RoomName; this._CabinetName = _CabinetName; //引线(用于绘制) this._LeadLine = new Polyline(); /**引线的起始位置,在引线坐标系+使用中点 */ this._LeadPos = new Vector2$1(30, this.TextSize * 1.2); this._ArmP1.setZ(0); this._ArmP2.setZ(0); } get Distance() { return this._ArmP1.distanceTo(this._ArmP2); } get FootP1() { return this._FootP1.clone().applyMatrix4(this._Matrix); } set FootP1(v) { this._FootP1.copy(v).applyMatrix4(this.OCSInv); this.Update(); } get FootP2() { return this._FootP2.clone().applyMatrix4(this._Matrix); } set FootP2(v) { this._FootP2.copy(v).applyMatrix4(this.OCSInv); this.Update(); } get ArmP1() { return this._ArmP1.clone().applyMatrix4(this._Matrix); } set ArmP1(v) { this.WriteAllObjectRecord(); this._ArmP1.copy(v).applyMatrix4(this.OCSInv); this._ArmP1.setZ(0); this.Update(); } get ArmP2() { return this._ArmP2.clone().applyMatrix4(this._Matrix); } set ArmP2(v) { this.WriteAllObjectRecord(); this._ArmP2.copy(v).applyMatrix4(this.OCSInv); this._ArmP2.setZ(0); this.Update(); } get TextPosition() { return midPoint(this._ArmP1, this._ArmP2).applyMatrix4(this._Matrix); } /**实际上这个是BasePoint 用来定位尺寸线的位置 */ set TextPosition(p) { this.WriteAllObjectRecord(); p = p.clone().applyMatrix4(this.OCSInv).setZ(0); let l = new Line(this._ArmP1, this._ArmP2); let cp = l.GetClosestPointTo(p, true); let v = p.clone().sub(cp); this._ArmP1.add(v); this._ArmP2.add(v); this.Update(); } //创建以arm1,arm2为x轴 position在foot1的坐标系 用于定位引线 get DalUcs() { let dalUcs = new Matrix4(); if (!equalv3(this._ArmP1, this._ArmP2)) { let vy = this.ParseTopDir()[0]; let vx = new Vector3().crossVectors(vy, ZAxis); dalUcs = new Matrix4().makeBasis(vx, vy, ZAxis); } dalUcs.setPosition(midPoint(this._ArmP1, this._ArmP2)); return dalUcs; } set TextRotation(angle) { this._TextRotation = angle; this.Update(); } get Text() { if (!this._Text.TextString) this.UpdateText(); return this._Text; } GetPrimitiveString() { return FixedNotZero(this._ArmP1.distanceTo(this._ArmP2), this.FractionDigits); } get BoundingBox() { this.SetDataToTempPolyline(); return snapPolyline.BoundingBox.union(this.Text.BoundingBox); } get LeadVisible() { return this._UseLead; } /**设置引线可见 */ set LeadVisible(visible) { if (this._UseLead === visible) return; this.WriteAllObjectRecord(); this._UseLead = visible; this.Update(); } set LeadInLeft(isLeft) { if (this._LeadPos.x < 0 === isLeft) return; this.FlippeLead(); } /**切换引线可见性 */ ToggleLeadVisible() { this.WriteAllObjectRecord(); this.LeadVisible = !this._UseLead; } /**切换引线翻转 */ FlippeLead() { this.WriteAllObjectRecord(); this._LeadPos.x = -this._LeadPos.x; this.Update(); } get LeadY() { return this._LeadPos.y; } /**引线偏移 */ set LeadY(y) { if (this.LeadY === y) return; this.WriteAllObjectRecord(); this._LeadPos.y = y; this.Update(); } get LeadX() { return this._LeadPos.x; } set LeadX(x) { if (this.LeadX === x) return; this.WriteAllObjectRecord(); this._LeadPos.x = x; this.Update(); } /**设置引线数据 */ SetLeadData(offset, isFlipped = false) { this.WriteAllObjectRecord(); this._LeadPos.copy(offset); if (isFlipped) this._LeadPos.x *= -1; this.Update(); } Explode() { this.UpdateText(this._Text.Position); let res = [ new Line(this._FootP1.clone(), this._ArmP1.clone()), new Line(this._ArmP2.clone(), this._ArmP1.clone()), new Line(this._ArmP2.clone(), this._FootP2.clone()), this._Text.Clone() ]; if (this._UseLead && this.NeedLead) { let mp = midPoint(this._ArmP1, this._ArmP2); res.push(new Line(mp.clone(), AsVector3(this._LeadPos).add(mp)), new Line(AsVector3(this._LeadPos).add(mp), AsVector3(this._LeadPos).add(mp).add(new Vector3(this.TextBoxWidth, 0, 0)))); } return res.map(en => en.ApplyMatrix(this._Matrix)); } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let footP1 = this.FootP1; let footP2 = this.FootP2; let armP1 = this.ArmP1; let armP2 = this.ArmP2; reviseMirrorMatrix(this._Matrix, 0); this.FootP1 = footP1; this.FootP2 = footP2; this.ArmP1 = armP1; this.ArmP2 = armP2; this.Update(UpdateDraw.Geometry); return this; } Clone() { let ent = super.Clone(); ent._Text.CopyFrom(this._Text); for (let [type, obj] of ent._CacheDrawObject) { for (let o of obj.children) if (o instanceof Line$1) o.geometry = o.geometry.clone(); } return ent; } InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); let colorMaterial = GetDimLineMaterial(this, renderType); let line; if (renderType === RenderType.WireframePrint) { const geo = new LineGeometry(); line = new Line2(geo, ColorMaterial.PrintLineMatrial); } else line = new Line$1(BufferGeometryUtils.CreateFromPts([this._FootP1, this._FootP2, this._ArmP1, this._ArmP2, this._ArmP2, this._ArmP2]), colorMaterial); let arrow1 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrow2 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); obj.add(line, arrow1, arrow2); this.UpdateDrawObject(renderType, obj); return obj; } ParseTopDir() { let [f1, f2, a1, a2] = [this._FootP1, this._FootP2, this._ArmP1, this._ArmP2].map(p => p.clone().setZ(0)); let armV = a1.clone().sub(a2); //#region 头部延伸线 let topDir1, topDir2; //↑头顶部的向量 用来突出肩膀(文字或者肩膀的线) if (!equalv3(f1, a1)) topDir1 = a1.clone().sub(f1).normalize(); if (!equalv3(f2, a2)) topDir2 = a2.clone().sub(f2).normalize(); let topDir = topDir1 ?? topDir2; if (!topDir) { topDir = armV.clone().normalize(); ROTATE_MTX2.applyVector(topDir); } if (!topDir1) topDir1 = topDir; if (!topDir2) topDir2 = topDir; return [topDir, topDir1, topDir2]; } UpdateDrawObject(renderType, obj) { obj.remove(...obj.children.slice(3)); let [line, arrow1, arrow2] = obj.children; let [f1, f2, a1, a2] = [this._FootP1, this._FootP2, this._ArmP1, this._ArmP2].map(p => p.clone().setZ(0)); let armV = a1.clone().sub(a2); //#region 头部延伸线 let [topDir, topDir1, topDir2] = this.ParseTopDir(); let topExt = this.GetDimStyleValue(DimStyleKeyCode.DIMEXE) ?? 10; let a11 = topDir1.clone().multiplyScalar(topExt).add(a1); let a21 = topDir2.clone().multiplyScalar(topExt).add(a2); //#endregion //#region 固定延伸线的尺寸 let length1 = f1.distanceTo(a1); let length2 = f2.distanceTo(a2); let newLength1 = length1; let newLength2 = length2; let dimexo = this.GetDimStyleValue(DimStyleKeyCode.DIMEXO) ?? 20; //脚线的偏移距离 newLength1 = Math.max(0, newLength1 - dimexo); newLength2 = Math.max(0, newLength2 - dimexo); if (this.GetDimStyleValue(DimStyleKeyCode.DIMFXLON)) //固定脚线长度 { let fixLength = this.GetDimStyleValue(DimStyleKeyCode.DIMFXL) ?? 100; //固定脚线长度 if (newLength1 > fixLength) newLength1 = fixLength; if (newLength2 > fixLength) newLength2 = fixLength; } if (newLength1 !== length1) f1.sub(a1).normalize().multiplyScalar(newLength1).add(a1); if (newLength2 !== length2) f2.sub(a2).normalize().multiplyScalar(newLength2).add(a2); //#endregion //#region 更新线部分 let linePts = [f1, a11, a1, a2, a21, f2]; if (renderType === RenderType.WireframePrint) { const geometry = line.geometry; let nums = []; for (let p of linePts) nums.push(p.x, p.y, p.z); geometry.setPositions(nums); } else BufferGeometryUtils.UpdatePts(line.geometry, linePts, true); //#endregion //#region 箭头部分 let arrowSize = this.GetDimStyleValue(DimStyleKeyCode.DIMASZ) ?? 10; if (renderType === RenderType.WireframePrint) //在打印模式下,改变箭头的大小? arrowSize *= HostApplicationServices.lineWidth * 0.5; arrow1.scale.set(arrowSize, arrowSize, arrowSize); arrow2.scale.set(arrowSize, arrowSize, arrowSize); arrow1.position.copy(this._ArmP1); arrow2.position.copy(this._ArmP2); let armAn = angle(armV); if (this._ArmP1.distanceTo(this._ArmP2) < 36) { arrow1.rotation.z = armAn + Math.PI / 2; arrow2.rotation.z = armAn - Math.PI / 2; } else { arrow1.rotation.z = armAn - Math.PI / 2; arrow2.rotation.z = armAn + Math.PI / 2; } arrow1.updateMatrix(); arrow2.updateMatrix(); //#endregion //#region 文字和引线 //更新引线this._LeadOutLine 并返回新的字体位置 let textPos = this.UpdateLeadLine(); let hasLead = this._UseLead && this.NeedLead; let textPosDir = this.GetDimStyleValue(DimStyleKeyCode.DIMTAD) ?? DimTextPosDir.Out; //文字在尺寸线上距离 let dimGAP = this.GetDimStyleValue(DimStyleKeyCode.DIMGAP) ?? 10; if (textPosDir === DimTextPosDir.Out) //文字在尺寸线(或引线)外部 { let textDir = topDir; //文字相对于基准线(引线) 的位置方向向量 if (hasLead && this._LeadPos.y < 0) //引线方向和头部方向相反 则逆转方向(dir = 引线的朝向) textDir = textDir.clone().negate(); if (equaln$1(textDir.y, 0)) //水平标注 this.Text.TextAligen = textDir.x > 0 ? TextAligen.Top : TextAligen.Down; else this._Text.TextAligen = textDir.y < 0 ? TextAligen.Top : TextAligen.Down; textPos.add(textDir.multiplyScalar(dimGAP)); } else //文字在尺寸线(或引线)上方 { let v = 1; let textDir = topDir; //文字相对于基准线(引线) 的位置方向向量 if (equaln$1(textDir.y, 0)) //水平标注 v = textDir.x > 0 ? -1 : 1; else v = textDir.y < 0 ? -1 : 1; this._Text.TextAligen = TextAligen.Down; textPos.add(topDir.multiplyScalar(dimGAP * v)); } if (hasLead) AddEntityDrawObject(obj, this._LeadLine, renderType); this.UpdateText(textPos); AddEntityDrawObject(obj, this._Text, renderType); //#endregion } get NeedLead() { let distance = this._ArmP1.distanceTo(this._ArmP2); let textBoxWidth = this.TextBoxWidth; let needLead = (distance < textBoxWidth - 2) && !(equaln$1(distance, 0)); //文字宽度是否比托盘更宽 是 则需要引线 return needLead; } /** * 更新引线并返回textPosition */ UpdateLeadLine() { let armMidP = midPoint(this._ArmP1, this._ArmP2); let textPosition = armMidP; if (this._UseLead && this.NeedLead) //引线可见且需要引线 { let dalUcs = this.DalUcs; let leadP1 = AsVector3(this._LeadPos).applyMatrix4(dalUcs); let leadP2 = AsVector3(this._LeadPos).add(new Vector3(this.TextBoxWidth * Math.sign(this._LeadPos.x))).applyMatrix4(dalUcs); textPosition.setFromMatrixColumn(dalUcs, 3); this._LeadLine.AutoUpdate = false; this._LeadLine.IsEmbedEntity = true; //使它可以捕捉 this._LeadLine.LineData = [textPosition, leadP1, leadP2].map(p => { return { pt: AsVector2(p), bul: 0 }; }); this._LeadLine.ColorIndex = this.DrawColorIndex; this._LeadLine.Visible = true; //可见性设置 this._LeadLine.AutoUpdate = true; this._LeadLine.DeferUpdate(); textPosition.copy(leadP1).add(leadP2).divideScalar(2); //引线中点 } else //引线不可见或不需要引线 { this._LeadLine.Visible = false; //可见性设置 } return textPosition; } UpdateText(textPos) { this._Text.AutoUpdate = false; let textRo = this._TextRotation ?? angleAndX(this._ArmP1.clone().sub(this._ArmP2)); this._Text.TextString = this.TextString; this._Text.Position = textPos ?? midPoint(this._ArmP1, this._ArmP2); this._Text.TextRotation = textRo; this._Text.ColorIndex = this.DrawColorIndex; this._Text.Height = this.TextSize * 0.923; //雅黑数字*0.923 this._Text.DeferUpdate(); this._Text.AutoUpdate = true; } OnChangeFootPt() { let l = new Line(this._FootP1.clone().setZ(0), this._FootP2.clone().setZ(0)); let cp = l.GetClosestPointTo(this._ArmP1, true); let v = cp.subVectors(this._ArmP1, cp); this._ArmP1.copy(this._FootP1.clone().add(v)); this._ArmP2.copy(this._FootP2.clone().add(v)); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { this.SetDataToTempPolyline(); let pts = snapPolyline.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); if (this._UseLead && this.NeedLead) { let dalUcs = this.DalUcs; let armMidP = midPoint(this._ArmP1, this._ArmP2); let leadP1 = AsVector3(this._LeadPos).applyMatrix4(dalUcs); let leadP2 = AsVector3(this._LeadPos).add(new Vector3(this.TextBoxWidth * Math.sign(this._LeadPos.x))).applyMatrix4(dalUcs); snapPolyline.LineData[0].pt.set(armMidP.x, armMidP.y); snapPolyline.LineData[1].pt.set(leadP1.x, leadP1.y); snapPolyline.LineData[2].pt.set(leadP2.x, leadP2.y); snapPolyline.LineData[3].pt.set(leadP2.x, leadP2.y); for (let p of snapPolyline.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)) pts.push(p); } return pts; } SetDataToTempPolyline() { snapPolyline.OCS = this._Matrix; snapPolyline.LineData[0].pt.set(this._FootP1.x, this._FootP1.y); snapPolyline.LineData[1].pt.set(this._ArmP1.x, this._ArmP1.y); snapPolyline.LineData[2].pt.set(this._ArmP2.x, this._ArmP2.y); snapPolyline.LineData[3].pt.set(this._FootP2.x, this._FootP2.y); if (!equaln$1(this._FootP1.z, 0)) snapPolyline.OCSNoClone.setPosition(snapPolyline.Position.add(snapPolyline.Normal.multiplyScalar(this._FootP1.z))); // snapPolyline.LineData[4].pt.set(this._LeadOutPts.dragPt.x, this._LeadOutPts.dragPt.y); //引线拖拽点 // snapPolyline.LineData[5].pt.set(this._LeadOutPts.endPt.x, this._LeadOutPts.endPt.y); } UpdateDrawObjectMaterial(renderType, obj, material) { if (renderType === RenderType.WireframePrint) return; let mtl = GetDimLineMaterial(this, renderType); this._LeadLine.ColorIndex = this.DrawColorIndex; let [line, arrow1, arrow2] = obj.children; line.material = mtl; arrow1.material = mtl; arrow2.material = mtl; this._Text.ColorIndex = this.DrawColorIndex; } GetGripPoints() { let pts = [this._FootP1, this._FootP2, this._ArmP1, this._ArmP2, midPoint(this._ArmP1, this._ArmP2)].map(p => p.clone().applyMatrix4(this._Matrix)); if (this._UseLead && this.NeedLead) pts.push(AsVector3(this._LeadPos).applyMatrix4(this.DalUcs).applyMatrix4(this._Matrix)); return pts; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); let inv = this.OCSInv; let inv0 = inv.clone().setPosition(0, 0, 0); let vec0 = vec.clone().applyMatrix4(inv0); for (let i of indexList) { if (i === 5) //引线 { let dalUCS = this.DalUcs.setPosition(0, 0, 0); dalUCS.getInverse(dalUCS); vec0.applyMatrix4(dalUCS); this._LeadPos.x += vec0.x; this._LeadPos.y += vec0.y; } else if (i >= 2) //肩膀点 文字点(拽拖这个点不会把标注变大) { let p = this.TextPosition.add(vec).applyMatrix4(inv); let l = new Line(this._ArmP1, this._ArmP2); let cp = l.GetClosestPointTo(p, true); let v = p.clone().sub(cp); this._ArmP1.add(v); this._ArmP2.add(v); } else //脚线 { if (i === 0) this._FootP1.add(vec0); else this._FootP2.add(vec0); this.OnChangeFootPt(); } } this.Update(); } GetStretchPoints() { return this.GetGripPoints(); } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); let lst = []; let bChangeText = false; for (let i of indexList) { if (i <= 1) lst.push(i); else bChangeText = true; } if (lst.length > 0) this.MoveGripPoints(lst, vec); if (bChangeText) this.MoveGripPoints([2], vec); } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._ArmP1.fromArray(file.Read()); this._ArmP2.fromArray(file.Read()); this._FootP1.fromArray(file.Read()); this._FootP2.fromArray(file.Read()); this._TextRotation = file.Read(); if (ver > 2) this._TextString = file.Read(); if (ver < 7) //升级到版本7之后 标注统一在OCS平面绘制 (脚线允许在不同的平面) { //升级文件版本,将z轴移除 let z = this._ArmP1.z; if (!equaln$1(z, 0)) { this._Matrix.elements[12] += this._Matrix.elements[8] * z; this._Matrix.elements[13] += this._Matrix.elements[9] * z; this._Matrix.elements[14] += this._Matrix.elements[10] * z; this._ArmP1.z = 0; this._ArmP2.z = 0; this._FootP1.z -= z; this._FootP2.z -= z; } this.SetDimStyleOverrideValue(DimStyleKeyCode.DIMEXO, 0); //原始图纸脚线偏移为9 this.SetDimStyleOverrideValue(DimStyleKeyCode.DIMTAD, DimTextPosDir.Top); //原始图纸的文字位置为上 //虽然原始的标注也没有文字偏移,但是太丑了,所以不还原这个设置 } if (ver > 3 && ver < 7) { this._UseLead = file.Read(); let leadFlipped = file.Read(); new Vector3().fromArray(file.Read()); let y = file.Read(); let x = file.Read(); if (!leadFlipped) x = -x; /**旧的引线坐标系 在OCS内部 */ const GetOldDalUcs = () => { let dalUcs = new Matrix4(); if (!equalv3(this._ArmP1, this._ArmP2)) { let vx = this._ArmP2.clone().sub(this._ArmP1).normalize(); if (equaln$1(this._ArmP1.x, this._ArmP2.x)) { if (this._ArmP1.y < this._ArmP2.y) vx.negate(); } else if (this._ArmP1.x < this._ArmP2.x) vx.negate(); if (isParallelTo(vx, ZAxis)) return dalUcs.setPosition(midPoint(this._ArmP1, this._ArmP2)); let vy = vx.clone().cross(ZAxis); dalUcs = new Matrix4().makeBasis(vx, vy, ZAxis); dalUcs.setPosition(midPoint(this._ArmP1, this._ArmP2)); } return dalUcs; }; let p = new Vector3(x, y); p.applyMatrix4(GetOldDalUcs()); let cs = this.DalUcs; p.applyMatrix4(cs.getInverse(cs)); this._LeadPos.copy(p); } if (ver > 4 && ver < 7) this.TextSize = file.Read(); if (ver > 5 && ver < 7) this.FractionDigits = file.Read() ?? 2; if (ver >= 7) { this._UseLead = file.Read(); this._LeadPos.set(file.Read(), file.Read()); this.ReadDimStyle(file); } if (ver > 7) this._DimStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentDimStyle; else this._DimStyle = HostApplicationServices.CurrentDimStyle; if (ver > 8) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(9); file.Write(this._ArmP1.toArray()); file.Write(this._ArmP2.toArray()); file.Write(this._FootP1.toArray()); file.Write(this._FootP2.toArray()); file.Write(this._TextRotation); file.Write(this._TextString); file.Write(this._UseLead); file.Write(this._LeadPos.x); file.Write(this._LeadPos.y); this.WriteDimStyle(file); file.WriteHardObjectId(this._DimStyle); this.WriteRoomAndCabinetName(file); } }; AlignedDimension = __decorate([ Factory ], AlignedDimension); let ArcDimension = class ArcDimension extends Dimension { constructor(_Center = new Vector3, _StartAngle = 0, _EndAngle = 1, _Clockwise = false, _Radius = 1, _TextRadiusAdd = 1, _TextString = "⌒<>") { super(); this._Center = _Center; this._StartAngle = _StartAngle; this._EndAngle = _EndAngle; this._Clockwise = _Clockwise; this._Radius = _Radius; this._TextRadiusAdd = _TextRadiusAdd; this._TextString = _TextString; this._Arc = new Arc; } get Text() { if (!this._Text.TextString) { this.PraseArc(); this.ParseText(); } return this._Text; } get Arc() { if (!this._Arc) this.PraseArc(); return this._Arc; } set TextRadiusAdd(ra) { if (equaln(ra, this._TextRadiusAdd)) return; this.WriteAllObjectRecord(); this._TextRadiusAdd = ra; this.Update(); } get TextRadiusAdd() { return this._TextRadiusAdd; } GetPrimitiveString() { return FixedNotZero(this._Arc.Length, this.FractionDigits); } //#region 拉伸相关 GetGripPoints() { this.PraseArc(); let pts = this._Arc.GetGripPoints(); for (let p of pts) p.applyMatrix4(this.OCSNoClone); return pts; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); this.PraseArc(); this._Arc.MoveGripPoints(indexList, vec); this.UpdateArcFromThisArc(); this.Update(); } GetStretchPoints() { this.PraseArc(); let pts = this._Arc.GetStretchPoints(); for (let p of pts) p.applyMatrix4(this.OCSNoClone); return pts; } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); vec = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); this.PraseArc(); this._Arc.MoveStretchPoints(indexList, vec); this.UpdateArcFromThisArc(); this.Update(); } UpdateArcFromThisArc() { this._Center.copy(this._Arc.Center); this._StartAngle = this._Arc.StartAngle; this._EndAngle = this._Arc.EndAngle; this._Radius = this._Arc.Radius; this._Clockwise = this._Arc.IsClockWise; } //#endregion //#region Draw InitDrawObject(renderType = RenderType.Wireframe) { let colorMaterial = GetDimLineMaterial(this, renderType); let obj = new Object3D(); let line = new Line$1(new BufferGeometry, colorMaterial); obj.add(line); let arrow1 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrow2 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrowSize = 10; arrow1.scale.set(arrowSize, arrowSize, arrowSize); arrow2.scale.set(arrowSize, arrowSize, arrowSize); obj.add(arrow1, arrow2); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(renderType, obj) { obj.remove(...obj.children.slice(3)); let [line, arrow1, arrow2] = obj.children; let colorMaterial = GetDimLineMaterial(this, renderType); line.material = colorMaterial; arrow1.material = colorMaterial; arrow2.material = colorMaterial; this.PraseArc(); //半径设置到实际尺寸的位置,获得正确的标注尺寸 let startFootPoint = this._Arc.StartPoint; //一定要在这个位置求脚点 let endFootPoint = this._Arc.EndPoint; this._Text.AutoUpdate = false; this._Text.ColorIndex = this.ColorIndex; this.ParseText(); this._Arc.DeferUpdate(); this._Text.DeferUpdate(); let linePoints = [startFootPoint]; for (let p of this._Arc.Shape.getPoints(8)) linePoints.push(AsVector3(p).add(this._Center)); linePoints.push(endFootPoint); let geo = line.geometry; if (!BufferGeometryUtils.UpdatePts(geo, linePoints)) { line.geometry.dispose(); line.geometry = BufferGeometryUtils.CreateFromPts(linePoints); } AddEntityDrawObject(obj, this._Text, renderType); //更新箭头的位置和旋转角度 arrow1.position.copy(this._Arc.StartPoint); arrow1.rotation.z = this._Arc.GetFirstDerivAngle(0) + Math.PI / 2; arrow1.updateMatrix(); arrow2.position.copy(this._Arc.EndPoint); arrow2.rotation.z = this._Arc.GetFirstDerivAngle(1) - Math.PI / 2; arrow2.updateMatrix(); } ParseText() { this._Text.TextString = this.TextString; this._Arc.Radius = this._Radius + this._TextRadiusAdd; //半径设置到文字的位置,获得文字的位置 let textP = this._Arc.GetPointAtParam(0.5); let ang = this._Arc.GetAngleAtParam(0.5); let textOCS = new Matrix4().makeRotationZ(ang + Math.PI * 3 / 2).setPosition(textP); this._Text.TextAligen = TextAligen.Down; this._Text.OCS = textOCS; this._Text.Height = this.TextSize; } UpdateDrawObjectMaterial(type, obj, material) { let colorMaterial = GetDimLineMaterial(this, type); let count = Math.min(3, obj.children.length); for (let i = 0; i < count; i++) { let l = obj.children[i]; l.material = colorMaterial; } this._Text.ColorIndex = this.ColorIndex; this.Text.DeferUpdate(); } PraseArc() { this._Arc.AutoUpdate = false; this._Arc.Center = this._Center; this._Arc.StartAngle = this._StartAngle; this._Arc.EndAngle = this._EndAngle; this._Arc.IsClockWise = this._Clockwise; this._Arc.Radius = this._Radius; } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this._Center.fromArray(file.Read()); this._Radius = file.Read(); this._TextRadiusAdd = file.Read(); this._Clockwise = file.Read(); this._StartAngle = file.Read(); this._EndAngle = file.Read(); this._TextString = file.Read(); if (ver < 3) this.TextSize = file.Read(); if (ver > 1 && ver < 3) this.FractionDigits = file.Read(); if (ver > 2) this.ReadDimStyle(file); if (ver > 3) this._DimStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentDimStyle; else this._DimStyle = HostApplicationServices.CurrentDimStyle; if (ver > 4) this.ReadRoomAndCabinetName(file); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(5); super.WriteFile(file); file.Write(this._Center.toArray()); file.Write(this._Radius); file.Write(this._TextRadiusAdd); file.Write(this._Clockwise); file.Write(this._StartAngle); file.Write(this._EndAngle); file.Write(this._TextString); this.WriteDimStyle(file); file.WriteHardObjectId(this._DimStyle); this.WriteRoomAndCabinetName(file); } }; ArcDimension = __decorate([ Factory ], ArcDimension); let RadiusDimension = class RadiusDimension extends Dimension { constructor(_Center = new Vector3(), _DiameterOrRadiusPoint = new Vector3(), _TextPoint = new Vector3()) { super(); this._Center = _Center; this._DiameterOrRadiusPoint = _DiameterOrRadiusPoint; this._TextPoint = _TextPoint; this.OnlyRenderType = true; this._TextString = "R<>"; this._Text.Height = HostApplicationServices.dimTextHeight; } get Text() { if (!this._Text.TextString) this.UpdateText(); return this._Text; } GetPrimitiveString() { return FixedNotZero(this._Center.distanceTo(this._DiameterOrRadiusPoint), this.FractionDigits); } get Center() { return this._Center.clone().applyMatrix4(this._Matrix); } set Center(v) { this.WriteAllObjectRecord(); this._Center.copy(v).applyMatrix4(this.OCSInv); this.Update(); } get DiameterOrRadiusPoint() { return this._DiameterOrRadiusPoint.clone().applyMatrix4(this._Matrix); } set DiameterOrRadiusPoint(v) { this.WriteAllObjectRecord(); this._DiameterOrRadiusPoint.copy(v).applyMatrix4(this.OCSInv); this.Update(); } get TextPoint() { return this._TextPoint.clone().applyMatrix4(this._Matrix); } set TextPoint(v) { this.WriteAllObjectRecord(); this._TextPoint.copy(v).applyMatrix4(this.OCSInv); this.Update(); } Explode() { this.UpdateText(); return [ new Line(this._Center, this._DiameterOrRadiusPoint), new Line(this._DiameterOrRadiusPoint, this._TextPoint), this._Text.Clone() ].map(en => en.ApplyMatrix(this._Matrix)); } Clone() { let ent = super.Clone(); ent._Text.CopyFrom(this._Text); for (let [type, obj] of ent._CacheDrawObject) { for (let o of obj.children) if (o instanceof Line$1) o.geometry = o.geometry.clone(); } return ent; } InitDrawObject(renderType = RenderType.Wireframe) { this._Text.ColorIndex = this.ColorIndex; let obj = new Object3D(); let colorMaterial = GetDimLineMaterial(this, renderType); let line; if (renderType === RenderType.WireframePrint) line = new Line2(undefined, ColorMaterial.PrintLineMatrial); else line = new Line$1(BufferGeometryUtils.CreateFromPts([this._Center, this._DiameterOrRadiusPoint, this._TextPoint]), colorMaterial); let arrow = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); obj.add(line, arrow); AddEntityDrawObject(obj, this._Text, renderType); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(renderType, obj) { let [line, arrow, textObj] = obj.children; let arrowSize = this.GetDimStyleValue(DimStyleKeyCode.DIMASZ) ?? 10; if (renderType === RenderType.WireframePrint) { arrowSize *= HostApplicationServices.lineWidth * 0.5; let geo = line.geometry; geo.setPositions([...this._Center.toArray(), ...this._DiameterOrRadiusPoint.toArray(), ...this._TextPoint.toArray()]); } else BufferGeometryUtils.UpdatePts(line.geometry, [this._Center, this._DiameterOrRadiusPoint, this._TextPoint], true); arrow.scale.set(arrowSize, arrowSize, arrowSize); let armV = this._TextPoint.clone().sub(this._DiameterOrRadiusPoint); let armAn = angle(armV); arrow.position.copy(this._DiameterOrRadiusPoint); arrow.rotation.z = armAn + Math.PI / 2; arrow.updateMatrix(); obj.remove(textObj); DisposeThreeObj(textObj); this.UpdateText(); AddEntityDrawObject(obj, this._Text, renderType); } UpdateDrawObjectMaterial(renderType, obj, material) { if (renderType === RenderType.WireframePrint) return; let [line, arrow, textObj] = obj.children; let lineMaterial = GetDimLineMaterial(this, renderType); line.material = lineMaterial; arrow.material = line.material; this._Text.ColorIndex = this.ColorIndex; //如果实体是拷贝的,那么可能修改材质失败 if (textObj.children[0]) { let mesh = textObj.children[0]; mesh.material = ColorMaterial.GetBasicMaterialDoubleSide(this.ColorIndex); //TODO:在布局时应该如何渲染? } } UpdateText() { this._Text.AutoUpdate = false; let armV = this._TextPoint.clone().sub(this._DiameterOrRadiusPoint); this._Text.TextString = this.TextString; this._Text.Position = this._TextPoint; this._Text.TextRotation = angleAndX(armV); this._Text.Height = this.TextSize; this._Text.DeferUpdate(); this._Text.AutoUpdate = true; } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { let l = new Line(this.Center, this.TextPoint); switch (snapMode) { case ObjectSnapMode.End: return this.GetStretchPoints(); default: return l.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); } } GetGripPoints() { return [this.Center, this.DiameterOrRadiusPoint, this.TextPoint]; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); let inv = this.OCSInv; let vec0 = vec.clone().applyMatrix4(inv.clone().setPosition(0, 0, 0)); let rad = this._Center.distanceTo(this._DiameterOrRadiusPoint); for (let i of indexList) { if (i === 1) { let center = this.DiameterOrRadiusPoint.add(vec).applyMatrix4(inv); let cir = new Circle(this._Center, rad); let cp = cir.GetClosestPointTo(center, true); this._DiameterOrRadiusPoint.copy(cp); let dist = this._Center.distanceTo(this._TextPoint); let dir = this._DiameterOrRadiusPoint.clone().sub(this._Center).normalize().multiplyScalar(dist); this._TextPoint.copy(this._Center.clone().add(dir)); } else { if (i === 0) this._Center.add(vec0); else this._TextPoint.add(vec0); let dir = this._TextPoint.clone().sub(this._Center).normalize().multiplyScalar(rad); this._DiameterOrRadiusPoint.copy(this._Center.clone().add(dir)); } } this.Update(); } GetStretchPoints() { return this.GetGripPoints(); } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); if (indexList.length >= 2) this.ApplyMatrix(MoveMatrix(vec)); else this.MoveGripPoints(indexList, vec); } ApplyMirrorMatrix(m) { this.WriteAllObjectRecord(); let p1 = this.Center; let p2 = this.DiameterOrRadiusPoint; let p3 = this.TextPoint; reviseMirrorMatrix(this._Matrix); this.Center = p1; this.DiameterOrRadiusPoint = p2; this.TextPoint = p3; return this; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._Center.fromArray(file.Read()); this._DiameterOrRadiusPoint.fromArray(file.Read()); this._TextPoint.fromArray(file.Read()); if (ver > 1) this._TextString = file.Read(); if (ver > 2 && ver < 5) this.TextSize = file.Read(); if (ver > 3 && ver < 5) this.FractionDigits = file.Read(); if (ver > 4) this.ReadDimStyle(file); if (ver > 5) this._DimStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentDimStyle; else this._DimStyle = HostApplicationServices.CurrentDimStyle; if (ver > 6) this.ReadRoomAndCabinetName(file); } WriteFile(file) { super.WriteFile(file); file.Write(7); file.Write(this._Center.toArray()); file.Write(this._DiameterOrRadiusPoint.toArray()); file.Write(this._TextPoint.toArray()); file.Write(this._TextString); this.WriteDimStyle(file); file.WriteHardObjectId(this._DimStyle); this.WriteRoomAndCabinetName(file); } }; RadiusDimension = __decorate([ Factory ], RadiusDimension); const LINE_EXTEND_VAL = 40; //尺寸线延长值 let DiameterDimension = class DiameterDimension extends RadiusDimension { constructor() { super(...arguments); this._TextString = "D<>"; } InitDrawObject(renderType = RenderType.Wireframe) { this._Text.ColorIndex = this.DrawColorIndex; let obj = new Object3D(); let colorMaterial = GetDimLineMaterial(this, renderType); let vec = this._DiameterOrRadiusPoint.clone().sub(this._Center).normalize().multiplyScalar(-LINE_EXTEND_VAL); let line; if (renderType === RenderType.WireframePrint) line = new Line2(undefined, ColorMaterial.PrintLineMatrial); else line = new Line$1(BufferGeometryUtils.CreateFromPts([this._Center.clone().add(vec), this._DiameterOrRadiusPoint, this._TextPoint]), colorMaterial); let arrow = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); let arrow2 = new Mesh(BufferGeometryUtils.ArrowGeometry(), colorMaterial); obj.add(line, arrow, arrow2); AddEntityDrawObject(obj, this._Text, renderType); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(renderType, obj) { let [line, arrow, arrow2, textObj] = obj.children; let vec = this._DiameterOrRadiusPoint.clone().sub(this._Center).normalize().multiplyScalar(-LINE_EXTEND_VAL); let arrowSize = 10; let sp = this._Center.clone().add(vec); if (renderType === RenderType.WireframePrint) { arrowSize *= HostApplicationServices.lineWidth * 0.5; let geo = line.geometry; geo.setPositions([...sp.toArray(), ...this._DiameterOrRadiusPoint.toArray(), ...this._TextPoint.toArray()]); } else BufferGeometryUtils.UpdatePts(line.geometry, [sp, this._DiameterOrRadiusPoint, this._TextPoint], true); arrow.scale.set(arrowSize, arrowSize, arrowSize); arrow2.scale.set(arrowSize, arrowSize, arrowSize); let armV = this._TextPoint.clone().sub(this._DiameterOrRadiusPoint); let armAn = angle(armV); arrow.position.copy(this._DiameterOrRadiusPoint); arrow.rotation.z = armAn + Math.PI / 2; arrow.updateMatrix(); arrow2.position.copy(this._Center); arrow2.rotation.z = armAn - Math.PI / 2; arrow2.updateMatrix(); this.UpdateText(); obj.remove(textObj); DisposeThreeObj(textObj); AddEntityDrawObject(obj, this._Text, renderType); } UpdateDrawObjectMaterial(renderType, obj, material) { if (renderType === RenderType.WireframePrint) return; let [line, arrow, arrow2, textObj] = obj.children; let lineMaterial = GetDimLineMaterial(this, renderType); line.material = lineMaterial; arrow.material = line.material; arrow2.material = lineMaterial; this._Text.ColorIndex = this.DrawColorIndex; //如果实体是拷贝的,那么可能修改材质失败 if (textObj.children[0]) { let mesh = textObj.children[0]; mesh.material = ColorMaterial.GetBasicMaterialDoubleSide(this.DrawColorIndex); //TODO:在布局时应该如何渲染? } } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); let v = vec.clone().applyMatrix4(this.OCSInv.setPosition(new Vector3())); let dia = this._Center.distanceTo(this._DiameterOrRadiusPoint); let cirCenter = midPoint(this._Center, this._DiameterOrRadiusPoint); for (let i of indexList) { if (i === 0) { this._Center.add(v); let dist = this._TextPoint.distanceTo(this._DiameterOrRadiusPoint); let v1 = cirCenter.clone().sub(this._Center); this._DiameterOrRadiusPoint.copy(cirCenter.clone().add(v1)); this._TextPoint.copy(this._DiameterOrRadiusPoint.clone().add(v1.normalize().multiplyScalar(dist))); } else if (i === 1) { let dist = this._TextPoint.distanceTo(this._DiameterOrRadiusPoint); let center = this.DiameterOrRadiusPoint.add(vec).applyMatrix4(this.OCSInv); let cir = new Circle(cirCenter, dia / 2); this._DiameterOrRadiusPoint.copy(cir.GetClosestPointTo(center, true)); let v1 = this._DiameterOrRadiusPoint.clone().sub(cirCenter).normalize().multiplyScalar(-dia); this._Center.copy(this._DiameterOrRadiusPoint.clone().add(v1)); this._TextPoint.copy(this._DiameterOrRadiusPoint.clone().add(v1.normalize().multiplyScalar(-dist))); } else { this._TextPoint.add(v); let v1 = this._TextPoint.clone().sub(cirCenter).normalize().multiplyScalar(dia / 2); this._DiameterOrRadiusPoint.copy(cirCenter.clone().add(v1)); this._Center.copy(cirCenter.add(v1.negate())); } } this.Update(); } }; DiameterDimension = __decorate([ Factory ], DiameterDimension); var DimDir; (function (DimDir) { /**水平 */ DimDir[DimDir["H"] = 0] = "H"; /**垂直 */ DimDir[DimDir["V"] = 1] = "V"; })(DimDir || (DimDir = {})); /** * 线性标注 http://docs.autodesk.com/ACD/2011/ENU/filesDXF/WS1a9193826455f5ff18cb41610ec0a2e719-7a23.htm * 如果按照Autodesk实现的线性标注,这个其实应该叫做AcDbRotatedDimension,而且提供50祖玛(旋转角度) 52(暂时不知道有啥用) * 现在的实现是根据ArmPt来实现固定的垂直标注或者水平标注 */ let LinearDimension = class LinearDimension extends AlignedDimension { constructor() { super(...arguments); this._DimDir = DimDir.H; } get TextPosition() { return super.TextPosition; } //在设置TextPos时,同时会影响标注的方向(H水平 V竖直) (严格来说,这个点应该是标注线的位置,因为文字位置总是总中心.) set TextPosition(p) { p = p.clone().applyMatrix4(this.OCSInv).setZ(0); let bit = 0; //x 1 y 2 if (isBetweenNums(this._FootP1.x, this._FootP2.x, p.x)) bit |= 1; if (isBetweenNums(this._FootP1.y, this._FootP2.y, p.y)) bit |= 2; if (bit === 1) this._DimDir = DimDir.H; else if (bit === 2) this._DimDir = DimDir.V; //else 叠加态(在中间时,不做变更) if (this._DimDir === DimDir.H) { this._ArmP1.copy(this._FootP1).setY(p.y).setZ(0); this._ArmP2.copy(this._FootP2).setY(p.y).setZ(0); } else { this._ArmP1.copy(this._FootP1).setX(p.x).setZ(0); this._ArmP2.copy(this._FootP2).setX(p.x).setZ(0); } this.Update(); } OnChangeFootPt() { let l = new Line(this._ArmP1, this._ArmP2); //这个代码有个bug,当footPt在肩线(标注线)上时,这个无法更新,更稳妥似乎应该用 if(!equalv2())??? if (!equaln$1(this._FootP1.x, this._ArmP1.x) && !equaln$1(this._FootP1.y, this._ArmP1.y)) { let cp1 = l.GetClosestPointTo(this._FootP1, true); this._ArmP1.copy(cp1).setZ(0); } if (!equaln$1(this._FootP2.x, this._ArmP2.x) && !equaln$1(this._FootP2.y, this._ArmP2.y)) { let cp2 = l.GetClosestPointTo(this._FootP2, true); this._ArmP2.copy(cp2).setZ(0); } } }; LinearDimension = __decorate([ Factory ], LinearDimension); //箭头引线标签标注 (箭头暂未实现) let MLeaderDimension = class MLeaderDimension extends Dimension { constructor(_TextString = "", // 文本 _StartPoint = new Vector3(), // 引线起点 _EndPoint = new Vector3(100)) { super(); this._TextString = _TextString; this._StartPoint = _StartPoint; this._EndPoint = _EndPoint; this._Text = new Text(); } get Text() { if (!this._Text.TextString) this.ParseText(); return this._Text; } 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(); } GetPrimitiveString() { return this._TextString; } //#region 拉伸相关 GetGripPoints() { return [this.StartPoint, new Vector3().addVectors(this.EndPoint, this.StartPoint).multiplyScalar(0.5), this.EndPoint]; } MoveGripPoints(indexList, vec) { 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); } } } //#endregion //#region Draw InitDrawObject(renderType = RenderType.Wireframe) { let colorMaterial = GetDimLineMaterial(this, renderType); let obj = new Object3D(); let line = new Line$1(new BufferGeometry, colorMaterial); obj.add(line); this.UpdateDrawObject(renderType, obj); return obj; } UpdateDrawObject(renderType, obj) { obj.remove(...obj.children.slice(1)); let line = obj.children[0]; let colorMaterial = GetDimLineMaterial(this, renderType); line.material = colorMaterial; this.ParseText(); this._Text.DeferUpdate(); let geo = line.geometry; if (!BufferGeometryUtils.UpdatePts(geo, [this._StartPoint, this._EndPoint])) { line.geometry.dispose(); line.geometry = BufferGeometryUtils.CreateFromPts([this._StartPoint, this._EndPoint]); } AddEntityDrawObject(obj, this._Text, renderType); } ParseText() { this._Text.AutoUpdate = false; this._Text.ColorIndex = this.ColorIndex; this._Text.TextString = this.TextString; this._Text.Height = this.TextSize; this._Text.TextAligen = TextAligen.LeftMid; this._Text.OCS = this.OCS.setPosition(this._EndPoint); this._Text.AutoUpdate = true; } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this._TextString = file.Read(); this._StartPoint = new Vector3(file.Read(), file.Read(), file.Read()); this._EndPoint = new Vector3(file.Read(), file.Read(), file.Read()); this.ReadDimStyle(file); this._DimStyle = file.ReadHardObjectId() ?? HostApplicationServices.CurrentDimStyle; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this._TextString); file.WriteVec3(this._StartPoint); file.WriteVec3(this._EndPoint); this.WriteDimStyle(file); file.WriteHardObjectId(this._DimStyle); } }; MLeaderDimension = __decorate([ Factory ], MLeaderDimension); let boxGeo; function GetBoxGeoBufferGeometry() { if (!boxGeo) boxGeo = new BoxBufferGeometry(1, 1, 1); return boxGeo; } let BoxSolid = class BoxSolid extends Entity { constructor(_length = 1, _width = 1, _height = 1) { super(); this._length = _length; this._width = _width; this._height = _height; this.OnlyRenderType = true; this._opacity = 0.5; } get Length() { return this._length; } get Width() { return this._width; } get Height() { return this._height; } set Length(v) { if (!equaln$1(v, this._length, 1e-5)) { this.WriteAllObjectRecord(); this._length = v; this.Update(); } } set Width(v) { if (!equaln$1(v, this._width, 1e-5)) { this.WriteAllObjectRecord(); this._width = v; this.Update(); } } set Height(v) { if (!equaln$1(v, this._height, 1e-5)) { this.WriteAllObjectRecord(); this._height = v; this.Update(); } } set Opacity(o) { if (o !== this._opacity) { this.WriteAllObjectRecord(); this._opacity = o; this.Update(); } } InitDrawObject(renderType = RenderType.Wireframe) { let box = new Object3D(); let geo = GetBoxGeoBufferGeometry(); let mat = ColorMaterial.GetBasicMaterialTransparent(7, this._opacity); box.add(new Mesh(geo, mat)); this.UpdateDrawObject(renderType, box); return box; } UpdateDrawObject(type, obj) { obj.children[0].scale.set(this._length, this._width, this._height); obj.children[0].material.opacity = this._opacity; obj.children[0].position.copy(new Vector3(this._length / 2, this._width / 2, this._height / 2)); obj.children[0].updateMatrix(); } get BoundingBoxInOCS() { return new Box3Ext(new Vector3, new Vector3(this._length, this._width, this._height)); } get BoundingBox() { return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); } }; BoxSolid = __decorate([ Factory ], BoxSolid); let Image = class Image extends Entity { constructor(imageSizeX, imageSizeY) { super(); this.OnlyRenderType = true; this._ImageSizeX = 1; this._ImageSizeY = 1; this._ImageScaleX = 1; this._ImageScaleY = 1; this._FaceSize = FrontSide; //默认只在前面显示 this.texture = new Texture(); this._ImageSizeX = imageSizeX; this._ImageSizeY = imageSizeY; } get ImageUrl() { return this._ImageUrl; } set ImageUrl(url) { if (this._ImageUrl !== url) { this.WriteAllObjectRecord(); this._ImageUrl = url; this.Update(); } } get ImageSizeX() { return this._ImageSizeX; } get ImageSizeY() { return this._ImageSizeY; } get ImageScaleX() { return this._ImageScaleX; } set ImageScaleX(v) { if (this._ImageScaleX !== v) { this.WriteAllObjectRecord(); this._ImageScaleX = v; this.Update(); } } get ImageScaleY() { return this._ImageScaleY; } set ImageScaleY(v) { if (this._ImageScaleY !== v) { this.WriteAllObjectRecord(); this._ImageScaleY = v; this.Update(); } } get ImageClipPoly() { return this._ImageClipPoly; } /** * 请注意 v不需要OCS 请调用MatrixIdentity清除 */ set ImageClipPoly(v) { this.WriteAllObjectRecord(); if (!v) { this._ImageClipPoly = undefined; } else { this._ImageClipPoly = new Polyline; this._ImageClipPoly.LineData = v.LineData.map(d => { return { pt: d.pt.clone(), bul: d.bul }; }); } this.Update(); } ApplyScaleMatrix(m) { this.WriteAllObjectRecord(); let p = this.Position; p.applyMatrix4(m); m.extractBasis(Entity._xa, Entity._ya, Entity._za); let scaleX = Entity._xa.length(); let scaleY = Entity._ya.length(); this._ImageScaleX *= scaleX; this._ImageScaleY *= scaleY; Entity._xa.normalize(); Entity._ya.normalize(); Entity._za.normalize(); m = new Matrix4().makeBasis(Entity._xa, Entity._ya, Entity._za); this.ApplyMatrix(m); this.Position = p; this.Update(); return this; } ; async Update(mode) { await this.UpdateTexture(); super.Update(); } async UpdateTexture(url) { url = url ?? this._ImageUrl; if (!this.texture?.image) this.texture = new TextureLoader().load(url); } //#region -------------------------捕捉------------------------- GetPolyline() { if (this._ImageClipPoly) return this._ImageClipPoly.Clone(); return new Polyline().Rectangle(this.ImageSizeX * this.ImageScaleX, this.ImageSizeY * this.ImageScaleY).ApplyMatrix(this.OCS); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform) { return this.GetPolyline().GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform); } GetGripPoints() { return this.GetPolyline().GetGripPoints(); } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); let i = indexList[0]; if (!this._ImageClipPoly) { let moveX = vec.x; let moveY = vec.y; let x = this._ImageSizeX * this._ImageScaleX; let y = this._ImageSizeY * this._ImageScaleY; let bl = y / x; switch (i) { case 0: this._ImageScaleX = this._ImageScaleX * (-moveX + x) / x; this._ImageScaleY = this._ImageScaleY * (-moveX * bl + y) / y; this.ApplyMatrix(new Matrix4().setPosition(vec.x, vec.x * bl, 0)); break; case 1: this._ImageScaleY = this._ImageScaleY * (-moveY + y) / y; this.ApplyMatrix(new Matrix4().setPosition(0, vec.y, 0)); break; case 2: this._ImageScaleX = this._ImageScaleX * (moveX + x) / x; this._ImageScaleY = this._ImageScaleY * (moveX * bl + y) / y; this.ApplyMatrix(new Matrix4().setPosition(0, -vec.x * bl, 0)); break; case 3: this._ImageScaleX = this._ImageScaleX * (moveX + x) / x; break; case 4: this._ImageScaleX = this._ImageScaleX * (moveX + x) / x; this._ImageScaleY = this._ImageScaleY * (moveX * bl + y) / y; break; case 5: this._ImageScaleY = this._ImageScaleY * (moveY + y) / y; break; case 6: this._ImageScaleX = this._ImageScaleX * (-moveX + x) / x; this._ImageScaleY = this._ImageScaleY * (-moveX * bl + y) / y; this.ApplyMatrix(new Matrix4().setPosition(vec.x, 0, 0)); break; case 7: this._ImageScaleX = this._ImageScaleX * (-moveX + x) / x; this.ApplyMatrix(new Matrix4().setPosition(vec.x, 0, 0)); break; } } this.Update(); } GetStretchPoints() { return this.GetPolyline().GetStretchPoints(); } MoveStretchPoints(indexList, vec) { if (!this._ImageClipPoly) { if (indexList.length === 1) this.MoveGripPoints([indexList[0] * 2], vec); if (indexList.length === 2) { const ids = indexList.toSorted((a1, a2) => a1 - a2); const directionMap = [[0, 1, 1], [1, 2, 3], [2, 3, 5], [0, 3, 7]]; //下右上左 for (const dir of directionMap) { if (ids[0] === dir[0] && ids[1] === dir[1]) { this.MoveGripPoints([dir[2]], vec); break; } } } } } 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() { if (!this._ImageClipPoly) { let polyline = new Polyline().Rectangle(this.ImageScaleX * this.ImageSizeX, this.ImageScaleY * this.ImageSizeY); this._EdgeGeometry = new ShapeBufferGeometry(polyline.Shape, 30); this._MeshGeometry = new ShapeBufferGeometry(polyline.Shape, 30); //注意修改uv 0-1 //TODO: // ScaleUV(geometry); TODO: } else { this.ImageClipPoly.MatrixAlignTo2; let shape = this._ImageClipPoly.Shape; this._EdgeGeometry = new ShapeBufferGeometry(shape, 30); this._MeshGeometry = new ShapeBufferGeometry(shape, 30); // ScaleUV(geometry); TODO: } let uvs = this._MeshGeometry.attributes.uv; for (let i = 0; i < uvs.count; i++) { let x = uvs.getX(i); let y = uvs.getY(i); uvs.setXY(i, x / (this._ImageSizeX * this._ImageScaleX), y / (this._ImageSizeY * this._ImageScaleY)); } this._MeshGeometry["IsMesh"] = true; } UpdateDrawGeometry() { if (this._EdgeGeometry) this._EdgeGeometry.dispose(); this._EdgeGeometry = undefined; if (this._MeshGeometry) this._MeshGeometry.dispose(); this._MeshGeometry = undefined; } //#endregion //#region -------------------------Draw------------------------- InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); let mtl = new MeshBasicMaterial({ map: this.texture }); let mesh = new Mesh(this.MeshGeometry, mtl); obj.add(mesh); return obj; } UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); obj.add(this.InitDrawObject(renderType)); return obj; } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this._ImageUrl = file.ReadString(); this._ImageSizeX = file.Read(); this._ImageSizeY = file.Read(); this._ImageScaleX = file.Read(); this._ImageScaleY = file.Read(); let count = file.Read(); if (count) { this._ImageClipPoly = new Polyline(); for (let i = 0; i < count; i++) { let v = new Vector2$1(file.Read(), file.Read()); let bul = file.Read(); this._ImageClipPoly.LineData.push({ pt: v, bul: bul }); } } else this._ImageClipPoly = undefined; this._FaceSize = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this._ImageUrl); file.Write(this._ImageSizeX); file.Write(this._ImageSizeY); file.Write(this._ImageScaleX); file.Write(this._ImageScaleY); const count = this._ImageClipPoly?.LineData?.length ?? 0; file.Write(count); for (let i = 0; i < count; i++) { let l = this._ImageClipPoly.LineData[i]; file.Write(l.pt.x); file.Write(l.pt.y); file.Write(l.bul); } file.Write(this._FaceSize); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; Image = __decorate([ Factory ], Image); let Point = class Point extends Entity { constructor(position = new Vector3()) { super(); this._Matrix.setPosition(position); } GetObjectSnapPoints(snapMode, pickPoint, lastPoint) { if (snapMode === ObjectSnapMode.End) return [this.Position]; return []; } GetGripPoints() { return [this.Position]; } MoveGripPoints(indexList, vec) { if (indexList.length === 1) { this.WriteAllObjectRecord(); this.ApplyMatrix(MoveMatrix(vec)); } } GetStretchPoints() { return this.GetGripPoints(); } /** * 拉伸夹点,用于Stretch命令 * * @param {Array} indexList 拉伸点索引列表. * @param {Vector3} vec 移动向量 */ MoveStretchPoints(indexList, vec) { this.MoveGripPoints(indexList, vec); } }; Point = __decorate([ Factory ], Point); class PointLightHelper extends Object3D { constructor(distance, color) { const geometry = new BufferGeometry(); const positions = []; for (let i = 0, j = 1, l = 32; i < l; i++, j++) { const p1 = (i / l) * Math.PI * 2; const p2 = (j / l) * Math.PI * 2; positions.push(Math.cos(p1) * distance, Math.sin(p1) * distance, 0, Math.cos(p2) * distance, Math.sin(p2) * distance, 0); } geometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); super(); this.color = color; this.matrixAutoUpdate = false; this.material = new LineBasicMaterial({ fog: false }); this.cone = []; this.mesh = []; this.cone[0] = new LineSegments(geometry, this.material[0]); this.cone[1] = this.cone[0].clone(); let moveMatInv = new Matrix4().getInverse(this.matrix); let roMat = new Matrix4().makeRotationAxis(new Vector3(0, 1, 0), MathUtils.degToRad(60)); let mtx = this.matrix.clone().multiply(roMat).multiply(moveMatInv); this.cone[1].applyMatrix4(mtx); this.cone[2] = this.cone[1].clone(); this.cone[2].applyMatrix4(mtx); let cylinderRoMat = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), MathUtils.degToRad(90)); let cylinderMtx = this.matrix.clone().multiply(cylinderRoMat).multiply(moveMatInv); let cylinderGeometry = new CylinderBufferGeometry(40, 40, 80, 32); //灯泡圆柱 this.mesh[0] = new Mesh(cylinderGeometry, new MeshBasicMaterial({ color: 0xFFEAAD })); this.mesh[0].applyMatrix4(cylinderMtx); this.mesh[0].position.add(new Vector3(0, 0, 50)); this.mesh[0].updateMatrix(); let sphereBufferGeometry = new SphereBufferGeometry(75, 32, 32); //灯泡球体 this.mesh[1] = new Mesh(sphereBufferGeometry, this.material); this.mesh[1].position.sub(new Vector3(0, 0, 30)); this.mesh[1].updateMatrix(); this.add(this.cone[0], this.cone[1], this.cone[2], this.mesh[0], this.mesh[1]); } dispose() { this.material.dispose(); this.cone[0].geometry.dispose(); //@ts-ignore this.cone[0].material.dispose(); this.cone[1].geometry.dispose(); //@ts-ignore this.cone[1].material.dispose(); this.cone[2].geometry.dispose(); //@ts-ignore this.cone[2].material.dispose(); this.mesh[0].geometry.dispose(); //@ts-ignore this.mesh[0].material.dispose(); this.mesh[1].geometry.dispose(); //@ts-ignore this.mesh[1].material.dispose(); } update() { //@ts-ignore this.mesh[1].material.color.set(this.color).multiplyScalar(0.9); } } /** * 点光源 */ let PointLight = class PointLight extends Light { constructor() { super(); /** * 光照长度,如果为0那么为无穷大 */ this._Distance = 20000; //20米 this._Intensity = 100; //强度 // 光线沿着光线的距离变暗的量 // 在物理上正确的模式下,衰减 = 2会导致物理上真实的光线衰减。 // 缺省值是1。 this._Decay = 0.45; //PointLightComponent this.SourceRadius = 10; //源半径 范围0-300 this.SoftSourceRadius = 0; //软源半径 范围0-300 this.SourceLength = 0; //源长度 默认0 范围0-1000 //LocalLightComponent //Radius:number 没设置这个 this.AttenuationRadius = 300; //衰减半径 10-1000 } get Decay() { return this._Decay; } set Decay(decay) { this.WriteAllObjectRecord(); this._Decay = decay; this.Update(); } get Distance() { return this._Distance; } set Distance(dist) { this.WriteAllObjectRecord(); this._Distance = dist; this.Update(); } get WebIntensity() { let x = this._Intensity / 2000; x = Math.pow(x, 0.7); return (x * 2000) / (4 * Math.PI); //流明转cd 文档是4pi } InitDrawObject(renderType = RenderType.Wireframe) { let lightGroup = new Group(); let ptLight = new PointLight$1(this._LightColor, this.WebIntensity, this._Distance, this._Decay); Object.defineProperty(ptLight, "castShadow", { get: () => HostApplicationServices.isShowLightShadow && this.CaseShadow }); ptLight.shadow.camera.matrixAutoUpdate = true; ptLight.shadow.camera.far = 10000; //绘制灯光助手 let lightGeo = new SphereGeometry(50); let geoMat = new MeshBasicMaterial({ color: this._LightColor }); ptLight.add(new Mesh(lightGeo, geoMat)); let helper = new PointLightHelper(ptLight.distance / 4, this.Color); lightGroup.add(ptLight, helper); lightGroup.matrixAutoUpdate = false; lightGroup.matrix.copy(this._Matrix); lightGroup.updateMatrixWorld(true); this.UpdateDrawObject(renderType, lightGroup); return lightGroup; } UpdateDrawObject(type, en) { let ptLight = en.children[0]; super.UpdateDrawObject(type, ptLight); ptLight.distance = this._Distance; ptLight.decay = this._Decay; let con = ptLight.children[0]; con.material = new LineBasicMaterial({ color: this.Color }); let helper = en.children[1]; helper.visible = this._ShowHelper; if (this._ShowHelper) helper.color = this.Color; helper.update(); } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); // this._Distance = file.Read(); // this._Decay = file.Read(); //屏蔽原先的2个属性 file.Read(); file.Read(); if (ver > 1) { this.SourceRadius = file.Read(); this.SoftSourceRadius = file.Read(); this.SourceLength = file.Read(); this.AttenuationRadius = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._Distance); file.Write(this._Decay); //ver 2 file.Write(this.SourceRadius); file.Write(this.SoftSourceRadius); file.Write(this.SourceLength); file.Write(this.AttenuationRadius); } }; __decorate([ AutoRecord ], PointLight.prototype, "SourceRadius", void 0); __decorate([ AutoRecord ], PointLight.prototype, "SoftSourceRadius", void 0); __decorate([ AutoRecord ], PointLight.prototype, "SourceLength", void 0); __decorate([ AutoRecord ], PointLight.prototype, "AttenuationRadius", void 0); PointLight = __decorate([ Factory ], PointLight); /** * This helper must be added as a child of the light (移植threejs最新的版本) */ class RectAreaLightHelper extends Line$1 { constructor(light, color) { const positions = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0, 0.618, 0.618, 0, // 0.618, 0.618, -100, // 0.618, 0.618, 0, -0.618, 0.618, 0, -1, 1, 0, -0.618, 0.618, 0, // -0.618, 0.618, -100, // -0.618, 0.618, 0, -0.618, -0.618, 0, -1, -1, 0, -0.618, -0.618, 0, // -0.618, -0.618, -100, // -0.618, -0.618, 0, 0.618, -0.618, 0, 1, -1, 0, 0.618, -0.618, 0, // 0.618, -0.618, -100, // 0.618, -0.618, 0, 0.618, 0.618, 0 ]; const geometry = new BufferGeometry(); let posatt = new Float32BufferAttribute(positions, 3); geometry.setAttribute('position', posatt); geometry.computeBoundingSphere(); const material = new LineBasicMaterial({ fog: false }); super(geometry, material); this._posAtt = posatt; this.light = light; this.color = color; // optional hardwired color for the helper this.type = 'RectAreaLightHelper'; // const positions2 = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0]; const geometry2 = new BufferGeometry(); this._indexAtt = new Float32BufferAttribute(positions2, 3); geometry2.setAttribute('position', this._indexAtt); geometry2.computeBoundingSphere(); this.add(new Mesh(geometry2, new MeshBasicMaterial({ side: BackSide, fog: false, transparent: true, opacity: 0.8 }))); } copy(source, recursive) { this.light = source.light.clone(); this.color = source.color.clone(); return super.copy(source, recursive); } updateMatrixWorld() { this.scale.set(0.5 * this.light.width, 0.5 * this.light.height, 1); if (this.color !== undefined) { this.material.color.set(this.color); //@ts-ignore this.children[0].material.color.set(this.color); } else { this.material.color.copy(this.light.color).multiplyScalar(this.light.intensity); // prevent hue shift const c = this.material.color; const max = Math.max(c.r, c.g, c.b); if (max > 1) c.multiplyScalar(1 / max); //@ts-ignore this.children[0].material.color.copy(this.material.color); } // ignore world scale on light this.matrixWorld.extractRotation(this.light.matrixWorld).scale(this.scale).copyPosition(this.light.matrixWorld); this.children[0].matrixWorld.copy(this.matrixWorld); } updataRange() { let [n, w, h] = [1, 1, 1]; let t = Math.sin(this.BarnDoorAngle * Math.PI / 180) * this.BarnDoorLength; let cosAngle = Math.cos(this.BarnDoorAngle * Math.PI / 180); if (Math.floor(cosAngle * 1e5)) { w = ((this.light.width + this.BarnDoorLength * 2) / this.light.width - 1) * cosAngle + 1; h = ((this.light.height + this.BarnDoorLength * 2) / this.light.height - 1) * cosAngle + 1; } let positions = [w, h, -t, -w, h, -t, -w, -h, -t, w, -h, -t, w, h, -t, n, n, 0, -n, n, 0, -w, h, -t, -n, n, 0, -n, -n, 0, -w, -h, -t, -n, -n, 0, n, -n, 0, w, -h, -t, n, -n, 0, n, n, 0 ]; let positions2 = [w, h, -t, -w, h, -t, -w, -h, -t, w, h, -t, -w, -h, -t, w, -h, -t]; this._posAtt.copyArray(positions); this._posAtt.needsUpdate = true; this._indexAtt.copyArray(positions2); this._indexAtt.needsUpdate = true; } dispose() { this.geometry.dispose(); this.material.dispose(); this.children[0].geometry.dispose(); //@ts-ignore this.children[0].material.dispose(); } } const TARGET_DISTANCE = -100; let RectAreaLight = class RectAreaLight extends Light { constructor() { super(...arguments); this._Intensity = 100; this._Width = 1; //UE SourceWidth this._Height = 1; //UE SourceHeight //LocalLightComponent //Radius:number 没设置这个 this.AttenuationRadius = 300; //衰减半径 10-1000 //RectLightComponent extends LocalLightComponent this._BarnDoorAngle = 0; //0-90 挡光板角度 this._BarnDoorLength = 0; //0-100 挡光板长度 } get Target() { return this.Position.add(this.Normal.multiplyScalar(TARGET_DISTANCE)); } set Target(p) { this.WriteAllObjectRecord(); this._Matrix.lookAt(this.Position, p, YAxis); this.Update(UpdateDraw.Geometry); } get Height() { return this._Height; } set Height(v) { if (equaln(v, this._Height, 1e-2)) return; this.WriteAllObjectRecord(); this._Height = v; this.Update(); } get Width() { return this._Width; } set Width(v) { if (equaln(v, this._Width, 1e-2)) return; this.WriteAllObjectRecord(); this._Width = v; this.Update(); } get BarnDoorAngle() { return this._BarnDoorAngle; } set BarnDoorAngle(v) { if (equaln(this._BarnDoorAngle, v, 1e-2)) return; this.WriteAllObjectRecord(); this._BarnDoorAngle = v; this.Update(); } get BarnDoorLength() { return this._BarnDoorLength; } set BarnDoorLength(v) { if (equaln(this._BarnDoorLength, v, 1e-2)) return; this.WriteAllObjectRecord(); this._BarnDoorLength = v; this.Update(); } get WebIntensity() { let x = this._Intensity / 2000; x = Math.pow(x, 0.5); return (x * 50) / (Math.PI); //流明转cd 文档是4pi } get BoundingBoxInOCS() { return new Box3Ext(new Vector3(-this._Width * 0.5, -this._Height * 0.5, 0), new Vector3(this._Width * 0.5, this._Height * 0.5, 0.01)); } get BoundingBox() { return new Box3().setFromCenterAndSize(new Vector3(), new Vector3(this._Width, this._Height)).applyMatrix4(this._Matrix); } GetGripPoints() { let heightHalf = this._Height * 0.5; let widthHalf = this.Width * 0.5; let pos = this.Position; let pts = [ new Vector3(-widthHalf, -heightHalf, 0).applyMatrix4(this.OCSNoClone), //左下 new Vector3(widthHalf, -heightHalf, 0).applyMatrix4(this.OCSNoClone), //右下 new Vector3(widthHalf, heightHalf, 0).applyMatrix4(this.OCSNoClone), //右上 new Vector3(-widthHalf, heightHalf, 0).applyMatrix4(this.OCSNoClone), //左上 pos, pos.clone().add(this.Normal.multiplyScalar(TARGET_DISTANCE)) ]; return pts; } MoveGripPoints(indexList, vec) { let vecInv = vec.clone().applyMatrix4(this.OCSInv.setPosition(0, 0, 0)); if (equalv3(vecInv, ZeroVec, 1e-4)) return; this.WriteAllObjectRecord(); let heightHalf = this._Height * 0.5; let widthHalf = this.Width * 0.5; let pts = [ new Vector3(-widthHalf, -heightHalf, 0), new Vector3(widthHalf, -heightHalf, 0), new Vector3(widthHalf, heightHalf, 0), new Vector3(-widthHalf, heightHalf, 0), ]; let i = indexList[0]; if (i < 4) { pts[i].add(vecInv); let newBox = new Box3; if (i === 0 || i === 2) newBox.setFromPoints([pts[0], pts[2]]); else newBox.setFromPoints([pts[1], pts[3]]); //变量复用 let size = newBox.getSize(new Vector3); this._Width = size.x; this._Height = size.y; //新的中心 let center = newBox.getCenter(size); center.setZ(0); center.applyMatrix4(this.OCSNoClone); this._Matrix.setPosition(center); this.Update(); } else if (i === 4) { this.Position = this.Position.add(vec); this.Update(UpdateDraw.Matrix); } else if (i === 5) { let target = this.Position.add(this.Normal.multiplyScalar(TARGET_DISTANCE)).add(vec); this.Target = target; } } GetStretchPoints() { let heightHalf = this._Height * 0.5; let widthHalf = this.Width * 0.5; let pos = this.Position; let pts = [ new Vector3(-widthHalf, -heightHalf, 0).applyMatrix4(this.OCSNoClone), //左下 new Vector3(widthHalf, -heightHalf, 0).applyMatrix4(this.OCSNoClone), //右下 new Vector3(widthHalf, heightHalf, 0).applyMatrix4(this.OCSNoClone), //右上 new Vector3(-widthHalf, heightHalf, 0).applyMatrix4(this.OCSNoClone), //左上 pos.clone().add(this.Normal.multiplyScalar(TARGET_DISTANCE)) ]; return pts; } MoveStretchPoints(ids, vec) { if (ids.length === 4) { this.Move(vec); return; } const MoveOneGrip = () => { let i = ids[0]; if (i < 4) this.MoveGripPoints(ids, vec); else this.MoveGripPoints([5], vec); }; if (ids.length === 1) { MoveOneGrip(); return; } //ids.length === 2,3 ids = ids.filter(i => i !== 4); //移除中心 if (ids.length === 1) { MoveOneGrip(); return; } //ids.length === 2 ids.sort((a1, a2) => a1 - a2); let inv = this.OCSInv.setPosition(0, 0, 0); vec = vec.clone().applyMatrix4(inv); let ocs = inv.copy(this._Matrix).setPosition(0, 0, 0); if (ids[0] === 0) { if (ids[1] === 1) //下 { vec.x = 0; vec.z = 0; vec.applyMatrix4(ocs); this.MoveGripPoints([0], vec); } else if (ids[1] === 3) //左 { vec.y = 0; vec.z = 0; vec.applyMatrix4(ocs); this.MoveGripPoints([0], vec); } } else if (ids[0] === 1) { if (ids[1] === 2) //右 { vec.y = 0; vec.z = 0; vec.applyMatrix4(ocs); this.MoveGripPoints([1], vec); } } else if (ids[0] === 2) { if (ids[1] === 3) //上 { vec.x = 0; vec.z = 0; vec.applyMatrix4(ocs); this.MoveGripPoints([2], vec); } } } InitDrawObject(renderType = RenderType.Wireframe) { let lightGroup = new Group(); let light = new RectAreaLight$1(this.Color, this.WebIntensity, this._Width, this._Height); lightGroup.add(light); this.UpdateDrawObject(renderType, lightGroup); return lightGroup; } UpdateDrawObject(type, obj) { let light = obj.children[0]; super.UpdateDrawObject(type, light); light.width = this._Width; light.height = this._Height; light.color.copy(this.Color); // light.castShadow = true;//threejs没有支持这个影子 let help; if (obj.children.length === 1) { help = new RectAreaLightHelper(light, light.color); obj.add(help); } else help = obj.children[1]; help.BarnDoorAngle = 90 - this.BarnDoorAngle; help.BarnDoorLength = this.BarnDoorLength; help.color = this.Color; help.updataRange(); help.updateMatrixWorld(); help = obj.children[1]; help.visible = this._ShowHelper; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._Height = file.Read(); this._Width = file.Read(); new Vector3().fromArray(file.Read()); //ver2 if (ver > 1) { this.AttenuationRadius = file.Read(); this._BarnDoorAngle = file.Read(); this._BarnDoorLength = file.Read(); this.SourceTexture = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { super.WriteFile(file); file.Write(2); file.Write(this._Height); file.Write(this._Width); file.Write([0, 0, 0]); //ver2 file.Write(this.AttenuationRadius); file.Write(this._BarnDoorAngle); file.Write(this._BarnDoorLength); file.Write(this.SourceTexture); } }; __decorate([ AutoRecord ], RectAreaLight.prototype, "AttenuationRadius", void 0); __decorate([ AutoRecord ], RectAreaLight.prototype, "SourceTexture", void 0); RectAreaLight = __decorate([ Factory ], RectAreaLight); class SpotLightHelper extends Object3D { constructor(light) { super(); this.light = light; this.cone = []; const geometry = new BufferGeometry(); const positions = [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, 1 ]; for (let i = 0, j = 1, l = 32; i < l; i++, j++) { const p1 = (i / l) * Math.PI * 2; const p2 = (j / l) * Math.PI * 2; positions.push(Math.cos(p1), Math.sin(p1), 1, Math.cos(p2), Math.sin(p2), 1); } geometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); this.cone[0] = new LineSegments(geometry, new LineBasicMaterial({ fog: false })); this.cone[1] = new LineSegments(geometry, new LineBasicMaterial({ fog: false })); this.cone[0].rotation.x = Math.PI; this.cone[1].rotation.x = Math.PI; this.add(this.cone[0], this.cone[1]); } dispose() { this.cone[0].geometry.dispose(); this.cone[1].geometry.dispose(); this.cone[0].material.dispose(); this.cone[1].material.dispose(); } update() { const coneLength = this.light.Distance ? this.light.Distance : 1000; const coneWidth1 = coneLength * Math.tan(this.light.Angle); const coneWidth2 = coneLength * Math.tan(MathUtils.degToRad(this.light.InnerConeAngle)); this.cone[0].scale.set(coneWidth1, coneWidth1, coneLength); this.cone[1].scale.set(coneWidth2, coneWidth2, coneLength); this.cone[0].updateMatrix(); this.cone[1].updateMatrix(); this.cone[0].material.color.set(this.light.Color).multiplyScalar(0.3); this.cone[1].material.color.set(this.light.Color); } } let SpotLight = class SpotLight extends Light { constructor() { super(...arguments); /** * If non-zero, light will attenuate linearly from maximum intensity at light position down to zero at distance. * Default — 0.0. */ this._Distance = 5000; this._Intensity = 100; //强度 // 光线沿着光线的距离变暗的量 // 在物理上正确的模式下,衰减 = 2会导致物理上真实的光线衰减。 // 缺省值是1。 this._Decay = 0.2; // 光线散射角度,最大为Math.PI/2 this._Angle = Math.PI / 4; //默认 // 聚光锥的半影衰减百分比。在0和1之间的值。 默认值 — 0.0。 this._Penumbra = 0; //SpotLightComponent extends PointLightComponent this.InnerConeAngle = 30; //椎体内部角度 默认0 范围0-90 this.OuterConeAngle = 40; //椎体外部角度 默认40 范围0-90 (弃用,使用Angle) //PointLightComponent this.SourceRadius = 10; //源半径 范围0-300 this.SoftSourceRadius = 0; //软源半径 范围0-300 this.SourceLength = 0; //源长度 默认0 范围0-1000 //LocalLightComponent //Radius:number 没设置这个 this.AttenuationRadius = 300; //衰减半径 10-1000 } get Target() { return this.Position.add(this.Normal.multiplyScalar(-this._Distance * 0.5)); } set Target(p) { if (!equalv3(p, this.Position)) { this.WriteAllObjectRecord(); this._Matrix.lookAt(this.Position, p, YAxis); this.Update(UpdateDraw.Matrix); } } get Angle() { return this._Angle; } set Angle(rad) { this.WriteAllObjectRecord(); this._Angle = rad; this.Update(); } get Decay() { return this._Decay; } set Decay(decay) { this.WriteAllObjectRecord(); this._Decay = decay; this.Update(); } get Distance() { return this._Distance; } set Distance(dist) { this.WriteAllObjectRecord(); this._Distance = dist; this.Update(); } get Penumbra() { return this._Penumbra; } set Penumbra(v) { this.WriteAllObjectRecord(); this._Penumbra = v; this.Update(); } get EndPoint() { return this.Position.add(this.Target.sub(this.Position).normalize().multiplyScalar(this._Distance)); } get WebIntensity() { let x = this._Intensity / 2000; x = Math.pow(x, 0.45); return (x * 125) / Math.PI; //流明转cd 文档是4pi } GetGripPoints() { let radius = this.Distance * Math.tan(this._Angle); let pts = [ this.Position, this.Target, new Vector3(radius, 0, -this._Distance).applyMatrix4(this._Matrix), new Vector3(-radius, 0, -this._Distance).applyMatrix4(this._Matrix), new Vector3(0, radius, -this._Distance).applyMatrix4(this._Matrix), new Vector3(0, -radius, -this._Distance).applyMatrix4(this._Matrix), ]; let a2 = MathUtils.degToRad(this.InnerConeAngle); if (this.InnerConeAngle >= 1 && !equaln(this._Angle, a2)) { let radius2 = this.Distance * Math.tan(a2); pts.push(new Vector3(radius2, 0, -this._Distance).applyMatrix4(this._Matrix), new Vector3(-radius2, 0, -this._Distance).applyMatrix4(this._Matrix), new Vector3(0, radius2, -this._Distance).applyMatrix4(this._Matrix), new Vector3(0, -radius2, -this._Distance).applyMatrix4(this._Matrix)); } return pts; } MoveGripPoints(indexList, vec) { this.WriteAllObjectRecord(); if (indexList[0] === 0) { this.Move(vec); } else if (indexList[0] === 1) this.Target = this.Target.add(vec); else { let pts = this.GetGripPoints(); let p = pts[indexList[0]].add(vec); let pos = this.Position; let dir = this.EndPoint.sub(pos).normalize(); let dir2 = p.sub(pos).normalize(); let angle = dir.angleTo(dir2); if (indexList[0] < 6) { this.Angle = angle; this.InnerConeAngle = Math.min(this.InnerConeAngle, MathUtils.radToDeg(angle)); } else { this.InnerConeAngle = MathUtils.radToDeg(angle); this.Angle = Math.max(angle, this.Angle); } this.Update(); } } GetStretchPoints() { return this.GetGripPoints(); } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); if (indexList.length === 1) return this.MoveGripPoints(indexList, vec); else if (indexList.length === 7) this.Move(vec); else if (indexList.length > 1) { if (indexList.indexOf(0) === -1) this.MoveGripPoints([1], vec); else this.Move(vec); } } GetObjectSnapPoints(snapMode, pickPoint, lastPoint) { switch (snapMode) { case ObjectSnapMode.End: return this.GetGripPoints(); } return []; } InitDrawObject(renderType = RenderType.Wireframe) { // if (renderType !== RenderType.Physical) return; let group = new Group(); let light = new SpotLight$1(this._LightColor, this.WebIntensity, this._Distance, this._Angle, this._Penumbra, this._Decay); light.position.set(0, 0, 0); light.updateMatrix(); light.target.position.set(0, 0, -1); light.target.updateMatrix(); light.add(light.target); Object.defineProperty(light, "castShadow", { get: () => HostApplicationServices.isShowLightShadow && this.CaseShadow }); light.shadow.camera.matrixAutoUpdate = true; light.shadow.camera.far = this._Distance; group.add(light); //灯光 //灯光圆锥 let con = new Mesh(new ConeGeometry(50, 80, 30, 1), new MeshBasicMaterial({ color: this._LightColor })); con.rotation.x = Math.PI * 0.5; con.position.z = -40; con.castShadow = false; con.updateMatrix(); group.add(con); //椎体 this.UpdateDrawObject(renderType, group); return group; } UpdateDrawObject(type, en) { // if (type !== RenderType.Physical) return; let [light, con, helper] = en.children; super.UpdateDrawObject(type, light); light.distance = this._Distance; light.decay = this._Decay; light.angle = this._Angle; light.penumbra = this._Penumbra; con.material.color.copy(this.Color); if (this._ShowHelper) { if (!helper) { helper = new SpotLightHelper(this); en.add(helper); } else helper.visible = true; helper.update(); } else if (helper) helper.visible = this._ShowHelper; } _ReadFile(file) { super._ReadFile(file); let ver = file.Read(); this._Distance = file.Read(); this._Decay = file.Read(); this._Angle = file.Read(); this._Penumbra = file.Read(); let target = new Vector3(); target.fromArray(file.Read()); if (ver < 3) this.Target = target; if (ver > 1) { this.InnerConeAngle = file.Read(); this.OuterConeAngle = file.Read(); this.SourceRadius = file.Read(); this.SoftSourceRadius = file.Read(); this.SourceLength = file.Read(); this.AttenuationRadius = file.Read(); } } WriteFile(file) { super.WriteFile(file); file.Write(3); file.Write(this._Distance); file.Write(this._Decay); file.Write(this._Angle); file.Write(this._Penumbra); file.Write([0, 0, 0]); //ver2 file.Write(this.InnerConeAngle); file.Write(this.OuterConeAngle); file.Write(this.SourceRadius); file.Write(this.SoftSourceRadius); file.Write(this.SourceLength); file.Write(this.AttenuationRadius); } }; __decorate([ AutoRecord ], SpotLight.prototype, "InnerConeAngle", void 0); __decorate([ AutoRecord ], SpotLight.prototype, "OuterConeAngle", void 0); __decorate([ AutoRecord ], SpotLight.prototype, "SourceRadius", void 0); __decorate([ AutoRecord ], SpotLight.prototype, "SoftSourceRadius", void 0); __decorate([ AutoRecord ], SpotLight.prototype, "SourceLength", void 0); __decorate([ AutoRecord ], SpotLight.prototype, "AttenuationRadius", void 0); SpotLight = __decorate([ Factory ], SpotLight); /** * 加工组信息设置. * 为了保证加工组的信息得到正确的添加和删除. * 请保证更新Entity的加工组数据,并更新ProcessingGroup的数据. */ let ProcessingGroupRecord = class ProcessingGroupRecord extends SymbolTableRecord { constructor() { super(); this.Objects = []; } get Name() { return this.name; } set Name(name) { if (name !== this.name) { this.WriteAllObjectRecord(); this.name = name; } } Purge() { arrayRemoveIf(this.Objects, id => !id || id.IsErase); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); let count = file.Read(); this.Objects.length = 0; for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.Objects.push(id); } } //对象将自身数据写入到文件 WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.Objects.length); for (let id of this.Objects) file.WriteObjectId(id); } }; __decorate([ AutoRecord ], ProcessingGroupRecord.prototype, "Objects", void 0); ProcessingGroupRecord = __decorate([ Factory ], ProcessingGroupRecord); var BulkheadCeiling_ContourType; (function (BulkheadCeiling_ContourType) { BulkheadCeiling_ContourType[BulkheadCeiling_ContourType["Hole"] = 0] = "Hole"; BulkheadCeiling_ContourType[BulkheadCeiling_ContourType["Land"] = 1] = "Land"; BulkheadCeiling_ContourType[BulkheadCeiling_ContourType["Sweep"] = 2] = "Sweep"; })(BulkheadCeiling_ContourType || (BulkheadCeiling_ContourType = {})); /** * 吊顶(灯带) */ let BulkheadCeiling = class BulkheadCeiling extends RoomBase { constructor() { super(); this._ContourData = []; this._GemoIdMap = new Map(); this._Height = 0; //吊顶高度 this._LineGeometry = []; this._MeshGeometry = []; } get ContourData() { return this._ContourData; } set ContourData(value) { this.WriteAllObjectRecord(); this._ContourData = value; this.Update(); } get Height() { return this._Height; } set Height(value) { this.WriteAllObjectRecord(); if (!equaln$1(value, this._Height)) { this._Height = value; this.Update(); } } get MeshMaterial() { const mtl = this._MaterialId; if (!mtl?.IsErase && mtl?.Object) return mtl.Object.Material; return HostApplicationServices.DefaultBulkheadCeilingMaterial; } get EdgeGeometrys() { if (!this._EdgeGeometrys) this.MeshGeometry; return this._EdgeGeometrys; } get LineGeometry() { if (this._LineGeometry.length !== 0) return this._LineGeometry; this.MeshGeometry; return this._LineGeometry; } get FaceGeometry() { if (this._FaceGeometry) return this._FaceGeometry; this.MeshGeometry; return this._FaceGeometry; } get MeshGeometry() { if (this._MeshGeometry.length !== 0) return this._MeshGeometry; //绘制底面start //#region //分析包含关系 let contours = []; let contour_data_Map = new Map(); for (let con_data of this._ContourData) { if (!con_data.Path?.IsClose || con_data.ContourType === BulkheadCeiling_ContourType.Sweep) continue; let contour = Contour.CreateContour(con_data.Path); if (!contour) continue; let contourNode = new ContourTreeNode(contour); contours.push(contourNode); contour_data_Map.set(contourNode, con_data); } ContourTreeNode.ParseContourTree(contours, true); //#endregion let faceGeoms = []; this._EdgeGeometrys = []; this._GemoIdMap.clear(); for (let contourNode of contours) { let con_data = contour_data_Map.get(contourNode); if (con_data.ContourType === BulkheadCeiling_ContourType.Land) //Land 岛 { //#region 绘制平面 if (contourNode.parent && contour_data_Map.get(contourNode.parent).ContourType === BulkheadCeiling_ContourType.Land) //上级也是岛 不需要在绘制平面了 continue; if (contourNode.children.length) con_data.Height = 0; let holes = []; for (let chiNode of contourNode.children) { let chi_con_data = contour_data_Map.get(chiNode); if (chi_con_data.ContourType === BulkheadCeiling_ContourType.Hole) { holes.push(chiNode.contour.Clone()); let height = chi_con_data.Height; if (height > con_data.Height) con_data.Height = height; } } let shape = new Shape(contourNode.contour, holes); let geom = new ShapeBufferGeometry(shape.Shape, 30); //应用高度 let mat = shape.Outline.Curve.OCS.multiply(shape.Outline.Curve.OCS.setPosition(new Vector3(0, 0, -con_data.Height))); geom.applyMatrix4(mat); { const indices = Array.from(geom.getIndex().array); // 翻转面片索引的顺序 const flippedIndices = []; for (let i = 0; i < indices.length; i += 3) flippedIndices.push(indices[i], indices[i + 2], indices[i + 1]); // 创建新的面片索引属性 const newIndices = new BufferAttribute(new Uint16Array(flippedIndices), 1); // 更新面片索引属性 geom.setIndex(newIndices); //翻转法线 for (let i = 0; i < geom.attributes.normal.array.length; i++) //@ts-ignore geom.attributes.normal.array[i] = -geom.attributes.normal.array[i]; } faceGeoms.push(geom); //#endregion } } //绘制底面end //Sweep let i = 0; for (; i < this._ContourData.length; i++) { let contour = this._ContourData[i].Contour; let path = this._ContourData[i].Path.Clone(); if (contour) { //范样上下偏移 let mat = this.OCS.clone().setPosition(0, 0, this._ContourData[i].Deviation - this._ContourData[i].Height); path.ApplyMatrix(mat); if (this._ContourData[i].ContourType === BulkheadCeiling_ContourType.Hole) path = path.Reverse(); let sweepGeo = new SweepGeometry(contour, path, this._ContourData[i].ShapeMaterialSlotData); this._GemoIdMap.set(sweepGeo.id, i); this._MeshGeometry.push(sweepGeo); //line geo this.GetLineGeometry(sweepGeo.edgePts); //edge geo let edgeGeom = new BufferGeometry().setAttribute('position', new Float32BufferAttribute(sweepGeo.edgePts, 3)); this._EdgeGeometrys.push(edgeGeom); sweepGeo.edgePts = undefined; } else { //给岛增加围栏 20 * height if (this._ContourData[i].ContourType === BulkheadCeiling_ContourType.Land && i !== 0) { let height = this._ContourData[i].Height; if (equaln$1(height, 0)) continue; let mat = this.OCS.clone().setPosition(0, 0, -height); path.ApplyMatrix(mat); const lineData = [ { bul: 0, pt: new Vector2$1(0, 0) }, { bul: 0, pt: new Vector2$1(-20, 0) }, { bul: 0, pt: new Vector2$1(-20, height) }, { bul: 0, pt: new Vector2$1(0, height) }, { bul: 0, pt: new Vector2$1(0, 0) } ]; let contour = new Polyline(lineData); let sweepGeo = new SweepGeometry(contour, path, [undefined]); this._GemoIdMap.set(sweepGeo.id, i); this._MeshGeometry.push(sweepGeo); } } } this._FaceGeometry = BufferGeometryUtils.MergeBufferGeometries(faceGeoms); this._GemoIdMap.set(this._FaceGeometry.id, i); return this._MeshGeometry; } get MeshGeometry2() { this.MeshGeometry; //TODO: 避免重复构造 let geos = [this._FaceGeometry]; if (!this._FaceGeometry.index) geos = []; for (let geo of this._MeshGeometry) { let buff = new BufferGeometry().fromGeometry(geo); const indexs = []; for (let i = 0; i < buff.attributes.position.count; i += 3) indexs.push(i, i + 1, i + 2); buff.setIndex(indexs); geos.push(buff); } let geo = BufferGeometryUtils.MergeBufferGeometries(geos, true); return geo; } get Materials2() { let materials = [this.MeshMaterial]; //所有的材质 for (let cd of this.ContourData) { for (let i = 0; i < cd.Materials.length; i++) { let mtl = cd.OverWriteMaterial?.get(i) ?? cd.Materials[i]; materials.push(mtl?.Object?.Material ?? this.MeshMaterial); } } return materials; } GetLineGeometry(pts) { let lineGeo = new LineGeometry(); let lineSegments = new Float32Array(pts); var instanceBuffer = new InstancedInterleavedBuffer(lineSegments, 6, 1); lineGeo.setAttribute('instanceStart', new InterleavedBufferAttribute(instanceBuffer, 3, 0)); lineGeo.setAttribute('instanceEnd', new InterleavedBufferAttribute(instanceBuffer, 3, 3)); this._LineGeometry.push(lineGeo); } InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); this.UpdateDrawObject(renderType, obj); return obj; } /** * 重载:更新绘制的实体 * @param {RenderType} renderType * @param {Object3D} obj */ UpdateDrawObject(renderType, obj) { DisposeThreeObj(obj); Object3DRemoveAll(obj); if (renderType === RenderType.Wireframe || renderType === RenderType.Jig) { for (let edgeGeo of this.EdgeGeometrys) obj.add(new LineSegments(edgeGeo, ColorMaterial.GetLineMaterial(this.ColorIndex))); } else if (renderType === RenderType.Conceptual) { let material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); obj.add(new Mesh(this.FaceGeometry, material)); for (let meshGeo of this.MeshGeometry) { obj.add(new Mesh(meshGeo, material)); } for (let edgeGeo of this.EdgeGeometrys) obj.add(new LineSegments(edgeGeo, ColorMaterial.GetLineMaterial(this.ColorIndex))); } else if (renderType === RenderType.Physical) { // 单一绘制对象 TODO:因为没有实现材质槽 所以暂时不用这个 // obj.add(new Mesh(this.MeshGeomtry2, this.Materials2)); obj.add(new Mesh(this.FaceGeometry, this.MeshMaterial)); for (let meshGeo of this.MeshGeometry) obj.add(new Mesh(meshGeo, this.GetMaterials(this._GemoIdMap.get(meshGeo.id)))); } else if (renderType === RenderType.Physical2) { obj.add(new Mesh(this.FaceGeometry, this.MeshMaterial)); for (let meshGeo of this.MeshGeometry) obj.add(new Mesh(meshGeo, this.GetMaterials(this._GemoIdMap.get(meshGeo.id)))); for (let edgeGeo of this.EdgeGeometrys) obj.add(new LineSegments(edgeGeo, ColorMaterial.GetPhysical2EdgeMaterial())); } else if (renderType === RenderType.Print) { for (let lineGeo of this.LineGeometry) obj.add(new Line2(lineGeo, ColorMaterial.PrintLineMatrial)); } return obj; } SetMaterial(mtl, intersection) { let id = intersection.object["geometry"]?.id; if (id) { this.WriteAllObjectRecord(); let contourIndex = this._GemoIdMap.get(id); if (contourIndex === undefined) this.Material = mtl; else { //因为这个索引是底部面的索引 if (contourIndex === this._ContourData.length) { this.Material = mtl; } else { let contourData = this._ContourData[contourIndex]; if (!contourData.OverWriteMaterial) contourData.OverWriteMaterial = new Map; contourData.OverWriteMaterial.set(intersection.face.materialIndex, mtl); } } this.Update(); } } //获取Sweep实体的材质列表 GetMaterials(index) { let contourData = this._ContourData[index]; return contourData.Materials.map((mtl, index) => { return contourData.OverWriteMaterial?.get(index)?.Object?.Material ?? mtl?.Object?.Material ?? this.MeshMaterial; }); } SetDefaultMaterialAtAllSlot() { this.WriteAllObjectRecord(); for (let data of this.ContourData) { //材质槽要默认添加 undefined data.Materials = [undefined]; data.ShapeMaterialSlotData = []; if (data.OverWriteMaterial) data.OverWriteMaterial = undefined; } this.Update(UpdateDraw.Geometry); } UpdateDrawObjectMaterial(renderType, obj, material) { if (renderType === RenderType.Physical) obj.traverse(o => { if (o instanceof Mesh) { let index = this._GemoIdMap.get(o.geometry.id); if (index !== undefined) { if (Array.isArray(o.material)) { let materials = this.GetMaterials(index); o.material = materials; } } else o.material = this.MeshMaterial; } }); else if (renderType === RenderType.Conceptual) { let material = ColorMaterial.GetConceptualMaterial(this.ColorIndex); let face = obj.children[0]; // face.material = material; for (let i = 1; i < obj.children.length; i++) { let chiObj = obj.children[i]; if (chiObj instanceof Mesh) chiObj.material = material; else if (chiObj instanceof LineSegments) chiObj.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } } else if (renderType === RenderType.Wireframe) { for (let lineSeg of obj.children) lineSeg.material = ColorMaterial.GetLineMaterial(this.ColorIndex); } else if (renderType === RenderType.Physical2) { const face = obj.children[0]; face.material = this.MeshMaterial; for (let i = 1; i < obj.children.length; i++) { let chiObj = obj.children[i]; if (chiObj instanceof Mesh) { const index = this._GemoIdMap.get(chiObj.geometry.id); if (index !== undefined && Array.isArray(chiObj.material)) { const materials = this.GetMaterials(index); chiObj.material = materials; } } else if (chiObj instanceof LineSegments) chiObj.material = ColorMaterial.GetPhysical2EdgeMaterial(); } } } UpdateDrawGeometry() { for (let gemo of this._MeshGeometry) gemo.dispose(); this._MeshGeometry = []; for (let gemo of this._LineGeometry) gemo.dispose(); this._LineGeometry = []; if (this._FaceGeometry) this._FaceGeometry.dispose(); this._FaceGeometry = undefined; } ClearDraw() { this.UpdateDrawGeometry(); return super.ClearDraw(); } GetGripPoints() { let pts = []; let mat = this.OCS.multiply(this.OCS.setPosition(new Vector3(0, 0, -this._Height))); for (let data of this._ContourData) pts.push(...data.Path.Clone().ApplyMatrix(mat).GetGripPoints()); return pts; } GetStretchPoints() { let pts = []; let lineData = this.ContourData[0].Path.LineData; for (let data of lineData) { let p = AsVector3(data.pt).applyMatrix4(this.ContourData[0].Path.OCS).applyMatrix4(this.OCSNoClone); pts.push(p); } return pts; } MoveStretchPoints(indexList, vec) { this.WriteAllObjectRecord(); let path = this.ContourData[0].Path; path.ApplyMatrix(this.OCS); path.MoveStretchPoints(indexList, vec); path.ApplyMatrix(this.OCSInv); this.Update(); } MoveGripPoints(indexList, moveVec) { this.WriteAllObjectRecord(); for (let i = 0; i < this._ContourData.length; i++) { let path = this._ContourData[i].Path.Clone(); let newIndexlist = []; let dataIndexList = []; let ptLength = path.GetGripPoints().length; for (let index of indexList) { if (ptLength < (index + 1)) newIndexlist.push(index - ptLength); else dataIndexList.push(index); } if (dataIndexList.length !== 0) { path.ApplyMatrix(this.OCS); path.MoveGripPoints(dataIndexList, moveVec); path.ApplyMatrix(this.OCSInv); this.ContourData[i].Path = path; } indexList = newIndexlist; } this.Update(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { let ver = file.Read(); super._ReadFile(file); this.ContourData = []; let contourDataCount = file.Read(); for (let i = 0; i < contourDataCount; i++) { let data = { ContourType: null, Path: null, SweepShapeTempalteId: null, Materials: [], Contour: null, ContourId: null, ShapeMaterialSlotData: [], OverWriteMaterial: null, Deviation: null, Height: null }; data.ContourType = file.Read(); data.Contour = file.ReadObject(); data.ContourId = file.Read(); data.Path = file.ReadObject(); data.SweepShapeTempalteId = file.ReadObjectId(); data.Deviation = ver > 1 ? file.Read() : 0; data.Height = ver > 1 ? file.Read() : 0; //覆盖材质 let overwriteMtlSize = file.Read(); if (overwriteMtlSize) data.OverWriteMaterial = new Map; else data.OverWriteMaterial = undefined; for (let i = 0; i < overwriteMtlSize; i++) { let index = file.Read(); let mtlId = file.ReadObjectId(); data.OverWriteMaterial.set(index, mtlId); } //原始材质 let mtlCount = file.Read(); for (let i = 0; i < mtlCount; i++) data.Materials.push(file.ReadObjectId()); //材质槽 let materialCount = file.Read(); data.ShapeMaterialSlotData = file.ReadArray(materialCount); this.ContourData.push(data); } this.RelativeRoomFlatTop = file.ReadObjectId(); if (ver > 2) this._LockMaterial = file.ReadBool(); else this._LockMaterial = false; } //对象将自身数据写入到文件. WriteFile(file) { file.Write(3); super.WriteFile(file); file.Write(this.ContourData.length); for (let data of this.ContourData) { file.Write(data.ContourType); file.WriteObject(data.Contour); file.Write(data.ContourId); file.WriteObject(data.Path); file.WriteObjectId(data.SweepShapeTempalteId); file.Write(data.Deviation); file.Write(data.Height); //覆盖材质 if (data.OverWriteMaterial) { file.Write(data.OverWriteMaterial.size); for (let [index, mtlId] of data.OverWriteMaterial) { file.Write(index); file.WriteObjectId(mtlId); } } else file.Write(0); //原始材质 file.Write(data.Materials.length); for (let material of data.Materials) file.WriteObjectId(material); //材质槽 file.Write(data.ShapeMaterialSlotData.length); for (let matData of data.ShapeMaterialSlotData) file.Write(matData); } file.WriteObjectId(this.RelativeRoomFlatTop); // ver3 file.WriteBool(this._LockMaterial); } }; __decorate([ AutoRecord ], BulkheadCeiling.prototype, "RelativeRoomFlatTop", void 0); BulkheadCeiling = __decorate([ Factory ], BulkheadCeiling); const TempP = new Vector3; /** * 直线洞 2点(暂时不要用这个 全部使用RoomHolePolyline) */ let RoomHoleLine = class RoomHoleLine extends RoomHoleBase { //虽然使用了三维的点,但是我们实际使用的是二维的点 z总是等于0 constructor(_StartPoint = new Vector3, _EndPoint = new Vector3) { super(); this._StartPoint = _StartPoint; this._EndPoint = _EndPoint; } get StartPoint() { return this._StartPoint.clone().applyMatrix4(this.OCSNoClone); } get EndPoint() { return this._EndPoint.clone().applyMatrix4(this.OCSNoClone); } set StartPoint(p) { p = TempP.copy(p).applyMatrix4(this.OCSInv).setZ(0); if (!equalv3(p, this._StartPoint, 1e-4)) { this.WriteAllObjectRecord(); this._StartPoint.copy(p); this.Update(); } } set EndPoint(p) { p = TempP.copy(p).applyMatrix4(this.OCSInv).setZ(0); if (!equalv3(p, this._EndPoint, 1e-4)) { this.WriteAllObjectRecord(); this._EndPoint.copy(p); this.Update(); } } //#region Draw InitDrawObject(renderType = RenderType.Wireframe) { let obj = new Object3D(); return obj; } UpdateDrawObject(type, obj) { } //#endregion //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this._StartPoint.set(file.Read(), file.Read(), 0); this._EndPoint.set(file.Read(), file.Read(), 0); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this._StartPoint.x); file.Write(this._StartPoint.y); file.Write(this._EndPoint.x); file.Write(this._EndPoint.y); } //局部撤销 ApplyPartialUndo(undoData) { super.ApplyPartialUndo(undoData); } }; RoomHoleLine = __decorate([ Factory ], RoomHoleLine); applyMixins(RoomHoleLine, Line); let BlockTable = class BlockTable extends SymbolTable { }; BlockTable = __decorate([ Factory ], BlockTable); var ThicknessDirection; (function (ThicknessDirection) { ThicknessDirection[ThicknessDirection["Center"] = 0] = "Center"; ThicknessDirection[ThicknessDirection["Back"] = 1] = "Back"; ThicknessDirection[ThicknessDirection["Front"] = 2] = "Front"; })(ThicknessDirection || (ThicknessDirection = {})); /** * 模版动作 */ let TempateThicknessAction = class TempateThicknessAction extends TemplateAction { constructor() { super(...arguments); this.EntityDirectionMap = new Map(); } _Update(paramDiff, newValue) { for (let [id, d] of this.EntityDirectionMap) { if (!id || id.IsErase) continue; let br = id.Object; br.Clear2DPathCache(); br.Thickness += paramDiff; if (d.Direction === ThicknessDirection.Back) br.Position = br.Position.sub(br.Normal.multiplyScalar(paramDiff)); else if (d.Direction === ThicknessDirection.Center) br.Position = br.Position.sub(br.Normal.multiplyScalar(paramDiff * 0.5)); for (let a of d.Actions) { a.parent = this.parent; a.Update(paramDiff, newValue); } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); let count = file.Read(); this.EntityDirectionMap.clear(); for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); let direction = file.Read(); let actionsCount = file.Read(); let actions = []; for (let j = 0; j < actionsCount; j++) { actions.push(file.ReadObject()); } this.EntityDirectionMap.set(id, { Direction: direction, Actions: actions }); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.EntityDirectionMap.size); for (let [id, d] of this.EntityDirectionMap) { file.WriteObjectId(id); file.Write(d.Direction); file.Write(d.Actions.length); for (let a of d.Actions) file.WriteObject(a); } } }; TempateThicknessAction = __decorate([ Factory ], TempateThicknessAction); let TemplateMoveAction = class TemplateMoveAction extends TemplateAction { constructor(StretchDirection = new Vector3, MoveEntitys = []) { super(); this.StretchDirection = StretchDirection; this.MoveEntitys = MoveEntitys; } _Update(paramDiff) { if (!this._CacheMoveVector) this._CacheMoveVector = new Vector3(); this._CacheMoveVector.copy(this.StretchDirection).multiplyScalar(paramDiff); let moveMatrix = MoveMatrix(this._CacheMoveVector); for (let id of this.MoveEntitys) { if (id?.Object) { let ent = id.Object; ent.ApplyMatrix(moveMatrix); } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.StretchDirection.fromArray(file.Read()); this.MoveEntitys.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadObjectId(); if (id) this.MoveEntitys.push(id); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.StretchDirection.toArray()); file.Write(this.MoveEntitys.length); for (let ent of this.MoveEntitys) file.WriteObjectId(ent); } }; TemplateMoveAction = __decorate([ Factory ], TemplateMoveAction); /** * 设置封边动作 */ let TemplateSetSealAction = class TemplateSetSealAction extends TemplateAction { constructor() { super(...arguments); this.EntitySealIndexsMap = []; } _Update(diff, newV) { for (let { entity, indexs } of this.EntitySealIndexsMap) { if (entity?.Object && !entity.IsErase) { let br = entity.Object; let highSealCurves = GetBoardSealingCurves(br, true); let highseals = GetBoardHighSeal(br, highSealCurves); for (let i of indexs) { if (highseals[i]) highseals[i].size = newV; } SetBoardTopDownLeftRightSealData(br, highseals, highSealCurves); br.BoardProcessOption.highSealed = highseals; } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.EntitySealIndexsMap.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let entity = file.ReadObjectId(); let indexs = file.Read(); if (entity) this.EntitySealIndexsMap.push({ entity, indexs }); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.EntitySealIndexsMap.length); for (let d of this.EntitySealIndexsMap) { file.WriteObjectId(d.entity); file.Write(arrayClone(d.indexs)); } } }; TemplateSetSealAction = __decorate([ Factory ], TemplateSetSealAction); /** * Stretch夹点动作 */ let TemplateStretchGripAction = class TemplateStretchGripAction extends TemplateMoveAction { constructor() { super(...arguments); this.EntityStretchPointMap = []; } _Update(dist) { super._Update(dist); for (let { entity, indexs } of this.EntityStretchPointMap) { if (entity?.Object) { let ent = entity.Object; ent.MoveStretchPoints(indexs, this._CacheMoveVector); } } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.EntityStretchPointMap.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let entity = file.ReadObjectId(); let indexs = file.Read(); if (entity) this.EntityStretchPointMap.push({ entity, indexs }); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.EntityStretchPointMap.length); for (let d of this.EntityStretchPointMap) { file.WriteObjectId(d.entity); file.Write(arrayClone(d.indexs)); } } }; TemplateStretchGripAction = __decorate([ Factory ], TemplateStretchGripAction); /** * 拽拖比例盒子动作 */ let TemplateStretchScaleBoxAction = class TemplateStretchScaleBoxAction extends TemplateMoveAction { /** * 正常不会直接修改下面的2个属性,如果真的需要修改,请调用 `this.WriteAllObjectRecord`; * 避免历史记录没有记录更新. */ constructor(StretchDirection = new Vector3(), EntityStretchData = []) { super(); this.StretchDirection = StretchDirection; this.EntityStretchData = EntityStretchData; } _Update(dist) { super._Update(dist); for (let { entity, scaleBox } of this.EntityStretchData) { if (!entity || entity.IsErase) continue; let ent = entity.Object; let pts = ent.GetStretchPoints(); let ocsInv = ent.OCSInv; let entityBox = ent.BoundingBoxInOCS; let size = entityBox.getSize(new Vector3()); scaleBox = scaleBox.clone(); scaleBox.min.multiply(size).sub(new Vector3(1, 1, 1)); scaleBox.max.multiply(size).add(new Vector3(1, 1, 1)); let stretchIndexs = []; for (let i = 0, length = pts.length; i < length; i++) { let p = pts[i]; p.applyMatrix4(ocsInv); if (scaleBox.containsPoint(p)) stretchIndexs.push(i); } ent.MoveStretchPoints(stretchIndexs, this._CacheMoveVector); } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.EntityStretchData.length = 0; let count = file.Read(); for (let i = 0; i < count; i++) { let entity = file.ReadObjectId(); let min = new Vector3().fromArray(file.Read()); let max = new Vector3().fromArray(file.Read()); this.EntityStretchData.push({ entity, scaleBox: new Box3(min, max) }); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.EntityStretchData.length); for (let d of this.EntityStretchData) { file.WriteObjectId(d.entity); file.Write(d.scaleBox.min.toArray()); file.Write(d.scaleBox.max.toArray()); } } }; TemplateStretchScaleBoxAction = __decorate([ Factory ], TemplateStretchScaleBoxAction); /** * 拽拖固定大小盒子动作(弃用) */ /* class TemplateStretchSizeBoxAction extends TemplateAction { StretchDirection: Vector3; EntityStretchPointMap: { entity: Entity, sizeBox: Box3 }[]; } */ function r2b(radius, dist, isBigArc = false) { let r = Math.abs(radius); let hlafDist = dist * 0.5; let bul = Math.tan(0.5 * Math.asin(hlafDist / r)); if (isBigArc) { let a = bul * hlafDist; bul = (r * 2 - a) / hlafDist; } bul *= Math.sign(radius); return bul; } var TempalteActionRadiusType; (function (TempalteActionRadiusType) { TempalteActionRadiusType[TempalteActionRadiusType["Radius"] = 0] = "Radius"; TempalteActionRadiusType[TempalteActionRadiusType["Bulge"] = 1] = "Bulge"; TempalteActionRadiusType[TempalteActionRadiusType["Height"] = 2] = "Height"; })(TempalteActionRadiusType || (TempalteActionRadiusType = {})); let TemplateaRadiusAction = class TemplateaRadiusAction extends TemplateFilletAction { constructor(radiusType) { super(); this.IsBigArc = false; //是大圆弧(仅在半径模式下生效) this.RadiusType = TempalteActionRadiusType.Radius; //半径类型 this.RadiusType = radiusType; } //修改半径(这里不是倒角了) Fillet(br, newValue, d) { let cu = br instanceof ExtrudeSolid ? br.ContourCurve : br.path; if (cu instanceof Circle) return; let radius = Math.sign(newValue) * Math.max(Math.abs(newValue), 0.1); let cuOld = cu; for (let arcParam of d.ArcParams) { if (this.RadiusType === TempalteActionRadiusType.Bulge) { cu = cu.Clone(); cu.LineData[Math.floor(arcParam)].bul = radius; } else { let p1 = cu.GetPointAtParam(Math.floor(arcParam)); const FixIndex = (index, count, isClose = true) => { if (index < 0) return count + index; //没有闭合的曲线最后一个索引不变回原点 else if (isClose && index >= count || !isClose && index > count) return index - count; else return index; }; let p2 = cu.GetPointAtParam(FixIndex(Math.floor(arcParam) + 1, cu.EndParam, cu.IsClose)); let dist = p1.distanceTo(p2); let bul; if (this.RadiusType === TempalteActionRadiusType.Radius) bul = r2b(radius, dist, this.IsBigArc); else //高度模式 bul = radius * 2 / dist; if (!isNaN(bul)) { cu = cu.Clone(); cu.LineData[Math.floor(arcParam)].bul = bul; } } } if (br instanceof ExtrudeSolid) { if (cu !== cuOld) br.ContourCurve = cu; } else { if (cu !== cuOld) br.path = cu; } } //#region -------------------------File------------------------- //对象应该实现dataIn和DataOut的方法,为了对象的序列化和反序列化 //对象从文件中读取数据,初始化自身 ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this.IsBigArc = file.Read(); if (ver < 4) { this.RadiusType = TempalteActionRadiusType.Radius; file.Read(); } else { this.RadiusType = file.Read(); } } //对象将自身数据写入到文件. WriteFile(file) { file.Write(4); super.WriteFile(file); file.Write(this.IsBigArc); file.Write(this.RadiusType); } }; __decorate([ AutoRecord ], TemplateaRadiusAction.prototype, "IsBigArc", void 0); __decorate([ AutoRecord ], TemplateaRadiusAction.prototype, "RadiusType", void 0); TemplateaRadiusAction = __decorate([ Factory ], TemplateaRadiusAction); let PositioningBoardSpace = class PositioningBoardSpace extends Positioning { /** * 定位 */ async Positioning() { this.SpaceCS = undefined; this.SpaceSize = undefined; if (this.ObjectId && !this.ObjectId.IsErase) { let ent = this.ObjectId.Object; this.SpaceCS = ent.OCS; this.SpaceSize = new Vector3(ent.Width, ent.Height, ent.Thickness); } } //#region File ReadFile(file) { file.Read(); this.ObjectId = file.ReadObjectId(); } WriteFile(file) { file.Write(1); file.WriteObjectId(this.ObjectId); } }; __decorate([ AutoRecord ], PositioningBoardSpace.prototype, "ObjectId", void 0); PositioningBoardSpace = __decorate([ Factory ], PositioningBoardSpace); /** * 拉手的定位空间 */ let PositioningHandleSpace = class PositioningHandleSpace extends Positioning { constructor(_ObjectId) { super(); this.ObjectId = _ObjectId; } /** * 定位 */ async Positioning() { this.SpaceCS = undefined; this.SpaceSize = undefined; if (this.ObjectId && !this.ObjectId.IsErase) { let ent = this.ObjectId.Object; this.SpaceCS = ent.SpaceOCS; let box = ent.GetBoundingBoxInMtx(ent.SpaceOCSInv); this.SpaceSize = box.getSize(new Vector3); let baseP = box.min.applyMatrix4(this.SpaceCS); this.SpaceCS.setPosition(baseP); } } //#region File ReadFile(file) { file.Read(); this.ObjectId = file.ReadObjectId(); } WriteFile(file) { file.Write(1); file.WriteObjectId(this.ObjectId); } }; __decorate([ AutoRecord ], PositioningHandleSpace.prototype, "ObjectId", void 0); PositioningHandleSpace = __decorate([ Factory ], PositioningHandleSpace); /** * 缺口(柱子、梁)柜内空间绘制层立板自动按缺口尺寸绘制出异形板件0 * 切割凸出部分 * * @param {Entity} knifeBrs 切割的刀 * @param {Board[]} brs */ function CuttingProtrudingPart(knifeBrs, brs) { // 获取包围框里的实体 let intersectsEnt = []; for (let entId of knifeBrs) { let ent = entId?.Object; if (ent && !ent.IsErase) intersectsEnt.push(ent); } // 获取包围框里的实体与需要绘制实体碰撞的实体 let brKfs = intersectsEnt.filter((ent) => ent instanceof Board); // 处理碰撞的复合实体 let hardwareEnt = intersectsEnt.filter((ent) => ent instanceof HardwareCompositeEntity); let hardwareKfs = []; for (let h of hardwareEnt) { let ens = h.GetAllEntity(true, e => e instanceof ExtrudeSolid || e instanceof ExtrudeHole); for (let e of ens) { if (e instanceof ExtrudeHole) hardwareKfs.push(e.Convert2ExtrudeSolid()); else hardwareKfs.push(e); } } let newBrs = []; //直接切掉 for (let br of brs) { let splitBrs = []; br.Subtract([...hardwareKfs, ...brKfs], splitBrs); splitBrs.push(br); splitBrs.sort((b1, b2) => b2.Volume - b1.Volume); //切割后留下大块的 newBrs.push(splitBrs[0]); } return newBrs; } /** * 板件模板的基类.(层板,立板,背板) */ let TemplateBoardRecord = class TemplateBoardRecord extends TemplateRecord { constructor() { super(...arguments); //板件初始化的时候需要设置周围板件的数据,通常只有一次 this.UseBoardProcessOption = false; this.DrawBoardCount = 1; } // InitBaseParams() // { // super.InitBaseParams(); // return this; // } get Option() { return Object.assign({}, this._option); } set Option(option) { this.WriteAllObjectRecord(); Object.assign(this._option, option); ExtendsBoardThickness(this, option.thickness); } set GrooveOption(option) { } async GeneralBoardList(space) { return []; } Purge() { super.Purge(); if (this.Objects.length === 0) this.IsClear = true; } async Update() { await super.Update(); if (this.IsClear) return; let thickness = this.GetParam("BH")?.value; if (thickness) { this._option.thickness = thickness; } let nbrs = await this.GeneralBoardList(this.SpaceParse); let sbrs = this.PositioningSupportBoards; if (this.BoardProcessOption) { for (let br of nbrs) br.BoardProcessOption = this.BoardProcessOption; this.BoardProcessOption = undefined; } if (this.UseBoardProcessOption && sbrs.length > 0) { let cname = sbrs[0].BoardProcessOption[EBoardKeyList.CabinetName]; let rname = sbrs[0].BoardProcessOption[EBoardKeyList.RoomName]; for (let br of nbrs) { br.BoardProcessOption[EBoardKeyList.CabinetName] = cname; br.BoardProcessOption[EBoardKeyList.RoomName] = rname; br.BoardProcessOption[EBoardKeyList.Mat] = sbrs[0].BoardProcessOption[EBoardKeyList.Mat]; br.BoardProcessOption[EBoardKeyList.BrMat] = sbrs[0].BoardProcessOption[EBoardKeyList.BrMat]; br.BoardProcessOption[EBoardKeyList.Color] = sbrs[0].BoardProcessOption[EBoardKeyList.Color]; } this.UseBoardProcessOption = false; } //记录按缺口切割前的板轮廓 const OldContour = nbrs[0]?.ContourCurve?.Clone(); //是否是被缺口切割 const IsIntersectSpaceEntitys = !!this.IntersectSpaceEntitys?.size; if (IsIntersectSpaceEntitys && nbrs?.length) { if (!this.IntersectHighDrill?.length) this.IntersectHighDrill = nbrs[0].BoardProcessOption.highDrill; nbrs = CuttingProtrudingPart(this.IntersectSpaceEntitys, nbrs); } //继承refBr的BoardProcessOption/IsChaiDan/RelevanceMeats属性 let refBr; //记录生成的异型缺口板高级排钻列表 const HighDrill = []; if (this.Objects.length > 0) refBr = this.Objects[0].Object; for (let i = nbrs.length; i < this.Objects.length; i++) this.Objects[i].Object.Erase(); for (let i = 0; i < nbrs.length; i++) { let br; if (i < this.Objects.length) { br = this.Objects[i].Object; if (i >= this.DrawBoardCount) br.Erase(false); //缺口切割旧板 使用切割后的的轮廓信息 if (IsIntersectSpaceEntitys) br.ContourCurve = nbrs[i].ContourCurve.Clone(); br.Position = nbrs[i].Position; br.Width = nbrs[i].Width; br.Height = nbrs[i].Height; br.Thickness = nbrs[i].Thickness; // br.KnifeRadius = nbrs[i].KnifeRadius; // br.GroovesAddDepth = nbrs[i].GroovesAddDepth; // br.GroovesAddLength = nbrs[i].GroovesAddLength; // br.GroovesAddWidth = nbrs[i].GroovesAddWidth; } else { br = nbrs[i]; if (refBr) { //新生成的缺口切割板 不使用原有的轮廓信息 if (!IsIntersectSpaceEntitys) br.ContourCurve = refBr.ContourCurve.Clone(); br.BoardProcessOption = refBr.BoardProcessOption; br.IsChaiDan = refBr.IsChaiDan; br.RelevanceMeats = refBr.RelevanceMeats; } this._db.ModelSpace.Append(br); this.Objects.push(br.Id); //新创建的板 关联切割 for (let meat of br.RelevanceMeats) { let meatBr = meat.Object; if (!meatBr.RelevanceKnifs.includes(br.objectId)) meatBr.RelevanceKnifs.push(br.objectId); } } //缺口生成的异型板 分析排钻 if (IsIntersectSpaceEntitys && !br.IsRect) { if (!HighDrill.length) { let indexMap = []; let newContour = br.ContourCurve; for (let i = 0; i < newContour.EndParam; i++) { let p = newContour.GetPointAtParam(i + 0.5); let cp = OldContour.GetClosestPointTo(p, false); let cparam = OldContour.GetParamAtPoint2(cp); indexMap.push(Math.floor(cparam)); } for (let index of indexMap) HighDrill.push(this.IntersectHighDrill[index]); } br.BoardProcessOption.highDrill = HighDrill; } } this.DrawBoardCount = nbrs.length; //保持SpaceCS for (let id of this.Objects) { if (id && !id.IsErase) id.Object.SpaceOCS = this._CacheSpaceCS; } } ReadFile(file) { file.Read(); super.ReadFile(file); this.DrawBoardCount = file.Read(); } WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.DrawBoardCount); } }; __decorate([ AutoRecord ], TemplateBoardRecord.prototype, "DrawBoardCount", void 0); TemplateBoardRecord = __decorate([ Factory ], TemplateBoardRecord); /** * 背板模板 */ let TemplateBehindBoard = class TemplateBehindBoard extends TemplateBoardRecord { constructor() { super(); this._option = { ...DefaultBehindBoardConfig }; this.grooveoption = { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "0", knifeRadius: "3", }; this.name = "背板(自动)"; } set Grooveoption(option) { this.WriteAllObjectRecord(); Object.assign(this.grooveoption, option); } get Option() { const thickness = this.GetParam("BH")?.value; if (thickness) this._option.exprThickness = thickness.toString(); this._option.exprCount = this._option.count.toString(); return Object.assign({}, this._option); } set Option(option) { super.Option = option; const exprThickness = option.exprThickness; if (exprThickness) { const BHParam = this.GetParam("BH"); if (BHParam) BHParam.expr = exprThickness; } } async GeneralBoardList(space) { const BHParam = this.GetParam("BH"); let expr = BHParam?.expr; if (expr) { if (typeof expr === "string") { if (expr.startsWith("_") || expr.startsWith("$")) this._option.exprThickness = this._option.thickness.toString(); else this._option.exprThickness = expr; } else this._option.exprThickness = expr.toString(); } else this._option.exprThickness = this._option.thickness.toString(); return await BuildBehindBoards(this._option, space, this.grooveoption); } ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this._option.type = file.Read(); this._option.name = file.Read(); this._option.leftExt = file.Read(); this._option.rightExt = file.Read(); this._option.topExt = file.Read(); this._option.bottomExt = file.Read(); this._option.thickness = file.Read(); this._option.boardPosition = file.Read(); this._option.calcHeight = file.Read(); this._option.moveDist = file.Read(); this._option.boardRelative = file.Read(); this._option.spaceSize = file.Read(); this._option.count = file.Read(); this.grooveoption.grooveAddLength = file.Read(); this.grooveoption.grooveAddWidth = file.Read(); this.grooveoption.grooveAddDepth = file.Read(); this.grooveoption.knifeRadius = file.Read(); if (ver === 1) { this._option.calcSpaceSize = this._option.spaceSize.toString(); this._option.calcMoveDist = this._option.moveDist.toString(); } if (ver >= 2) { this._option.calcSpaceSize = file.Read(); this._option.calcMoveDist = file.Read(); } this._option.exprCount = this._option.count.toString(); } WriteFile(file) { file.Write(2); super.WriteFile(file); file.Write(this._option.type); file.Write(this._option.name); file.Write(this._option.leftExt); file.Write(this._option.rightExt); file.Write(this._option.topExt); file.Write(this._option.bottomExt); file.Write(this._option.thickness); file.Write(this._option.boardPosition); file.Write(this._option.calcHeight); file.Write(this._option.moveDist); file.Write(this._option.boardRelative); file.Write(this._option.spaceSize); file.Write(this._option.count); file.Write(this.grooveoption.grooveAddLength); file.Write(this.grooveoption.grooveAddWidth); file.Write(this.grooveoption.grooveAddDepth); file.Write(this.grooveoption.knifeRadius); file.Write(this._option.calcSpaceSize); file.Write(this._option.calcMoveDist); } }; TemplateBehindBoard = __decorate([ Factory ], TemplateBehindBoard); /** * 来用包裹EntityRef 使他们自动成为一个模块 */ let TemplateEntityRef = class TemplateEntityRef extends TemplateRecord { InitBaseParams() { super.InitBaseParams(); if (this.Objects[0]?.Object) { let ent = this.Objects[0]?.Object; let size = ent.CurSize; this.LParam.value = size.x; this.WParam.value = size.y; this.HParam.value = size.z; let scs = ent.OCS; let p = ent.BoundingBoxInOCS.min.applyMatrix4(scs); scs.setPosition(p); ent.SpaceOCS = scs; } return this; } async Update() { await super.Update(); if (this.Objects[0]?.Object) { let ent = this.Objects[0]?.Object; let scs = ent.SpaceOCS; let scaleSize = new Vector3(this.LParam.value, this.WParam.value, this.HParam.value); ent.ScaleSize = scaleSize; //变换到实际的位置 let box = ent.BoundingBoxInOCS; let mtx1 = ent.OCSInv; let mtx2 = new Matrix4().setPosition(box.min.negate()); let mtx3 = this._CacheSpaceCS; mtx1.premultiply(mtx2).premultiply(mtx3); ent.ApplyMatrix(mtx1); ent.SpaceOCS = scs; } } ReadFile(file) { super.ReadFile(file); file.Read(); } WriteFile(file) { super.WriteFile(file); file.Write(1); } }; TemplateEntityRef = __decorate([ Factory ], TemplateEntityRef); class GripScene extends Object3D { constructor() { super(); this._GripMap = new Map(); this._GripMaterial = new PointsMaterial({ size: 15, color: 0x001dfa, sizeAttenuation: false }); } //增加 Append(obj) { if (this._GripMap.has(obj)) return; if (IsEntity(obj)) { let en = GetEntity(obj); let pts = en.GetGripPoints(); if (pts.length > 1000) return; //显示太多的夹点会导致性能过低 let geo = BufferGeometryUtils.CreateFromPts(pts); let ptsObj = new Points(geo, this._GripMaterial); ptsObj.userData.Entity = en; this.add(ptsObj); this._GripMap.set(obj, ptsObj); } } Remove(obj) { let pts = this._GripMap.get(obj); if (pts) { DisposeThreeObj(pts); this.remove(pts); this._GripMap.delete(obj); } } //删除所有. Clear() { for (let [, pts] of this._GripMap) { this.remove(pts); pts.geometry.dispose(); } this._GripMap.clear(); } Update(entity) { this.Remove(entity); this.Append(entity); } UpdateAll() { for (let [obj, ptsObj] of this._GripMap) { if (obj.parent && obj.visible) { let en = GetEntity(obj); let geo = ptsObj.geometry; let pts = en.GetGripPoints(); if (!BufferGeometryUtils.UpdatePts(geo, pts)) { geo.dispose(); ptsObj.geometry = BufferGeometryUtils.CreateFromPts(pts); } } else { this.Remove(obj); } } } } function CheckFilter(obj, filter) { if (!obj.visible || obj instanceof GripScene || IsFreeze(obj)) return false; let ent = GetEntity(obj); // if (!ent) return false; (禁止这个代码(gizmo依赖这个) if (ent?.Freeze && !filter?.selectFreeze) return false; if (!filter) return true; if (filter.filterErase && (!ent || ent.Id === undefined || ent.IsErase)) return false; if (filter.filterFunction && !filter.filterFunction(obj, ent)) return false; if (filter.filterTypes && filter.filterTypes.some(T => ent instanceof T) !== true) return false; return true; } //https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2015/ENU/AutoCAD-AutoLISP/files/GUID-0F37CC5E-1559-4011-B8CF-A3BA0973B2C3-htm.html var SelectType; (function (SelectType) { SelectType[SelectType["None"] = 0] = "None"; SelectType[SelectType["C"] = 1] = "C"; SelectType[SelectType["W"] = 2] = "W"; //Window selection. })(SelectType || (SelectType = {})); new Vector2$1; new Vector2$1; function Raycast(ray, objectCol, filter) { let pick; for (let obj of objectCol) { if (!CheckFilter(obj, filter)) continue; let intersects = []; obj.traverse(o => { //@ts-ignore if (intersects.length === 0 && o.visible && !o.isLine && !o.isPoints) o.raycast(ray, intersects); }); if (intersects.length > 0) { for (let i of intersects) { if (!pick || pick.distance > i.distance || (equaln$1(pick.distance, i.distance) && pick.object.id < i.object.id)) { pick = i; pick.object = obj; } } } } return pick; } class ActivityLayerBoardTool { /** * 求面与板的交点 得到板和交点 * @param face 层板的侧面 * @param objects 其他板 * @param br 层板 * @returns 交点信息(点 实体) */ GetBoardIntersection(face, objects, br) { for (let s of [0.5, 0.1, 0.9]) { let center = new Vector3(face.Length * s, face.Width * s).applyMatrix4(face.OCS).add(face.Normal.negate()); let intersection = this.RayPoint(center, face.Normal, objects); if (intersection && intersection.object) { let en = GetEntity(intersection.object); if (en.BoardProcessOption.roomName === br.BoardProcessOption.roomName && en.BoardProcessOption.cabinetName === br.BoardProcessOption.cabinetName) return intersection; } } } IntersectFace(otherBoard, face) { let size = new Vector3(); let min = new Vector3(); let diffMat = face.OCSInv.multiply(otherBoard.OCS); if (otherBoard.IsSpecialShape) { let rect = new Polyline().RectangleFrom2Pt(new Vector3(), new Vector3(face.Length, face.Width)); let con = otherBoard.ContourCurve.Clone().ApplyMatrix(diffMat); con.Z0(); let shape1 = new Shape(Contour.CreateContour([rect])); let shape2 = new Shape(Contour.CreateContour([con])); let shapes = shape1.IntersectionBoolOperation(shape2); if (shapes.length > 0) { let box = shapes[0].BoundingBox; min.copy(box.min); size = box.getSize(new Vector3); } } else { let b1 = new Box3(new Vector3(), new Vector3(otherBoard.Width, otherBoard.Height)); b1.applyMatrix4(diffMat); let box = new Box2$1().setFromPoints([AsVector2(b1.min), AsVector2(b1.max)]); let box2 = new Box2$1(new Vector2$1(), new Vector2$1(face.Length, face.Width)); box2.intersect(box); size = box2.getSize(new Vector2$1); min.set(box2.min.x, box2.min.y, 0); } face.Length = size.x; face.Width = size.y; face.OCS.multiply(MoveMatrix(min)); } GetShrinkDist(face, br, option) { let fNoarmal = face.Normal; let xVec = new Vector3().setFromMatrixColumn(br.OCS, 0); let yVec = new Vector3().setFromMatrixColumn(br.OCS, 1); let shrink = 0; if (isParallelTo(fNoarmal, xVec)) { if (equalv3(fNoarmal, xVec, 1e-5)) shrink = option.back; else shrink = option.front; } else if (isParallelTo(fNoarmal, yVec)) { if (equalv3(fNoarmal, yVec, 1e-5)) shrink = option.left; else shrink = option.right; } return shrink; } GetShrinkBoardIndexesMap(face, br, shrink, indexMap) { if (shrink) { const FUZZ = 0.1; let scaleBox = new Box3().setFromPoints([new Vector3(-FUZZ, -FUZZ, -FUZZ), new Vector3(face.Length + FUZZ, face.Width + FUZZ, FUZZ)]).applyMatrix4(face.OCS); let pts = br.GetStretchPoints(); let stretchIndexs = []; for (let i = 0, length = pts.length; i < length; i++) { let p = pts[i]; if (scaleBox.containsPoint(p)) stretchIndexs.push(i); } indexMap.set(face.Normal.negate().multiplyScalar(shrink), stretchIndexs); } } GetRuleCount(width, rules) { if (this.NailRules) rules = this.NailRules; for (let rule of rules) { if (width > rule.startDist && width <= rule.endDist + 1e-6) { return rule.count; } } return 0; } BuildNails(initNail, nailOpt, face, fYVec, nailCount) { let fXVec = new Vector3().setFromMatrixColumn(face.OCS, 0); let addCount = nailOpt.addCount; let dist = nailOpt.dist; let frontDist = nailOpt.front; let backDist = nailOpt.behind; let singleDist; //绘制数量为1时,层板钉在中间位置 if (nailCount === 1) singleDist = face.Length / 2 - frontDist; else singleDist = (face.Length - frontDist - backDist) / (nailCount - 1); let buildNails = []; //构建层板钉 for (let i = 0; i < nailCount; i++) { if (nailCount === 1) { initNail.ApplyMatrix(MoveMatrix(fXVec.multiplyScalar(singleDist))); buildNails.push(initNail); } else { let nail = initNail.Clone(); nail.ApplyMatrix(MoveMatrix(fXVec.clone().multiplyScalar(i * singleDist))); buildNails.push(nail); } } //增加的层板钉 let addNails = []; for (let i = 1; i <= addCount; i++) { for (let nail of buildNails) { addNails.push(nail.Clone().ApplyMatrix(MoveMatrix(fYVec.clone().multiplyScalar(dist * i)))); addNails.push(nail.Clone().ApplyMatrix(MoveMatrix(fYVec.clone().negate().multiplyScalar(dist * i)))); } } buildNails.push(...addNails); return buildNails; } RayPoint(p, n, brs) { let ray = new Raycaster(p, n); let intersection = Raycast(ray, brs); return intersection; } Start(brs, nailOption, rules = [], option) { if (brs.length === 0) return; let objects = []; //如果层板和左右板得距离大于这个值,则这边不绘制层板钉 let refDist = nailOption.length - nailOption.depth; for (let br of brs[0].Db.ModelSpace.Entitys) { if (!br.IsErase && br.Visible && br instanceof Board) { objects.push(br.GetDrawObjectFromRenderType(RenderType.Physical)); } } for (let br of brs) { let faces = BoardGetFace.GetAllSidesFaces(br, true); let vecIndexMap = new Map(); let xVec = new Vector3().setFromMatrixColumn(br.OCSNoClone, 0); if (option?.name) br.Name = option.name; let nailBoardMap = new WeakMap(); let allNails = []; for (let face of faces) { if (face.type === BoardFaceType.Side) { let shrink = 0; if (option) //求内缩值 { shrink = this.GetShrinkDist(face, br, option); this.GetShrinkBoardIndexesMap(face, br, shrink, vecIndexMap); } //#region 画层板钉 if (!nailOption.isDraw) continue; if (!nailOption.isInBack && isParallelTo(face.Normal, xVec)) continue; let intersection = this.GetBoardIntersection(face, objects, br); //射线求交 得到板和交点 if (!intersection || (intersection.distance - 1 + shrink >= refDist)) //防止板件悬空 #I1DPHR continue; let otherBoard = GetEntity(intersection.object); if (isParallelTo(otherBoard.Normal, br.Normal)) //层板和层板组成层板钉没有意义 #I7CB67 continue; this.IntersectFace(otherBoard, face); let nail = CylinderHole.CreateCylHole(nailOption.rad, nailOption.length, GangDrillType.Nail); nail.ColorIndex = 4; let fNor = face.Normal; let ang = -angleTo(fNor, otherBoard.Normal.negate()); if (equaln$1(Math.abs(ang), Math.PI, 1e-3)) ang = 0; let xDist = nailOption.front; let yDist = -nailOption.rad - shrink * Math.sin(ang); let zDist = 0; let zRoMat = new Matrix4().makeRotationX(ang); let p = new Vector3().setFromMatrixPosition(face.OCS.clone().multiply(zRoMat)).applyMatrix4(otherBoard.OCSInv); if (p.z < 1e-6) { zDist = -p.z; } else if (p.z > otherBoard.Thickness - 1e-6) { zDist = p.z - otherBoard.Thickness; } else { console.error("不该存在的情况"); } zDist += (-nail.Height + nailOption.depth); nail.Position = nail.Position.add(new Vector3(xDist, yDist, zDist)); nail.ApplyMatrix(zRoMat).ApplyMatrix(face.OCS); //层板钉数 let nailCount = nailOption.count; if (option || this.NailRules) nailCount = this.GetRuleCount(face.Length, rules); let yVec = new Vector3().setFromMatrixColumn(otherBoard.OCS, 1); let nails = this.BuildNails(nail, nailOption, face, yVec, nailCount); for (let nail of nails) nailBoardMap.set(nail, otherBoard); allNails.push(...nails); //#endregion } } this.AppendBoard(br, nailBoardMap, allNails); for (let [vec, indexes] of vecIndexMap) { br.MoveStretchPoints(indexes, vec); vec = null; } } this.NailRules = null; } AppendBoard(br, brNailMap, nails) { let oldNailIds = br.LayerNails.filter(n => n?.Object); this.HandleNailList(nails, oldNailIds, brNailMap); for (let i = nails.length; i < oldNailIds.length; i++) oldNailIds[i]?.Object?.Erase(); for (let i = 0; i < nails.length; i++) { let nId; let otherBoard = brNailMap.get(nails[i]); if (i < oldNailIds.length && oldNailIds[i]) { let nail = oldNailIds[i].Object; if (nail.IsErase) nail.Erase(false); if (!equalv3(nail.Normal, nails[i].Normal)) nail.OCS = nails[i].OCS; nail.Radius = nails[i].Radius; nail.Height = nails[i].Height; nail.Position = nails[i].Position; nId = nail.Id; if (nail.FId !== otherBoard.Id) { nail.FId = otherBoard.Id; otherBoard.AppendNails([nId]); } } else { br.Db.ModelSpace.Append(nails[i]); nId = nails[i].Id; br.AppendNails([nId]); nails[i].MId = br.Id; nails[i].FId = otherBoard.Id; otherBoard.AppendNails([nId]); } } } HandleNailList(newNails, oldNails, naiMap) { if (oldNails.length < newNails.length) { let temp = []; for (let i = 0; i < oldNails.length; i++) { let nail = oldNails[i].Object; if (newNails.length === 0) break; let j = 0; for (; j < newNails.length; j++) { let newNail = newNails[j]; let otherBoard = naiMap.get(newNail); if (otherBoard.Id === nail.FId) break; } if (j < newNails.length) temp.push(...newNails.splice(j, 1)); } newNails.unshift(...temp); } else { let temp = []; for (let i = 0; i < newNails.length; i++) { let nail = newNails[i]; let otherBoard = naiMap.get(nail); let j = 0; for (; j < oldNails.length; j++) { let oldNail = oldNails[j].Object; if (oldNail.FId === otherBoard.Id) break; } if (j < oldNails.length) temp.push(...oldNails.splice(j, 1)); else temp.push(undefined); //不存在可以复用的层板钉,占个位 重新添加 } oldNails.unshift(...temp); } } } const activityLayerBoardTool = new ActivityLayerBoardTool(); /** * 层板模板 */ let TemplateLayerBoard = class TemplateLayerBoard extends TemplateBoardRecord { constructor() { super(); this._option = { ...DefaultLayerBoardConfig }; this._nailOption = { ...DefaultNailOption }; this.grooveOption = { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "0", knifeRadius: "3", }; //空间分析缺口切割需要的板件 this.IntersectSpaceEntitys = new Set(); this.IntersectHighDrill = []; this.name = "层板(自动)"; } get Option() { const thickness = this.GetParam("BH")?.value; if (thickness) this._option.exprThickness = thickness.toString(); this._option.exprCount = this._option.count.toString(); return Object.assign({}, this._option); } set Option(option) { super.Option = option; const exprThickness = option.exprThickness; if (exprThickness) { const BHParam = this.GetParam("BH"); if (BHParam) BHParam.expr = exprThickness; } } set GrooveOption(option) { this.WriteAllObjectRecord(); Object.assign(this.grooveOption, option); } get NailOption() { return { ...this._nailOption }; } set NailOption(nailOpt) { this.WriteAllObjectRecord(); this._nailOption = { ...nailOpt }; } async GeneralBoardList(space) { const BHParam = this.GetParam("BH"); let expr = BHParam?.expr; if (expr) { if (typeof expr === "string") { if (expr.startsWith("_") || expr.startsWith("$")) this._option.exprThickness = this._option.thickness.toString(); else this._option.exprThickness = expr; } else this._option.exprThickness = expr.toString(); } else this._option.exprThickness = this._option.thickness.toString(); return await BuildLayerBoards(this._option, space, this.grooveOption); } async Update() { await super.Update(); let brs = []; for (let id of this.Objects) { if (!id.IsErase) { let b = id.Object; brs.push(b); } } if (this._option.isActive) activityLayerBoardTool.Start(brs, this._nailOption); else { brs.forEach(br => { if (br.LayerNails.length) br.ClearLayerNails(); }); } } ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this._option.type = file.Read(); this._option.name = file.Read(); this._option.frontShrink = file.Read(); this._option.leftShrink = file.Read(); this._option.rightShrink = file.Read(); this._option.calcHeight = file.Read(); this._option.isTotalLength = file.Read(); this._option.boardRelative = file.Read(); this._option.thickness = file.Read(); this._option.count = file.Read(); this._option.spaceSize = file.Read(); this._option.isActive = file.Read(); if (ver > 1) { this._nailOption.isDraw = file.Read(); this._nailOption.addCount = file.Read(); this._nailOption.dist = file.Read(); this._nailOption.isGroup = file.Read(); this._nailOption.isInBack = file.Read(); this._nailOption.front = file.Read(); this._nailOption.behind = file.Read(); this._nailOption.count = file.Read(); this._nailOption.rad = file.Read(); this._nailOption.length = file.Read(); this._nailOption.depth = file.Read(); } if (ver >= 3) { this._option.calcSpaceSize = file.Read(); this._option.calcFrontShrink = file.Read(); this._option.calcLeftShrink = file.Read(); this._option.calcRightShrink = file.Read(); } else { this._option.calcSpaceSize = this._option.spaceSize.toString(); this._option.calcFrontShrink = this._option.frontShrink.toString(); this._option.calcLeftShrink = this._option.leftShrink.toString(); this._option.calcRightShrink = this._option.rightShrink.toString(); } if (ver > 3) { this.grooveOption.grooveAddLength = file.Read(); this.grooveOption.grooveAddWidth = file.Read(); this.grooveOption.grooveAddDepth = file.Read(); this.grooveOption.knifeRadius = file.Read(); } else { this.grooveOption.grooveAddLength = this.grooveOption.grooveAddLength.toString(); this.grooveOption.grooveAddWidth = this.grooveOption.grooveAddWidth.toString(); this.grooveOption.grooveAddDepth = this.grooveOption.grooveAddDepth.toString(); this.grooveOption.knifeRadius = this.grooveOption.knifeRadius.toString(); } this.IntersectSpaceEntitys.clear(); if (ver > 4) { let size = file.Read(); for (let i = 0; i < size; i++) { let entId = file.ReadObjectId(); if (entId) this.IntersectSpaceEntitys.add(entId); } size = file.Read(); this.IntersectHighDrill = file.ReadArray(size); } this._option.exprCount = this._option.count.toString(); } WriteFile(file) { file.Write(5); super.WriteFile(file); file.Write(this._option.type); file.Write(this._option.name); file.Write(this._option.frontShrink); file.Write(this._option.leftShrink); file.Write(this._option.rightShrink); file.Write(this._option.calcHeight); file.Write(this._option.isTotalLength); file.Write(this._option.boardRelative); file.Write(this._option.thickness); file.Write(this._option.count); file.Write(this._option.spaceSize); file.Write(this._option.isActive); //ver2 file.Write(this._nailOption.isDraw); file.Write(this._nailOption.addCount); file.Write(this._nailOption.dist); file.Write(this._nailOption.isGroup); file.Write(this._nailOption.isInBack); file.Write(this._nailOption.front); file.Write(this._nailOption.behind); file.Write(this._nailOption.count); file.Write(this._nailOption.rad); file.Write(this._nailOption.length); file.Write(this._nailOption.depth); file.Write(this._option.calcSpaceSize); file.Write(this._option.calcFrontShrink); file.Write(this._option.calcLeftShrink); file.Write(this._option.calcRightShrink); //ver4 file.Write(this.grooveOption.grooveAddLength); file.Write(this.grooveOption.grooveAddWidth); file.Write(this.grooveOption.grooveAddDepth); file.Write(this.grooveOption.knifeRadius); //ver5 file.Write(this.IntersectSpaceEntitys.size); for (let ent of this.IntersectSpaceEntitys) file.WriteObjectId(ent); file.Write(this.IntersectHighDrill.length); for (let type of this.IntersectHighDrill) file.Write(type); } }; TemplateLayerBoard = __decorate([ Factory ], TemplateLayerBoard); /** * 左右侧板模板 */ let TemplateLeftRightBoardRecord = class TemplateLeftRightBoardRecord extends TemplateRecord { constructor() { super(); this.name = "左右侧板(自动)"; } InitBaseParams() { super.InitBaseParams(); let lparam = new TemplateParam(); lparam.name = "ZS"; lparam.description = "左缩"; lparam.type = TemplateParamType.Float; lparam.value = 0; this.Params.push(lparam); let rparam = new TemplateParam(); rparam.name = "YS"; rparam.description = "右缩"; rparam.type = TemplateParamType.Float; rparam.value = 0; this.Params.push(rparam); return this; } UpdateEntitys() { let [lId, rId] = this.Objects; if (!lId || lId.IsErase || !rId || rId.IsErase) return; let lBr = lId.Object; let rBr = rId.Object; let thickness = this.GetParam("BH")?.value ?? 18; let zs = this.GetParam("ZS")?.value ?? 0; //左缩 let ys = this.GetParam("YS")?.value ?? 0; //右缩 EntitysUpdateWrap([lBr, rBr], () => { lBr.Thickness = thickness; rBr.Thickness = thickness; if (!this._CacheSpaceSize) { console.warn("左右侧板模板数据错误无法更新"); return; } lBr.Height = this._CacheSpaceSize.z; rBr.Height = this._CacheSpaceSize.z; lBr.Width = this._CacheSpaceSize.y - zs; //似乎用拉伸来做比较好,这样能保持住背板对他的拉槽. rBr.Width = this._CacheSpaceSize.y - ys; lBr.Position = new Vector3(0, zs, 0); rBr.Position = new Vector3(this._CacheSpaceSize.x - rBr.Thickness, ys, 0); }); } ReadFile(file) { super.ReadFile(file); if (this._Version < 3) { if (!this.GetParam(("ZS"))) { let lparam = new TemplateParam(); lparam.name = "ZS"; lparam.description = "左缩"; lparam.type = TemplateParamType.Float; lparam.value = 0; this.Params.push(lparam); let rparam = new TemplateParam(); rparam.name = "YS"; rparam.description = "右缩"; rparam.type = TemplateParamType.Float; rparam.value = 0; this.Params.push(rparam); } } } }; TemplateLeftRightBoardRecord = __decorate([ Factory ], TemplateLeftRightBoardRecord); let TemplateSizeBoard = class TemplateSizeBoard extends TemplateRecord { async Update() { await super.Update(); if (this.Objects.length === 0) return; let originBr = this.Objects[0].Object; if (!originBr.IsErase) { if (originBr.BoardType === BoardType.Layer) { originBr.Height = this._CacheSpaceSize.x; originBr.Width = this._CacheSpaceSize.y; originBr.Thickness = this._CacheSpaceSize.z; } else if (originBr.BoardType === BoardType.Vertical) { originBr.Height = this._CacheSpaceSize.z; originBr.Width = this._CacheSpaceSize.y; originBr.Thickness = this._CacheSpaceSize.x; } else { originBr.Height = this._CacheSpaceSize.z; originBr.Width = this._CacheSpaceSize.x; originBr.Thickness = this._CacheSpaceSize.y; } } } }; TemplateSizeBoard = __decorate([ Factory ], TemplateSizeBoard); /** * 立板模板 */ let TemplateVerticalBoard = class TemplateVerticalBoard extends TemplateBoardRecord { constructor() { super(); this._option = { ...DefaultVerticalBoardConfig }; this.grooveOption = { grooveAddLength: "0", grooveAddWidth: "0", grooveAddDepth: "0", knifeRadius: "3", }; //空间分析缺口切割需要的板件 this.IntersectSpaceEntitys = new Set(); this.IntersectHighDrill = []; this.name = "立板(自动)"; } set GrooveOption(option) { this.WriteAllObjectRecord(); Object.assign(this.grooveOption, option); } get Option() { const thickness = this.GetParam("BH")?.value; if (thickness) this._option.exprThickness = thickness.toString(); this._option.exprCount = this._option.count.toString(); return Object.assign({}, this._option); } set Option(option) { super.Option = option; const exprThickness = option.exprThickness; if (exprThickness) { const BHParam = this.GetParam("BH"); if (BHParam) BHParam.expr = exprThickness; } } async GeneralBoardList(space) { const BHParam = this.GetParam("BH"); let expr = BHParam?.expr; if (expr) { if (typeof expr === "string") { if (expr.startsWith("_") || expr.startsWith("$")) this._option.exprThickness = this._option.thickness.toString(); else this._option.exprThickness = expr; } else this._option.exprThickness = expr.toString(); } else this._option.exprThickness = this._option.thickness.toString(); return await BuildVerticalBoards(this._option, space, this.grooveOption); } ReadFile(file) { let ver = file.Read(); super.ReadFile(file); this._option.type = file.Read(); this._option.name = file.Read(); this._option.frontShrink = file.Read(); this._option.bottomShrink = file.Read(); this._option.calcWidth = file.Read(); this._option.calcHeight = file.Read(); this._option.isTotalLength = file.Read(); this._option.isTotalWidth = file.Read(); this._option.boardRelative = file.Read(); this._option.thickness = file.Read(); this._option.count = file.Read(); this._option.spaceSize = file.Read(); if (ver >= 2) { this._option.calcSpaceSize = file.Read(); this._option.calcFrontShrink = file.Read(); this._option.calcBottomShrink = file.Read(); } else { this._option.calcSpaceSize = this._option.spaceSize.toString(); this._option.calcFrontShrink = this._option.frontShrink.toString(); this._option.calcBottomShrink = this._option.bottomShrink.toString(); } if (ver > 2) { this.grooveOption.grooveAddLength = file.Read(); this.grooveOption.grooveAddWidth = file.Read(); this.grooveOption.grooveAddDepth = file.Read(); this.grooveOption.knifeRadius = file.Read(); } else { this.grooveOption.grooveAddLength = this.grooveOption.grooveAddLength.toString(); this.grooveOption.grooveAddWidth = this.grooveOption.grooveAddWidth.toString(); this.grooveOption.grooveAddDepth = this.grooveOption.grooveAddDepth.toString(); this.grooveOption.knifeRadius = this.grooveOption.knifeRadius.toString(); } this.IntersectSpaceEntitys.clear(); //版本4弃用ReadObject if (ver === 4) { let size = file.Read(); let bakDb = file.database; file.database = new Database(false, false, true); for (let i = 0; i < size; i++) { let ent = file.ReadObject(); if (ent.objectId) { let id = bakDb.GetObjectId(ent.Id.Index, true); if (id) this.IntersectSpaceEntitys.add(id); } } file.database = bakDb; } if (ver > 3) { let size = file.Read(); this.IntersectHighDrill = file.ReadArray(size); } if (ver > 4) { //版本5 采用读取ID let size = file.Read(); for (let i = 0; i < size; i++) { let entId = file.ReadObjectId(); if (entId) this.IntersectSpaceEntitys.add(entId); } } this._option.exprCount = this._option.count.toString(); } WriteFile(file) { file.Write(5); super.WriteFile(file); file.Write(this._option.type); file.Write(this._option.name); file.Write(this._option.frontShrink); file.Write(this._option.bottomShrink); file.Write(this._option.calcWidth); file.Write(this._option.calcHeight); file.Write(this._option.isTotalLength); file.Write(this._option.isTotalWidth); file.Write(this._option.boardRelative); file.Write(this._option.thickness); file.Write(this._option.count); file.Write(this._option.spaceSize); file.Write(this._option.calcSpaceSize); file.Write(this._option.calcFrontShrink); file.Write(this._option.calcBottomShrink); file.Write(this.grooveOption.grooveAddLength); file.Write(this.grooveOption.grooveAddWidth); file.Write(this.grooveOption.grooveAddDepth); file.Write(this.grooveOption.knifeRadius); //ver4 file.Write(this.IntersectHighDrill.length); for (let type of this.IntersectHighDrill) file.Write(type); //ver5 file.Write(this.IntersectSpaceEntitys.size); for (let ent of this.IntersectSpaceEntitys) file.WriteObjectId(ent); } }; TemplateVerticalBoard = __decorate([ Factory ], TemplateVerticalBoard); let TemplateVisualSpace = class TemplateVisualSpace extends TemplateRecord { constructor() { super(); this.IsVisible = true; this.Name = "可视化模块空间"; } async Update() { await super.Update(); if (!this.Db) return; //如果没有绘制到DB空间,那么将不会进行更新(否则绘制失败) //在这里重建虚拟空间,(未来我们可能会避免自动重建) if (this.Objects.length === 0 || this.Objects[0].Object === undefined) { this.Objects.length = 0; let visualEntity = new VisualSpaceBox(this.LParam.value, this.WParam.value, this.HParam.value); visualEntity.ApplyMatrix(this._CacheSpaceCS); visualEntity.SpaceOCS = this._CacheSpaceCS; //这里避免使用app.Database,否则插入图纸后,会错误的在app的图纸里面绘制一个空间,导致程序错误. this.Db.ModelSpace.Append(visualEntity); this.Objects.push(visualEntity.Id); } let visualEntity = this.Objects[0].Object; let depth = this.NodeDepth; visualEntity.ColorIndex = depth === 0 ? 7 : depth + 1; let IsRoot = this.Parent === undefined; let isVisual = IsRoot || this.Children.length === 0; visualEntity.Visible = this.IsVisible && isVisual; visualEntity.SetSize(this.LParam.value, this.WParam.value, this.HParam.value); visualEntity.IsRoot = this.Children.length !== 0; if (!IsRoot) { let root = this.Root; visualEntity.DisplayLength = this.LParam.value !== root.LParam.value; visualEntity.DisplayWidth = this.WParam.value !== root.WParam.value; visualEntity.DisplayHeight = this.HParam.value !== root.HParam.value; } } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 ReadFile(file) { file.Read(); super.ReadFile(file); this.IsVisible = file.Read(); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this.IsVisible); } }; __decorate([ AutoRecord ], TemplateVisualSpace.prototype, "IsVisible", void 0); TemplateVisualSpace = __decorate([ Factory ], TemplateVisualSpace); /** * 更新视口实体的时机: * ->在布局状态下才应该更新,否则则标记为需要更新(或者不需要标记) * ->在缩放相机时,标记所有的需要更新(异步更新任务)(优先更新可见视口) * ->切换到布局时,更新全部视口(使用更新任务) */ let ViewportEntity2 = class ViewportEntity2 extends Entity { constructor(_Width = 1, _Height = 1) { super(); this._Width = _Width; this._Height = _Height; this.OnlyRenderType = true; //当前视口的渲染类型 this._RenderType = RenderType.Conceptual; //当前视口绘制的图形列表 this._DisplayEntitys = new Set(); this._CameraData = new CameraUpdate; this._Target = new Vector3(0, 0, -1); } SetSize(width, height) { if (this._Width === width && this._Height === height) return; this.WriteAllObjectRecord(); this._Width = width; this._Height = height; this.Update(); } AppendEntity(ens) { this.WriteAllObjectRecord(); for (let e of ens) this._DisplayEntitys.add(e.Id); this.Update(); } Destroy() { super.Destroy(); } //#region -------------------------File------------------------- //对象从文件中读取数据,初始化自身 _ReadFile(file) { file.Read(); super._ReadFile(file); this._Width = file.Read(); this._Height = file.Read(); this._DisplayEntitys.clear(); let count = file.Read(); for (let i = 0; i < count; i++) { let id = file.ReadSoftObjectId(); if (id) this._DisplayEntitys.add(id); } this._RenderType = file.Read(); this._Target.fromArray(file.Read()); } //对象将自身数据写入到文件. WriteFile(file) { file.Write(1); super.WriteFile(file); file.Write(this._Width); file.Write(this._Height); file.Write(this._DisplayEntitys.size); for (let id of this._DisplayEntitys) file.WriteSoftObjectId(id); file.Write(this._RenderType); file.Write(this._Target.toArray()); } }; ViewportEntity2 = __decorate([ Factory ], ViewportEntity2); export { AlignLineGroupRecord, AlignLineGroupTable, AlignedDimension, AllObjectData, AmbientLight, Arc, ArcBoardBuild, ArcDimension, AxisCS, BUL_IS_LINE_FUZZ, BlockTable, BlockTableRecord, Board, BoxSolid, BulkheadCeiling, BulkheadCeiling_ContourType, CADFactory, CADFiler, CADObject, CSG2Geometry, CSG2Geometry2, CURVE_DIR_TYPE_KEY, CURVE_FACE_TYPE_KEY, CURVE_MESH_NAMES, CURVE_WALL_TYPE_KEY, CameraSnapshootRecord, CameraType, CameraUpdate, Circle, ColorMap, CommandHistoryRecord, CompositeEntity, ContourTreeNode, CreateObjectData, Curve, CurveTapeShape, CylinderHole, Cylineder, Database, DbCurve, DbText, DepthType, DiameterDimension, DimStyleRecord, DimStyleTable, DimTextPosDir, Dimension, DirectionalLight, DisposeTextShapeCache, DuplicateRecordCloning, Ellipse, Entity, EntityColorHistoryRecord, EntityFbx, EntityRef, EraseEntityData, ExtendType$1 as ExtendType, ExtrudeBuildConfig, ExtrudeGeometryBuilder, ExtrudeHole, ExtrudeSolid, FaceEntity, Factory, FastDrillingEdgeGeometry, FastDrillingMeshGeometry, FastMeshGeometry, FontStyleRecord, FontStyleTable, GangDrillType, GenLocalUv, Geometry2CSG, Geometry2CSG2, GetBoxGeoBufferGeometry, GetHoleLengthOfIndex, Groove, GroupRecord, GroupTable, HardwareCompositeEntity, HardwareTopline, HemisphereLight, HistoricManage, HistorycRecord, Hole, Image, InitClipperCpp, IsPhysical, LayerNode, LayerTable, LayerTableRecord, Light, Line, LineAngularDimension, LinearDimension, MLeaderDimension, MaterialTableRecord, MaxDrawGrooveCount, NameData, ObjectAllDataHistoryRecord, ObjectCollection, ObjectId, ObjectIdData, ObjectTreeData, PhysicalMaterialRecord, Point, PointLight, Polyline, Positioning, PositioningBoardSpace, PositioningClampSpace, PositioningFixed, PositioningHandleSpace, PositioningTemporary, ProcessingGroupRecord, ProcessingGroupTable, Purge, PurgeTemplateTreeRoot, ROOM_REGION_CURVES_KEY, RadiusDimension, RectAreaLight, Region, RelevancyType, RemoveObjectData, RenderState, RenderType, RevolveSolid, RoomBase, RoomFlatBase, RoomFlatFloor, RoomFlatTop, RoomHoleBase, RoomHoleLine, RoomHolePolyline, RoomRegion, RoomRegionParse, RoomWallArc, RoomWallBase, RoomWallLine, RoomWallParse, Shape2, Spline, SpotLight, Status, SubtractRange, SubtractRange2, SweepSolid, SymbolTable, SymbolTableRecord, TEXT_HEIGHT_SCALE, Tape, TempPolyline$1 as TempPolyline, TempalteActionRadiusType, TempateDefaultParamCount, TempateThicknessAction, TemplateAction, TemplateArcWindowRecord, TemplateBehindBoard, TemplateBoardRecord, TemplateEntityRef, TemplateFilletAction, TemplateLatticeRecord, TemplateLayerBoard, TemplateLeftRightBoardRecord, TemplateMaterialAction, TemplateMoveAction, TemplateParam, TemplateRecord, TemplateRoomDoorRecord, TemplateSetSealAction, TemplateSizeBoard, TemplateStretchGripAction, TemplateStretchScaleBoxAction, TemplateTable, TemplateTopBottomBoard, TemplateVerticalBoard, TemplateVisualSpace, TemplateWindowRecord, TemplateWineRackRecord, TemplateaRadiusAction, Text, TextAligen, TextureTable, TextureTableEventBus, TextureTableRecord, ThicknessDirection, UVType, UpdateDraw, UpdateHoleFakerWallsAndUpdateDraw, UpdateRelevanceWallHole, UpdateTempPolyline, UpdateWallHolesDataAndUpdateDraw, ViewNearMinSize, ViewportEntity, ViewportEntity2, VisualSpaceBox, WallCurveDirType, WallFaceType, applyMixins, boardUVGenerator, boardUVGenerator2, clipperCpp }; //# sourceMappingURL=api.esm.js.map