整理代码

This commit is contained in:
GG Z
2025-04-24 20:56:10 +08:00
parent 4d798e5d99
commit 155cef46f8
532 changed files with 1018 additions and 854 deletions

View File

@@ -0,0 +1,20 @@
using System;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
public class AddInsulationCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<AddInsulationView>(new AddInsulationViewModel(Document));
}
}

View File

@@ -0,0 +1,108 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.RvKits.RvMEP.AddInsulationView"
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:ui="https://github.com/ShrlAlgo/WPFluent"
xmlns:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="保温层"
Width="620"
MinHeight="300"
d:DataContext="{d:DesignInstance Type=local:AddInsulationViewModel}"
SizeToContent="Height"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:StackPanel
Grid.RowSpan="2"
Grid.Column="0"
Margin="5"
IsEnabled="{Binding AddToPipe, Mode=TwoWay}"
Spacing="5">
<ui:ComboBoxEx
DisplayMemberPath="Name"
Header="管道系统类型"
ItemsSource="{Binding PipingSystemTypes}"
SelectedItem="{Binding SelectedPipingSystemType}" />
<DataGrid
ui:StackPanel.Fill="Fill"
AutoGenerateColumns="False"
ItemsSource="{Binding PipeInsulationItems}"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding MinDiameter}" Header="最小管径" />
<DataGridTextColumn Binding="{Binding MaxDiameter}" Header="最大管径" />
<DataGridTextColumn Binding="{Binding Thickness}" Header="保温层厚度" />
<!--<DataGridTextColumn Binding="{Binding PipeInsulationType.Name}" Header="保温层材料" />-->
<!--<DataGridComboBoxColumn
DisplayMemberPath=""
Header="保温层材料"
ItemsSource="{Binding PipeInsulationTypes}"
SelectedItemBinding="{Binding PipeInsulationType, UpdateSourceTrigger=PropertyChanged}"
TextBinding="{Binding PipeInsulationType.Name}" />-->
<DataGridTemplateColumn Width="*" Header="保温层材料">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<ComboBox
DisplayMemberPath="Name"
ItemsSource="{Binding DataContext.PipeInsulationTypes, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"
SelectedItem="{Binding PipeInsulationType, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</ui:StackPanel>
<ui:StackPanel
Grid.Column="1"
Margin="5"
IsEnabled="{Binding AddToDuct, Mode=TwoWay}"
Spacing="5">
<ui:ComboBoxEx
DisplayMemberPath="Name"
Header="风管系统类型:"
ItemsSource="{Binding DuctSystemTypes}"
SelectedItem="{Binding SelectedDuctSystem}" />
<ui:ComboBoxEx
DisplayMemberPath="Name"
Header="保温层类型:"
ItemsSource="{Binding DuctInsulationTypes}"
SelectedItem="{Binding DuctInsulationItem.DuctInsulationType}" />
<ui:TextBoxEx Header="保温层厚度:" Text="{Binding DuctInsulationItem.Thickness, UpdateSourceTrigger=PropertyChanged}" />
</ui:StackPanel>
<StackPanel
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<CheckBox
MinWidth="50"
Margin="5"
Content="管道"
IsChecked="{Binding AddToPipe}" />
<CheckBox
MinWidth="50"
Margin="5"
Content="风管"
IsChecked="{Binding AddToDuct}" />
<Button
Grid.Row="1"
HorizontalAlignment="Right"
Command="{Binding AddInsulationCommand}"
Content="添加保温层" />
</StackPanel>
</Grid>
</ui:FluentWindowEx>

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 ShrlAlgo.RvKits.RvMEP;
/// <summary>
/// AddInsulationView.xaml 的交互逻辑
/// </summary>
public partial class AddInsulationView
{
public AddInsulationView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,191 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
namespace ShrlAlgo.RvKits.RvMEP
{
public partial class AddInsulationViewModel : ObservableObject
{
private readonly ActionEventHandler addInsulationHandler = new();
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AddInsulationCommand))]
private bool addToDuct;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AddInsulationCommand))]
private bool addToPipe;
/// <summary>
/// 定义的风管保温层
/// </summary>
[ObservableProperty]
private InsulationItem ductInsulationItem;
/// <summary>
/// 定义的管道保温层
/// </summary>
[ObservableProperty]
private List<InsulationItem> pipeInsulationItems;
/// <summary>
/// 选中的风管系统类型
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AddInsulationCommand))]
private MechanicalSystemType selectedDuctSystem;
/// <summary>
/// 选中的管道系统类型
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AddInsulationCommand))]
private PipingSystemType selectedPipingSystemType;
public AddInsulationViewModel(Document doc)
{
PipingSystemTypes = doc.OfClass<PipingSystemType>().Cast<PipingSystemType>().ToList();
DuctSystemTypes = doc.OfClass<MechanicalSystemType>().Cast<MechanicalSystemType>().ToList();
DuctInsulationTypes = doc.OfClass<DuctInsulationType>().Cast<DuctInsulationType>().ToList();
PipeInsulationTypes = doc.OfClass<PipeInsulationType>().Cast<PipeInsulationType>().ToList();
PipeInsulationItems = [
new InsulationItem(){MinDiameter=15,MaxDiameter=25, Thickness=25,PipeInsulationType=PipeInsulationTypes.FirstOrDefault()},
new InsulationItem(){MinDiameter=32,MaxDiameter=80, Thickness=30,PipeInsulationType=PipeInsulationTypes.FirstOrDefault()},
new InsulationItem(){MinDiameter=100,MaxDiameter=1000, Thickness=35,PipeInsulationType=PipeInsulationTypes.FirstOrDefault()},
];
DuctInsulationItem = new() { Thickness = 50, DuctInsulationType = DuctInsulationTypes.FirstOrDefault() };
}
[RelayCommand(CanExecute = nameof(CanAdd))]
private void AddInsulation()
{
addInsulationHandler.Raise(
uiapp =>
{
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
doc.Invoke(
ts =>
{
if (AddToDuct)
{
var systems = doc.OfClass<MechanicalSystem>().Cast<MechanicalSystem>();
foreach (var system in systems)
{
var elems = system.DuctNetwork;
foreach (Element elem in elems)
{
var id = InsulationLiningBase.GetInsulationIds(doc, elem.Id).FirstOrDefault();
if (id == null)
{
//风管或风管管件
if (elem is Duct duct ||
(elem is FamilyInstance instance &&
instance.Category.Id.IntegerValue == -2008010))
{
Autodesk.Revit.DB.Mechanical.DuctInsulation
.Create(
doc,
elem.Id,
DuctInsulationItem.DuctInsulationType.Id,
DuctInsulationItem.Thickness / 304.8);
}
}
else
{
var insulation = doc.GetElement(id) as DuctInsulation;
insulation.ChangeTypeId(DuctInsulationItem.DuctInsulationType.Id);
insulation.Thickness = DuctInsulationItem.Thickness / 304.8;
}
}
}
}
if (AddToPipe)
{
var systems = doc.OfClass<PipingSystem>().Cast<PipingSystem>();
foreach (var system in systems)
{
var elems = system.PipingNetwork;
foreach (Element elem in elems)
{
//水管或水管管件
if (elem is Pipe pipe ||
(elem is FamilyInstance instance &&
instance.Category.Id.IntegerValue == -2008049))
{
InsulationItem pipeInsulation = default;
foreach (var item in PipeInsulationItems)
{
//直径
var str = elem.get_Parameter(BuiltInParameter.RBS_CALCULATED_SIZE)
.AsString()
.Split(' ')
.FirstOrDefault();
if (double.TryParse(str, out var d))
{
if (d >= item.MinDiameter && d <= item.MaxDiameter)
{
pipeInsulation = item;
break;
}
}
}
var id = InsulationLiningBase.GetInsulationIds(doc, elem.Id).FirstOrDefault();
if (id == null)
{
if (pipeInsulation != null)
{
Autodesk.Revit.DB.Plumbing.PipeInsulation
.Create(
doc,
elem.Id,
pipeInsulation.PipeInsulationType.Id,
pipeInsulation.Thickness / 304.8);
}
}
else
{
var insulation = doc.GetElement(id) as PipeInsulation;
insulation.ChangeTypeId(pipeInsulation.PipeInsulationType.Id);
insulation.Thickness = pipeInsulation.Thickness / 304.8;
}
}
}
}
}
}, "添加保温层");
});
}
//private bool CanAdd()
//{
// return (AddToDuct && SelectedDuctSystem != null) || (AddToPipe && SelectedPipingSystemType != null);
//}
private bool CanAdd => (AddToDuct && SelectedDuctSystem != null) ||
(AddToPipe && SelectedPipingSystemType != null);
/// <summary>
/// 所有风管保温层类型
/// </summary>
public List<DuctInsulationType> DuctInsulationTypes { get; set; }
/// <summary>
/// 所有风管系统类型
/// </summary>
public List<MechanicalSystemType> DuctSystemTypes { get; set; }
/// <summary>
/// 所有管道保温层类型
/// </summary>
public List<PipeInsulationType> PipeInsulationTypes { get; set; }
/// <summary>
/// 所有管道系统类型
/// </summary>
public List<PipingSystemType> PipingSystemTypes { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using System.Windows.Controls;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB.Plumbing;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.RvKits.RvMEP;
[Transaction(TransactionMode.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 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
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:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel 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}" />
</ui:StackPanel>
</ui:FluentWindowEx>

View File

@@ -0,0 +1,28 @@
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 ShrlAlgo.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,22 @@
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;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
internal class ArrangeMEPCurveCmd : ExternalCommand
{
public override void Execute()
{
WinDialogHelper.ShowModeless<ArrangeMEPCurveView>(new ArrangeMEPCurveViewModel());
}
}

View File

@@ -0,0 +1,45 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.RvKits.RvMEP.ArrangeMEPCurveView"
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:ui="https://github.com/ShrlAlgo/WPFluent"
xmlns:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="整理管线"
Width="270"
MinHeight="180"
d:DataContext="{d:DesignInstance Type=local:ArrangeMEPCurveViewModel}"
SizeToContent="Height"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel Margin="5" Spacing="5">
<GroupBox Header="参考方向">
<UniformGrid Rows="1">
<RadioButton Content="水平方向" IsChecked="{Binding IsHorizon}" />
<RadioButton Content="垂直方向" IsChecked="{Binding IsHorizon, Converter={StaticResource InvertBooleanConverter}}" />
</UniformGrid>
</GroupBox>
<ui:TextBoxEx
Prefix="间距:"
Suffix="mm"
Text="{Binding Gap, UpdateSourceTrigger=PropertyChanged}" />
<UniformGrid Rows="1">
<RadioButton
MinWidth="50"
Content="管中心"
IsChecked="{Binding MEPCurveCenter}" />
<RadioButton
Content="管外壁/考虑保温层"
IsChecked="{Binding MEPCurveCenter, Converter={StaticResource InvertBooleanConverter}}"
ToolTip="若无保温,则仅考虑管外壁" />
</UniformGrid>
<Button
HorizontalAlignment="Stretch"
Command="{Binding ArrangeCommand}"
Content="整理" />
</ui:StackPanel>
</ui:FluentWindowEx>

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 ShrlAlgo.RvKits.RvMEP;
/// <summary>
/// ArrangeMEPCurveView.xaml 的交互逻辑
/// </summary>
public partial class ArrangeMEPCurveView
{
public ArrangeMEPCurveView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,366 @@
using System.Windows;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
namespace ShrlAlgo.RvKits.RvMEP
{
public partial class ArrangeMEPCurveViewModel : ObservableObject
{
private readonly ActionEventHandler arrangeHandler = new();
[ObservableProperty]
private double gap = 250;
[ObservableProperty]
private bool mEPCurveCenter = true;
[ObservableProperty]
private bool isHorizon = true;
[RelayCommand]
private void Arrange()
{
arrangeHandler.Raise(
uiapp =>
{
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
doc.Invoke(
ts =>
{
try
{
var ids = uidoc.Selection
.PickElementsByRectangle(
new FuncFilter(
e => e is MEPCurve &&
e is not InsulationLiningBase &&
e is not FlexDuct &&
e is not FlexPipe &&
!(e.GetCurve() as Line).Direction.IsParallelTo(XYZ.BasisZ)),
"请选择要整理的管线")
.Select(e => e.Id)
.ToList();
if (ids.Count <= 1)
{
return;
}
var baseId = uidoc.Selection
.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
new FuncFilter(e => ids.Contains(e.Id)),
"请选择基准管线")
.ElementId;
var baseElement = doc.GetElement(baseId);
var baseLine = baseElement.GetCurve() as Line;
baseLine.MakeUnbound();
if (IsHorizon)
{
//移除基准
ids.Remove(baseId);
List<ArrangeElement> arranges = [];
foreach (var id in ids)
{
var elem = doc.GetElement(id);
var l = elem.GetCurve() as Line;
if (!l.Direction.IsParallelTo(baseLine.Direction))
{
MessageBox.Show("所选管线不平行", "错误");
return;
}
var d = baseLine.Distance(l.Origin);
var project = baseLine.Project(l.Origin).XYZPoint;
var vector = l.Origin.Flatten() - project.Flatten();
var ae = new ArrangeElement
{
ElementToMove = elem,
//用来确定与基准管线的位置和方向
HorizonDistanceVector = vector
};
arranges.Add(ae);
}
var groups = arranges.OrderBy(ar => ar.HorizonDistanceVector.GetLength())
.GroupBy(
ar => XYZ.BasisZ
.CrossProduct(baseLine.Direction)
.IsAlmostEqualTo(ar.HorizonDistanceVector.Normalize()));
if (groups.Count() > 2)
{
MessageBox.Show("管线定位线存在误差");
return;
}
//管中心距离
if (MEPCurveCenter)
{
foreach (var group in groups)
{
for (var i = 0; i < group.Count(); i++)
{
var arrange = group.ElementAt(i);
//当前的距离
var dis = arrange.HorizonDistanceVector.GetLength();
var moveUnitDir = arrange.HorizonDistanceVector.Normalize();
//最终的距离
var actualDis = (i + 1) * Gap / 304.8;
var translate = moveUnitDir * (actualDis - dis);
ElementTransformUtils.MoveElement(
doc,
arrange.ElementToMove.Id,
translate);
}
}
}
else
{
//遍历两个方向的分组
foreach (var group in groups)
{
double actualDis = 0;
double previousHalfSize = 0;
//基准半宽
if (baseElement is Pipe pipe)
{
previousHalfSize = pipe.get_Parameter(BuiltInParameter.RBS_PIPE_OUTER_DIAMETER).AsDouble() / 2;
}
else if (baseElement is Conduit conduit)
{
previousHalfSize = conduit.get_Parameter(BuiltInParameter.RBS_CONDUIT_OUTER_DIAM_PARAM).AsDouble() / 2;
}
else if (baseElement is CableTray tray || baseElement is Duct duct)
{
var baseConnector = baseElement.GetConnectors()
.OfType<Connector>()
.FirstOrDefault();
if (baseConnector.Shape == ConnectorProfileType.Oval || baseConnector.Shape == ConnectorProfileType.Rectangular)
{
previousHalfSize = baseConnector.Width / 2;
}
else if (baseConnector.Shape == ConnectorProfileType.Round)
{
previousHalfSize = baseConnector.Radius;
}
}
if (baseElement is Pipe or Duct)
{
//基准保温层厚度
var insulationId = InsulationLiningBase.GetInsulationIds(
doc,
baseElement.Id).FirstOrDefault();
if (insulationId != null)
{
var insulation = doc.GetElement(insulationId) as InsulationLiningBase;
previousHalfSize += insulation.Thickness;
}
}
for (var i = 0; i < group.Count(); i++)
{
var arrange = group.ElementAt(i);
//当前的距离
var dis = arrange.HorizonDistanceVector.GetLength();
var moveUnitDir = arrange.HorizonDistanceVector.Normalize();
double currentHalfSize = 0;
if (arrange.ElementToMove is Pipe p)
{
currentHalfSize = p.get_Parameter(BuiltInParameter.RBS_PIPE_OUTER_DIAMETER).AsDouble() / 2;
}
else if (arrange.ElementToMove is Conduit conduit)
{
currentHalfSize = conduit.get_Parameter(BuiltInParameter.RBS_CONDUIT_OUTER_DIAM_PARAM).AsDouble() / 2;
}
else if (arrange.ElementToMove is CableTray tray || arrange.ElementToMove is Duct duct)
{
var conn = arrange.ElementToMove.GetConnectors()
.OfType<Connector>()
.FirstOrDefault();
if (conn.Shape == ConnectorProfileType.Oval || conn.Shape == ConnectorProfileType.Rectangular)
{
currentHalfSize = conn.Width / 2;
}
else if (conn.Shape == ConnectorProfileType.Round)
{
currentHalfSize = conn.Radius;
}
}
//只有管道和风管有保温
if (arrange.ElementToMove is Pipe or Duct)
{
//考虑保温层
var currentInsulationId = InsulationLiningBase.GetInsulationIds(
doc,
arrange.ElementToMove.Id).FirstOrDefault();
if (currentInsulationId != null)
{
var currentInsulation = doc.GetElement(currentInsulationId) as InsulationLiningBase;
currentHalfSize += currentInsulation.Thickness;
}
}
//最终的距离叠加,附加上次的距离,使得每次只要增加一个上一个半宽+当前的半宽+间隙
actualDis += previousHalfSize + currentHalfSize + Gap / 304.8;
var translate = moveUnitDir * (actualDis - dis);
ElementTransformUtils.MoveElement(
doc,
arrange.ElementToMove.Id,
translate);
//修改上个半宽,用于当前分组里下一个元素循环
previousHalfSize = currentHalfSize;
}
}
}
}
//垂向
else
{
//通过LocationCurve确定相对位置关系
var orderIds = ids.Select(id => doc.GetElement(id) as MEPCurve)
.OrderBy(c => (c.GetCurve() as Line).Origin.Z).Select(e => e.Id).ToList();
//中心距
if (MEPCurveCenter)
{
var baseIndex = orderIds.IndexOf(baseId);
for (var i = 0; i < orderIds.Count; i++)
{
var currentZ = (doc.GetElement(orderIds[i]).GetCurve() as Line).Origin.Z;
var dis = Math.Abs(baseLine.Origin.Z - currentZ);
var actualDis = Math.Abs((baseIndex - i)) * Gap / 304.8;
//判断方向
var vector = i > baseIndex ? XYZ.BasisZ : -XYZ.BasisZ;
var translate = (actualDis - dis) * vector;
ElementTransformUtils.MoveElement(doc, orderIds[i], translate);
//if (i > baseIndex)
//{
// var dis = currentZ - baseLine.Origin.Z;//正的
// var actualDis = (i - baseIndex) * Gap / 304.8;//正的
// var translate = (actualDis - dis) * XYZ.BasisZ;
// ElementTransformUtils.MoveElement(doc, orderIds[i], translate);
//}
}
}
//外距
else
{
ids.Remove(baseId);
var groups = ids.Select(id => doc.GetElement(id) as MEPCurve)
.OrderBy(c => (c.GetCurve() as Line).Origin.Z).GroupBy(e => (e.GetCurve() as Line).Origin.Z > baseLine.Origin.Z);
var baseTop = baseElement.get_BoundingBox(null).Max.Z;
var baseBottom = baseElement.get_BoundingBox(null).Min.Z;
if (baseElement is Pipe or Duct)
{
//基准保温层厚度
var insulationId = InsulationLiningBase.GetInsulationIds(
doc,
baseElement.Id).FirstOrDefault();
if (insulationId != null)
{
var insulation = doc.GetElement(insulationId) as InsulationLiningBase;
baseTop = insulation.get_BoundingBox(null).Max.Z;
baseBottom = insulation.get_BoundingBox(null).Min.Z;
}
}
foreach (var group in groups)
{
if (group.Key)
{
var sumHeight = 0.0;
var vector = XYZ.BasisZ;
for (var i = 0; i < group.Count(); i++)
{
var mep = group.ElementAt(i);
var currentTop = mep.get_BoundingBox(null).Max.Z;
var currentBottom = mep.get_BoundingBox(null).Min.Z;
if (mep is Pipe or Duct)
{
//基准保温层厚度
var insulationId = InsulationLiningBase.GetInsulationIds(
doc,
mep.Id).FirstOrDefault();
if (insulationId != null)
{
var insulation = doc.GetElement(insulationId) as InsulationLiningBase;
currentTop = insulation.get_BoundingBox(null).Max.Z;
currentBottom = insulation.get_BoundingBox(null).Min.Z;
}
}
//当前间距
var dis = currentBottom - baseTop;
var actualDis = (i + 1) * Gap / 304.8 + sumHeight;
var translate = (actualDis - dis) * vector;
ElementTransformUtils.MoveElement(doc, mep.Id, translate);
//整体高度
sumHeight += currentTop - currentBottom;
}
}
else
{
var sumHeight = 0.0;
var vector = -XYZ.BasisZ;
for (var i = group.Count() - 1; i >= 0; i--)
{
var mep = group.ElementAt(i);
var currentTop = mep.get_BoundingBox(null).Max.Z;
var currentBottom = mep.get_BoundingBox(null).Min.Z;
if (mep is Pipe or Duct)
{
//基准保温层厚度
var insulationId = InsulationLiningBase.GetInsulationIds(
doc,
mep.Id).FirstOrDefault();
if (insulationId != null)
{
var insulation = doc.GetElement(insulationId) as InsulationLiningBase;
currentTop = insulation.get_BoundingBox(null).Max.Z;
currentBottom = insulation.get_BoundingBox(null).Min.Z;
}
}
//当前间距
var dis = baseBottom - currentTop;
var actualDis = (group.Count() - i) * Gap / 304.8 + sumHeight;
var translate = (actualDis - dis) * vector;
ElementTransformUtils.MoveElement(doc, mep.Id, translate);
//整体高度
sumHeight += currentTop - currentBottom;
}
}
}
}
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
}
},
"整理管线");
});
}
}
public class ArrangeElement
{
public double ElementHeight => ElementToMove.get_BoundingBox(null).Max.Z - ElementToMove.get_BoundingBox(null).Min.Z;
public Element ElementToMove { get; set; }
public BoundingBoxXYZ Box { get; set; }
/// <summary>
/// 与基准元素的间距及方向向量
/// </summary>
public XYZ HorizonDistanceVector { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,564 @@
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 OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using System.Collections;
using System.Diagnostics;
namespace ShrlAlgo.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 FuncFilter(e => e is FamilyInstance ins && ins.GetConnectors(true).Size > 0),
"请选择族实例");
elemIds.Add(Document.GetElement(reference).Id);
}
foreach (var elemId in elemIds)
{
var pipeTypeId = Document.OfClass<PipeType>().FirstElementId();
var cableTrayTypeId = Document.OfClass<CableTrayType>().FirstElementId();
var conduitTypeId = Document.OfClass<ConduitType>().FirstElementId();
var ductTypeCollector = Document.OfClass<DuctType>().Cast<DuctType>();
var roundDuctTypeId = ElementId.InvalidElementId;
var rectangleDuctTypeId = ElementId.InvalidElementId;
var ovalDuctTypeId = ElementId.InvalidElementId;
//设置默认的风管类型
foreach (var ductType in ductTypeCollector)
{
if (ductType.FamilyName == "圆形风管" || ductType.FamilyName.Contains("Round Duct"))
{
roundDuctTypeId = ductType.Id;
}
else if (ductType.FamilyName == "矩形风管" || ductType.FamilyName.Contains("Rectangular Duct"))
{
rectangleDuctTypeId = ductType.Id;
}
else if (ductType.FamilyName == "椭圆形风管" || ductType.FamilyName.Contains("Oval Duct"))
{
ovalDuctTypeId = ductType.Id;
}
}
var fabricationConfiguration = FabricationConfiguration.GetFabricationConfiguration(Document);
var elem = Document.GetElement(elemId);
if (elem.GetConnectors(true).IsEmpty && elem is not FamilyInstance)
{
continue;
}
CableTray referCabTray = null;
Conduit referConduit = null;
//根据连接件拿到连接的管线的类型
foreach (Connector conn in elem.GetConnectors())
{
if (conn.IsConnected)
{
var connector = conn.GetConnectedConnector();
if (connector == null)
{
continue;
}
if (conn.Domain == Domain.DomainPiping && connector.Owner is Pipe)
{
pipeTypeId = connector.Owner.GetTypeId();
}
else if (conn.Domain == Domain.DomainHvac)
{
switch (connector.Shape)
{
case ConnectorProfileType.Invalid:
break;
case ConnectorProfileType.Round:
roundDuctTypeId = connector.Owner.GetTypeId();
break;
case ConnectorProfileType.Rectangular:
rectangleDuctTypeId = connector.Owner.GetTypeId();
break;
case ConnectorProfileType.Oval:
ovalDuctTypeId = connector.Owner.GetTypeId();
break;
default:
break;
}
}
else if (conn.Domain == Domain.DomainCableTrayConduit)
{
if (connector.Owner is CableTray cableTray)
{
referCabTray = cableTray;
cableTrayTypeId = cableTray.GetTypeId();
}
else if (connector.Owner is Conduit conduit)
{
referConduit = conduit;
conduitTypeId = conduit.GetTypeId();
}
}
}
}
Document.Invoke(
_ =>
{
var connectors = elem.GetConnectors(true);
foreach (Connector connector in connectors)
{
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;
}
}
continue;
}
var extensionLength = connector.GetExtensionLength();
var origin = connector.Origin;
//延伸后的点
var xyz2 = origin + (extensionLength * connector.CoordinateSystem.BasisZ);
var levelId = elem.LevelId;
if (levelId == ElementId.InvalidElementId)
{
var pa = elem.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM);
if (pa != null)
{
levelId = pa.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,
roundDuctTypeId,
levelId,
connector,
xyz2
);
Document.Regenerate();
element.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM).Set(connector.Radius * 2);
break;
case ConnectorProfileType.Rectangular:
element = Duct.Create(
Document,
rectangleDuctTypeId,
levelId,
connector,
xyz2
);
Document.Regenerate();
var width = Math.Max(connector.Width, connector.Height);
var height = Math.Min(connector.Width, connector.Height);
element.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM).Set(width);
element.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM).Set(height);
break;
case ConnectorProfileType.Oval:
element = Duct.Create(Document, ovalDuctTypeId, levelId, connector, xyz2);
Document.Regenerate();
var w = Math.Max(connector.Width, connector.Height);
var h = Math.Min(connector.Width, connector.Height);
element.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM).Set(w);
element.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM).Set(h);
break;
default:
element = Duct.Create(
Document,
roundDuctTypeId,
levelId,
connector,
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 = Document.OfClass<PipeType>().FirstElementId();
}
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);
element.get_Parameter(BuiltInParameter.RBS_CONDUIT_DIAMETER_PARAM).Set(connector.Radius * 2);
if (referConduit != null)
{
var value = referConduit.get_Parameter(
BuiltInParameter.RBS_CTC_SERVICE_TYPE)
.AsString();
element.get_Parameter(BuiltInParameter.RBS_CTC_SERVICE_TYPE).Set(value);
}
break;
case ConnectorProfileType.Rectangular:
var width = Math.Max(connector.Width, connector.Height);
var height = Math.Min(connector.Width, connector.Height);
element = CableTray.Create(Document, cableTrayTypeId, origin, xyz2, levelId);
element.get_Parameter(BuiltInParameter.RBS_CABLETRAY_WIDTH_PARAM).Set(width);
element.get_Parameter(BuiltInParameter.RBS_CABLETRAY_HEIGHT_PARAM).Set(height);
if (referCabTray != null)
{
var value = referCabTray.get_Parameter(
BuiltInParameter.RBS_CTC_SERVICE_TYPE)
.AsString();
element.get_Parameter(BuiltInParameter.RBS_CTC_SERVICE_TYPE).Set(value);
}
break;
case ConnectorProfileType.Oval:
break;
}
break;
}
Document.Regenerate();
//新建管线连接
if (element == null)
{
continue;
}
var curve = element as MEPCurve;
var conns = curve.ConnectorManager.UnusedConnectors.Cast<Connector>();
foreach (var connect in conns)
{
if (connect.Origin.IsAlmostEqualTo(connector.Origin))
{
Debug.WriteLine("新建管线:");
Debug.WriteLine(connect.CoordinateSystem.BasisX);
Debug.WriteLine(connect.CoordinateSystem.BasisY);
Debug.WriteLine(connect.CoordinateSystem.BasisZ);
Debug.WriteLine("管件:");
Debug.WriteLine(connector.CoordinateSystem.BasisX);
Debug.WriteLine(connector.CoordinateSystem.BasisY);
Debug.WriteLine(connector.CoordinateSystem.BasisZ);
var isStandMEPCurve = connect.CoordinateSystem.BasisZ.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero);
if (isStandMEPCurve)
{
var angleX = Math.Acos(connect.CoordinateSystem.BasisX.DotProduct(connector.CoordinateSystem.BasisX));
Debug.WriteLine(angleX);
var crossProduct = connect.CoordinateSystem.BasisX
.CrossProduct(connector.CoordinateSystem.BasisX).Normalize();
var angle = Math.Acos(connect.CoordinateSystem.BasisX.DotProduct(connector.CoordinateSystem.BasisX));
if (crossProduct.IsAlmostEqualTo(XYZ.Zero))
{
if (connect.CoordinateSystem.BasisX
.IsAlmostEqualTo(connector.CoordinateSystem.BasisX))
{
ElementTransformUtils.RotateElement(
Document,
element.Id,
Line.CreateUnbound(origin, XYZ.BasisZ),
Math.PI / 2);
}
}
else
{
if (angleX > Math.PI / 2)
{
ElementTransformUtils.RotateElement(
Document,
element.Id,
Line.CreateUnbound(origin, XYZ.BasisZ),
angleX - Math.PI / 2);
}
else
{
ElementTransformUtils.RotateElement(
Document,
element.Id,
Line.CreateUnbound(origin, XYZ.BasisZ),
Math.PI / 2 - angleX);
}
}
//if (Math.Abs(angleX - Math.PI / 2) > 0.001)
//{
// ElementTransformUtils.RotateElement(
// Document,
// element.Id,
// Line.CreateUnbound(origin, crossProduct),
// angleX - Math.PI / 2
// );
// Debug.WriteLine("旋转后新建管线:");
// Debug.WriteLine(connect.CoordinateSystem.BasisX);
// Debug.WriteLine(connect.CoordinateSystem.BasisY);
// Debug.WriteLine(connect.CoordinateSystem.BasisZ);
// Document.Regenerate();
// angle = Math.Acos(connect.CoordinateSystem.BasisX.DotProduct(connector.CoordinateSystem.BasisX));
// Debug.WriteLine(nameof(angle) + angle);
//}
}
var conn = elem.GetConnectors(true).GetNearestConnector(connector.Origin);
if (!connect.IsConnectedTo(connector))
{
connect.ConnectTo(connector);
}
break;
}
}
}
},
"引出短管"
);
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
Result = Result.Cancelled;
}
}
}

