添加项目文件。

This commit is contained in:
GG Z
2024-09-22 11:05:41 +08:00
parent fb5d55723a
commit 49ceaae6a8
764 changed files with 78850 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
using System.Windows.Controls;
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class AnyConnectCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<AnyConnectView>(new AnyConnectViewModel());
#if REVIT2018
//WinDialogHelper.ShowModeless<AnyConnectView>(new AnyConnectViewModel());
#elif REVIT2020
//OptionsBarAssist.RegisterOptionsBar((AutoConnectOptionsView view, AnyConnectViewModel viewModel) =>
//{
// viewModel.ConnectCommand.Execute(null);
//});
#endif
}
}

View File

@@ -0,0 +1,116 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.AnyConnectView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="任意连接"
Width="270"
Height="350"
MinHeight="350"
d:DataContext="{d:DesignInstance Type=local:AnyConnectViewModel}"
SizeToContent="Height"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<ui:InfoBar
VerticalAlignment="Top"
IsOpen="{Binding ActiveSnackbar, Mode=OneWay}"
Message="{Binding Message}" />
<GroupBox Header="角度" ToolTip="平行或垂直连接时生效">
<UniformGrid Columns="3">
<RadioButton Content="默认" ToolTip="根据当前位置自动连接">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>0</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="15°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>15</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="22.5°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>22.5</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="30°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>30</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="45°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>45</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="60°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>60</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="90°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>90</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton x:Name="LbCustom" Content="自定义" />
<TextBox
VerticalAlignment="Center"
IsEnabled="{Binding IsChecked, ElementName=LbCustom}"
Text="{Binding Angle, UpdateSourceTrigger=PropertyChanged}" />
</UniformGrid>
</GroupBox>
<GroupBox Header="选择方式">
<UniformGrid Rows="1">
<RadioButton
Content="多选"
IsChecked="{Binding IsMultiSelect}"
ToolTip="选择后完成" />
<RadioButton
Content="框选"
IsChecked="{Binding IsMultiSelect, Converter={StaticResource InvertBooleanConverter}}"
ToolTip="矩形框框选" />
</UniformGrid>
</GroupBox>
<ui:Button
HorizontalAlignment="Stretch"
Command="{Binding ConnectCommand}"
Content="连接管线"
Icon="{ui:SymbolIcon Connector24}" />
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Wpf.Ui.Controls;
namespace Sai.RvKits.RvMEP
{
/// <summary>
/// AnyConnectView.xaml 的交互逻辑
/// </summary>
public partial class AnyConnectView
{
public AnyConnectView()
{
InitializeComponent();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
<StackPanel
x:Class="Sai.RvKits.RvMEP.AutoConnectOptionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Sai.Toolkit.Mvvm.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Height="26"
d:DataContext="{d:DesignInstance Type=local:AnyConnectViewModel}"
Background="#FFE5F0D7"
Orientation="Horizontal"
mc:Ignorable="d">
<StackPanel.Resources>
<converters:ComparisonConverter x:Key="ComparisonConverter" />
<converters:InvertBooleanConverter x:Key="InvertBooleanConverter" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</StackPanel.Resources>
<TextBlock
Padding="10,0,10,0"
VerticalAlignment="Center"
Text="任意管线 | 选项" />
<Border
Width="3"
Background="Gray"
BorderBrush="Azure"
BorderThickness="1,0" />
<TextBlock
Margin="10,0,10,0"
VerticalAlignment="Center"
Text="连接角度:"
ToolTip="平行或垂直连接时生效" />
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<RadioButton
VerticalAlignment="Center"
Content="默认"
ToolTip="根据当前位置自动连接">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>0</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="15°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>15</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="22.5°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>22.5</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="30°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>30</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="45°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>45</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="60°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>60</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton VerticalAlignment="Center" Content="90°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<system:Double>90</system:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton
x:Name="LbCustom"
VerticalAlignment="Center"
Content="自定义:" />
<TextBox
Width="80"
VerticalAlignment="Center"
Text="{Binding Angle, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0}°}"
Visibility="{Binding IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=LbCustom}" />
</StackPanel>
</StackPanel>

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// AutoConnectOptionsView.xaml 的交互逻辑
/// </summary>
public partial class AutoConnectOptionsView
{
public AutoConnectOptionsView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,465 @@
using System.Collections;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Revit.Helpers;
namespace Sai.RvKits.RvMEP;
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
internal class BloomConnectorCmd : ExternalCommand //根据连接件创建一根短管
{
public override void Execute()
{
try
{
var elemIds = UiDocument.Selection.GetElementIds();
if (elemIds.Count == 0)
{
var reference = UiDocument.Selection.PickObject(ObjectType.Element, new GenericFilter<FamilyInstance>(), "请选择族实例");
elemIds.Add(Document.GetElement(reference).Id);
}
var pipeTypeId = ElementId.InvalidElementId;
var cableTrayTypeId = ElementId.InvalidElementId;
var conduitTypeId = ElementId.InvalidElementId;
var filteredElementCollector = new FilteredElementCollector(Document).OfClass(typeof(DuctType));
var roundTypeId = ElementId.InvalidElementId;
var rectangleTypeId = ElementId.InvalidElementId;
var ovalTypeId = ElementId.InvalidElementId;
foreach (var element2 in filteredElementCollector)
{
var ductType = (DuctType)element2;
if (ductType.FamilyName == "圆形风管" || ductType.FamilyName.Contains("Round Duct"))
{
roundTypeId = ductType.Id;
}
else if (ductType.FamilyName == "矩形风管" || ductType.FamilyName.Contains("Rectangular Duct"))
{
rectangleTypeId = ductType.Id;
}
else if (ductType.FamilyName == "椭圆形风管" || ductType.FamilyName.Contains("Oval Duct"))
{
ovalTypeId = ductType.Id;
}
}
var fabricationConfiguration = FabricationConfiguration.GetFabricationConfiguration(Document);
foreach (var elemId in elemIds)
{
var elem = Document.GetElement(elemId);
if (elem.GetConnectors(true).IsEmpty)
{
continue;
}
//拿到连接的管线的类型
foreach (Connector conn in elem.GetConnectors())
{
if (conn.IsConnected)
{
var connector = conn.GetConnectedConnector();
if (connector == null)
{
continue;
}
if (conn.Domain == Domain.DomainPiping)
{
pipeTypeId = connector.Owner.GetTypeId();
}
else if (conn.Domain == Domain.DomainHvac)
{
switch (connector.Shape)
{
case ConnectorProfileType.Invalid:
break;
case ConnectorProfileType.Round:
roundTypeId = connector.Owner.GetTypeId();
break;
case ConnectorProfileType.Rectangular:
rectangleTypeId = connector.Owner.GetTypeId();
break;
case ConnectorProfileType.Oval:
ovalTypeId = connector.Owner.GetTypeId();
break;
default:
break;
}
}
else if (conn.Domain == Domain.DomainCableTrayConduit)
{
if (connector.Owner is CableTray cableTray)
{
cableTrayTypeId = cableTray.GetTypeId();
}
else if (connector.Owner is Conduit conduit)
{
conduitTypeId = conduit.GetTypeId();
}
}
}
}
Document.Invoke(
_ =>
{
foreach (Connector connector in elem.GetConnectors(true))
{
Element element = null;
if (elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_FabricationPipework)
{
var serviceId = (elem as FabricationPart).ServiceId;
var button = fabricationConfiguration.GetService(serviceId).GetButton(0, 0);
var fabricationPart = FabricationPart.Create(Document, button, 0, elem.LevelId);
var enumerator2 = fabricationPart.ConnectorManager.Connectors.GetEnumerator();
if (enumerator2.MoveNext())
{
var connector2 = (Connector)enumerator2.Current;
ElementTransformUtils.MoveElement(
Document,
fabricationPart.Id,
connector.Origin - connector2.Origin
);
var basisZ = connector.CoordinateSystem.BasisZ;
var basisZ2 = connector2.CoordinateSystem.BasisZ;
var num = basisZ.DotProduct(basisZ2);
if (Math.Abs(num - -1.0) < 0.0001)
{
connector2.ConnectTo(connector);
continue;
}
var xyz = basisZ.CrossProduct(basisZ2);
var line = Line.CreateBound(connector.Origin, connector.Origin + (10000000000000000.0 * xyz));
var angleTo = basisZ.AngleTo(basisZ2);
var angle = Math.PI - angleTo;
ElementTransformUtils.RotateElement(Document, fabricationPart.Id, line, angle);
if (connector2.Shape == ConnectorProfileType.Invalid)
{
connector2.Radius = connector.Radius;
}
else
{
connector2.Width = connector.Width;
connector2.Height = connector.Height;
}
}
}
else
{
var extensionLength = connector.GetExtensionLength();
var origin = connector.Origin;
var xyz2 = origin + (extensionLength * connector.CoordinateSystem.BasisZ);
var levelId = elem.LevelId;
if (levelId == ElementId.InvalidElementId)
{
levelId = elem.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM).AsElementId();
}
if (levelId == ElementId.InvalidElementId)
{
if (elem is FamilyInstance { Host: Level } instance)
{
levelId = instance.Host.Id;
}
}
switch (connector.Domain)
{
case Domain.DomainUndefined:
break;
//风管
case Domain.DomainHvac:
var mechanicalSystemTypes = new FilteredElementCollector(Document)
.OfClass(typeof(MechanicalSystemType))
.Cast<MechanicalSystemType>();
//风管系统类型
var mechanicalSystem =
connector.MEPSystem == null
? connector.DuctSystemType switch
{
//送风
DuctSystemType.SupplyAir
=> mechanicalSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.SupplyAir
),
//回风
DuctSystemType.ReturnAir
=> mechanicalSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.ReturnAir
),
//排风
DuctSystemType.ExhaustAir
=> mechanicalSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.ExhaustAir
),
_
=> mechanicalSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.OtherAir
)
}
: Document.GetElement(connector.MEPSystem.GetTypeId()) as MechanicalSystemType;
//if (new FilteredElementCollector(Document).OfClass(typeof(DuctType)).FirstElement() is not DuctType)
//{
// break;
//}
//风管形状
switch (connector.Shape)
{
case ConnectorProfileType.Round:
element = Duct.Create(
Document,
mechanicalSystem.Id,
roundTypeId,
levelId,
origin,
xyz2
);
Document.Regenerate();
element.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM).Set(connector.Radius * 2);
break;
case ConnectorProfileType.Rectangular:
element = Duct.Create(
Document,
mechanicalSystem.Id,
rectangleTypeId,
levelId,
origin,
xyz2
);
Document.Regenerate();
element.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM).Set(connector.Width);
element.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM).Set(connector.Height);
break;
case ConnectorProfileType.Oval:
element = Duct.Create(Document, mechanicalSystem.Id, ovalTypeId, levelId, origin, xyz2);
Document.Regenerate();
element.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM).Set(connector.Width);
element.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM).Set(connector.Height);
break;
default:
element = Duct.Create(
Document,
mechanicalSystem.Id,
roundTypeId,
levelId,
origin,
xyz2
);
break;
}
break;
//电气
case Domain.DomainElectrical:
break;
//水管
case Domain.DomainPiping:
var pipingSystemTypes = new FilteredElementCollector(Document)
.OfClass(typeof(PipingSystemType))
.Cast<PipingSystemType>();
if (pipeTypeId == ElementId.InvalidElementId
)
{
pipeTypeId = new FilteredElementCollector(Document).OfClass(
typeof(PipeType))
.FirstElementId();
break;
}
var system = connector.MEPSystem;
var pipingSystemType =
system != null
? Document.GetElement(system.GetTypeId()) as PipingSystemType
: connector.PipeSystemType switch
{
//循环供水
PipeSystemType.SupplyHydronic
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.SupplyHydronic
),
//循环回水
PipeSystemType.ReturnHydronic
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.ReturnHydronic
),
//卫生设备
PipeSystemType.Sanitary
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.Sanitary
),
//家用热水
PipeSystemType.DomesticHotWater
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.DomesticHotWater
),
//家用冷水
PipeSystemType.DomesticColdWater
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.DomesticColdWater
),
//其他
PipeSystemType.OtherPipe
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.OtherPipe
),
//湿式消防系统
PipeSystemType.FireProtectWet
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.FireProtectWet
),
//干式消防系统
PipeSystemType.FireProtectDry
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.FireProtectDry
),
//预作用消防系统
PipeSystemType.FireProtectPreaction
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.FireProtectPreaction
),
//其他消防系统
PipeSystemType.FireProtectOther
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.FireProtectOther
),
//通风孔
PipeSystemType.Vent
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.Vent
),
_
=> pipingSystemTypes.FirstOrDefault(
pst =>
pst.SystemClassification
== Autodesk.Revit.DB.MEPSystemClassification.OtherPipe
)
};
element = Pipe.Create(Document, pipingSystemType.Id, pipeTypeId, levelId, origin, xyz2);
element.get_Parameter(BuiltInParameter.RBS_PIPE_DIAMETER_PARAM).Set(connector.Radius * 2);
break;
//电力
case Domain.DomainCableTrayConduit:
if (cableTrayTypeId == ElementId.InvalidElementId)
{
cableTrayTypeId = new FilteredElementCollector(Document)
.OfClass(typeof(CableTrayType))
.FirstElementId();
}
if (conduitTypeId == ElementId.InvalidElementId)
{
conduitTypeId = new FilteredElementCollector(Document)
.OfClass(typeof(ConduitType))
.FirstElementId();
}
//switch (connector.ElectricalSystemType)
//{
// //电力
// case ElectricalSystemType.PowerCircuit:
// break;
//}
switch (connector.Shape)
{
case ConnectorProfileType.Invalid:
break;
case ConnectorProfileType.Round:
element = Conduit.Create(Document, conduitTypeId, origin, xyz2, levelId);
break;
case ConnectorProfileType.Rectangular:
element = CableTray.Create(Document, cableTrayTypeId, origin, xyz2, levelId);
break;
case ConnectorProfileType.Oval:
break;
}
break;
}
Document.Regenerate();
//新建管线连接
if (element == null)
{
continue;
}
var curve = element as MEPCurve;
var manager = curve.ConnectorManager;
var lter = manager.UnusedConnectors.ForwardIterator();
while (lter.MoveNext())
{
var connect = lter.Current as Connector;
if (connect.Origin.IsAlmostEqualTo(connector.Origin))
{
var conn = elem.GetConnectors(true).GetNearestConnector(connector.Origin);
conn.ConnectTo(connect);
break;
}
}
}
}
},
"引出短管"
);
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
Result = Result.Cancelled;
}
}
}

