diff --git a/__test__/Polyline/offset_tool_sq.test.ts b/__test__/Polyline/offset_tool_sq.test.ts new file mode 100644 index 000000000..cf46f238f --- /dev/null +++ b/__test__/Polyline/offset_tool_sq.test.ts @@ -0,0 +1,23 @@ +import { Polyline } from "../../src/DatabaseServices/Entity/Polyline"; +import { PolylineJoinType } from "../../src/GraphicsSystem/OffsetPolyline"; +import { LoadCurvesFromFileData } from "../Utils/LoadEntity.util"; +import "../Utils/jest.util"; + +test('走刀 补直线', () => +{ + /** + * 因为中间圆裁剪后,线连接不起来了,提高连接的容差精度后就可以了 + */ + let d = + { "file": [1, "Polyline", 10, 2, 154, 0, 1, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2601.0945127774107, 949.9743923671341, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 18, [4.547473508864641e-13, 1.1368683772161603e-13], 0, [-120.2931727439086, 22.053748336383364], 0, [-120.2931727439086, -166.85479437718436], 0, [-244.3936752564714, -190.2960004073351], 0, [-459.50121294491373, -543.2929853319582], 0, [-227.84694158812954, -517.0939903570838], -0.40210168311676253, [-316.096187819287, -701.8658496535651], 0, [-142.35548430169774, -701.8658496535662], 0, [-309.2017154574901, -868.7120808093316], 0, [-139.59769535697387, -914.2155983972847], 0, [257.5239126832266, -967.9924828193951], 0, [395.41335991940787, -592.9331863369833], 0, [613.2786865525736, -592.9331863369833], 0, [391.2766765023225, -256.48293508070196], 0, [376.1088373063426, -68.95328683949595], 0, [269.6684368006304, -49.43921341344867], -0.5459751344667229, [141.84056613947723, 1.061209382390416], -0.6541961494111228, [0, 0], 0, false], "basePt": { "x": 2141.593299832497, "y": -18.01809045226105, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + + let pl = LoadCurvesFromFileData(d)[0] as Polyline; + + for (let dist of [3]) + { + let cus = pl.GetFeedingToolPath(dist, 9 * 2.1, PolylineJoinType.Square); + expect(cus.length).toMatchSnapshot(); + for (let c of cus) + expect(c.Length).toMatchNumberSnapshot(); + } +}); diff --git a/src/Add-on/Offset.ts b/src/Add-on/Offset.ts index f9c3093bf..515c80c9f 100644 --- a/src/Add-on/Offset.ts +++ b/src/Add-on/Offset.ts @@ -10,6 +10,7 @@ import { Command } from '../Editor/CommandMachine'; import { JigUtils } from '../Editor/JigUtils'; import { PromptStatus } from '../Editor/PromptResult'; import { isParallelTo } from '../Geometry/GeUtils'; +import { PolylineJoinType } from '../GraphicsSystem/OffsetPolyline'; const OffsetKey = "offset"; @@ -265,33 +266,52 @@ export class Command_DynOffsetToolPath let pl = enRes.Entity as Polyline; let basePoint: Vector3 = new Vector3(); - let dir: number = 0; - let distRes = await app.Editor.GetDistance( - { - Msg: "指定通过点或输入偏移距离:", - BasePoint: basePoint, - CalcDistance: (baseP, p: Vector3) => - { - basePoint.copy(pl.GetClosestPointTo(p, false)); - dir = GetPointAtCurveDir(pl, p); - return p.distanceTo(basePoint) * dir; - }, - Callback: (dis: number) => - { - JigUtils.Destroy(); - pl.GetFeedingToolPath(dis).forEach(c => JigUtils.Draw(c)); - } - }); + let joinType = PolylineJoinType.Round; - if (distRes.Status === PromptStatus.OK) + while (true) { - let offsetDist = distRes.Distance; - if (dir !== Math.sign(offsetDist)) - offsetDist = -offsetDist; + let dir: number = 0; + let distRes = await app.Editor.GetDistance( + { + Msg: `当前衔接类型(${joinType === PolylineJoinType.Round ? "圆边" : "切边"}),指定通过点或输入偏移距离:`, + KeyWordList: [joinType === PolylineJoinType.Round ? { msg: "切边", key: "S" } : { msg: "圆边", key: "R" }], + BasePoint: basePoint, + CalcDistance: (baseP, p: Vector3) => + { + basePoint.copy(pl.GetClosestPointTo(p, false)); + dir = GetPointAtCurveDir(pl, p); + return p.distanceTo(basePoint) * dir; + }, + Callback: (dis: number) => + { + JigUtils.Destroy(); + pl.GetFeedingToolPath(dis, (dis ** 2) * 2.1, joinType).forEach(c => JigUtils.Draw(c)); + } + }); - let pls = pl.GetFeedingToolPath(offsetDist); - for (let pl of pls) - app.Database.ModelSpace.Append(pl); + if (distRes.StringResult === "S") + { + joinType = PolylineJoinType.Square; + } + else if (distRes.StringResult === "R") + { + joinType = PolylineJoinType.Round; + } + else if (distRes.Status === PromptStatus.OK) + { + let offsetDist = distRes.Distance; + if (dir !== Math.sign(offsetDist)) + offsetDist = -offsetDist; + + let pls = pl.GetFeedingToolPath(offsetDist, (offsetDist ** 2) * 2.1, joinType); + for (let pl of pls) + app.Database.ModelSpace.Append(pl); + break; + } + else + { + break; + } } } } diff --git a/src/Add-on/OffsetX.ts b/src/Add-on/OffsetX.ts index e4f2f16f5..d5b61900c 100644 --- a/src/Add-on/OffsetX.ts +++ b/src/Add-on/OffsetX.ts @@ -3,6 +3,7 @@ import { Curve } from "../DatabaseServices/Entity/Curve"; import { Polyline } from "../DatabaseServices/Entity/Polyline"; import { Command } from "../Editor/CommandMachine"; import { PromptStatus } from "../Editor/PromptResult"; +import { PolylineJoinType } from "../GraphicsSystem/OffsetPolyline"; import { TestDraw } from "./test/TestUtil"; //无限内偏移 @@ -15,21 +16,45 @@ export class OffsetX implements Command if (ssRes.Status != PromptStatus.OK) return; let pl = ssRes.Entity as Polyline; - let disRes = await app.Editor.GetDistance({ - Msg: "指定偏移距离:", - KeyWordList: [{ msg: "通过", key: "T" }], - Default: this.offsetDis - }); + let joinType = PolylineJoinType.Round; + let offsetDis: number; - let offsetDis = Math.abs(disRes.Distance); - TestDraw(pl.GetFeedingToolPath(offsetDis * Math.sign(pl.Area2)), 3);//外偏移 + while (true) + { + let disRes = await app.Editor.GetDistance({ + Msg: `当前衔接类型(${joinType === PolylineJoinType.Round ? "圆边" : "切边"}),指定偏移距离:`, + KeyWordList: [{ msg: "通过", key: "T" }, joinType === PolylineJoinType.Round ? { msg: "切边", key: "S" } : { msg: "圆边", key: "R" }], + Default: this.offsetDis + }); + if (disRes.StringResult === "S") + { + joinType = PolylineJoinType.Square; + } + else if (disRes.StringResult === "R") + { + joinType = PolylineJoinType.Round; + } + else if (disRes.Status === PromptStatus.OK) + { + offsetDis = Math.abs(disRes.Distance); + break; + } + else + { + return; + } + } + + const offsetDist = offsetDis * Math.sign(pl.Area2); + TestDraw(pl.GetFeedingToolPath(offsetDist, (offsetDist ** 2) * 2.1, joinType), 3);//外偏移} let offsetQueue: Curve[] = [pl]; while (offsetQueue.length > 0) { let pl = offsetQueue.pop() as Polyline; - let offsets = pl.GetFeedingToolPath(offsetDis * -Math.sign(pl.Area2)); + const offsetDist = offsetDis * -Math.sign(pl.Area2); + let offsets = pl.GetFeedingToolPath(offsetDist, (offsetDist ** 2) * 2.1, joinType); for (let c of offsets) { if (c.IsClose) diff --git a/src/DatabaseServices/Entity/Polyline.ts b/src/DatabaseServices/Entity/Polyline.ts index ccf9f28a3..4f274810f 100644 --- a/src/DatabaseServices/Entity/Polyline.ts +++ b/src/DatabaseServices/Entity/Polyline.ts @@ -9,7 +9,7 @@ import { Box3Ext } from '../../Geometry/Box'; import { CreatePolylinePath } from '../../Geometry/CreatePolylinePath'; import { AsVector2, AsVector3, MatrixIsIdentityCS, equaln, equalv2, equalv3 } from '../../Geometry/GeUtils'; import { IntersectOption, IntersectPolylineAndCurve } from '../../GraphicsSystem/IntersectWith'; -import { OffsetPolyline } from '../../GraphicsSystem/OffsetPolyline'; +import { OffsetPolyline, PolylineJoinType } from '../../GraphicsSystem/OffsetPolyline'; import { Factory } from '../CADFactory'; import { CADFiler } from '../CADFiler'; import { IsPointInPolyLine } from '../PointInPolyline'; @@ -600,17 +600,23 @@ export class Polyline extends Curve { if (equaln(dist, 0)) return 0; - let cus = this.Explode(); - for (let i = 0; i < cus.length; i++) + let cus: (Line | Arc)[] = []; + for (let i = 0; i < this.EndParam; i++) { - let cu = cus[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(dist, len, 1e-8)) return i + 1; + dist -= len; } + if (!this._ClosedMark) return cus.length + cus[cus.length - 1].GetParamAtDist(dist); @@ -1236,10 +1242,10 @@ export class Polyline extends Curve cu.ColorIndex = this.ColorIndex; return curves; } - GetFeedingToolPath(offsetDist: number, offsetDistSq = (offsetDist ** 2) * 2.1): Polyline[] + GetFeedingToolPath(offsetDist: number, offsetDistSq = (offsetDist ** 2) * 2.1, joinType: PolylineJoinType = PolylineJoinType.Round): Polyline[] { if (equaln(offsetDist, 0)) return []; - let polyOffestUtil = new OffsetPolyline(this, offsetDist, true, offsetDistSq); + let polyOffestUtil = new OffsetPolyline(this, offsetDist, true, offsetDistSq, joinType); return polyOffestUtil.Do(); } /** diff --git a/src/GraphicsSystem/OffsetPolyline.ts b/src/GraphicsSystem/OffsetPolyline.ts index 168add4ab..801699ae8 100644 --- a/src/GraphicsSystem/OffsetPolyline.ts +++ b/src/GraphicsSystem/OffsetPolyline.ts @@ -130,6 +130,12 @@ export class CurveTreeNode } } +export enum PolylineJoinType +{ + Square = 0, + Round = 1, +} + export class OffsetPolyline { //多段线信息 @@ -158,8 +164,17 @@ export class OffsetPolyline _IsTopoOffset = false;//局部偏移,允许特殊延伸,参考测试用例 + /** + * + * @param _Polyline + * @param _OffsetDist + * @param [_ToolPath=false] 走刀模式(在这个模式下,我们会进行圆弧过渡(或者直线过渡)避免尖角过大) + * @param [_OffsetDistSq=(_OffsetDist ** 2) * 2.1] 允许的最大尖角长度 默认值差不多是矩形的尖角大一点 + * @param [JoinType=PolylineJoinType.Round] 尖角的处理方式,默认是圆弧过渡,可以切换成直线过渡 + */ constructor(public _Polyline: Polyline, public _OffsetDist: number, public _ToolPath = false, - private _OffsetDistSq = (_OffsetDist ** 2) * 2.1//对直角走刀不进行圆弧过度 + private _OffsetDistSq = (_OffsetDist ** 2) * 2.1,//对直角走刀不进行圆弧过度 + private JoinType = PolylineJoinType.Round //仅在走刀路径时生效 ) { } @@ -297,7 +312,11 @@ export class OffsetPolyline let distSq = iPts[0].distanceToSquared(refP); if (this._ToolPath && distSq > this._OffsetDistSq) { - curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)]; + 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 @@ -307,7 +326,7 @@ export class OffsetPolyline // curveResNow.paddingCurve = [new Line(sp, ep)]; } } - else + else//直线和圆弧 圆弧和圆弧 { let refP = this._Vertexs[curveResNext.index]; @@ -394,7 +413,14 @@ export class OffsetPolyline curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)];//补圆弧 } else - curveResNow.paddingCurve = [this.CreateArc(refP, sp, ep)];//补圆弧 + { + 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);//因为局部偏移可能未提供圆 @@ -938,6 +964,30 @@ export class OffsetPolyline let arc = new Arc(center, Math.abs(this._OffsetDist), sa, ea, this._OffsetDist < 0); return arc; } + + protected CreateSquare(center: Vector3, curveNow: IOffsetResult, curveNext: IOffsetResult, entTypeCode: number) + { + 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: Vector3, sp: Vector3; + 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: Curve)