View File

@@ -0,0 +1,81 @@
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 ShrlAlgo.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.GetCurve() as Line;
var d = loc.Direction;
var halfGap = 20 / 304.8;
var point1 = pointPicked - d * halfGap;
var point2 = pointPicked + d * halfGap;
var breakPoint1 = mepCurve.GetCurve().Project(point1).XYZPoint;
var breakPoint2 = mepCurve.GetCurve().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 ElementToMove;
// var third = mepCurve.BreakByPoint(breakPoint1);
// Document.Delete(third);
//}
}, "打断管线");
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
}
}
}

View File

@@ -0,0 +1,110 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
namespace ShrlAlgo.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.ElementToMove, 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.GetCurve().GetEndPoint(0),
mepCurve.GetCurve().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.GetCurve() 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 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
Title="线缆布置"
Width="220"
Height="200"
MinWidth="220"
d:DataContext="{d:DesignInstance Type=electricDesign:CableLayoutViewModel}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel Margin="5" Spacing="5">
<ui: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>
</ui:ComboBoxEx>
<ui: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>
</ui:StackPanel>
</ui:FluentWindowEx>

View File

@@ -0,0 +1,20 @@
using Autodesk.Revit.DB;
using System.Windows;
namespace ShrlAlgo.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,83 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Electrical;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Input;
namespace ShrlAlgo.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,20 @@
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.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 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
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:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<ui: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>
<ui:StackPanel
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="刷新" />
</ui:StackPanel>
</ui:AutoGrid>
</ui:FluentWindowEx>