View File

@@ -0,0 +1,79 @@
using System.Windows;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// Revit执行命令
/// </summary>
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class BreakMEPCurveCmd : ExternalCommand
{
public override void Execute()
{
try
{
while (true)
{
var filter1 = new FuncFilter(
e => e is MEPCurve and not InsulationLiningBase and not FlexDuct and not FlexPipe);
var refer1 = UiDocument.Selection.PickObject(
ObjectType.PointOnElement, filter1
, "拾取打断管线的位置");
var pointPicked = refer1.GlobalPoint;
var mepCurve = UiDocument.Document.GetElement(refer1) as MEPCurve;
var loc = mepCurve.GetLocCurve() as Line;
var d = loc.Direction;
var halfGap = 20 / 304.8;
var point1 = pointPicked - d * halfGap;
var point2 = pointPicked + d * halfGap;
var breakPoint1 = mepCurve.GetLocCurve().Project(point1).XYZPoint;
var breakPoint2 = mepCurve.GetLocCurve().Project(point2).XYZPoint;
if (!loc.IsInsideEx(breakPoint1, halfGap) || !loc.IsInsideEx(breakPoint2, halfGap))
{
MessageBox.Show("打断点距离管线端点太近");
continue;
}
//var direction = breakPoint2 - breakPoint1;
//var flag = direction.Normalize().IsAlmostEqualTo(loc.Direction);
Document.Invoke(_ =>
{
var second = mepCurve.BreakByPoint(breakPoint1);
var third = mepCurve.BreakByPoint(breakPoint2);
Document.Delete(third);
//if (flag)
//{
// var second = mepCurve.BreakByPoint(breakPoint1);
// var third = mepCurve.BreakByPoint(breakPoint2);
// Document.Delete(third);
//}
//else
//{
// var second = mepCurve.BreakByPoint(breakPoint2);
// //var mepCurve2 = Document.GetElement(second) as MEPCurve;
// var third = mepCurve.BreakByPoint(breakPoint1);
// Document.Delete(third);
//}
}, "打断管线");
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
}
}
}

View File

@@ -0,0 +1,108 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class CableLayoutCmd : ExternalCommand
{
private readonly List<Element> elems = new();
public override void Execute()
{
CableLayoutView view = new(Document);
if (view.ShowDialog() != true)
{
return;
}
var count = view.ViewModel.Count;
var conduitType = view.ViewModel.SelectedConduitType;
var size = view.ViewModel.Size;
Document.Invoke(
_ =>
{
//Reference refer = UiDocument.Selection.PickObject(ObjectType.Element, new TypeSelectionFilter<CableTray>(), "请选择桥架");
//var ct = UiDocument.Document.GetElement(refer) as CableTray;
var refer = UiDocument.Selection.PickObject(ObjectType.Element, new GenericFilter<CableTray>(), "请选择需要敷设桥架");
var ct = UiDocument.Document.GetElement(refer) as MEPCurve;
var xyz = refer.GlobalPoint;
//Curve curve = (ct.Location as LocationCurve).Curve;
//XYZ project = curve.ProjectOf(xyz).XYZPoint;
elems.Add(ct);
var connector = ct.ConnectorManager.Connectors.GetNearestConnector(xyz);
connector.GetAllRefsElements(elems);
Dictionary<int, List<Conduit>> dictionary = new();
List<Conduit> firstConduits = new();
var interval = size.OuterDiameter;
foreach (var elem in elems)
{
if (elem is MEPCurve mepCurve)
{
var conduit = Conduit.Create(
Document,
conduitType.Id,
mepCurve.GetLocCurve().GetEndPoint(0),
mepCurve.GetLocCurve().GetEndPoint(1),
mepCurve.LevelId
);
firstConduits.Add(conduit);
}
}
dictionary.Add(0, firstConduits);
if (count > 2)
{
for (var i = 0; i < count; i++)
{
List<Conduit> conduits = new();
var x = Math.Pow(-1, i);
var y = Math.Ceiling(i / 2.0);
var offest = x * y * interval;
if (count % 2 == 0)
{
offest += interval / 2;
}
foreach (var conduit in firstConduits)
{
var line = conduit.GetLocCurve() as Line;
var direction = line!.Direction.CrossProduct(XYZ.BasisZ);
var id = ElementTransformUtils.CopyElement(Document, conduit.Id, direction * offest).FirstOrDefault();
var conduitCopied = Document.GetElement(id) as Conduit;
conduits.Add(conduitCopied);
}
dictionary.Add(i + 1, conduits);
}
Document.Regenerate();
}
for (var i = 0; i < dictionary.Count; i++)
{
var conduits = dictionary[i];
for (var j = 0; j < conduits.Count - 1; j++)
{
var conduit = conduits[j];
var conduit1 = conduits[j + 1];
var list = ConnectorAssist.GetNearestConnectors(conduit, conduit1);
if (list[0] != null && list[1] != null)
{
Document.Create.NewElbowFitting(list[0], list[1]);
}
}
}
if (count % 2 == 0)
{
Document.Delete(firstConduits.Select(e => e.Id).ToList());
}
},
"敷设电缆"
);
}
}

View File

@@ -0,0 +1,50 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.CableLayoutView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:electricDesign="clr-namespace:Sai.RvKits.RvMEP"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="线缆布置"
Width="220"
Height="200"
MinWidth="220"
d:DataContext="{d:DesignInstance Type=electricDesign:CableLayoutViewModel}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<ex:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding Specifications}"
PlaceholderText="线缆型号"
SelectedItem="{Binding SelectedConduitType, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="SelectionChanged">
<b:InvokeCommandAction Command="{Binding SelectionTypeCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</ex:ComboBoxEx>
<ex:ComboBoxEx
DisplayMemberPath="Key"
ItemsSource="{Binding Sizes}"
PlaceholderText="线缆线径"
SelectedValue="{Binding Size, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value" />
<ui:TextBox
InputMethod.IsInputMethodEnabled="False"
PlaceholderText="线缆数量"
Text="{Binding Count, UpdateSourceTrigger=PropertyChanged}" />
<Button HorizontalAlignment="Stretch" Content="布置">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Click">
<b:InvokeCommandAction Command="{Binding CloseWinCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</Button>
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,19 @@
using System.Windows;
using Autodesk.Revit.DB;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// CableLayoutView.xaml 的交互逻辑
/// </summary>
public partial class CableLayoutView
{
public CableLayoutViewModel ViewModel = null;
public CableLayoutView(Document doc)
{
ViewModel = new CableLayoutViewModel(doc);
DataContext = ViewModel;
InitializeComponent();
}
}

View File

@@ -0,0 +1,80 @@
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Input;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Sai.RvKits.RvMEP;
public partial class CableLayoutViewModel : ObservableValidator
{
private readonly Document doc;
public CableLayoutViewModel(Document doc)
{
this.doc = doc;
specifications = new FilteredElementCollector(doc).OfClass(typeof(ConduitType)).Cast<ConduitType>().ToList();
}
[ObservableProperty]
private ConduitType selectedConduitType;
[Range(1, 20, ErrorMessage = "输入值有误!")]
[ObservableProperty]
[NotifyDataErrorInfo]
private int count;
//public int Count
//{
// get => count;
// set => SetProperty(ref count, value, true);
//}
[ObservableProperty]
private List<ConduitType> specifications;
[ObservableProperty]
private ConduitSize size;
[ObservableProperty]
private Dictionary<string, ConduitSize> sizes;
[RelayCommand]
private void CloseWin(object obj)
{
if (obj is System.Windows.Window window && Count > 0 && Size != null && SelectedConduitType != null)
{
window.DialogResult = true;
}
}
[RelayCommand]
private void SelectionType()
{
var enumerator = ConduitSizeSettings.GetConduitSizeSettings(doc).GetEnumerator();
var standardName = SelectedConduitType.get_Parameter(BuiltInParameter.CONDUIT_STANDARD_TYPE_PARAM).AsValueString();
ConduitSizes sizes = null;
Dictionary<string, ConduitSize> dictionary = new();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current.Key == standardName)
{
sizes = current.Value;
break;
}
}
if (sizes != null)
{
foreach (var size in sizes)
{
var key = (size.NominalDiameter * 304.8).ToString();
dictionary.Add(key, size);
}
}
Sizes = dictionary;
}
}

View File

@@ -0,0 +1,19 @@
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Core.Helpers;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// Revit执行命令
/// </summary>
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ClashReportCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<ClashReportView>(new ClashReportViewModel(UiApplication));
}
}

View File

