using System.Diagnostics; using System.Windows; using Autodesk.Revit.DB; using Autodesk.Revit.UI.Selection; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Nice3point.Revit.Toolkit.External.Handlers; using ShrlAlgoToolkit.RevitAddins.Common.Assists; namespace ShrlAlgoToolkit.RevitAddins.RvMEP; public partial class ClashResolveViewModel : ObservableObject { [ObservableProperty] public partial AdjustDirection AdjustDirection { get; set; } [ObservableProperty] public partial AdjustType AdjustType { get; set; } = AdjustType.OneSide; /// /// 正在执行命令 /// [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(RevitAddins.RvMEP.ClashResolveViewModel.ResolveCommand))] public partial bool CanRunning { get; set; } = true; [ObservableProperty] public partial double Angle { get; set; } = 90.0; [ObservableProperty] public partial double Offset { get; set; } = 800; [ObservableProperty] public partial LocationType LocationType { get; set; } = LocationType.Manual; public ActionEventHandler ActionEventHandler { get; } = new(); partial void OnLocationTypeChanged(LocationType value) { KeyIntPtrHelper.RaiseEscTwice(); } [RelayCommand] private static void Closing() { //关闭窗口退出选择 KeyIntPtrHelper.RaiseEscTwice(); } [RelayCommand(CanExecute = nameof(RevitAddins.RvMEP.ClashResolveViewModel.CanRunning))] private void Resolve() { CanRunning = false; ActionEventHandler.Raise(uiapp => { var uidoc = uiapp.ActiveUIDocument; var doc = uidoc.Document; MEPCurve mepCurveToBend = default; XYZ breakPoint1 = default; XYZ breakPoint2 = default; doc.Invoke( ts => { try { switch (LocationType) { case LocationType.Manual: { var reference1 = uidoc.Selection.PickObject( ObjectType.PointOnElement, new FuncFilter( e => e is MEPCurve mepCurve and not InsulationLiningBase && mepCurve.GetCurve() is Line line && !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero) ), "请选择翻弯的管线上的点" ); mepCurveToBend = doc.GetElement(reference1) as MEPCurve; if (mepCurveToBend.Pinned) { MessageBox.Show("错误", "请解锁图元"); CanRunning = true; return; } var reference2 = uidoc.Selection.PickObject( ObjectType.PointOnElement, new FuncFilter(e => e.Id == mepCurveToBend!.Id), "请选择另一个翻弯管线上的点或确定单侧翻弯的需要偏移一侧" ); //两个断点,breakPoint1距离原直线起点近,反向时,交换值 breakPoint1 = mepCurveToBend.GetCurve().Project(reference1.GlobalPoint).XYZPoint; breakPoint2 = mepCurveToBend.GetCurve().Project(reference2.GlobalPoint).XYZPoint; } break; case LocationType.Reference: { var reference = uidoc.Selection.PickObject( ObjectType.Element, new FuncFilter( e => e is MEPCurve mepCurve and not InsulationLiningBase && mepCurve.GetCurve() is Line line && !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero) ), "请选择参照的管线" ); //参考的管线定位 var referenceMEPCurve = doc.GetElement(reference); var referenceLine = referenceMEPCurve.GetCurve() as Line; var reference1 = uidoc.Selection.PickObject( ObjectType.PointOnElement, new FuncFilter( e => e is MEPCurve mepCurve and not InsulationLiningBase && mepCurve.GetCurve() is Line line && !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero) && e.Id != referenceMEPCurve!.Id ), "请选择翻弯的管线或确定单侧翻弯的需要偏移一侧" ); //翻弯的管线定位 mepCurveToBend = doc.GetElement(reference1) as MEPCurve; var bendLine = mepCurveToBend.GetCurve() as Line; var result = bendLine!.Intersect(referenceLine, out var array); XYZ intersectPoint = default; switch (result) { case SetComparisonResult.Overlap: intersectPoint = array.get_Item(0).XYZPoint; break; case SetComparisonResult.Disjoint: { IList points = []; bendLine.ComputeClosestPoints(referenceLine, true, true, false, out points); var point = points.FirstOrDefault()?.XYZPointOnFirstCurve; if (bendLine.IsInsideEx(point, 0.2)) { intersectPoint = point; } break; } } breakPoint1 = intersectPoint - (bendLine.Direction * Offset / 304.8); breakPoint2 = intersectPoint + (bendLine.Direction * Offset / 304.8); if ( reference1.GlobalPoint.DistanceTo(breakPoint1) < reference1.GlobalPoint.DistanceTo(breakPoint2) ) //距离近的是breakpoint2 { (breakPoint1, breakPoint2) = (breakPoint2, breakPoint1); } if ( intersectPoint == default || !bendLine.IsInsideEx(breakPoint1, 0.2) || !bendLine.IsInsideEx(breakPoint2, 0.2) ) { return; } } break; } var originBaseLine = mepCurveToBend.GetCurve() as Line; var baseLine = originBaseLine!.Clone() as Line; //原管线的方向 var direction = baseLine?.Direction; var startPoint = baseLine!.GetEndPoint(0); var endPoint = baseLine.GetEndPoint(1); var breakLine = Line.CreateBound(breakPoint1, breakPoint2); //var minLength = mepCurveToBend.GetConnectors().OfType().FirstOrDefault().GetExtensionLength(); //if (breakLine.Length < minLength) //{ // return; //} var translateDirection = AdjustDirection switch { AdjustDirection.Up => XYZ.BasisZ, AdjustDirection.Down => -XYZ.BasisZ, AdjustDirection.Left => XYZ.BasisZ.CrossProduct(breakLine.Direction), AdjustDirection.Right => -XYZ.BasisZ.CrossProduct(breakLine.Direction), _ => null }; switch (AdjustType) { case AdjustType.OneSide: { MEPCurve baseMepCurve1; MEPCurve baseMepCurve2; var newMepCurveId = mepCurveToBend.BreakByPoint(breakPoint1); var newMepCurve = doc.GetElement(newMepCurveId) as MEPCurve; //baseMepCurve2始终是打断后生成的新管线,并且是需要偏移的管线 if ((newMepCurve.GetCurve() as Line).IsInsideEx(breakPoint2)) { baseMepCurve1 = mepCurveToBend; baseMepCurve2 = newMepCurve; } else { baseMepCurve1 = newMepCurve; baseMepCurve2 = mepCurveToBend; } //移动新生成的管线 ElementTransformUtils.MoveElement( doc, baseMepCurve2.Id, translateDirection * Offset / 304.8 ); doc.Regenerate(); ////偏移变换 //var translateTransform = Transform.CreateTranslation(translateDirection * Offset / 304.8); // //var originOffsetUnboundLine = baseLine.CreateTransformed(translateTransform) as Line; //需要偏移直线定位线 var originOffsetUnboundLine = baseMepCurve2.GetCurve(); //用于判断交点是否在偏移后的直线内,避免生成不了 var originOffsetLine = originOffsetUnboundLine.Clone() as Line; originOffsetUnboundLine.MakeUnbound(); var radian = Angle.ToRadian(); //反向时角度取补角 if (breakLine.Direction.IsAlmostEqualTo(-direction)) { radian = (180 - Angle).ToRadian(); } var rotation = Transform.CreateRotationAtPoint( direction.CrossProduct(translateDirection), radian, breakPoint1 ); //因为原来的管线可能被移动,所以用baseline的克隆线 var unboundAngleLine = baseLine.CreateTransformed(rotation) as Line; unboundAngleLine!.MakeUnbound(); //偏移管线与倾斜管线交点 var offsetIntersectPoint = originOffsetUnboundLine.IntersectionPoint(unboundAngleLine); var isInside = originOffsetLine.IsInsideEx(offsetIntersectPoint); //当角度线于偏移直线交点在偏移直线内时 //var flag2 = breakPoint1!.DistanceTo(offsetIntersectPoint) > minLength; //偏移间距大于一定一定值 if (!isInside) { MessageBox.Show("管线偏移角度过大或过小,导致交点不在偏移的管线范围内"); break; } //if (!flag2) //{ // MessageBox.Show("管线偏移距离过近"); // break; //} //if (isInside) else { var angleLine = Line.CreateBound(breakPoint1, offsetIntersectPoint); var angleMepCurve = mepCurveToBend.CopyAndSetLocationCurve(angleLine); //拿到角度管的偏移交点处的连接件 var angleMEPCurveIntersectPointConnector = angleMepCurve .GetConnectors(true) .GetNearestConnector(offsetIntersectPoint); //移动后的管线原有的连接件中,不用连接角度曲线的连接件,用来确定移动后管线的定位线 Connector originOffsetConnector = null; foreach (Connector conn in baseMepCurve2.GetConnectors(true)) { //90度时,偏移管线的两个连接都是90度,但要选择偏移管原有的,非打断处的连接件 if ( conn.CoordinateSystem.BasisZ.DotProduct( angleMEPCurveIntersectPointConnector.CoordinateSystem.BasisZ ) >= 0 && !offsetIntersectPoint.IsAlmostEqualTo(conn.Origin) ) //锐角 { originOffsetConnector = conn; break; } } try { //if (originOffsetConnector != null && offsetIntersectPoint.DistanceTo(originOffsetConnector.Origin) > minLength) if (originOffsetConnector != null) { var finalOffsetLine = Line.CreateBound( offsetIntersectPoint, originOffsetConnector.Origin ); baseMepCurve2.SetLocationCurve(finalOffsetLine); } var conn1 = ConnectorExtensions.GetNearestConnectors( baseMepCurve1, angleMepCurve ); doc.Create.NewElbowFitting(conn1[0], conn1[1]); var conn2 = ConnectorExtensions.GetNearestConnectors( angleMepCurve, baseMepCurve2 ); doc.Create.NewElbowFitting(conn2[0], conn2[1]); } catch (Exception ex) { Debug.WriteLine(ex.Message); ex.Message.ToLog(); } } } break; case AdjustType.TwoSide: { MEPCurve baseMepCurve1; MEPCurve baseMepCurve2; //选择点的方向与原直线方向相反时,使得breakPoint1~breakPoint2始终与原直线同向 if (breakLine.Direction.IsAlmostEqualTo(-baseLine.Direction)) { //交换值 (breakPoint1, breakPoint2) = (breakPoint2, breakPoint1); } //doc.Regenerate(); //偏移变换 var translateTransform = Transform.CreateTranslation( translateDirection * Offset / 304.8 ); //偏移直线 var offsetLine = baseLine.CreateTransformed(translateTransform) as Line; offsetLine!.MakeUnbound(); //根据角度旋转的矩阵变换,创建变换后的直线 //第一个交点 var rotateTransform1 = Transform.CreateRotationAtPoint( direction.CrossProduct(translateDirection), Angle.ToRadian(), breakPoint1 ); var angleLine1 = baseLine.CreateTransformed(rotateTransform1) as Line; angleLine1!.MakeUnbound(); var offsetPoint1 = offsetLine.IntersectionPoint(angleLine1); //根据角度旋转的矩阵变换,创建变换后的直线 //第二个交点 var rotateTransform2 = Transform.CreateRotationAtPoint( direction.CrossProduct(translateDirection), (180 - Angle).ToRadian(), breakPoint2 ); var angleLine2 = baseLine.CreateTransformed(rotateTransform2) as Line; angleLine2!.MakeUnbound(); var offsetPoint2 = offsetLine.IntersectionPoint(angleLine2); var b = Line.CreateBound(offsetPoint1, offsetPoint2); if (b.Direction.IsAlmostEqualTo(-baseLine.Direction)) { Debug.WriteLine("翻弯的管线交叉"); MessageBox.Show( "两点距离太近,无法翻弯", "提醒", MessageBoxButton.OK, MessageBoxImage.Information ); ts.RollBack(); break; } var newMepCurveId = mepCurveToBend.BreakByPoint(breakPoint1); var newMepCurve = doc.GetElement(newMepCurveId) as MEPCurve; //判断打断生成的新管线的位置是原管线的前端还是后端 if ((newMepCurve.GetCurve() as Line).IsInsideEx(breakPoint2)) //后端 { baseMepCurve1 = mepCurveToBend; baseMepCurve2 = newMepCurve; } else { baseMepCurve1 = newMepCurve; baseMepCurve2 = mepCurveToBend; } //第一条管线 var newBaseLine1 = Line.CreateBound(startPoint, breakPoint1); //第二条管线 var newBaseLine2 = Line.CreateBound(breakPoint2, endPoint); baseMepCurve1.SetLocationCurve(newBaseLine1); baseMepCurve2.SetLocationCurve(newBaseLine2); //生成的偏移线方向相反时,会导致两条翻弯的管线交叉 //第一根翻弯管 var firstMepCurve = mepCurveToBend.CopyAndSetLocationCurve( Line.CreateBound(breakPoint1, offsetPoint1) ); //偏移管 var secondMepCurve = mepCurveToBend.CopyAndSetLocationCurve( Line.CreateBound(offsetPoint1, offsetPoint2) ); //第二根翻弯管 var thirdMepCurve = mepCurveToBend.CopyAndSetLocationCurve( Line.CreateBound(offsetPoint2, breakPoint2) ); doc.Regenerate(); try { var connectors1 = ConnectorExtensions.GetNearestConnectors( baseMepCurve1, firstMepCurve ); doc.Create.NewElbowFitting(connectors1[0], connectors1[1]); var connectors4 = ConnectorExtensions.GetNearestConnectors( thirdMepCurve, baseMepCurve2 ); doc.Create.NewElbowFitting(connectors4[0], connectors4[1]); } catch (Autodesk.Revit.Exceptions.InvalidOperationException) { MessageBox.Show("角度过大或过小", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { ex.Message.ToLog(); } try { var connectors2 = ConnectorExtensions.GetNearestConnectors( firstMepCurve, secondMepCurve ); doc.Create.NewElbowFitting(connectors2[0], connectors2[1]); var connectors3 = ConnectorExtensions.GetNearestConnectors( secondMepCurve, thirdMepCurve ); doc.Create.NewElbowFitting(connectors3[0], connectors3[1]); } catch (Autodesk.Revit.Exceptions.InvalidOperationException) { MessageBox.Show("角度过大或过小", "提示", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { ex.Message.ToLog(); } } break; } } catch (Autodesk.Revit.Exceptions.OperationCanceledException) { CanRunning = true; return; } catch (Exception) { CanRunning = true; return; } }, "碰撞处理" ); CanRunning = true; }); } } public enum LocationType { Manual, Reference } public enum AdjustType { TwoSide, OneSide } public enum AdjustDirection { Up, Down, Left, Right }