View File

@@ -0,0 +1,13 @@
namespace ShrlAlgo.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 ShrlAlgo.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,25 @@
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class ClashResolveCmd : ExternalCommand
{
public override void Execute()
{
try
{
WinDialogHelper.ShowModeless<ClashResolveView>(new ClashResolveViewModel());
}
catch (Exception)
{
throw;
}
}
}

View File

@@ -0,0 +1,128 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
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:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel 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>
<ui:StackPanel Orientation="Horizontal" Spacing="5">
<GroupBox ui:StackPanel.Fill="Fill" Header="参考方式">
<UniformGrid Rows="1">
<RadioButton
Content="手动"
IsChecked="{Binding LocationType, ConverterParameter={x:Static local:LocationType.Manual}, Converter={StaticResource ComparisonConverter}}"
ToolTip="手动选取两点" />
<RadioButton
Content="参考"
IsChecked="{Binding LocationType, ConverterParameter={x:Static local:LocationType.Reference}, Converter={StaticResource ComparisonConverter}}"
ToolTip="选择相交的两条管线,将翻弯管线偏移一定距离" />
</UniformGrid>
</GroupBox>
<GroupBox ui:StackPanel.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>
</ui:StackPanel>
<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>
<ui:TextBoxEx
ui:StackPanel.Fill="Fill"
Prefix="偏移量:"
Suffix="mm"
Text="{Binding Offset, UpdateSourceTrigger=PropertyChanged}" />
<Button
HorizontalAlignment="Stretch"
ui:StackPanel.Fill="Fill"
Command="{Binding ResolveCommand}"
Content="调整" />
</ui:StackPanel>
</ui:FluentWindowEx>

