2026-02-12 21:29:00 +08:00
|
|
|
|
using Autodesk.Revit.Attributes;
|
|
|
|
|
|
using Autodesk.Revit.DB;
|
|
|
|
|
|
using Autodesk.Revit.UI.Selection;
|
|
|
|
|
|
|
|
|
|
|
|
namespace ShrlAlgo.Addin.Test
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2026-02-22 20:03:42 +08:00
|
|
|
|
/// 对齐管线到楼板的命令
|
2026-02-12 21:29:00 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Transaction(TransactionMode.Manual)]
|
|
|
|
|
|
[Regeneration(RegenerationOption.Manual)]
|
|
|
|
|
|
public class AlignMEPCurvesToSlabCmd : IExternalCommand
|
|
|
|
|
|
{
|
|
|
|
|
|
public void AlignMEPCurveToSlab(Document doc, MEPCurve mepCurve, Floor slab)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 获取管线的定位曲线
|
|
|
|
|
|
LocationCurve locationCurve = mepCurve.Location as LocationCurve;
|
|
|
|
|
|
if (locationCurve == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
Curve curve = locationCurve.Curve;
|
|
|
|
|
|
XYZ startPoint = curve.GetEndPoint(0);
|
|
|
|
|
|
XYZ endPoint = curve.GetEndPoint(1);
|
|
|
|
|
|
|
|
|
|
|
|
View3D view3D = doc.ActiveView as View3D;
|
|
|
|
|
|
if (view3D == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试获取第一个非模板的3D视图
|
|
|
|
|
|
FilteredElementCollector collector = new FilteredElementCollector(doc);
|
|
|
|
|
|
view3D = collector.OfClass(typeof(View3D))
|
|
|
|
|
|
.Cast<View3D>()
|
|
|
|
|
|
.FirstOrDefault(v => !v.IsTemplate);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 注意:实际开发中最好手动指定一个确定的3D视图,这里为了演示简单取了第一个非模板视图
|
|
|
|
|
|
|
|
|
|
|
|
if (view3D == null) return; // 如果没有3D视图无法进行
|
|
|
|
|
|
|
|
|
|
|
|
// 2.2 直接传入 slab.Id,表示只与这个元素做碰撞检测
|
|
|
|
|
|
ReferenceIntersector intersector = new ReferenceIntersector(slab.Id, FindReferenceTarget.Element, view3D);
|
|
|
|
|
|
intersector.FindReferencesInRevitLinks = false;
|
|
|
|
|
|
var level= doc.GetElement(slab.LevelId) as Level;
|
|
|
|
|
|
var offsetInFeet = ((Line)locationCurve.Curve).Origin.Z - level.Elevation;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 计算新的起点和终点
|
|
|
|
|
|
XYZ newStart = GetProjectedPoint(intersector, startPoint, offsetInFeet);
|
|
|
|
|
|
XYZ newEnd = GetProjectedPoint(intersector, endPoint, offsetInFeet);
|
|
|
|
|
|
|
|
|
|
|
|
if (newStart == null || newEnd == null) return; // 投影失败
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 更新管线位置
|
|
|
|
|
|
using (Transaction t = new Transaction(doc, "管线对齐楼板"))
|
|
|
|
|
|
{
|
|
|
|
|
|
t.Start();
|
|
|
|
|
|
//if (mepCurve is CableTray)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// DisconnectMEPCurve(mepCurve);
|
|
|
|
|
|
//}
|
|
|
|
|
|
doc.Regenerate();
|
|
|
|
|
|
// 断开连接的逻辑建议加上,避免报错
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新直线
|
|
|
|
|
|
Line newLine = Line.CreateBound(newStart, newEnd);
|
|
|
|
|
|
locationCurve.Curve = newLine;
|
|
|
|
|
|
|
|
|
|
|
|
t.Commit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助方法:获取投影后的点
|
|
|
|
|
|
private XYZ GetProjectedPoint(ReferenceIntersector intersector, XYZ point, double offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 从高处向下投射,或者从点的位置向下/向上投射
|
|
|
|
|
|
// 这里假设管线在板上方,向下投射 (-Z)
|
|
|
|
|
|
XYZ rayOrigin = new XYZ(point.X, point.Y, point.Z + 100); // 抬高起点确保能射到
|
|
|
|
|
|
XYZ rayDirection = XYZ.BasisZ.Negate(); // 向下
|
|
|
|
|
|
|
|
|
|
|
|
ReferenceWithContext context = intersector.FindNearest(rayOrigin, rayDirection);
|
|
|
|
|
|
|
|
|
|
|
|
if (context != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Reference refer = context.GetReference();
|
|
|
|
|
|
XYZ intersectionPoint = refer.GlobalPoint;
|
|
|
|
|
|
|
|
|
|
|
|
// 关键:新的Z值 = 板表面交点Z + 偏移量
|
|
|
|
|
|
return new XYZ(point.X, point.Y, intersectionPoint.Z + offset);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null; // 未找到交点
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
|
|
|
|
|
|
{
|
|
|
|
|
|
var uiApp = commandData.Application;
|
|
|
|
|
|
var UiDocument = uiApp.ActiveUIDocument;
|
|
|
|
|
|
var Document = UiDocument.Document;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var curves = UiDocument.Selection.PickElementsByRectangle(new MEPCurveFilter(), "请选择需要对齐的管道")
|
|
|
|
|
|
.OfType<MEPCurve>()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
var reference = UiDocument.Selection.PickObject(ObjectType.Element, new FloorSelectionFilter(), "请选择楼板");
|
|
|
|
|
|
var slab = Document.GetElement(reference);
|
|
|
|
|
|
using(TransactionGroup tg=new TransactionGroup(Document, "管线对齐楼板"))
|
|
|
|
|
|
{
|
|
|
|
|
|
tg.Start();
|
|
|
|
|
|
foreach (var curve in curves)
|
|
|
|
|
|
{
|
|
|
|
|
|
AlignMEPCurveToSlab(Document, curve, slab as Floor);
|
|
|
|
|
|
}
|
|
|
|
|
|
tg.Assimilate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Result.Succeeded;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 用户取消选择,正常退出
|
|
|
|
|
|
return Result.Cancelled;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 断开管线/风管的所有物理连接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="mepCurve">需要断开的管线</param>
|
|
|
|
|
|
private void DisconnectMEPCurve(MEPCurve mepCurve)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取连接管理器
|
|
|
|
|
|
ConnectorManager cm = mepCurve.ConnectorManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历该管线自带的所有连接器
|
|
|
|
|
|
foreach (Connector connector in cm.Connectors)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果该端口没有连接,则跳过
|
|
|
|
|
|
if (!connector.IsConnected) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取该端口连接到的所有“其他连接器”
|
|
|
|
|
|
// AllRefs 包含了与之连接的所有对象(包括物理连接和逻辑系统连接)
|
|
|
|
|
|
ConnectorSet connectedRefs = connector.AllRefs;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (Connector otherConnector in connectedRefs)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 排除自身(虽然很少见,但为了安全)
|
|
|
|
|
|
if (otherConnector.Owner.Id == mepCurve.Id) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤:只断开物理连接(End或Curve类型),保留逻辑连接通常不影响移动,
|
|
|
|
|
|
// 但为了防止移动报错,通常建议断开物理连接。
|
|
|
|
|
|
// 这里的判断可以根据需要放宽,直接断开所有也行。
|
|
|
|
|
|
if (otherConnector.ConnectorType == ConnectorType.End ||
|
|
|
|
|
|
otherConnector.ConnectorType == ConnectorType.Curve ||
|
|
|
|
|
|
otherConnector.ConnectorType == ConnectorType.Physical)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 执行断开操作
|
|
|
|
|
|
connector.DisconnectFrom(otherConnector);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal class MEPCurveFilter : ISelectionFilter
|
|
|
|
|
|
{
|
|
|
|
|
|
public bool AllowElement(Element elem)
|
|
|
|
|
|
{
|
|
|
|
|
|
return elem is MEPCurve;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool AllowReference(Reference reference, XYZ position)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal class FloorSelectionFilter : ISelectionFilter
|
|
|
|
|
|
{
|
|
|
|
|
|
public bool AllowElement(Element elem)
|
|
|
|
|
|
{
|
|
|
|
|
|
return elem is Floor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool AllowReference(Reference reference, XYZ position)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|