基于Revit API实现管道自动布线及管件连接报错求助
问题:Revit API创建T型接头时角度报错的解决思路
问题背景
我正尝试通过Revit API实现管道自动布线,目标是让公司内的给排水工程师只需指定集水井及若干相关端点(如马桶、淋浴间)的位置,即可由插件自动规划合适的管道路径,将端点与集水井连接。我了解到布线属于NP-Hard问题,但希望在端点数量较少时,通过启发式算法得出合理方案。目前开发POC阶段,尝试连接两根平行管道与中间管道时,触发报错:
Fitting cannot be created between the input connectors because the angle between them is too small or too large.
打印连接器角度后发现是45度和135度的组合,推测135度触发了报错,怀疑是管道方向问题,但不确定如何指定管道方向。另外说明:实际需要的Y型接头属于T型接头子集,调用NewTeeFitting方法是正确的。
问题代码
using (Transaction t = new Transaction(doc, "create pipes")) { t.Start(); XYZ location1_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z); Pipe pipe1 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location1_start, location1_end); // Insert second pipe parallel to the first, beneath it XYZ location2_start = new XYZ(location1_start.X, location1_start.Y, location1_start.Z - zOffset); XYZ location2_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z - zOffset); Pipe pipe2 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location2_start, location2_end); // pipe3 starts from someplace in the middle of pipe 1, and reaches someplace in the middle of pipe 2. XYZ location3_start = new XYZ(location1_start.X, location1_start.Y + 10 , location1_start.Z); XYZ location3_end = new XYZ(location1_start.X, location1_start.Y + 15, location1_start.Z - zOffset); Pipe pipe3 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location3_start, location3_end); // By default there are two connectors for a pipe - one at the starting point, and one at the end. ConnectorManager cm1 = pipe1.ConnectorManager; ConnectorManager cm2 = pipe2.ConnectorManager; ConnectorManager cm3 = pipe3.ConnectorManager; Connector pipe1Connector = cm1.Connectors.Cast<Connector>().FirstOrDefault(); Connector pipe2Connector = cm2.Connectors.Cast<Connector>().FirstOrDefault(); // Where the third pipe begins (somewhere in the middle of pipe 1) Connector thirdPipeConnector1 = cm3.Connectors.Cast<Connector>().FirstOrDefault(); // Where the third pipe ends (somewhere in the middle of pipe 2) Connector thirdPipeConnector2 = cm3.Connectors.Cast<Connector>().LastOrDefault(); // From this point on, I'm not sure if the code is right. Here, I tried splitting pipes 1 and 2 at the locations where pipe 3 started and ended, so // that new connectors could then be defined at those points, and those connectors could be used to connect to pipe 3. ElementId pipe1Segment2Id = PlumbingUtils.BreakCurve(doc, pipe1.Id, location3_start); ElementId pipe2Segment2Id = PlumbingUtils.BreakCurve(doc, pipe2.Id, location3_end); Pipe pipe1Segment1 = doc.GetElement(pipe1.Id) as Pipe; Pipe pipe1Segment2 = doc.GetElement(pipe1Segment2Id) as Pipe; Pipe pipe2Segment1 = doc.GetElement(pipe2.Id) as Pipe; Pipe pipe2Segment2 = doc.GetElement(pipe2Segment2Id) as Pipe; // The connector right before the split of pipe 1? Connector pipe1SplitConnector1 = pipe1Segment1.ConnectorManager.Connectors.Cast<Connector>().LastOrDefault(); // End of first segment // The connector right after the split of pipe 1? Connector pipe1SplitConnector2 = pipe2Segment2.ConnectorManager.Connectors.Cast<Connector>().FirstOrDefault(); // Start of second segment Connector pipe2SplitConnector1 = pipe2Segment1.ConnectorManager.Connectors.Cast<Connector>().LastOrDefault(); // End of first segment Connector pipe2SplitConnector2 = pipe2Segment2.ConnectorManager.Connectors.Cast<Connector>().FirstOrDefault(); // Start of second segment // This was to help in debugging. double ang1_3 = CalculateAngleBetweenConnectors(pipe1SplitConnector1, thirdPipeConnector1); double ang1_end_3 = CalculateAngleBetweenConnectors(pipe1SplitConnector2, thirdPipeConnector1); double ang2_3 = CalculateAngleBetweenConnectors(pipe2SplitConnector1, thirdPipeConnector2); double ang2_end_3 = CalculateAngleBetweenConnectors(pipe2SplitConnector2, thirdPipeConnector2); TaskDialog.Show("Angles", $"angle between pipe 1 and pipe 3: {ang1_3}, {ang1_end_3}, angle between pipe 2 and pipe 3: {ang2_3},{ang2_end_3}"); // The errors are triggered by either of the following two lines. //doc.Create.NewTeeFitting(pipe1SplitConnector1, thirdPipeConnector1, pipe1SplitConnector2); //doc.Create.NewTeeFitting(pipe2SplitConnector1, thirdPipeConnector2, pipe2SplitConnector2); t.Commit(); }
核心问题分析
- 连接器方向不匹配:Revit的管件创建对连接器的流向有严格要求,
NewTeeFitting的三个参数需要符合「主管道两端+分支管道」的逻辑,且分支管道的连接器方向要指向主管道的分流点。135度角度本质是管道方向反转导致的,分支管道端点方向背离了主管道分流点。 - 管道创建顺序错误:先创建分支管道pipe3再拆分主管道,导致分支管道的位置、方向无法和主管道分流点对齐,拆分后的主管道连接器也容易和分支管道连接器方向冲突。
- 连接器选择逻辑不可靠:通过
FirstOrDefault()和LastOrDefault()选择连接器不精准,管道的连接器顺序不一定和创建时的起点终点一致,需通过坐标或方向向量匹配分流点连接器。
解决思路
1. 调整管道创建顺序
先处理主管道拆分,再创建分支管道,确保分支管道的起点/终点严格对齐主管道分流点:
- 先创建pipe1和pipe2,拆分后获取分流点连接器
- 以两个分流点为起点终点创建pipe3,保证分支管道连接器方向自然指向主管道
2. 精准匹配连接器
拆分主管道后,通过坐标定位获取分流点连接器,替代依赖顺序的选择方式:
// 获取pipe1拆分后的分流点连接器(位置等于location3_start) Connector pipe1SplitConnector = pipe1Segment1.ConnectorManager.Connectors .Cast<Connector>() .First(c => c.Origin.IsAlmostEqualTo(location3_start)); // 同理获取pipe2分流点连接器 Connector pipe2SplitConnector = pipe2Segment1.ConnectorManager.Connectors .Cast<Connector>() .First(c => c.Origin.IsAlmostEqualTo(location3_end));
3. 修正NewTeeFitting参数顺序
NewTeeFitting的参数顺序为:主管道入口连接器、分支管道连接器、主管道出口连接器,三个连接器的方向需形成合理分流角度。若角度超出族允许范围,需检查当前管道类型对应的管件族是否支持135度Y型接头。
4. 反转分支管道方向
若需保留现有创建顺序,可交换分支管道的起点终点,让连接器指向主管道:
// 交换pipe3的起点终点,反转方向 Pipe pipe3 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location3_end, location3_start);
修正后的代码示例
using (Transaction t = new Transaction(doc, "create pipes")) { t.Start(); XYZ location1_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z); Pipe pipe1 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location1_start, location1_end); // Insert second pipe parallel to the first, beneath it XYZ location2_start = new XYZ(location1_start.X, location1_start.Y, location1_start.Z - zOffset); XYZ location2_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z - zOffset); Pipe pipe2 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location2_start, location2_end); // 先拆分主管道,获取分流点位置的连接器 ElementId pipe1Segment2Id = PlumbingUtils.BreakCurve(doc, pipe1.Id, location3_start); ElementId pipe2Segment2Id = PlumbingUtils.BreakCurve(doc, pipe2.Id, location3_end); Pipe pipe1Segment1 = doc.GetElement(pipe1.Id) as Pipe; Pipe pipe1Segment2 = doc.GetElement(pipe1Segment2Id) as Pipe; Pipe pipe2Segment1 = doc.GetElement(pipe2.Id) as Pipe; Pipe pipe2Segment2 = doc.GetElement(pipe2Segment2Id) as Pipe; // 通过位置精准获取分流点连接器 Connector pipe1SplitIn = pipe1Segment1.ConnectorManager.Connectors .Cast<Connector>() .First(c => !c.Origin.IsAlmostEqualTo(location1_start)); Connector pipe1SplitOut = pipe1Segment2.ConnectorManager.Connectors .Cast<Connector>() .First(c => !c.Origin.IsAlmostEqualTo(location1_end)); Connector pipe2SplitIn = pipe2Segment1.ConnectorManager.Connectors .Cast<Connector>() .First(c => !c.Origin.IsAlmostEqualTo(location2_start)); Connector pipe2SplitOut = pipe2Segment2.ConnectorManager.Connectors .Cast<Connector>() .First(c => !c.Origin.IsAlmostEqualTo(location2_end)); // 以两个分流点为起点终点创建分支管道,确保方向正确 Pipe pipe3 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location3_start, location3_end); Connector pipe3Conn1 = pipe3.ConnectorManager.Connectors .Cast<Connector>() .First(c => c.Origin.IsAlmostEqualTo(location3_start)); Connector pipe3Conn2 = pipe3.ConnectorManager.Connectors .Cast<Connector>() .First(c => c.Origin.IsAlmostEqualTo(location3_end)); // 创建T型接头,参数顺序:主管道入口、分支、主管道出口 doc.Create.NewTeeFitting(pipe1SplitIn, pipe3Conn1, pipe1SplitOut); doc.Create.NewTeeFitting(pipe2SplitIn, pipe3Conn2, pipe2SplitOut); t.Commit(); }
额外注意事项
- 检查当前管道系统对应的管件族是否支持135度Y型接头,部分族可能仅允许90度T型,需更换合适的族类型
- 使用
IsAlmostEqualTo()比较XYZ坐标,避免浮点精度问题导致的连接器匹配错误 - 可通过
Connector.Direction属性获取连接器方向向量,验证是否与预期流向一致
内容的提问来源于stack exchange,提问作者JMS
相关产品推荐
相关产品推荐