View File

@@ -0,0 +1,36 @@
using System.Windows;
namespace ShrlAlgo.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,503 @@
using System;
using System.Diagnostics;
using System.Windows;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using Autodesk.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nice3point.Revit.Toolkit.External.Handlers;
using ShrlAlgo.Toolkit.Core.Assist;
using ShrlAlgoToolkit.Core.Assist;
namespace ShrlAlgo.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();
partial void OnLocationTypeChanged(LocationType value)
{
KeyIntPtrHelper.RaiseEscTwice();
}
[RelayCommand]
private static void Closing()
{
//关闭窗口退出选择
KeyIntPtrHelper.RaiseEscTwice();
}
[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 =>
{
try
{
switch (LocationType)
{
case LocationType.Manual:
{
var reference1 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(
e =>
e is MEPCurve mepCurve and not InsulationLiningBase
&& mepCurve.GetCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
),
"请选择翻弯的管线上的点"
);
mepCurveToBend = doc.GetElement(reference1) as MEPCurve;
if (mepCurveToBend.Pinned)
{
MessageBox.Show("错误", "请解锁图元");
CanRunning = true;
return;
}
var reference2 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(e => e.Id == mepCurveToBend!.Id),
"请选择另一个翻弯管线上的点或确定单侧翻弯的需要偏移一侧"
);
//两个断点,breakPoint1距离原直线起点近反向时交换值
breakPoint1 = mepCurveToBend.GetCurve().Project(reference1.GlobalPoint).XYZPoint;
breakPoint2 = mepCurveToBend.GetCurve().Project(reference2.GlobalPoint).XYZPoint;
}
break;
case LocationType.Reference:
{
var reference = uidoc.Selection.PickObject(
ObjectType.Element,
new FuncFilter(
e =>
e is MEPCurve mepCurve and not InsulationLiningBase
&& mepCurve.GetCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
),
"请选择参照的管线"
);
//参考的管线定位
var referenceMEPCurve = doc.GetElement(reference);
var referenceLine = referenceMEPCurve.GetCurve() as Line;
var reference1 = uidoc.Selection.PickObject(
ObjectType.PointOnElement,
new FuncFilter(
e =>
e is MEPCurve mepCurve and not InsulationLiningBase
&& mepCurve.GetCurve() is Line line
&& !line.Direction.CrossProduct(XYZ.BasisZ).IsAlmostEqualTo(XYZ.Zero)
&& e.Id != referenceMEPCurve!.Id
),
"请选择翻弯的管线或确定单侧翻弯的需要偏移一侧"
);
//翻弯的管线定位
mepCurveToBend = doc.GetElement(reference1) as MEPCurve;
var bendLine = mepCurveToBend.GetCurve() as Line;
var result = bendLine!.Intersect(referenceLine, out var array);
XYZ intersectPoint = default;
switch (result)
{
case SetComparisonResult.Overlap:
intersectPoint = array.get_Item(0).XYZPoint;
break;
case SetComparisonResult.Disjoint:
{
IList<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.GetCurve() as Line;
var baseLine = originBaseLine!.Clone() as Line;
//原管线的方向
var direction = baseLine?.Direction;
var startPoint = baseLine!.GetEndPoint(0);
var endPoint = baseLine.GetEndPoint(1);
var breakLine = Line.CreateBound(breakPoint1, breakPoint2);
//var minLength = mepCurveToBend.GetConnectors().OfType<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.GetCurve() as Line).IsInsideEx(breakPoint2))
{
baseMepCurve1 = mepCurveToBend;
baseMepCurve2 = newMepCurve;
}
else
{
baseMepCurve1 = newMepCurve;
baseMepCurve2 = mepCurveToBend;
}
//移动新生成的管线
ElementTransformUtils.MoveElement(
doc,
baseMepCurve2.Id,
translateDirection * Offset / 304.8
);
doc.Regenerate();
////偏移变换
//var translateTransform = Transform.CreateTranslation(translateDirection * Offset / 304.8);
//
//var originOffsetUnboundLine = baseLine.CreateTransformed(translateTransform) as Line;
//需要偏移直线定位线
var originOffsetUnboundLine = baseMepCurve2.GetCurve();
//用于判断交点是否在偏移后的直线内,避免生成不了
var originOffsetLine = originOffsetUnboundLine.Clone() as Line;
originOffsetUnboundLine.MakeUnbound();
var radian = Angle.ToRadian();
//反向时角度取补角
if (breakLine.Direction.IsAlmostEqualTo(-direction))
{
radian = (180 - Angle).ToRadian();
}
var rotation = Transform.CreateRotationAtPoint(
direction.CrossProduct(translateDirection),
radian,
breakPoint1
);
//因为原来的管线可能被移动所以用baseline的克隆线
var unboundAngleLine = baseLine.CreateTransformed(rotation) as Line;
unboundAngleLine!.MakeUnbound();
//偏移管线与倾斜管线交点
var offsetIntersectPoint = originOffsetUnboundLine.IntersectionPoint(unboundAngleLine);
var isInside = originOffsetLine.IsInsideEx(offsetIntersectPoint); //当角度线于偏移直线交点在偏移直线内时
//var flag2 = breakPoint1!.DistanceTo(offsetIntersectPoint) > minLength; //偏移间距大于一定一定值
if (!isInside)
{
MessageBox.Show("管线偏移角度过大或过小,导致交点不在偏移的管线范围内");
break;
}
//if (!flag2)
//{
// MessageBox.Show("管线偏移距离过近");
// break;
//}
//if (isInside)
else
{
var angleLine = Line.CreateBound(breakPoint1, offsetIntersectPoint);
var angleMepCurve = mepCurveToBend.CopyAndSetLocationCurve(angleLine);
//拿到角度管的偏移交点处的连接件
var angleMEPCurveIntersectPointConnector = angleMepCurve
.GetConnectors(true)
.GetNearestConnector(offsetIntersectPoint);
//移动后的管线原有的连接件中,不用连接角度曲线的连接件,用来确定移动后管线的定位线
Connector originOffsetConnector = null;
foreach (Connector conn in baseMepCurve2.GetConnectors(true))
{
//90度时偏移管线的两个连接都是90度但要选择偏移管原有的非打断处的连接件
if (
conn.CoordinateSystem.BasisZ.DotProduct(
angleMEPCurveIntersectPointConnector.CoordinateSystem.BasisZ
) >= 0
&& !offsetIntersectPoint.IsAlmostEqualTo(conn.Origin)
) //锐角
{
originOffsetConnector = conn;
break;
}
}
try
{
//if (originOffsetConnector != null && offsetIntersectPoint.DistanceTo(originOffsetConnector.Origin) > minLength)
if (originOffsetConnector != null)
{
var finalOffsetLine = Line.CreateBound(
offsetIntersectPoint,
originOffsetConnector.Origin
);
baseMepCurve2.SetLocationCurve(finalOffsetLine);
}
var conn1 = 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.GetCurve() as Line).IsInsideEx(breakPoint2)) //后端
{
baseMepCurve1 = mepCurveToBend;
baseMepCurve2 = newMepCurve;
}
else
{
baseMepCurve1 = newMepCurve;
baseMepCurve2 = mepCurveToBend;
}
//第一条管线
var newBaseLine1 = Line.CreateBound(startPoint, breakPoint1);
//第二条管线
var newBaseLine2 = Line.CreateBound(breakPoint2, endPoint);
baseMepCurve1.SetLocationCurve(newBaseLine1);
baseMepCurve2.SetLocationCurve(newBaseLine2);
//生成的偏移线方向相反时,会导致两条翻弯的管线交叉
//第一根翻弯管
var firstMepCurve = mepCurveToBend.CopyAndSetLocationCurve(
Line.CreateBound(breakPoint1, offsetPoint1)
);
//偏移管
var secondMepCurve = mepCurveToBend.CopyAndSetLocationCurve(
Line.CreateBound(offsetPoint1, offsetPoint2)
);
//第二根翻弯管
var thirdMepCurve = mepCurveToBend.CopyAndSetLocationCurve(
Line.CreateBound(offsetPoint2, breakPoint2)
);
doc.Regenerate();
try
{
var connectors1 = 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;
}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
CanRunning = true;
return;
}
catch (Exception)
{
CanRunning = true;
return;
}
},
"碰撞处理"
);
CanRunning = true;
});
}
}
public enum LocationType
{
Manual,
Reference
}
public enum AdjustType
{
TwoSide,
OneSide
}
public enum AdjustDirection
{
Up,
Down,
Left,
Right
}