@@ -0,0 +1,142 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.ClashReportView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="碰撞报告"
Width="700"
Height="450"
MinHeight="450"
d:DataContext="{d:DesignInstance Type=local:ClashReportViewModel}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<ex:AutoGrid
ChildMargin="5"
Columns="*"
Rows="*,Auto">
<DataGrid
AutoGenerateColumns="True"
CanUserAddRows="True"
IsReadOnly="True"
ItemsSource="{Binding CurrentViewItems.DefaultView}"
VerticalScrollBarVisibility="Visible">
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseDoubleClick">
<b:InvokeCommandAction Command="{Binding LocationCommand}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}, Mode=FindAncestor}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<DataGrid.Template>
<ControlTemplate>
<Border
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ScrollViewer
x:Name="DG_ScrollViewer"
CanContentScroll="True"
Focusable="false">
<b:Interaction.Triggers>
<b:EventTrigger EventName="ScrollChanged">
<b:InvokeCommandAction Command="{Binding LvScrollCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ScrollViewer}, Mode=FindAncestor}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button
Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Command="{x:Static DataGrid.SelectAllCommand}"
Focusable="false"
Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle,
TypeInTargetAssembly={x:Type DataGrid}}}"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<DataGridColumnHeadersPresenter
x:Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter"
Grid.Row="1"
Grid.ColumnSpan="2"
CanContentScroll="{TemplateBinding CanContentScroll}" />
<ScrollBar
x:Name="PART_VerticalScrollBar"
Grid.Row="1"
Grid.Column="2"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ScrollBar
x:Name="PART_HorizontalScrollBar"
Grid.Column="1"
Maximum="{TemplateBinding ScrollableWidth}"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<StackPanel IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</DataGrid.Template>
</DataGrid>
<ex:StackPanelEx
Grid.Row="1"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="5">
<StackPanel Orientation="Horizontal">
<CheckBox
Content="当前视图"
IsChecked="{Binding InCurrentView, UpdateSourceTrigger=PropertyChanged}"
ToolTip="在当前视图中去定位" />
<CheckBox
Content="修改剖面框"
IsChecked="{Binding IsSetSectionBox, UpdateSourceTrigger=PropertyChanged}"
ToolTip="只选中构件,不修改剖面框" />
</StackPanel>
<Button
Width="75"
Command="{Binding OpenFileCommand}"
Content="打开" />
<Button
Width="75"
Command="{Binding RefreshCommand}"
Content="刷新" />
</ex:StackPanelEx>
</ex:AutoGrid>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,13 @@
namespace Sai.RvKits.RvMEP;
/// <summary>
/// ClashReportView.xaml 的交互逻辑
/// </summary>
public partial class ClashReportView
{
public ClashReportView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,312 @@
using System.Data;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HtmlAgilityPack;
using Microsoft.Win32;
using Nice3point.Revit.Toolkit.External.Handlers;
namespace Sai.RvKits.RvMEP;
public partial class ClashReportViewModel(UIApplication uiapp) : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(RefreshCommand))]
private string filePathName;
public UIApplication UiApp { get; set; } = uiapp;
[ObservableProperty]
private bool inCurrentView = true;
[ObservableProperty]
private bool isSetSectionBox;
public DataTable DataSource { get; set; } = new DataTable();
[ObservableProperty]
private DataTable currentViewItems = new();
[RelayCommand]
private void LvScroll(ScrollViewer scrollViewer)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (!isLoadingData && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight == scrollViewer.ExtentHeight)//必须等于
{
LoadMoreData();
}
}
private const int VbBatchSize = 20;
private bool isLoadingData;
private void LoadInitialData()
{
isLoadingData = true;
CurrentViewItems.Clear();
var size = VbBatchSize;
if (DataSource.Rows.Count < VbBatchSize)
{
size = DataSource.Rows.Count;
}
for (var i = 0; i < size; i++)
{
CurrentViewItems.ImportRow(DataSource.Rows[i]);
}
//CurrentViewItems = CurrentViewItems;
isLoadingData = false;
}
private void LoadMoreData()
{
isLoadingData = true;
var startIndex = CurrentViewItems.Rows.Count;
var end = startIndex + VbBatchSize;
if (end > DataSource.Rows.Count)
{
end = DataSource.Rows.Count;
}
if (startIndex < end + 1)
{
for (var i = startIndex; i < end; i++)
{
CurrentViewItems.ImportRow(DataSource.Rows[i]);
}
}
isLoadingData = false;
}
private void LoadHtmlTableData(string filePath)
{
var htmlContent = ReadLocalHtmlFile(filePath);
DataSource = ParseHtmlToPairs(htmlContent);
LoadInitialData();
}
//private async Task<string> GetHtmlContent(string url)
//{
// try
// {
// using (HttpClient httpClient = new HttpClient())
// {
// HttpResponseMessage response = await httpClient.GetAsync(url);
// response.EnsureSuccessStatusCode();
// return await response.Content.ReadAsStringAsync();
// }
// }
// catch (Exception ex)
// {
// MessageBox.Show("Error while fetching HTML content: " + ex.ViewMessage);
// return null;
// }
//}
private DataTable ParseHtmlToPairs(string html)
{
DataTable dataTable = new();
try
{
HtmlDocument doc = new();
doc.LoadHtml(html);
var tableNode = doc.DocumentNode.SelectSingleNode("//table");
if (tableNode != null)
{
var rows = tableNode.SelectNodes("tr");
for (var i = 0; i < rows.Count; i++)
{
if (i == 0)
{
var row = rows[i].SelectNodes("td");
foreach (var cell in row)
{
dataTable.Columns.Add(cell.InnerText == string.Empty ? WebUtility.HtmlDecode("序号") : WebUtility.HtmlDecode(cell.InnerText.Trim()));
CurrentViewItems = dataTable.Copy();
}
}
else
{
var dataRow = dataTable.NewRow();
var columnIndex = 0;
foreach (var cell in rows[i].SelectNodes("td"))
{
dataRow[columnIndex] = WebUtility.HtmlDecode(cell.InnerText.Trim());
columnIndex++;
}
dataTable.Rows.Add(dataRow);
}
}
}
else
{
MessageBox.Show("html中未发现表格。");
}
}
catch (Exception ex)
{
MessageBox.Show("解析html表格数据时出错" + ex.Message);
return null;
}
return dataTable;
}
private string ReadLocalHtmlFile(string filePath)
{
try
{
return File.ReadAllText(filePath);
}
catch (Exception ex)
{
MessageBox.Show("读取本地html文件时出错" + ex.Message);
return null;
}
}
private readonly ActionEventHandler handler = new();
[RelayCommand]
private void Location(object obj)
{
var rowView = obj as DataRowView;
var dr = rowView?.Row;
var uidoc = UiApp.ActiveUIDocument;
var doc = uidoc.Document;
var ids = GetElementIds(dr);
uidoc.Selection.SetElementIds(ids);
var view = doc.ActiveView;
if (!ids.Any())
{
return;
}
if (view is View3D view3D)
{
handler.Raise(_ => doc.Invoke(ts =>
{
if (IsSetSectionBox)
{
view = view3D.SectionBoxElements(ids, InCurrentView);
ts.Commit();
if (UiApp.ActiveUIDocument.ActiveView.Id != view.Id)
{
UiApp.ActiveUIDocument.ActiveView = view;
}
//var box = elem.get_BoundingBox(UiDocument.ActiveGraphicalView);
var box = view3D.GetSectionBox();
var uiView = uidoc.GetOpenUIViews().FirstOrDefault(v => v.ViewId == uidoc.ActiveGraphicalView.Id);
uiView.ZoomAndCenterRectangle(box.Min, box.Max);
}
else
{
view.ZoomElement(uidoc, ids.FirstOrDefault());
}
}, "定位碰撞元素"));
}
//RevitCommandId revitCommandId = RevitCommandId.LookupPostableCommandId(PostableCommand.SelectionBox);
//try
//{
// uiapp.PostCommand(revitCommandId);
//}
//catch (Exception)
//{
// throw;
//}
//RevitCommandId.LookupCommandId()
//List<ElementId> ids = GetElementIds(dr);
//ElementId id = null;
//if (uiapp.ActiveUIDocument.Document.GetElement(ids[0]).IsValidObject)
//{
// id = ids[0];
//}
//else
//{
// ids.Remove(ids[0]);
// if (uiapp.ActiveUIDocument.Document.GetElement(ids[1]).IsValidObject)
// {
// id = ids[1];
// }
// else
// {
// ids.Remove(ids[1]);
// }
//}
//if (id != null)
//{
// uiapp.ActiveUIDocument.ShowElements(id);
//}
//var index = str.IndexOf("ID ");
//var elementId1 = str.Substring(index,)
}
/// <summary>
/// 获取当前文档的有效元素ID
/// </summary>
/// <param name="dr"></param>
/// <returns></returns>
private List<ElementId> GetElementIds(DataRow dr)
{
var list = new List<ElementId>();
var str = dr[1].ToString();
var idStr = str.Split(' ').Last();
#if REVIT2018 || REVIT2020
var id = new ElementId(int.Parse(idStr));
#elif REVIT2025
var id = new ElementId(long.Parse(idStr));
#endif
var str1 = dr[2].ToString();
var idStr1 = str1.Split(' ').Last();
#if REVIT2018 || REVIT2020
var id1 = new ElementId(int.Parse(idStr1));
#elif REVIT2025
var id1 = new ElementId(long.Parse(idStr1));
#endif
var e = UiApp.ActiveUIDocument.Document.GetElement(id);
var e1 = UiApp.ActiveUIDocument.Document.GetElement(id1);
if (e is { IsValidObject: true } && !e.Document.IsLinked)
{
list.Add(id);
}
if (e1 is { IsValidObject: true } && !e1.Document.IsLinked)
{
list.Add(id1);
}
return list;
}
[RelayCommand(CanExecute = nameof(CanRefresh))]
private void Refresh()
{
CurrentViewItems.Clear();
LoadHtmlTableData(FilePathName);
}
private bool CanRefresh => !string.IsNullOrEmpty(FilePathName);
[RelayCommand]
private void OpenFile()
{
var fileOpenDialog = new OpenFileDialog()
{
Filter = "Html文件 (*.html)|*.html",
Multiselect = false,
};
if (fileOpenDialog.ShowDialog() == true)
{
FilePathName = fileOpenDialog.FileName;
LoadHtmlTableData(FilePathName);
}
}
}

View File

@@ -0,0 +1,17 @@
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Core.Helpers;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ClashResolveCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<ClashResolveView>(new ClashResolveViewModel());
}
}

View File

