diff --git a/src/Add-on/DrawLine.ts b/src/Add-on/DrawLine.ts index 8c527246f..39d68d013 100644 --- a/src/Add-on/DrawLine.ts +++ b/src/Add-on/DrawLine.ts @@ -1,6 +1,8 @@ +import { Intent } from '@blueprintjs/core'; import hotkeys from 'hotkeys-js-ext'; import { Vector3 } from 'three'; import { app } from '../ApplicationServices/Application'; +import { CircleInternalTangentLines, CircleOuterTangentLines, GetTanPtsOnArcOrCircle } from '../Common/CurveUtils'; import { Arc } from '../DatabaseServices/Entity/Arc'; import { Circle } from '../DatabaseServices/Entity/Circle'; import { Line } from '../DatabaseServices/Entity/Line'; @@ -11,7 +13,8 @@ import { PromptStatus } from '../Editor/PromptResult'; import { SelectPick } from '../Editor/SelectPick'; import { UCSUtils } from '../Editor/UCSRAII'; import { userConfig } from '../Editor/UserConfig'; -import { equalv3, ZeroVec } from '../Geometry/GeUtils'; +import { ZeroVec, equalv3 } from '../Geometry/GeUtils'; +import { AppToaster } from '../UI/Components/Toaster'; export class DrawLine implements Command { @@ -24,18 +27,20 @@ export class DrawLine implements Command UCSUtils.SetUCSFromPointRes(ptRes); let ptLast = ptRes.Point; - let isTan = ptRes.SnaoMode === ObjectSnapMode.Tan; - let cir: Circle | Arc; - if (isTan) + let basePoint = ptRes.Point; + let firstSnapModeIsTan = ptRes.SnaoMode === ObjectSnapMode.Tan; + let secondSnapModelIsTan = false; + + let firstTangeCircle: Circle | Arc; + if (firstSnapModeIsTan) { let pick = new SelectPick(app.Viewer, app.Viewer.WorldToScreen(ptRes.Point)); pick.Select(app.Viewer.VisibleObjects, { filterTypes: [Circle, Arc] }); - let cirs = pick.SelectEntityList as Circle[]; for (let e of cirs) { if (e.PtOnCurve(ptRes.Point)) - cir = e; + firstTangeCircle = e; } } @@ -49,34 +54,62 @@ export class DrawLine implements Command let drawLines: Line[] = []; let pts: Vector3[] = [ptRes.Point]; + let snapMode: ObjectSnapMode; + while (true) { let line = new Line(); - let ucsMatrix = app.Editor.UCSMatrix; - line.ApplyMatrix(ucsMatrix); - line.StartPoint = ptLast; - - let isFirstAndTan = (isTan && drawLines.length === 0 && cir); - if (isFirstAndTan) + if (firstSnapModeIsTan || secondSnapModelIsTan) JigUtils.Draw(line); + line.StartPoint = ptLast; - let updateEndPt = (p: Vector3) => + const UpdateEndPt = (p: Vector3) => { - if (isFirstAndTan) + if (snapMode) + { + app.Editor.GetPointServices.snapServices.SnapModeEnable = snapMode; + snapMode = undefined; + } + + if (app.Editor.GetPointServices.snapServices.SnapModeEnable === ObjectSnapMode.Tan && !secondSnapModelIsTan) + { + secondSnapModelIsTan = true; + snapMode = ObjectSnapMode.Tan; + app.Editor.GetPointServices.ReturnKeyword("R");//重画 + return; + } + else if (app.Editor.GetPointServices.snapServices.SnapModeEnable !== ObjectSnapMode.Tan && secondSnapModelIsTan) { - let tanP = cir.GetObjectSnapPoints(ObjectSnapMode.Tan, ptLast, p)[0]; + secondSnapModelIsTan = false; + app.Editor.GetPointServices.ReturnKeyword("R");//重画 + return; + } + + if (firstSnapModeIsTan) + { + //第一个是圆切点时 时实更新切点 + const cirCenter = firstTangeCircle.Center; + let circlePointFirst = p.clone().sub(cirCenter); + let circlePoinSecond = ptLast.clone().sub(cirCenter); + + //计算离鼠标最近的一个点 + const crossProduct = new Vector3().crossVectors(circlePointFirst, circlePoinSecond).applyMatrix4(app.Editor.UCSMatrixInv); + let tanP: Vector3; + if (crossProduct.z > 0) + tanP = firstTangeCircle.GetObjectSnapPoints(ObjectSnapMode.Tan, basePoint, p)[0]; + else + tanP = firstTangeCircle.GetObjectSnapPoints(ObjectSnapMode.Tan, basePoint, p)[1]; if (tanP) { line.StartPoint = tanP; - ptLast.copy(tanP); + basePoint.copy(tanP); } } line.EndPoint = p; }; - ptRes = await app.Editor.GetPoint({ Msg: "请输入点2:", - BasePoint: isFirstAndTan ? undefined : ptLast, + BasePoint: (firstSnapModeIsTan || secondSnapModelIsTan) ? undefined : basePoint, AllowDrawRubberBand: true, AllowNone: true, SupportSnapPoints: pts, @@ -84,16 +117,126 @@ export class DrawLine implements Command { msg: "放弃", key: "U" }, { msg: "闭合", key: "C" }, ], - Callback: updateEndPt, + Callback: UpdateEndPt, }); + let secondTangeCircle: Circle; if (ptRes.Status === PromptStatus.OK) { - updateEndPt(ptRes.Point); + //点选圆切线 + if (ptRes.SnaoMode === ObjectSnapMode.Tan) + { + let pick = new SelectPick(app.Viewer, app.Viewer.WorldToScreen(ptRes.Point)); + pick.Select(app.Viewer.VisibleObjects, { filterTypes: [Circle, Arc] }); + let cirs = pick.SelectEntityList as Circle[]; + + for (let e of cirs) + { + if (e.PtOnCurve(ptRes.Point)) + secondTangeCircle = e; + } + + if (firstTangeCircle) + { + //第一个是切点 第二个也是选切点 + //计算最优切线 + let lines = CircleInternalTangentLines(firstTangeCircle as Circle, secondTangeCircle as Circle).concat(CircleOuterTangentLines(firstTangeCircle as Circle, secondTangeCircle as Circle)); + if (!lines?.length) + { + AppToaster.show({ + message: "选择的点位无法构成切线", + timeout: 3000, + intent: Intent.WARNING, + }); + continue; + } + + let minDistance = Infinity; + let distance = Infinity; + let nearestObjectIndex = -1; + + for (let i = 0; i < lines.length; i++) + { + { + distance = Math.sqrt( + Math.pow(lines[i].StartPoint.x - line.StartPoint.x, 2) + + Math.pow(lines[i].StartPoint.y - line.StartPoint.y, 2) + ); + } + if (distance < minDistance) + { + minDistance = distance; + nearestObjectIndex = i; + } + } + + line = lines[nearestObjectIndex]; + } + else + { + //第一个是固定点点 第二个是选切点 + let tanPts = GetTanPtsOnArcOrCircle(secondTangeCircle, ptLast); + let tanP: Vector3; + + if (tanPts?.length) + { + if (tanPts.length > 1) + { + const cirCenter = secondTangeCircle.Center; + let firstPt = ptLast.clone().sub(cirCenter); + let secondPt = ptRes.Point.clone().sub(cirCenter); + + //计算离选点最近的一个点 如果没有切点就重新选 + const crossProduct = new Vector3().crossVectors(firstPt, secondPt).applyMatrix4(app.Editor.UCSMatrixInv); + + if (crossProduct.z > 0) + tanP = tanPts[0]; + else if (tanPts.length > 1) + tanP = tanPts[1]; + } + else + tanP = tanPts[0]; + + line.EndPoint = tanP; + } + else + { + AppToaster.show({ + message: "选择的点位无法构成切线", + timeout: 3000, + intent: Intent.WARNING, + }); + JigUtils.Destroy(); + snapMode = ObjectSnapMode.Tan; + continue; + } + } + + app.LayoutTool.AppendDatabaseSpace(line); + + drawLines.push(line); + basePoint = line.EndPoint; + ptLast = line.EndPoint; + firstSnapModeIsTan = false; + secondSnapModelIsTan = false; + JigUtils.Destroy(); + continue; + } + + //正常画线 app.LayoutTool.AppendDatabaseSpace(line); drawLines.push(line); + basePoint = ptRes.Point; ptLast = ptRes.Point; - pts.push(ptLast); + pts.push(basePoint); + snapMode = ObjectSnapMode.All; + + if (firstSnapModeIsTan) + firstSnapModeIsTan = false; + + if (firstTangeCircle) + firstTangeCircle = undefined; + continue; } else if (ptRes.Status === PromptStatus.Keyword) @@ -105,9 +248,8 @@ export class DrawLine implements Command let lastLine = drawLines[drawLines.length - 1]; app.LayoutTool.CurrentSpace.Remove(lastLine); drawLines.pop(); - ptLast = lastLine.StartPoint; - + basePoint = lastLine.StartPoint; pts.pop(); } } @@ -120,11 +262,15 @@ export class DrawLine implements Command break; } } + else if (ptRes.StringResult === "R") + { + JigUtils.Destroy(); + continue; + } } else break; } - hotkeys.unbind("Control+Z", hotkeys.getScope(), TempUndo); } } diff --git a/src/Add-on/Tangent.ts b/src/Add-on/Tangent.ts index 59e85512b..d3a4ad42c 100644 --- a/src/Add-on/Tangent.ts +++ b/src/Add-on/Tangent.ts @@ -1,8 +1,8 @@ -import { Command } from "../Editor/CommandMachine"; import { app } from "../ApplicationServices/Application"; -import { Circle } from "../DatabaseServices/Entity/Circle"; -import { Arc } from "../DatabaseServices/Entity/Arc"; import { CircleInternalTangentLines, CircleOuterTangentLines } from "../Common/CurveUtils"; +import { Arc } from "../DatabaseServices/Entity/Arc"; +import { Circle } from "../DatabaseServices/Entity/Circle"; +import { Command } from "../Editor/CommandMachine"; import { PromptStatus } from "../Editor/PromptResult"; export class DrawTangentLine implements Command diff --git a/src/Common/CommandNames.ts b/src/Common/CommandNames.ts index 486299e20..8dd908c7a 100644 --- a/src/Common/CommandNames.ts +++ b/src/Common/CommandNames.ts @@ -14,6 +14,7 @@ export enum CommandNames FBXImport = "FBX", Insert = "INSERT", Line = "LINE", //直线 + Tangent = "TANGENT", //切线 XLine = "XLINE", Undo = "UNDO", //撤销 Redo = "REDO", //重做 diff --git a/src/Common/CurveUtils.ts b/src/Common/CurveUtils.ts index e33d4ef92..a31925181 100644 --- a/src/Common/CurveUtils.ts +++ b/src/Common/CurveUtils.ts @@ -393,7 +393,6 @@ export function CircleInternalTangentLines(cir0: Circle, cir1: Circle): Line[] let [c0p0, c0p1] = GetTanPtsOnArcOrCircle(cir0, i); let [c1p0, c1p1] = GetTanPtsOnArcOrCircle(cir1, i); - return [ new Line(c0p0, c1p0), new Line(c0p1, c1p1), diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index 6b3f3941d..c0f0e4d82 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -506,7 +506,7 @@ export function registerCommand() commandMachine.RegisterCommand("ptcopy", new Command_CopyPoint()); - commandMachine.RegisterCommand("tan", new DrawTangentLine()); + commandMachine.RegisterCommand(CommandNames.Tangent, new DrawTangentLine()); commandMachine.RegisterCommand(CommandNames.Divide, new CMD_Divide()); commandMachine.RegisterCommand(CommandNames.Point, new CMD_DrawPoint()); diff --git a/src/Editor/GetPointServices.ts b/src/Editor/GetPointServices.ts index add4b3363..328985798 100644 --- a/src/Editor/GetPointServices.ts +++ b/src/Editor/GetPointServices.ts @@ -6,10 +6,10 @@ import { app } from '../ApplicationServices/Application'; import { HostApplicationServices } from '../ApplicationServices/HostApplicationServices'; import { ColorMaterial } from '../Common/ColorPalette'; import { DisposeThreeObj } from '../Common/Dispose'; -import { safeEval } from '../Common/eval'; import { InputState, KeyWord, MenuDividerKWD } from '../Common/InputState'; import { KeyBoard, MouseKey } from '../Common/KeyEnum'; import { LogType } from '../Common/Log'; +import { safeEval } from '../Common/eval'; import { Entity } from '../DatabaseServices/Entity/Entity'; import { BufferGeometryUtils } from '../Geometry/BufferGeometryUtils'; import { isParallelTo } from '../Geometry/GeUtils'; @@ -26,7 +26,7 @@ import { GenerateRaycaster, Raycast } from './PointPick'; import { GetPointPrompt } from "./PromptOptions"; import { PromptPointResult, PromptStatus } from './PromptResult'; import { Filter } from './SelectFilter'; -import { SnapMenuKW, SNAPMODE } from './ShowSnapMenu'; +import { SNAPMODE, SnapMenuKW } from './ShowSnapMenu'; import { SnapServices } from './SnapServices'; const MinP = new Vector3(-2e6, -2e6, -2e6); @@ -48,6 +48,13 @@ export class GetPointServices implements EditorService return; } this.UpdateCurPointEvent();//触摸的时候会因为没有鼠标移动导致这个位置错误! + + if (this.snapServices.SnapModeEnable === ObjectSnapMode.Tan && !this.curPointIsSnap)//切线捕捉时 必须在圆上 + { + app.Editor.Prompt("点无效!切点必须在圆上!", LogType.Error); + return; + } + this.ReturnPoint(this.curPoint, this.curPointIsSnap ? this.snapServices.SnapType : undefined); return true; } diff --git a/src/Editor/PromptOptions.ts b/src/Editor/PromptOptions.ts index 1a9cd1f79..dbf4cdbbc 100644 --- a/src/Editor/PromptOptions.ts +++ b/src/Editor/PromptOptions.ts @@ -53,7 +53,6 @@ export interface GetPointPrompt extends PromptOptions RaycastFilter?: Filter; RaycastPreExecFunc?: Function; DisableAngleDynInput?: boolean; - SupportSnapPoints?: Vector3[]; } diff --git a/src/UI/Components/CommandPanel/CommandList.ts b/src/UI/Components/CommandPanel/CommandList.ts index 2a044cc20..7b1d17533 100644 --- a/src/UI/Components/CommandPanel/CommandList.ts +++ b/src/UI/Components/CommandPanel/CommandList.ts @@ -195,7 +195,15 @@ export const CommandList: ICommand[] = [ chName: "计算封闭线段的面积", chDes: "计算封闭线段的面积", }, - + { + typeId: "i2d", + link: `#`, + defaultCustom: CommandNames.Tangent, + command: CommandNames.Tangent, + type: "二维", + chName: "圆与圆之间相切的线", + chDes: "圆与圆之间相切的线", + }, //三维命令 { icon: IconEnum.UCS,