View File

@@ -0,0 +1,176 @@
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 Nice3point.Revit.Toolkit.Utils;
namespace ShrlAlgo.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 CorrectSlope()
{
var ids = UiDocument.Selection.GetElementIds();
Document.Invoke(ts =>
{
foreach (var id in ids)
{
var elem = Document.GetElement(id);
if (elem is MEPCurve mep)
{
var loc = mep.GetCurve() as Line;
var x = Math.Round(loc.Direction.X);
var y = Math.Round(loc.Direction.Y);
var z = Math.Round(loc.Direction.Z);
//if (Math.Abs(x) < 10E-5)
//{
// x = 0;
//}
//if (Math.Abs(y) < 10E-5)
//{
// y = 0;
//}
//if (Math.Abs(z) < 10E-5)
//{
// z = 0;
//}
var dir = new XYZ(x, y, z);
var endPoint = loc.Origin + dir * loc.Length;
var li = Line.CreateBound(loc.Origin, endPoint);
var locx = mep.GetLocationCurve();
locx.Curve = li;
}
}
},"修正坡度");
}
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);
var loc = mepCurve.GetCurve() as Line;
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,45 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
namespace ShrlAlgo.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,47 @@
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 ShrlAlgo.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;
namespace ShrlAlgo.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,154 @@
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 ShrlAlgo.RvKits.Windows;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.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.GetCurve().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 ElementToMove, (_, _) => true))
// .OfType<ElementToMove>();
// 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<ElementToMove>();
// if (double.TryParse(inputMessage.InputContent, out var headroomValue))
// {
// CheckHeadroom(elems, headroomValue, 1000, 300);
// }
// }
//}
//catch (Autodesk.Revit.Exceptions.OperationCanceledException)
//{
//}
}
}