@@ -0,0 +1,122 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.ClashResolveView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="避让处理"
Width="320"
Height="450"
MinWidth="320"
MinHeight="480"
d:DataContext="{d:DesignInstance Type=local:ClashResolveViewModel}"
mc:Ignorable="d">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Closing">
<b:InvokeCommandAction Command="{Binding ClosingCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<GroupBox Header="角度">
<UniformGrid Columns="3">
<!--<ui:CardColor />-->
<RadioButton Content="15°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>15</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="22.5°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>22.5</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="30°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>30</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="45°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>45</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="60°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>60</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="90°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<sys:Double>90</sys:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton x:Name="LbCustom" Content="自定义" />
<TextBox IsEnabled="{Binding IsChecked, ElementName=LbCustom}" Text="{Binding Angle, UpdateSourceTrigger=PropertyChanged}" />
</UniformGrid>
</GroupBox>
<ex:StackPanelEx Orientation="Horizontal" Spacing="5">
<GroupBox ex:StackPanelEx.Fill="Fill" Header="参考方式">
<UniformGrid Rows="1">
<RadioButton Content="手动" IsChecked="{Binding LocationType, ConverterParameter={x:Static local:LocationType.Manual}, Converter={StaticResource ComparisonConverter}}" />
<RadioButton Content="参考" IsChecked="{Binding LocationType, ConverterParameter={x:Static local:LocationType.Reference}, Converter={StaticResource ComparisonConverter}}" />
</UniformGrid>
</GroupBox>
<GroupBox ex:StackPanelEx.Fill="Fill" Header="调整方式">
<UniformGrid Rows="1">
<RadioButton Content="单侧" IsChecked="{Binding AdjustType, ConverterParameter={x:Static local:AdjustType.OneSide}, Converter={StaticResource ComparisonConverter}}" />
<RadioButton Content="双侧" IsChecked="{Binding AdjustType, ConverterParameter={x:Static local:AdjustType.TwoSide}, Converter={StaticResource ComparisonConverter}}" />
</UniformGrid>
</GroupBox>
</ex:StackPanelEx>
<GroupBox Grid.Row="2" Header="调整方向">
<UniformGrid Rows="1">
<RadioButton Content="上" IsChecked="{Binding AdjustDirection, ConverterParameter={x:Static local:AdjustDirection.Up}, Converter={StaticResource ComparisonConverter}}" />
<RadioButton Content="下" IsChecked="{Binding AdjustDirection, ConverterParameter={x:Static local:AdjustDirection.Down}, Converter={StaticResource ComparisonConverter}}" />
<RadioButton Content="左" IsChecked="{Binding AdjustDirection, ConverterParameter={x:Static local:AdjustDirection.Left}, Converter={StaticResource ComparisonConverter}}" />
<RadioButton Content="右" IsChecked="{Binding AdjustDirection, ConverterParameter={x:Static local:AdjustDirection.Right}, Converter={StaticResource ComparisonConverter}}" />
</UniformGrid>
</GroupBox>
<ex:TextBoxEx
ex:StackPanelEx.Fill="Fill"
Prefix="偏移量:"
Suffix="mm"
Text="{Binding Offset, UpdateSourceTrigger=PropertyChanged}" />
<Button
HorizontalAlignment="Stretch"
ex:StackPanelEx.Fill="Fill"
Command="{Binding ResolveCommand}"
Content="调整" />
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,37 @@
using System.Windows;
using Wpf.Ui.Appearance;
namespace Sai.RvKits.RvMEP
{
/// <summary>
/// ClashResolveView.xaml 的交互逻辑
/// </summary>
public partial class ClashResolveView
{
public ClashResolveView()
{
InitializeComponent();
//ApplicationThemeManager.Apply(this);
//ApplicationThemeManager.Changed -= ApplicationThemeManager_Changed;
//ApplicationThemeManager.Changed += ApplicationThemeManager_Changed;
}
//private void ApplicationThemeManager_Changed(ApplicationTheme currentApplicationTheme, Color systemAccent)
//{
// ApplicationThemeManager.Apply(this);
//}
//private void Button_Click(object sender, RoutedEventArgs e)
//{
// if (ApplicationThemeManager.GetAppTheme()==ApplicationTheme.Light)
// {
// ApplicationThemeManager.Apply(ApplicationTheme.Dark);
// }
// else
// {
// ApplicationThemeManager.Apply(ApplicationTheme.Light);
// }
//}
}
}

View File

@@ -0,0 +1,479 @@
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;
namespace Sai.RvKits.RvMEP;
public partial class ClashResolveViewModel : ObservableObject
{
[ObservableProperty]
private AdjustDirection adjustDirection;
[ObservableProperty]
private AdjustType adjustType = AdjustType.OneSide;
/// <summary>
/// 正在执行命令
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ResolveCommand))]
private bool canRunning = true;
[ObservableProperty]
private double angle = 90.0;
[ObservableProperty]
private double offset = 800;
[ObservableProperty]
private LocationType locationType = LocationType.Manual;
public ActionEventHandler ActionEventHandler { get; } = new();
[RelayCommand]
private void Closing()
{
//关闭窗口退出选择
if (!CanRunning)
{
KeyIntPtrHelper.ActionEsc();
}
}
[RelayCommand(CanExecute = nameof(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 =>
{
switch (LocationType)
{
case LocationType.Manual:
{
var reference1 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(
e =>
e is MEPCurve mepCurve and not InsulationLiningBase
&& mepCurve.GetLocCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
),
"请选择翻弯的管线上的点"
);
mepCurveToBend = doc.GetElement(reference1) as MEPCurve;
var reference2 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(e => e.Id == mepCurveToBend!.Id),
"请选择另一个翻弯管线上的点或确定单侧翻弯的需要偏移一侧"
);
//两个断点,breakPoint1距离原直线起点近反向时交换值
breakPoint1 = mepCurveToBend.GetLocCurve().Project(reference1.GlobalPoint).XYZPoint;
breakPoint2 = mepCurveToBend.GetLocCurve().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.GetLocCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
),
"请选择参照的管线"
);
//参考的管线定位
var referenceMEPCurve = doc.GetElement(reference);
var referenceLine = referenceMEPCurve.GetLocCurve() as Line;
var reference1 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(
e =>
e is MEPCurve mepCurve and not InsulationLiningBase
&& mepCurve.GetLocCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
&& e.Id != referenceMEPCurve!.Id
),
"请选择翻弯的管线或确定单侧翻弯的需要偏移一侧"
);
//翻弯的管线定位
mepCurveToBend = doc.GetElement(reference1) as MEPCurve;
var bendLine = mepCurveToBend.GetLocCurve() 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<ClosestPointsPairBetweenTwoCurves> 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.GetLocCurve() 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<Connector>().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.GetLocCurve() 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.GetLocCurve();
//用于判断交点是否在偏移后的直线内,避免生成不了
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;
}
}
//if (originOffsetConnector != null && offsetIntersectPoint.DistanceTo(originOffsetConnector.Origin) > minLength)
if (originOffsetConnector != null)
{
var finalOffsetLine = Line.CreateBound(
offsetIntersectPoint,
originOffsetConnector.Origin
);
baseMepCurve2.SetLocationCurve(finalOffsetLine);
}
try
{
var conn1 = ConnectorAssist.GetNearestConnectors(
baseMepCurve1,
angleMepCurve
);
doc.Create.NewElbowFitting(conn1[0], conn1[1]);
var conn2 = ConnectorAssist.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.GetLocCurve() 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 = ConnectorAssist.GetNearestConnectors(
baseMepCurve1,
firstMepCurve
);
doc.Create.NewElbowFitting(connectors1[0], connectors1[1]);
var connectors4 = ConnectorAssist.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 = ConnectorAssist.GetNearestConnectors(
firstMepCurve,
secondMepCurve
);
doc.Create.NewElbowFitting(connectors2[0], connectors2[1]);
var connectors3 = ConnectorAssist.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;
}
},
"碰撞处理"
);
CanRunning = true;
});
}
}
public enum LocationType
{
Manual,
Reference
}
public enum AdjustType
{
TwoSide,
OneSide
}
public enum AdjustDirection
{
Up,
Down,
Left,
Right
}

View File

@@ -0,0 +1,138 @@
using System.Security.Cryptography;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class CorrectMEPCurveSlopeCmd : ExternalCommand
{
public override void Execute()
{
var mepCurves = UiDocument.SelectObjectsByRectangle<MEPCurve>();
if (mepCurves == null)
{
return;
}
Document.Invoke(_ =>
{
foreach (var mepCurve in mepCurves)
{
if (!IsErrorMEPCurve(mepCurve))
{
continue;
}
var loc = mepCurve.Location as LocationCurve;
var line = loc.Curve as Line;
var endPoint = line.GetEndPoint(0);
var endPoint1 = line.GetEndPoint(1);
Line unboundLine;
Line l;
var xoy1 = endPoint.Flatten();
var xoy2 = endPoint1.Flatten();
var tan = Math.Abs(endPoint.Z - endPoint1.Z) / xoy1.DistanceTo(xoy2);
var dir = -XYZ.BasisZ;
if (endPoint.Z > endPoint1.Z)//以高点作为基准,修改低点
{
if (tan > 1)
{
unboundLine = Line.CreateUnbound(endPoint, dir);
var startPoint = unboundLine.Project(endPoint1).XYZPoint;
l = Line.CreateBound(endPoint, startPoint);
}
else
{
dir = xoy2 - xoy1;
unboundLine = Line.CreateUnbound(endPoint, dir);
var startPoint = unboundLine.Project(endPoint1).XYZPoint;
l = Line.CreateBound(endPoint, startPoint);
}
}
else
{
if (tan > 1)
{
unboundLine = Line.CreateUnbound(endPoint1, dir);
var startPoint = unboundLine.Project(endPoint).XYZPoint;
l = Line.CreateBound(startPoint, endPoint1);
}
else
{
dir = xoy2 - xoy1;
unboundLine = Line.CreateUnbound(endPoint1, dir);
var startPoint = unboundLine.Project(endPoint).XYZPoint;
l = Line.CreateBound(startPoint, endPoint1);
}
}
loc.Curve = l;
}
}, "修正坡度");
}
private void ModifyMEPCurve(MEPCurve mepCurve)
{
var loc = mepCurve.Location as LocationCurve;
var line = loc.Curve as Line;
var dir = line.Direction;
var newDir = new XYZ(dir.X.Rounding(), dir.Y.Rounding(), dir.Z.Rounding());
var conns = mepCurve.ConnectorManager.Connectors.OfType<Connector>().ToList();
//全部连接
var allConnector = conns.All(conn => conn.IsConnected);
//全部未连接
var allNotConnector = conns.All(conn => !conn.IsConnected);
Line newLine = null;
if (allConnector || conns.ElementAt(0).IsConnected || allNotConnector)
{
var end = line.GetEndPoint(0) + newDir * line.Length;
newLine = Line.CreateBound(line.GetEndPoint(0), end);
}
else
{
var start = line.GetEndPoint(1) - newDir * line.Length;
newLine = Line.CreateBound(start, line.GetEndPoint(1));
}
Document.Invoke(
_ =>
{
loc.Curve = newLine;
}, "修正定位线");
}
private static bool IsErrorMEPCurve(MEPCurve mepCurve)
{
var isError = false;
switch (mepCurve)
{
case Pipe:
{
var param = mepCurve.get_Parameter(BuiltInParameter.RBS_PIPE_SLOPE);
isError = param.HasValue && ((param.AsDouble() < 0.025 && param.AsDouble() > 0) || param.AsDouble() > 0.055); //坡度过大或过小时
break;
}
case Duct:
{
var param = mepCurve.get_Parameter(BuiltInParameter.RBS_DUCT_SLOPE);
isError = param.HasValue && ((param.AsDouble() < 0.025 && param.AsDouble() > 0) || param.AsDouble() > 0.055); //坡度过大或过小时
break;
}
case Conduit or CableTray:
{
var p1 = mepCurve.get_Parameter(BuiltInParameter.RBS_START_OFFSET_PARAM).AsDouble();
var p2 = mepCurve.get_Parameter(BuiltInParameter.RBS_END_OFFSET_PARAM).AsDouble();
var l = mepCurve.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble();
var sin = Math.Abs(p1 - p2) / l;
var radian = Math.Asin(sin);
var slope = Math.Tan(radian);
isError = slope is (> 0 and < 0.025) or > 0.055;
break;
}
}
return isError;
}
}

View File