View File

@@ -0,0 +1,149 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
Title="净空检查"
Width="400"
Height="800"
d:DataContext="{d:DesignInstance local:HeadroomCheckViewModel}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;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" />
<ui:StackPanel
Grid.Row="4"
Grid.Column="0"
Margin="5"
Spacing="5">
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="排除高度低的管线(mm)" />-->
<ui:TextBoxEx
MinWidth="120"
Header="排除高度低的管线:"
HeaderPlacement="Left"
Suffix="mm"
Text="1000" />
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="排除长度短的管线(mm)" />-->
<ui:TextBoxEx
MinWidth="120"
Header="排除长度短的管线:"
HeaderPlacement="Left"
Suffix="mm"
Text="500" />
<!--<TextBlock
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="净高要求高度(mm)" />-->
<ui: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="生成三维房间实体" />
</ui:StackPanel>
</Grid>
</ui:FluentWindowEx>

View File

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

View File

@@ -0,0 +1,178 @@
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 ShrlAlgo.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,57 @@
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Plumbing;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using ShrlAlgoToolkit.Mvvm.Attributes;
namespace ShrlAlgo.RvKits.RvMEP;
public partial class InsulationItem : ObservableValidator
{
[GreaterThan(nameof(MinDiameter))]
[Range(15, 1000)]
public double MaxDiameter
{
get => maxDiameter;
set
{
SetProperty(ref maxDiameter, value, true);
}
}
private double maxDiameter;
[Range(0, 1000)]
public double MinDiameter
{
get => minDiameter;
set
{
SetProperty(ref minDiameter, value, true);
ValidateProperty(MaxDiameter, nameof(MaxDiameter));
}
}
private double minDiameter;
[Required]
[Range(10, 1000)]
[NotifyDataErrorInfo]
[ObservableProperty]
private double thickness;
//{
// get => thickness;
// set => SetProperty(ref thickness, value, true);
//}
//private double thickness;
[ObservableProperty]
private PipeInsulationType pipeInsulationType;
[ObservableProperty]
private DuctInsulationType ductInsulationType;
}

View File

@@ -0,0 +1,180 @@
using System.Windows;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
namespace ShrlAlgo.RvKits.RvMEP;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class MoveConnectCmd : ExternalCommand
{
public override void Execute()
{
Reference baseReference;
Reference moveReference;
try
{
baseReference = UiDocument.Selection.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
new FuncFilter(e => e.GetConnectors(true)?.Size > 0),
"请选择基准的管线、管件、设备实例"
);
moveReference = UiDocument.Selection.PickObject(
Autodesk.Revit.UI.Selection.ObjectType.Element,
new FuncFilter(
e =>
e.Id != baseReference.ElementId
&& e.GetConnectors(true)?.Size > 0
),
"请选择需要移动连接的管线、管件、设备实例"
);
}
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.GetCurve() 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,20 @@
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.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,59 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:local="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
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:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel 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>
<ui:TextBoxEx
Prefix="距离/间距:"
Suffix="mm"
Text="{Binding Distance}"
ToolTip="上下移动的距离(需要区分正负)或与参考的间距" />
<ui:StackPanel Orientation="Horizontal" Spacing="5">
<Button
HorizontalAlignment="Stretch"
ui:StackPanel.Fill="Fill"
Command="{Binding SelectCommand}"
Content="选择" />
<Button
HorizontalAlignment="Stretch"
ui:StackPanel.Fill="Fill"
Command="{Binding MoveCommand}"
Content="移动" />
</ui:StackPanel>
</ui:StackPanel>
</ui: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 ShrlAlgo.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 ShrlAlgoToolkit.Mvvm.Attributes;
namespace ShrlAlgo.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.RaiseEscTwice();
}
#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 ShrlAlgo.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 ShrlAlgo.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,24 @@
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;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.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,101 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:ui="https://github.com/ShrlAlgo/WPFluent"
xmlns:local="clr-namespace:ShrlAlgo.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="Height"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel 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="当选中构件时,则直接旋转,未选中时,则根据选项决定是多选还是单选" />
</ui:StackPanel>
</ui: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 ShrlAlgo.RvKits.RvMEP
{
/// <summary>
/// RotateMEPView.xaml 的交互逻辑
/// </summary>
public partial class RotateMEPView
{
public RotateMEPView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,108 @@
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 ShrlAlgo.RvKits.RvMEP
{
public partial class RotateMEPViewModel : ObservableObject
{
[ObservableProperty]
private double angle = 90;
[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 elemIds)
{
var elem = uidoc.Document.GetElement(id);
var referConn = elem.GetConnectors().OfType<Connector>().FirstOrDefault(c =>
{
if (c.IsConnected)
{
if (c.CoordinateSystem.BasisZ.X > 0)
{
return true;
}
else
{
if (c.CoordinateSystem.BasisZ.Y > 0)
{
return true;
}
else
{
if (c.CoordinateSystem.BasisZ.Z > 0)
{
return true;
}
}
}
}
return false;
});
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 ShrlAlgo.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,15 @@
using Nice3point.Revit.Toolkit.External;
using ShrlAlgo.Toolkit.Core.Assist;
namespace ShrlAlgo.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 @@
<ui:FluentWindowEx
x:Class="ShrlAlgo.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:rvMep="clr-namespace:ShrlAlgo.RvKits.RvMEP"
xmlns:ui="https://github.com/ShrlAlgo/WPFluent"
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:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<ui:StackPanel Margin="5" Spacing="5">
<ui:StackPanel 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>
<ui:StackPanel Spacing="5" Visibility="{Binding IsPipe, Converter={StaticResource BooleanToVisConverter}}">
<ui:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding PipeTypes}"
PlaceholderText="&lt;管道类型&gt;"
SelectedItem="{Binding SelectedPipeType}" />
<ui:ComboBoxEx
DisplayMemberPath="Name"
ItemsSource="{Binding PipeSystemTypes}"
PlaceholderText="&lt;系统类型&gt;"
SelectedItem="{Binding SelectedPipeSystemType}" />
</ui:StackPanel>
<ui:StackPanel Spacing="5" Visibility="{Binding IsDuct, Converter={StaticResource BooleanToVisConverter}}">
<ui:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding DuctTypes}"
PlaceholderText="&lt;风管类型&gt;"
SelectedItem="{Binding SelectedDuctType}" />
<ui:ComboBoxEx
DisplayMemberPath="Name"
ItemsSource="{Binding DuctSystemTypes}"
PlaceholderText="&lt;系统类型&gt;"
SelectedItem="{Binding SelectedDuctSystemType}" />
</ui:StackPanel>
<ui:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding ConduitTypes}"
PlaceholderText="&lt;线管类型&gt;"
SelectedItem="{Binding SelectedConduitType}"
Visibility="{Binding IsConduit, Converter={StaticResource BooleanToVisConverter}}" />
<ui:ComboBoxEx
ItemTemplate="{StaticResource MultiDisplayMemberPath}"
ItemsSource="{Binding CableTrayTypes}"
PlaceholderText="&lt;桥架类型&gt;"
SelectedItem="{Binding SelectedCableTrayType}"
Visibility="{Binding IsCableTray, Converter={StaticResource BooleanToVisConverter}}" />
</ui:StackPanel>
<Button
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Command="{Binding CreateMepCurveCommand}"
Content="创建立管" />
</ui:StackPanel>
</ui:FluentWindowEx>

View File

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

View File

@@ -0,0 +1,177 @@
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 ShrlAlgo.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.RaiseEscTwice();
}
[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,65 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Plumbing;
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel;
namespace ShrlAlgo.RvKits.RvMEP
{
public partial class SystemModel : ObservableObject
{
public SystemModel(MEPSystemType systemType)
{
//PipingSystemType;
//MechanicalSystemType
SystemType = systemType;
Name = systemType.Name;
MEPSystemClassification = (MEPSystemClassification)systemType.SystemClassification;
FillColor = systemType.FillColor;
Abbreviation = systemType.Abbreviation;
}
[ObservableProperty]
private System.Windows.Media.Brush backgroundColor;
[ObservableProperty]
private Color fillColor;
[ObservableProperty]
private MEPSystemClassification mEPSystemClassification;
[ObservableProperty]
private string name;
[ObservableProperty]
private PipeSystemType pipeSystemType;
//系统类型
[ObservableProperty]
private MEPSystemType systemType;
[ObservableProperty]
private Color lineColor;
[ObservableProperty]
private string abbreviation;
}
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,56 @@
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 ShrlAlgo.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) { }
}
}