@@ -0,0 +1,44 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Revit.Helpers;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class DisconnectCmd : ExternalCommand
{
public override void Execute()
{
try
{
var filter = new FuncFilter(e => e is MEPCurve || (e is FamilyInstance instance && instance.MEPModel.ConnectorManager != null));
var reference1 = UiDocument.Selection.PickObject(Autodesk.Revit.UI.Selection.ObjectType.Element, filter, "请选择需要断开连接的第一个设备或管线");
var reference2 = UiDocument.Selection.PickObject(Autodesk.Revit.UI.Selection.ObjectType.Element, filter, "请选择需要断开连接的第二个设备或管线");
var element1 = Document.GetElement(reference1);
var element2 = Document.GetElement(reference2);
Document.Invoke(
_ =>
{
if (element1.Id == element2.Id)
{
return;
}
foreach (Connector conn in element1.GetConnectors(false))
{
var connectedConn = conn.GetConnectedConnector();
if (connectedConn != null && connectedConn.Owner.Id == element2.Id)
{
conn.DisconnectFrom(connectedConn);
}
}
},
"取消连接"
);
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException) { }
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using CommunityToolkit.Mvvm.DependencyInjection;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class FlipWorkplaneCmd : ExternalCommand
{
public override void Execute()
{
ICollection<FamilyInstance> familyInstances;
if (UiDocument.Selection.GetElementIds().Count > 0)
{
familyInstances = UiDocument.Selection.GetElementIds().Select(Document.GetElement).OfType<FamilyInstance>().ToList();
}
else
{
familyInstances = UiDocument.SelectObjects<FamilyInstance>("请选择族实例");
}
Document.Invoke(
ts =>
{
foreach (var ins in familyInstances)
{
if (ins.CanFlipWorkPlane)
{
ins.IsWorkPlaneFlipped = !ins.IsWorkPlaneFlipped;
}
}
});
}
}
}

View File

@@ -0,0 +1,121 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Plumbing;
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Revit.Helpers;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ForceConnectCmd : ExternalCommand
{
public override void Execute()
{
using Transaction trans = new(Document, "强制连接系统");
try
{
while (true)
{
var pipeList = UiDocument.Selection.PickElementsByRectangle(new GenericFilter<Pipe>()).Cast<Pipe>().ToList();
try
{
trans.Start();
if (pipeList.Count == 2)
{
var pipe1 = pipeList[0];
var pipe2 = pipeList[1];
var l1 = (pipe1.Location as LocationCurve)?.Curve as Line;
var l2 = (pipe2.Location as LocationCurve)?.Curve as Line;
var l3 = l2.Clone();
var p1 = l1.Origin;
var projectPoint = l2.Project(p1).XYZPoint;
var collection1 = pipe1.ConnectorManager.UnusedConnectors;
var collection2 = pipe2.ConnectorManager.UnusedConnectors;
var di = double.MaxValue;
var conns = new List<Connector>();
foreach (Connector connector in collection1)
{
foreach (Connector connector1 in collection2)
{
var temp = connector.Origin.DistanceTo(connector1.Origin);
if (temp < di)
{
conns.Clear();
di = temp;
conns.Add(connector);
conns.Add(connector1);
}
}
}
Document.Create.NewElbowFitting(conns[0], conns[1]);
//conns[0].ConnectTo(conns[1]);
//var comparisonResult= l1.Intersect(l3, out var result);
// if (comparisonResult == SetComparisonResult.Overlap)
// {
// var p = result.get_Item(0).XYZPoint;
// }
}
if (pipeList.Count == 3)
{
var pipe1 = pipeList[0];
var pipe2 = pipeList[1];
var pipe3 = pipeList[2];
var l1 = (pipe1.Location as LocationCurve).Curve as Line;
var l2 = (pipe2.Location as LocationCurve).Curve as Line;
var l3 = (pipe3.Location as LocationCurve).Curve as Line;
if (l1.Direction.IsAlmostEqualTo(l2.Direction))
{
var collection1 = pipe1.ConnectorManager.UnusedConnectors;
var collection2 = pipe2.ConnectorManager.UnusedConnectors;
foreach (Connector connector in collection1)
{
foreach (Connector connector1 in collection2)
{
//var temp = connector.Origin.DistanceTo(connector1.Origin);
//if (temp < di)
//{
// conns.Clear();
// di = temp;
// conns.Add(connector);
// conns.Add(connector1);
//}
}
}
//doc.Create.NewTeeFitting();
}
}
trans.Commit();
}
catch (Exception)
{
trans.Commit();
return;
}
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
if (trans.GetStatus() == TransactionStatus.Started)
{
trans.Commit();
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
if (trans.GetStatus() == TransactionStatus.Started)
{
trans.Commit();
}
return;
}
}
}

View File

@@ -0,0 +1,151 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using Nice3point.Revit.Toolkit.External;
using Sai.RvKits.Windows;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// Revit执行命令
/// </summary>
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class HeadroomCheckCmd : ExternalCommand
{
private void CheckHeadroom(IEnumerable<MEPCurve> elems, double compareMetric, double minValueMetric, double minLengthMetric)
{
var messages = new List<MessageModel>();
var headroomValue = compareMetric / 304.8;
var ignoreBelowValue = minValueMetric / 304.8;
var minHeadroom = double.MaxValue;
var minLength = minLengthMetric / 304.8;
Element element = null;
foreach (var item in elems)
{
if (item.GetLocCurve().Length < minLength)
{
continue;
}
var conns = item.ConnectorManager.Connectors.OfType<Connector>().ToList();
var level = item.ReferenceLevel;
#if REVIT2018 || REVIT2020
if (conns.Any(conn => conn.GetConnectedConnector()?.Owner.Category.Id.IntegerValue == -2001140))
#elif REVIT2025
if (conns.Any(conn => conn.GetConnectedConnector()?.Owner.Category.Id.Value == -2001140))
#endif
{
continue;
}
var s = item.get_Parameter(BuiltInParameter.RBS_DUCT_SLOPE);
if (item is Duct duct && s.HasValue && Math.Atan(s.AsDouble()) < Math.PI / 3)
{
var p1 = item.get_Parameter(BuiltInParameter.RBS_OFFSET_PARAM).AsDouble();
var subtract = conns.FirstOrDefault()!.Shape switch
{
ConnectorProfileType.Round => duct.Diameter / 2,
ConnectorProfileType.Rectangular => duct.Height / 2,
ConnectorProfileType.Oval => duct.Height / 2,
_ => default
};
if (p1 - subtract < headroomValue && p1 - subtract > ignoreBelowValue)
{
var lowOffset = p1 - subtract;
if (lowOffset < minHeadroom)
{
minHeadroom = lowOffset;
element = item;
}
MessageModel checkElement = new(item, $"底高程: {Math.Round(lowOffset * 304.8, 1)} mm\n\r参照标高{level.Name}");
messages.Add(checkElement);
}
}
var p = item.get_Parameter(BuiltInParameter.RBS_PIPE_SLOPE);
if (item is Pipe pipe && p.HasValue && Math.Atan(p.AsDouble()) < Math.PI / 3)
{
var p1 = item.get_Parameter(BuiltInParameter.RBS_OFFSET_PARAM).AsDouble();
if (p1 - (pipe.Diameter / 2) < headroomValue && p1 - (pipe.Diameter / 2) > ignoreBelowValue)
{
var lowOffset = p1 - (pipe.Diameter / 2);
if (lowOffset < minHeadroom)
{
minHeadroom = lowOffset;
element = item;
}
MessageModel checkElement = new(item, $"底高程:{Math.Round(lowOffset * 304.8, 1)}mm\n\r参照标高{level.Name}");
messages.Add(checkElement);
}
}
if (item is Conduit or CableTray)
{
var p1 = item.get_Parameter(BuiltInParameter.RBS_START_OFFSET_PARAM).AsDouble();
var p2 = item.get_Parameter(BuiltInParameter.RBS_END_OFFSET_PARAM).AsDouble();
if (Math.Abs(p1 - p2) < 0.00001)
{
var p3 = item.get_Parameter(BuiltInParameter.RBS_CTC_BOTTOM_ELEVATION).AsDouble();
if (p3 < minHeadroom)
{
minHeadroom = p3;
element = item;
}
if (p3 < headroomValue && p3 > ignoreBelowValue)
{
MessageModel checkElement = new(item, $"底高程:{Math.Round(p3 * 304.8)}mm\n\r参照标高{level.Name}");
messages.Add(checkElement);
}
}
}
}
MessageViewModel viewmodel = new(UiDocument, messages, "净高检查结果");
if (element != null)
{
viewmodel.Footer = $"最低净高={Math.Round(minHeadroom * 304.8)}mm,元素:{element.Name},Id{element.Id}";
UiDocument.Selection.SetElementIds(new List<ElementId> { element.Id });
}
WinDialogHelper.ShowModeless<MessageWin>(viewmodel);
}
public override void Execute()
{
WinDialogHelper.ShowModeless<HeadroomCheckView>(new HeadroomCheckViewModel(UiApplication));
//try
//{
// InputMessageBox inputMessage = new("净高限制", "请输出净高要求高度mm");
// inputMessage.ShowDialog();
// if (inputMessage.DialogResult == true)
// {
// var elems = UiDocument.Selection
// .PickElementsByRectangle(new FuncFilter(e => e is MEPCurve, (_, _) => true))
// .OfType<MEPCurve>();
// if (double.TryParse(inputMessage.InputContent, out var headroomValue))
// {
// CheckHeadroom(elems, headroomValue, 1000, 300);
// }
// }
// else
// {
// var elem = UiDocument.SelectObject<DirectShape>();
// var clashFilter = new ElementIntersectsElementFilter(elem, false);
// var elems = Document.OfCollector().WherePasses(clashFilter).OfType<MEPCurve>();
// if (double.TryParse(inputMessage.InputContent, out var headroomValue))
// {
// CheckHeadroom(elems, headroomValue, 1000, 300);
// }
// }
//}
//catch (Autodesk.Revit.Exceptions.OperationCanceledException)
//{
//}
}
}

View File

@@ -0,0 +1,149 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.HeadroomCheckView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="净空检查"
Width="400"
Height="800"
d:DataContext="{d:DesignInstance local:HeadroomCheckViewModel}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid
Grid.Row="0"
Grid.Column="0"
Height="200"
Margin="5"
d:ItemsSource="{d:SampleData ItemCount=5}"
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding Rooms}"
SelectionMode="Extended">
<DataGrid.Columns>
<DataGridCheckBoxColumn
Width="60"
Binding="{Binding IsSelected}"
ElementStyle="{StaticResource DataGridCheckBoxElementDefaultStyle}"
Header="选择" />
<DataGridTextColumn
Binding="{Binding Name}"
Header="房间名称"
IsReadOnly="True" />
<DataGridTemplateColumn Header="颜色" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Color, Converter={StaticResource Revit2MediaColorConverter}}" ToolTip="生成三维房间时的颜色" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" />
<DataGrid
Grid.Row="2"
Grid.Column="0"
Height="340"
Margin="5"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
ItemsSource="{Binding ErrorModels}"
ToolTip="双击行可快速定位">
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseDoubleClick">
<b:InvokeCommandAction Command="{Binding ShowElementCommand}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}, Mode=FindAncestor}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Element.Category.Name}" Header="类别名称" />
<DataGridTextColumn Binding="{Binding Element.Name}" Header="元素名称" />
<DataGridTextColumn Binding="{Binding Level.Name}" Header="主体" />
<DataGridTextColumn Binding="{Binding Element.Id}" Header="元素Id" />
<DataGridTextColumn Binding="{Binding ErrorMessage}" Header="错误信息" />
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!-- 按钮参数绑定到当前行的绑定的Item -->
<Button
Command="{Binding DataContext.ShowElementCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}, Mode=FindAncestor}}"
CommandParameter="{Binding}"
Content="定位构件" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- 树状结构,按楼层=>专业=>管线 -->
<GridSplitter Grid.Row="3" HorizontalAlignment="Stretch" />
<ex:StackPanelEx
Grid.Row="4"
Grid.Column="0"
Margin="5"
Spacing="5">
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="排除高度低的管线(mm)" />-->
<ex:TextBoxEx
MinWidth="120"
Header="排除高度低的管线:"
HeaderPlacement="Left"
Suffix="mm"
Text="1000" />
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="排除长度短的管线(mm)" />-->
<ex:TextBoxEx
MinWidth="120"
Header="排除长度短的管线:"
HeaderPlacement="Left"
Suffix="mm"
Text="500" />
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="净高要求高度(mm)" />-->
<ex:TextBoxEx
MinWidth="120"
Header="净高要求高度:"
HeaderPlacement="Left"
Suffix="mm"
Text="2600" />
<Button
HorizontalAlignment="Stretch"
Command="{Binding AnalysisHeadroomCommand}"
Content="净高分析"
ToolTip="得到净高分析的平面以及不满足净高要求的管线" />
<!--<Button
Command="{Binding FindOverCommand}"
Content="查询管线"
ToolTip="根据净高要求查询" />-->
<Button
HorizontalAlignment="Stretch"
Command="{Binding ResetRoomHeightCommand}"
Content="重设高度"
ToolTip="重新设置房间高度为4000mm避免影响平面查看" />
<Button
HorizontalAlignment="Stretch"
Command="{Binding GenerateRoom3DCommand}"
Content="房间实体"
ToolTip="生成三维房间实体" />
</ex:StackPanelEx>
</Grid>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,17 @@
using System;
using System.Linq;
using System.Windows;
namespace Sai.RvKits.RvMEP
{
/// <summary>
/// HeadroomCheckView.xaml 的交互逻辑
/// </summary>
public partial class HeadroomCheckView
{
public HeadroomCheckView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,177 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.UI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
using RandomColorGenerator;
namespace Sai.RvKits.RvMEP;
public partial class HeadroomCheckViewModel : ObservableObject
{
private readonly UIApplication uiapp;
public HeadroomCheckViewModel(UIApplication uiapp)
{
this.uiapp = uiapp;
document = uiapp.ActiveUIDocument.Document;
Rooms = GetRoomItems();
checkHeadroom = new ActionEventHandler();
generateRoom3d = new ActionEventHandler();
}
private readonly ActionEventHandler checkHeadroom;
private readonly ActionEventHandler generateRoom3d;
private readonly Document document;
[ObservableProperty]
private List<RoomCheckItem> rooms;
private List<RoomCheckItem> GetRoomItems()
{
var roomItems = new List<RoomCheckItem>();
var allRooms = uiapp.ActiveUIDocument.Document.OfClass<SpatialElement>().OfType<Room>();
var groups = allRooms.GroupBy(r => r.Name);
IEnumerable<IGrouping<string, Room>> enumerable = groups as IGrouping<string, Room>[] ?? groups.ToArray();
var colors = GetRandomColors(enumerable.Count());
for (int i = 0; i < enumerable.Count(); i++)
{
var group = enumerable.ElementAt(i);
var color = colors.ElementAt(i);
foreach (var room in group)
{
var r = new RoomCheckItem()
{
Room = room,
Name = room.Name,
Color = color,
};
roomItems.Add(r);
}
}
return roomItems;
}
private static IEnumerable<Color> GetRandomColors(int count)
{
var colors = RandomColor.GetColors(ColorScheme.Random, Luminosity.Light, count);
return colors.Select(color => new Color(color.R, color.G, color.B));
}
private bool CanClick()
{
return Rooms.Any();
}
[RelayCommand(CanExecute = nameof(CanClick))]
private void AnalysisHeadroom()
{
checkHeadroom.Raise(_ =>
{
var view = document.ActiveView;
//var elev = view.GenLevel.Bottom;
document.Invoke(_ =>
{
ElementClassFilter filter = new(typeof(Floor));
foreach (var item in Rooms)
{
var room = item.Room;
Level baseLevel = room.Level;
var levels = document.OfClass<Level>().Cast<Level>();
Level aboveLevel = null;
double tempElev = double.MaxValue;
foreach (var level in levels)
{
if (level.Elevation > baseLevel.Elevation && (level.Elevation - baseLevel.Elevation) < tempElev)
{
aboveLevel = level;
tempElev = level.Elevation;
}
}
Element hitBottomElement = null;
Element hitTopElement = null;
document.XRayFindNearest(filter, FindReferenceTarget.Element, room.GetLocXYZ(), -XYZ.BasisZ, ref hitBottomElement);
document.XRayFindNearest(filter, FindReferenceTarget.Element, room.GetLocXYZ(), XYZ.BasisZ, ref hitTopElement);
if (hitBottomElement is Floor bottomFloor && bottomFloor.LevelId == baseLevel.Id)//击中的楼板是上层的楼板
{
//绝对标高
var baseOffset = bottomFloor.get_Parameter(BuiltInParameter.STRUCTURAL_ELEVATION_AT_BOTTOM).AsDouble();
//底部偏移
room.BaseOffset = baseOffset - baseLevel.Elevation;
}
else//找不到楼板则为0
{
room.BaseOffset = 0;
}
if (hitTopElement is Floor topFloor && topFloor.LevelId == aboveLevel?.Id)//击中的楼板是当前的楼板
{
//绝对标高
var limitOffset = topFloor.get_Parameter(BuiltInParameter.STRUCTURAL_ELEVATION_AT_TOP).AsDouble();
//高度偏移
room.LimitOffset = limitOffset - room.UpperLimit.Elevation;
//全部高度
//view.GetViewRange().GetLevelId(PlanViewPlane.TopClipPlane)
//item.Room
}
else//找不到楼板则为上部标高
{
room.LimitOffset = aboveLevel.Elevation - room.UpperLimit.Elevation;
}
document.Regenerate();
var elements = room.GetIntersectElements().Where(e => e is MEPCurve || e.Category.Id == new ElementId(BuiltInCategory.OST_StructuralFraming));
foreach (var elem in elements)
{
var boundingBox = elem.get_BoundingBox(view);
//最低绝对标高
var roomTopOffset = boundingBox.Min.Z;
room.LimitOffset = roomTopOffset - room.UpperLimit.Elevation;
}
}
});
});
}
[RelayCommand(CanExecute = nameof(CanClick))]
private void GenerateRoom3D()
{
document.Invoke(_ =>
{
var list = new List<GeometryObject>();
DirectShape ds = DirectShape.CreateElement(document, new ElementId(BuiltInCategory.OST_GenericModel));
FillPatternElement solidFillPattern = document
.OfClass<FillPatternElement>()
.Cast<FillPatternElement>()
.FirstOrDefault(a => a.GetFillPattern().IsSolidFill);
foreach (var item in Rooms)
{
list.Add(item.Room.GetSolid());
Color color = item.Color;
//TextNote.Create(document, viewId, position, text, options)
OverrideGraphicSettings ogs = new();
ogs.SetProjectionLineColor(color);
#if REVIT2018
ogs.SetSurfaceTransparency(50);
ogs.SetProjectionFillColor(color);
ogs.SetProjectionFillPatternId(solidFillPattern.Id);
ogs.SetCutFillColor(color);
ogs.SetCutFillPatternId(solidFillPattern.Id);
document.ActiveView.SetElementOverrides(ds.Id, ogs);
#else
ogs.SetSurfaceBackgroundPatternColor(color);
ogs.SetSurfaceForegroundPatternId(solidFillPattern.Id);
ogs.SetSurfaceForegroundPatternColor(color);
ogs.SetCutBackgroundPatternColor(color);
ogs.SetCutForegroundPatternColor(color);
ogs.SetCutForegroundPatternId(solidFillPattern.Id);
ogs.SetSurfaceTransparency(50);
document.ActiveView.SetElementOverrides(ds.Id, ogs);
#endif
}
ds.AppendShape(list);
}, "创建三维房间实体");
}
}

View File

@@ -0,0 +1,195 @@
using System.Windows;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class MoveConnectCmd : ExternalCommand
{
public override void Execute()
{
Reference baseReference;
Reference moveReference;
try
{
FuncFilter filter =
new(
e =>
((e is MEPCurve mep and not InsulationLiningBase) && mep.GetConnectors(true).Size > 0)
|| (
e is FamilyInstance instance
&& instance.MEPModel.ConnectorManager != null
&& instance.MEPModel.ConnectorManager.UnusedConnectors.Size > 0
)
);
baseReference = UiDocument.Selection.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
filter,
"请选择基准的管线、管件、设备实例"
);
FuncFilter filter1 =
new(
e =>
e.Id != baseReference.ElementId
&& ((e is MEPCurve mep and not InsulationLiningBase) && mep.GetConnectors(true).Size > 0)
|| (
e is FamilyInstance instance
&& instance.MEPModel.ConnectorManager != null
&& instance.MEPModel.ConnectorManager.UnusedConnectors.Size > 0
)
);
moveReference = UiDocument.Selection.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
filter,
"请选择需要移动连接的管线、管件、设备实例"
);
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return;
}
var baseElement = Document.GetElement(baseReference);
var elementToMove = Document.GetElement(moveReference);
var list = ConnectorAssist.GetNearestDomainConnectors(baseElement, elementToMove);
if (list.Count != 2)
{
MessageBox.Show("缺少可连接的连接件", "提示");
return;
}
Document.Invoke(
_ =>
{
var conn1 = list[0];
var conn2 = list.Last();
//if (conn1.Domain != conn2.Domain)//类型不一样不能连接
//{
// return;
//}
var origin1 = conn1.Origin;
var origin2 = conn2.Origin;
var direction1 = conn1.CoordinateSystem.BasisZ;
var direction2 = conn2.CoordinateSystem.BasisZ;
//夹角
var radian = Math.Acos(direction1.DotProduct(direction2));
//都是管线角度小于135,主次分支对齐连接,仅支持中心对齐
if (
baseElement is MEPCurve mainMEPCurve
&& elementToMove is MEPCurve branchMEPCurve
&& radian < 135.0.ToRadian()
)
{
//if ((conn2.Shape == ConnectorProfileType.Oval || conn2.Shape == ConnectorProfileType.Rectangular))
//{
// var verticalJustification = mainMEPCurve.get_Parameter(BuiltInParameter.RBS_CURVE_VERT_OFFSET_PARAM);
// var delta = XYZ.BasisZ * (branchMEPCurve.Height / 2 - mainMEPCurve.Height / 2);
// //底部
// if (verticalJustification.AsInteger() == 1)
// {
// origin2 -= delta;
// }
// //顶部
// if (verticalJustification.AsInteger() == 2)
// {
// origin2 += delta;
// }
//}
//管线所在的垂直于xoy平面的面法向
var normal = XYZ.BasisZ.CrossProduct(direction2);
//如果是立管,则取不移动的管线的方向,过移动管线做平面
if (normal.IsZeroLength())
{
normal = direction1;
}
var plane = Plane.CreateByNormalAndOrigin(normal, origin2);
//找到交点
var intersectPoint = plane.IntersectPoint(Line.CreateUnbound(origin1, direction1));
//不相交则直接移动
if (intersectPoint == null)
{
ElementTransformUtils.MoveElement(Document, elementToMove!.Id, origin1 - origin2);
}
else
{
//避免交点在原管线外,导致反向连接
if ((mainMEPCurve.GetLocCurve() as Line).IsInsideEx(intersectPoint, 0.5))
{
ElementTransformUtils.MoveElement(Document, elementToMove!.Id, intersectPoint - origin2);
}
else
{
ElementTransformUtils.MoveElement(Document, elementToMove!.Id, origin1 - origin2);
}
}
mainMEPCurve.ConnectTo(branchMEPCurve);
}
else
{
if (baseElement is MEPCurve baseMEPCurve && elementToMove is MEPCurve moveMEPCurve)
{
if (
(
conn2.Shape == ConnectorProfileType.Oval
|| conn2.Shape == ConnectorProfileType.Rectangular
)
)
{
var verticalJustification = baseMEPCurve.get_Parameter(
BuiltInParameter.RBS_CURVE_VERT_OFFSET_PARAM
);
var delta = XYZ.BasisZ * (baseMEPCurve.Height / 2 - moveMEPCurve.Height / 2);
//底部
if (verticalJustification.AsInteger() == 1)
{
origin2 += delta;
}
//顶部
if (verticalJustification.AsInteger() == 2)
{
origin2 -= delta;
}
}
}
var xyz = origin1 - origin2;
Document.InvokeSub(_ =>
{
if (!xyz.IsZeroLength())
{
ElementTransformUtils.MoveElement(Document, elementToMove!.Id, xyz);
}
});
if (direction1.IsAlmostEqualTo(direction2.Negate())) //对齐
{
conn1.ConnectByFitting(conn2);
}
else
{
var xyz2 = direction1.CrossProduct(direction2); //旋转轴方向
if (direction1.IsAlmostEqualTo(direction2))
{
xyz2 = conn1.CoordinateSystem.BasisY;
}
var line = Line.CreateUnbound(origin1, xyz2);
var angle = direction1.AngleTo(direction2);
//互补角
var complementaryAngle = Math.PI - angle;
ElementTransformUtils.RotateElement(Document, elementToMove!.Id, line, complementaryAngle);
conn1.ConnectByFitting(conn2);
}
}
},
"移动连接"
);
}
}

View File

@@ -0,0 +1,18 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// Revit执行命令
/// </summary>
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class MoveMEPCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<MoveMEPCurveView>(new MoveMEPCurveViewModel());
}
}

View File

@@ -0,0 +1,60 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.MoveMEPCurveView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="移动管线"
Width="250"
Height="310"
MinWidth="250"
MinHeight="310"
d:DataContext="{d:DesignInstance Type=local:MoveMEPCurveViewModel}"
mc:Ignorable="d">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Closing">
<b:InvokeCommandAction Command="{Binding ClosingCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<GroupBox Header="移动管线方式">
<UniformGrid Rows="1">
<RadioButton
Content="单独"
IsChecked="{Binding IsSingle}"
ToolTip="仅移动当前选择管线与Revit默认修改逻辑一致" />
<RadioButton Content="整体" ToolTip="把与选择管线的所有连接管线整体移动" />
</UniformGrid>
</GroupBox>
<GroupBox Header="移动方式">
<UniformGrid Rows="1">
<RadioButton Content="距离" IsChecked="{Binding ByDistance}" />
<RadioButton Content="参考" />
</UniformGrid>
</GroupBox>
<ex:TextBoxEx
Prefix="距离/间距:"
Suffix="mm"
Text="{Binding Distance}"
ToolTip="上下移动的距离(需要区分正负)或与参考的间距" />
<ex:StackPanelEx Orientation="Horizontal" Spacing="5">
<Button
HorizontalAlignment="Stretch"
ex:StackPanelEx.Fill="Fill"
Command="{Binding SelectCommand}"
Content="选择" />
<Button
HorizontalAlignment="Stretch"
ex:StackPanelEx.Fill="Fill"
Command="{Binding MoveCommand}"
Content="移动" />
</ex:StackPanelEx>
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Sai.RvKits.RvMEP;
/// <summary>
/// MoveMEPCurveView.xaml 的交互逻辑
/// </summary>
public partial class MoveMEPCurveView
{
public MoveMEPCurveView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,166 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
using Sai.Toolkit.Mvvm.Attributes;
namespace Sai.RvKits.RvMEP;
public partial class MoveMEPCurveViewModel : ObservableValidator
{
[ObservableProperty]
private bool isSingle = true;
[ObservableProperty]
private bool byDistance = true;
private readonly ActionEventHandler moveHandler = new();
[IsNumeric]
[ObservableProperty]
[NotifyDataErrorInfo]
private double distance;
//public double Distance
//{
// get => distance;
// set => SetProperty(ref distance, value, true);
//}
/// <summary>
/// 正在执行命令
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(MoveCommand))]
private bool canRunning = true;
[RelayCommand]
private void Closing()
{
#region 退
if (!CanRunning)
{
KeyIntPtrHelper.ActionEsc();
}
#endregion
}
[RelayCommand]
private void Select()
{
moveHandler.Raise(uiapp =>
{
var uiDocument = uiapp.ActiveUIDocument;
var doc = uiDocument.Document;
try
{
var reference = uiDocument.Selection.PickObject(ObjectType.Element, new GenericFilter<MEPCurve>(), "请选择移动管线");
var mepCurve = doc.GetElement(reference) as MEPCurve;
var ids = new List<ElementId>();
mepCurve.GetAllConnectedElements(ids);
uiDocument.Selection.SetElementIds(ids);
}
catch (Exception)
{
return;
}
CanRunning = true;
});
}
[RelayCommand]
private void Move()
{
CanRunning = false;
moveHandler.Raise(uiapp =>
{
var uiDocument = uiapp.ActiveUIDocument;
var doc = uiDocument.Document;
doc.Invoke(
_ =>
{
double z;
MEPCurve mepCurve;
try
{
var reference = uiDocument.Selection.PickObject(ObjectType.Element, new GenericFilter<MEPCurve>(), "请选择移动管线");
mepCurve = doc.GetElement(reference) as MEPCurve;
}
catch (Exception)
{
CanRunning = true;
return;
}
if (ByDistance)
{
z = Distance / 304.8;
}
else
{
Reference reference = null;
ElementInLinkOrCurrentDocument linkOrCurrentDocument = new(doc);
try
{
reference = uiDocument.Selection.PickObject(ObjectType.PointOnElement, linkOrCurrentDocument, "请选择基准元素");
}
catch (Exception)
{
// ignored
}
if (reference == null)
{
CanRunning = true;
return;
}
var referenceElem = linkOrCurrentDocument.LastCheckedWasFromLink
? linkOrCurrentDocument.LinkedDocument.GetElement(reference.LinkedElementId)
: doc.GetElement(reference);
var referMaxZ = referenceElem.get_BoundingBox(doc.ActiveView).Max.Z;
var referMinZ = referenceElem.get_BoundingBox(doc.ActiveView).Min.Z;
var maxZ = mepCurve.get_BoundingBox(doc.ActiveView).Max.Z;
var minZ = mepCurve.get_BoundingBox(doc.ActiveView).Min.Z;
//参考底部大于原管线顶部
if (referMinZ > maxZ)
{
z = (referMinZ - Math.Abs(Distance) / 304.8) - maxZ;
}
//参考顶部大于原管线底部
else if (referMaxZ < minZ)
{
z = (referMaxZ + Math.Abs(Distance) / 304.8) - minZ;
}
else
{
CanRunning = true;
return;
}
}
if (IsSingle)
{
ElementTransformUtils.MoveElement(doc, mepCurve.Id, XYZ.BasisZ * z);
}
else
{
var ids = new List<ElementId>();
mepCurve.GetAllConnectedElements(ids);
ElementTransformUtils.MoveElements(doc, ids, XYZ.BasisZ * z);
}
},
"移动管线"
);
CanRunning = true;
});
}
}

View File

@@ -0,0 +1,40 @@
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
namespace Sai.RvKits.RvMEP;
public class RevitCommandEndedMonitor
{
private readonly UIApplication _revitUiApplication;
private bool _initializingCommandMonitor;
public event EventHandler CommandEnded;
public RevitCommandEndedMonitor(UIApplication revituiApplication)
{
_revitUiApplication = revituiApplication;
_initializingCommandMonitor = true;
_revitUiApplication.Idling += OnRevitUiApplicationIdling;
}
private void OnRevitUiApplicationIdling(object sender, IdlingEventArgs idlingEventArgs)
{
if (_initializingCommandMonitor)
{
_initializingCommandMonitor = false;
return;
}
_revitUiApplication.Idling -= OnRevitUiApplicationIdling;
OnCommandEnded();
}
protected virtual void OnCommandEnded()
{
CommandEnded?.Invoke(this, EventArgs.Empty);
}
}

View File

@@ -0,0 +1,23 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Linq;
namespace Sai.RvKits.RvMEP
{
public partial class RoomCheckItem : ObservableObject
{
[ObservableProperty]
private bool isSelected;
[ObservableProperty]
private string name;
[ObservableProperty]
private Room room;
[ObservableProperty]
private Color color;
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
class RotateInstanceCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<RotateMEPView>(new RotateMEPViewModel());
}
}
}

View File

@@ -0,0 +1,100 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.RotateMEPView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:local="clr-namespace:Sai.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
Title="旋转模型"
Width="250"
Height="280"
MinWidth="250"
MinHeight="280"
d:DataContext="{d:DesignInstance Type={x:Type local:RotateMEPViewModel}}"
SizeToContent="WidthAndHeight"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<GroupBox
Grid.Row="0"
Grid.Column="0"
Header="角度(°)">
<UniformGrid Columns="3" Rows="3">
<RadioButton Content="15°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>15</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="22.5°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>22.5</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="30°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>30</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="45°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>45</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="60°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>60</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton Content="90°">
<RadioButton.IsChecked>
<Binding Converter="{StaticResource ComparisonConverter}" Path="Angle">
<Binding.ConverterParameter>
<System:Double>90</System:Double>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
<RadioButton x:Name="LbCustom" Content="自定义" />
<TextBox Text="{Binding Angle, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=LbCustom}" />
</UniformGrid>
</GroupBox>
<CheckBox
Grid.Row="1"
Grid.Column="0"
Content="是否单选"
IsChecked="{Binding IsSingleSelect}"
ToolTip="单选构件,取消则可以多选构件" />
<Button
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Stretch"
Command="{Binding RotateInstanceCommand}"
Content="旋转"
ToolTip="当选中构件时,则直接旋转,未选中时,则根据选项决定是多选还是单选" />
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Sai.RvKits.RvMEP
{
/// <summary>
/// RotateMEPView.xaml 的交互逻辑
/// </summary>
public partial class RotateMEPView
{
public RotateMEPView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
namespace Sai.RvKits.RvMEP
{
public partial class RotateMEPViewModel : ObservableObject
{
[ObservableProperty]
private double angle;
[ObservableProperty]
private bool isSingleSelect = true;
[RelayCommand]
private void RotateInstance()
{
rotateHandler.Raise(
uiapp =>
{
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
ICollection<ElementId> elemIds = [];
if (uidoc.Selection.GetElementIds().Any())
{
elemIds = uidoc.Selection.GetElementIds();
}
else
{
if (IsSingleSelect)
{
var r = uidoc.Selection.PickObject(ObjectType.Element, new FuncFilter(e =>
{
return e is FamilyInstance && e.GetConnectors().OfType<Connector>().Any(c => c.IsConnected);
}), "请选择可以连管的构件");
elemIds.Add(r.ElementId);
}
else
{
elemIds = uidoc.Selection.PickObjects(ObjectType.Element, new FuncFilter(e =>
{
return e is FamilyInstance && e.GetConnectors().OfType<Connector>().Any(c => c.IsConnected);
}), "请选择可以连管的构件,并完成").Select(r => doc.GetElement(r).Id).ToList();
}
}
foreach (var id in uidoc.Selection.GetElementIds())
{
var elem = uidoc.Document.GetElement(id);
var referConn = elem.GetConnectors().OfType<Connector>().FirstOrDefault(c => c.IsConnected);
if (referConn != null)
{
doc.Invoke(
ts =>
{
ElementTransformUtils.RotateElement(
doc,
elem.Id,
Line.CreateUnbound(
referConn.CoordinateSystem.Origin,
referConn.CoordinateSystem.BasisZ),
Angle.ToRadian());
}, "旋转");
}
}
});
}
readonly ActionEventHandler rotateHandler = new();
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
namespace Sai.RvKits.RvMEP
{
public enum RotationAngle
{
[Description("22.5°")]
TwentyTwoPointFive,
[Description("30°")]
Thirty,
[Description("45°")]
FortyFive,
[Description("60°")]
Sixty,
[Description("90°")]
Ninety
}
}

View File

@@ -0,0 +1,14 @@
using Nice3point.Revit.Toolkit.External;
using Sai.Toolkit.Core.Helpers;
namespace Sai.RvKits.RvMEP;
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class StandMepCurveCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<StandMepCurveView>(new StandMepCurveViewModel(Document));
}
}

View File

@@ -0,0 +1,97 @@
<ex:FluentWindowEx
x:Class="Sai.RvKits.RvMEP.StandMepCurveView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="https://github.com/sherlockforrest/Wpf.Ui.Extend"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:rvMep="clr-namespace:Sai.RvKits.RvMEP"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="创建立管"
Width="300"
Height="320"
MinWidth="300"
MinHeight="320"
d:DataContext="{d:DesignInstance Type=rvMep:StandMepCurveViewModel}"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<b:Interaction.Triggers>
<!--<b:EventTrigger EventName="Deactivated">
<b:InvokeCommandAction Command="{Binding CreateMepCurveCommand}" />
</b:EventTrigger>-->
<b:EventTrigger EventName="Closing">
<b:InvokeCommandAction Command="{Binding CancelCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Sai.RvKits;component/WPFUI.xaml" />
</Window.Resources>
<ex:StackPanelEx Margin="5" Spacing="5">
<ex:StackPanelEx Spacing="5">
<UniformGrid Rows="1">
<RadioButton
x:Name="RbPipe"
Content="水管"
IsChecked="{Binding IsPipe}" />
<RadioButton
x:Name="RbDuct"
Content="风管"
IsChecked="{Binding IsDuct}" />
<RadioButton
x:Name="RbCableTray"
Content="桥架"
IsChecked="{Binding IsCableTray}" />
<RadioButton
x:Name="RbConduit"
Content="线管"
IsChecked="{Binding IsConduit}" />
</UniformGrid>
<ex:StackPanelEx Spacing="5" Visibility="{Binding IsPipe, Converter={StaticResource BooleanToVisConverter}}">
<ex:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding PipeTypes}"
PlaceholderText="&lt;管道类型&gt;"
SelectedItem="{Binding SelectedPipeType}" />
<ex:ComboBoxEx
DisplayMemberPath="Name"
ItemsSource="{Binding PipeSystemTypes}"
PlaceholderText="&lt;系统类型&gt;"
SelectedItem="{Binding SelectedPipeSystemType}" />
</ex:StackPanelEx>
<ex:StackPanelEx Spacing="5" Visibility="{Binding IsDuct, Converter={StaticResource BooleanToVisConverter}}">
<ex:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding DuctTypes}"
PlaceholderText="&lt;风管类型&gt;"
SelectedItem="{Binding SelectedDuctType}" />
<ex:ComboBoxEx
DisplayMemberPath="Name"
ItemsSource="{Binding DuctSystemTypes}"
PlaceholderText="&lt;系统类型&gt;"
SelectedItem="{Binding SelectedDuctSystemType}" />
</ex:StackPanelEx>
<ex:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding ConduitTypes}"
PlaceholderText="&lt;线管类型&gt;"
SelectedItem="{Binding SelectedConduitType}"
Visibility="{Binding IsConduit, Converter={StaticResource BooleanToVisConverter}}" />
<ex:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding CableTrayTypes}"
PlaceholderText="&lt;桥架类型&gt;"
SelectedItem="{Binding SelectedCableTrayType}"
Visibility="{Binding IsCableTray, Converter={StaticResource BooleanToVisConverter}}" />
</ex:StackPanelEx>
<Button
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Command="{Binding CreateMepCurveCommand}"
Content="创建立管" />
</ex:StackPanelEx>
</ex:FluentWindowEx>

View File

@@ -0,0 +1,17 @@
using System.Windows;
using Wpf.Ui.Controls;
namespace Sai.RvKits.RvMEP
{
/// <summary>
/// StandMepCurveView.xaml 的交互逻辑
/// </summary>
public partial class StandMepCurveView
{
public StandMepCurveView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,175 @@
using System.Diagnostics.CodeAnalysis;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.UI.Selection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
namespace Sai.RvKits.RvMEP;
public partial class StandMepCurveViewModel : ObservableObject
{
public StandMepCurveViewModel(Document doc)
{
PipeTypes = doc.OfClass<PipeType>().Cast<PipeType>();
DuctTypes = doc.OfClass<DuctType>().Cast<DuctType>();
ConduitTypes = doc.OfClass<ConduitType>().Cast<ConduitType>();
CableTrayTypes = doc.OfClass<CableTrayType>().Cast<CableTrayType>();
PipeSystemTypes = doc.OfClass<PipingSystemType>().Cast<PipingSystemType>();
DuctSystemTypes = doc.OfClass<MechanicalSystemType>().Cast<MechanicalSystemType>();
}
private readonly ActionEventHandler standMepCurveEventHandler = new();
[ObservableProperty]
private IEnumerable<CableTrayType> cableTrayTypes;
[ObservableProperty]
private IEnumerable<ConduitType> conduitTypes;
[ObservableProperty]
private IEnumerable<MechanicalSystemType> ductSystemTypes;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private IEnumerable<DuctType> ductTypes;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private bool? isCableTray;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private bool? isConduit;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private bool? isDuct;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private bool? isPipe = true;
[ObservableProperty]
private IEnumerable<PipingSystemType> pipeSystemTypes;
[ObservableProperty]
private IEnumerable<PipeType> pipeTypes;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private CableTrayType selectedCableTrayType;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private ConduitType selectedConduitType;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private MechanicalSystemType selectedDuctSystemType;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private DuctType selectedDuctType;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private PipingSystemType selectedPipeSystemType;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateMepCurveCommand))]
private PipeType selectedPipeType;
partial void OnSelectedPipeTypeChanged(PipeType value)
{
var rule = value.RoutingPreferenceManager.GetRule(RoutingPreferenceRuleGroupType.Segments, 0);
var segment = value.Document.GetElement(rule.MEPPartId) as PipeSegment;
PipeSizes = segment.GetSizes();
}
[ObservableProperty]
private ICollection<MEPSize> pipeSizes;
private bool isRunning;
private bool CanCreate()
{
return (IsPipe == true && SelectedPipeType != null && SelectedPipeSystemType != null)
|| (IsDuct == true && SelectedDuctType != null && SelectedDuctSystemType != null)
|| (IsConduit == true && SelectedConduitType != null)
|| (IsCableTray == true && SelectedCableTrayType != null && !isRunning);
}
[RelayCommand]
private void Cancel()
{
if (!isRunning)
{
return;
}
KeyIntPtrHelper.ActionEsc();
}
[RelayCommand(CanExecute = nameof(CanCreate))]
private void CreateMepCurve()
{
isRunning = true;
standMepCurveEventHandler.Raise(app =>
{
try
{
while (true)
{
var uidoc = app.ActiveUIDocument;
var view = uidoc.ActiveView;
var doc = uidoc.Document;
const ObjectSnapTypes ss = ObjectSnapTypes.Points
| ObjectSnapTypes.Endpoints
| ObjectSnapTypes.Intersections
| ObjectSnapTypes.Centers
| ObjectSnapTypes.Midpoints
| ObjectSnapTypes.Perpendicular
| ObjectSnapTypes.Quadrants
| ObjectSnapTypes.Tangents;
var point = uidoc.Selection.PickPoint(ss, "选择创建立管的点");
var startPoint = new XYZ(point.X, point.Y, view.GenLevel.Elevation);
var endPoint = new XYZ(point.X, point.Y, view.GenLevel.Elevation + (2000 / 304.8));
doc.Invoke(
_ =>
{
if (IsPipe == true)
{
Pipe.Create(doc, SelectedPipeSystemType.Id, SelectedPipeType.Id, view.GenLevel.Id, startPoint, endPoint);
}
else if (IsDuct == true)
{
Duct.Create(doc, SelectedDuctSystemType.Id, SelectedDuctType.Id, view.GenLevel.Id, startPoint, endPoint);
}
else if (IsConduit == true)
{
Conduit.Create(doc, SelectedConduitType.Id, startPoint, endPoint, view.GenLevel.Id);
}
else if (IsCableTray == true)
{
CableTray.Create(doc, SelectedCableTrayType.Id, startPoint, endPoint, view.GenLevel.Id);
}
},
"创建立管"
);
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
}
});
isRunning = false;
}
}

View File

@@ -0,0 +1,112 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Plumbing;
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel;
namespace Sai.RvKits.RvMEP
{
public class SystemModel : ObservableObject
{
public SystemModel(MEPSystemType systemType)
{
//PipingSystemType;
//MechanicalSystemType
SystemType = systemType;
Name = systemType.Name;
MEPSystemClassification = (MEPSystemClassification)systemType.SystemClassification;
FillColor = systemType.FillColor;
Abbreviation = systemType.Abbreviation;
}
private string abbreviation;
private System.Windows.Media.Brush backgroundColor;
private Color fillColor;
private Color lineColor;
private MEPSystemClassification mepSystemClassification;
private string name;
private PipeSystemType pipeSystemType;
private MEPSystemType systemType;
public Color LineColor
{
get => lineColor;
set => SetProperty(ref lineColor, value);
}
public string Abbreviation
{
get => abbreviation;
set => SetProperty(ref abbreviation, value);
}
public Color FillColor
{
get => fillColor;
set => SetProperty(ref fillColor, value);
}
public PipeSystemType PipeSystemType
{
get => pipeSystemType;
set => SetProperty(ref pipeSystemType, value);
}
/// <summary>
/// 系统类型
/// </summary>
public MEPSystemType SystemType
{
get => systemType;
set => SetProperty(ref systemType, value);
}
public MEPSystemClassification MEPSystemClassification
{
get => mepSystemClassification;
set => SetProperty(ref mepSystemClassification, value);
}
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
public System.Windows.Media.Brush BackgroundColor
{
get => backgroundColor;
//if (fillColor.IsValid)
//{
// return backgroundColor = new System.Windows.Media.SolidColorBrush(System.Windows.Media.SelectedColor.FromRgb(fillColor.Red, fillColor.Green, fillColor.Blue));
//}
//return backgroundColor = new System.Windows.Media.SolidColorBrush();
set => SetProperty(ref backgroundColor, value);
}
}
public enum MEPSystemClassification
{
[Description("送风")] SupplyAir = 1,
[Description("回风")] ReturnAir = 2,
[Description("排风")] ExhaustAir = 3,
[Description("循环供水")] SupplyHydronic = 7,
[Description("循环回水")] ReturnHydronic = 8,
[Description("卫生设备")] Sanitary = 16,
[Description("通风孔")] Vent = 17,
[Description("雨水")] Storm = 18,
[Description("家用热水")] DomesticHotWater = 19,
[Description("家用冷水")] DomesticColdWater = 20,
[Description("中水")] Recirculation = 21,
[Description("其他")] OtherPipe = 22,
[Description("湿式消防系统")] FireProtectWet = 23,
[Description("干式消防系统")] FireProtectDry = 24,
[Description("预作用消防系统")] FireProtectPreaction = 25,
[Description("其他消防系统")] FireProtectOther = 26
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Nice3point.Revit.Toolkit.External;
namespace Sai.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class TerminalConnectToDuctCmd : ExternalCommand
{
public override void Execute()
{
try
{
#if REVIT2018 || REVIT2020
var terminalId = UiDocument.Selection
.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
new FuncFilter(e => e.Category.Id.IntegerValue == -2008013),
"请选择风管末端"
)
.ElementId;
#elif REVIT2025
var terminalId = UiDocument.Selection
.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
new FuncFilter(e => e.Category.Id.Value == -2008013),
"请选择风管末端"
)
.ElementId;
#endif
var ductId = UiDocument.Selection
.PickObject(Autodesk.Revit.UI.Selection.ObjectType.Element, new GenericFilter<Duct>(), "请选择连接到的风管")
.ElementId;
Document.Invoke(_ =>
{
MechanicalUtils.ConnectAirTerminalOnDuct(Document, terminalId, ductId);
});
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
}
catch (Exception) { }
}
}