整体思路: ①选取房间或房间标记(点击按钮后再选择或先选择都可以) ②找到房间边界上的所有门,及门边和房间的交点(每个门2个) ②对房间边界的角点、房间边界上的若干采样点、房间内部的若干采样点进行疏散距离测算,找到最远点(近似) ③绘制从最远点到门边的详图线表示疏散路径 下面附上python代码,AI写了600多行,改了不下200次,辛苦AI了。(未考虑周全所有情形,慎用):
下面附上python代码,AI写了600多行,改了不下200次,辛苦AI了。(未考虑周全所有情形,慎用): # coding: utf-8
import clr
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *
from System.Collections.Generic import List
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
def get_room_boundary(room, get_curves=True, get_walls=False):
"""获取房间边界线段或墙,处理多边界情况"""
opt = SpatialElementBoundaryOptions()
all_boundary_loops = room.GetBoundarySegments(opt)
if len(all_boundary_loops) == 0:
return []
# 合并所有边界循环
all_segments = []
for loop in all_boundary_loops:
all_segments.extend(loop)
if get_curves:
return [seg.GetCurve() for seg in all_segments]
if get_walls:
walls = []
for seg in all_segments:
element = doc.GetElement(seg.ElementId)
if isinstance(element, Wall):
walls.append(element)
return walls
return all_segments
def get_room_doors(room):
"""获取房间的门"""
doors = []
room_walls = get_room_boundary(room, get_curves=False, get_walls=True)
room_wall_ids = set(wall.Id for wall in room_walls)
door_collector = FilteredElementCollector(doc)\
.OfCategory(BuiltInCategory.OST_Doors)\
.WhereElementIsNotElementType()\
.ToElements()
for door in door_collector:
host_wall = door.Host
if host_wall and host_wall.Id in room_wall_ids:
doors.append(door)
return doors
def get_door_width(door):
"""获取门的宽度"""
# 尝试多种方式获取门宽度
width_param = door.get_Parameter(BuiltInParameter.DOOR_WIDTH)
if not width_param or width_param.AsDouble() == 0:
width_param = door.LookupParameter("宽度")
if not width_param or width_param.AsDouble() == 0:
door_type = doc.GetElement(door.GetTypeId())
width_param = door_type.LookupParameter("宽度")
if not width_param or width_param.AsDouble() == 0:
return 0
return width_param.AsDouble()
def get_door_boundary_points(door, room):
"""获取门与房间边界的两个交点"""
door_width = get_door_width(door)
if door_width == 0:
TaskDialog.Show("错误", "无法获取门宽度")
return None
door_point = door.Location.Point
# 获取房间边界
boundary_segs = room.GetBoundarySegments(SpatialElementBoundaryOptions())[0]
# 找到门所在的墙的边界线段
host_wall = door.Host
wall_seg = None
for seg in boundary_segs:
try:
if doc.GetElement(seg.ElementId).Id == host_wall.Id:
wall_seg = seg
break
except:
pass
if not wall_seg:
return None
wall_curve = wall_seg.GetCurve()
wall_start = wall_curve.GetEndPoint(0)
wall_end = wall_curve.GetEndPoint(1)
# 计算墙的方向向量
wall_vector = wall_end - wall_start
wall_length = wall_vector.GetLength()
wall_direction = wall_vector.Normalize()
# 计算门在墙上的投影点
door_vector = door_point - wall_start
door_distance = door_vector.DotProduct(wall_direction)
door_on_wall = wall_start + wall_direction * door_distance
# 计算门两侧的点
half_width = door_width / 2
point1 = door_on_wall + wall_direction * half_width
point2 = door_on_wall - wall_direction * half_width
# 确保点在墙的范围内
for point in [point1, point2]:
point_vector = point - wall_start
point_distance = point_vector.DotProduct(wall_direction)
if point_distance < 0:
point = wall_start
elif point_distance > wall_length:
point = wall_end
return [point1, point2]
def calculate_distance(point1, point2):
"""计算两点之间的距离"""
return point1.DistanceTo(point2)
def get_boundary_points(room_curves):
"""从房间边界曲线获取角点(不再进行额外采样)"""
boundary_points = []
# 获取最小长度阈值(800mm)用于过滤小柱子
app_version = doc.Application.VersionNumber
if int(app_version) >= 2022:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, UnitTypeId.Millimeters)
else:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, DisplayUnitType.DUT_MILLIMETERS)
for curve in room_curves:
# 过滤掉小于阈值的边界(柱子等小障碍物)
if curve.Length < min_length_threshold:
continue
# 只添加曲线的端点(角点)
boundary_points.append(curve.GetEndPoint(0))
boundary_points.append(curve.GetEndPoint(1))
# 移除重复点
unique_points = []
for point in boundary_points:
if not any(point.IsAlmostEqualTo(p) for p in unique_points):
unique_points.append(point)
return unique_points
def is_line_intersect_curves(start_point, end_point, curves, room):
"""检查线段是否与曲线集合相交或在房间外部"""
# 检查两点之间的距离是否太小
if start_point.DistanceTo(end_point) <= doc.Application.ShortCurveTolerance:
return False
# 获取最小长度阈值用于过滤小柱子
app_version = doc.Application.VersionNumber
if int(app_version) >= 2022:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, UnitTypeId.Millimeters)
else:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, DisplayUnitType.DUT_MILLIMETERS)
try:
test_line = Line.CreateBound(start_point, end_point)
# 计算线段中点并检查是否在房间内
mid_point = (start_point + end_point) * 0.5
if not room.IsPointInRoom(mid_point): # 如果中点不在房间内,线段无效
return True
# 检查是否与边界相交(忽略小于阈值的边界)
for curve in curves:
# 跳过小于阈值的曲线(小柱子等)
if curve.Length < min_length_threshold:
continue
result = test_line.Intersect(curve)
# 如果相交结果不是端点,则认为相交
if result != SetComparisonResult.Disjoint:
# 检查是否只是端点相交
if curve.Distance(start_point) > doc.Application.ShortCurveTolerance and \
curve.Distance(end_point) > doc.Application.ShortCurveTolerance:
return True
return False
except:
return False
def is_point_visible(point, target, room_curves, room):
"""检查从point是否可以直接看到target(连线不与边界相交且在房间内)"""
if point.DistanceTo(target) <= doc.Application.ShortCurveTolerance:
return True
try:
# 使用上面定义的函数检查线段是否与边界相交
if is_line_intersect_curves(point, target, room_curves, room):
return False
# 如果线段不与任何边界相交,则点可见
return True
except Exception as e:
# 输出异常信息便于调试
TaskDialog.Show("可见性检查出错: " + str(e))
return False
def get_visibility_boundary_points(point, boundary_points, room_curves, room):
"""获取从point可见的边界点(仅考虑原始边界角点)"""
visible_points = []
# 只检查边界角点的可见性
for boundary_point in boundary_points:
if is_point_visible(point, boundary_point, room_curves, room):
visible_points.append(boundary_point)
return visible_points
def find_nearest_boundary_point(point, room_curves, boundary_points, room):
"""找到距离point最近的可见边界点"""
visible_points = get_visibility_boundary_points(point, boundary_points, room_curves, room)
if not visible_points:
return None
nearest_point = None
min_distance = float('inf')
for visible_point in visible_points:
distance = point.DistanceTo(visible_point)
if distance < min_distance:
min_distance = distance
nearest_point = visible_point
return nearest_point, min_distance
def find_best_path(start_point, end_point, boundary_points, room_curves, room):
"""找到两点之间的最佳路径,基于可见性多边形,使用优化的A*算法"""
# 尝试直接路径
if is_point_visible(start_point, end_point, room_curves, room):
return [start_point, end_point], start_point.DistanceTo(end_point)
# 将起点和终点加入节点集合
all_points = [start_point, end_point] + boundary_points
# 使用缓存减少重复的可见性检查
visibility_cache = {}
def is_visible_cached(p1_idx, p2_idx):
"""带缓存的可见性检查"""
cache_key = (min(p1_idx, p2_idx), max(p1_idx, p2_idx))
if cache_key not in visibility_cache:
result = is_point_visible(all_points[p1_idx], all_points[p2_idx], room_curves, room)
visibility_cache[cache_key] = result
return visibility_cache[cache_key]
# 使用优先队列实现A*算法
import heapq
# 启发函数:到终点的直线距离
def heuristic(point_idx):
return all_points[point_idx].DistanceTo(all_points[1])
# 初始化优先队列,元素为(f值, 当前距离, 节点索引)
# f值 = g值(当前距离) + h值(启发值)
open_set = [(0 + heuristic(0), 0, 0)]
# 记录已访问节点和路径信息
came_from = {}
g_score = {0: 0} # 从起点到当前节点的实际距离
closed_set = set() # 已处理完毕的节点
while open_set:
# 获取f值最小的节点
_, current_g, current = heapq.heappop(open_set)
# 跳过已处理的节点
if current in closed_set:
continue
closed_set.add(current)
# 如果到达终点,重建路径并返回
if current == 1:
# 重建路径
path = []
node = current
while node in came_from:
path.append(all_points[node])
node = came_from[node]
path.append(all_points[0]) # 添加起点
path.reverse()
return path, current_g
# 检查所有与当前节点可见的节点
for neighbor in range(len(all_points)):
if neighbor == current or neighbor in closed_set:
continue
# 如果可见,计算路径长度
if is_visible_cached(current, neighbor):
# 计算通过当前节点到邻居的距离
tentative_g = current_g + all_points[current].DistanceTo(all_points[neighbor])
# 如果找到更短的路径,或者这是第一次访问该节点
if neighbor not in g_score or tentative_g < g_score[neighbor]:
# 更新路径
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score = tentative_g + heuristic(neighbor)
# 添加到优先队列
heapq.heappush(open_set, (f_score, tentative_g, neighbor))
# 如果无法到达终点
return None, 0
def find_shortest_path_to_door(point, door_points, room_curves, boundary_points, room):
"""找到从点到任意门的最短路径"""
min_path_length = float('inf')
best_path = None
for door_point in door_points:
path, path_length = find_best_path(point, door_point, boundary_points, room_curves, room)
if path and path_length < min_path_length:
min_path_length = path_length
best_path = path
return best_path, min_path_length
def is_point_on_boundary(point, room_curves, tolerance=0.01):
"""检查点是否位于房间边界上,并返回所在的边界线段"""
for curve in room_curves:
if curve.Distance(point) <= tolerance:
return True, curve
return False, None
def find_room_farthest_point(room_curves, door_points, boundary_points, room, candidate_count=50):
"""查找房间内的最远点,优先检查角点,减少计算量"""
# 转换800mm为内部单位
app_version = doc.Application.VersionNumber
if int(app_version) >= 2022:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, UnitTypeId.Millimeters)
else:
min_length_threshold = UnitUtils.ConvertToInternalUnits(800, DisplayUnitType.DUT_MILLIMETERS)
# 过滤掉小于阈值的边界曲线(柱子等小障碍物)- 仅用于近似寻找最远点
filtered_curves = [curve for curve in room_curves if curve.Length >= min_length_threshold]
# 同样过滤边界点,仅保留来自较长边界的点
filtered_boundary_points = []
for point in boundary_points:
for curve in filtered_curves:
if (point.IsAlmostEqualTo(curve.GetEndPoint(0)) or
point.IsAlmostEqualTo(curve.GetEndPoint(1))):
filtered_boundary_points.append(point)
break
# 第一步:先检查所有角点,这通常是最远点的主要来源
corner_max_distance = -1
corner_farthest_point = None
corner_best_path = None
for point in filtered_boundary_points:
# 使用过滤后的曲线和边界点来寻找潜在的最远点
path, path_length = find_shortest_path_to_door(
point, door_points, filtered_curves, filtered_boundary_points, room)
if path and path_length > corner_max_distance:
corner_max_distance = path_length
corner_farthest_point = point
corner_best_path = path
# 如果最远角点的距离足够大,直接返回结果,但使用完整边界重新计算实际路径
corner_threshold = 100.0 # 任意长度阈值
if corner_max_distance > corner_threshold and corner_farthest_point:
# 重要:使用完整的边界重新计算确切的路径
actual_path, actual_path_length = find_best_path(
corner_farthest_point, door_points[0], boundary_points, room_curves, room)
# 找到到所有门的最短路径
for door_point in door_points[1:]:
test_path, test_path_length = find_best_path(
corner_farthest_point, door_point, boundary_points, room_curves, room)
if test_path and test_path_length < actual_path_length:
actual_path = test_path
actual_path_length = test_path_length
return corner_farthest_point, actual_path, actual_path_length
# 如果角点检查不够,继续检查其他候选点
candidates = []
# 1. 在过滤后的边界线段中间采样更多点
for curve in filtered_curves:
# 10个采样点
for param in [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]:
point = curve.Evaluate(param, True)
candidates.append(point)
# 2. 在房间内部网格采样
bbox = room.get_BoundingBox(doc.ActiveView)
if bbox:
min_point, max_point = bbox.Min, bbox.Max
step_x = (max_point.X - min_point.X) / 10
step_y = (max_point.Y - min_point.Y) / 10
for i in range(11):
for j in range(11):
x = min_point.X + i * step_x
y = min_point.Y + j * step_y
test_point = XYZ(x, y, min_point.Z)
if room.IsPointInRoom(test_point):
candidates.append(test_point)
# 从候选点中过滤掉与门点过近的点
filtered_candidates = []
min_door_distance = 2.0
for point in candidates:
if all(point.DistanceTo(door_point) >= min_door_distance for door_point in door_points):
filtered_candidates.append(point)
candidates = filtered_candidates
# 如果候选点太少,使用角点结果
if len(candidates) < 10:
if corner_farthest_point:
# 重要:使用完整的边界重新计算确切的路径
actual_path, actual_path_length = find_shortest_path_to_door(
corner_farthest_point, door_points, room_curves, boundary_points, room)
return corner_farthest_point, actual_path, actual_path_length
# 计算所有候选点到最近门的距离 - 使用过滤后的边界进行快速筛选
max_distance = corner_max_distance # 初始化为角点的最大距离
farthest_point = corner_farthest_point
for candidate in candidates:
path, path_length = find_shortest_path_to_door(
candidate, door_points, filtered_curves, filtered_boundary_points, room)
if path and path_length > max_distance:
max_distance = path_length
farthest_point = candidate
# 如果没有找到最远点,使用角点结果
if not farthest_point:
if corner_farthest_point:
# 重要:使用完整的边界重新计算确切的路径
actual_path, actual_path_length = find_shortest_path_to_door(
corner_farthest_point, door_points, room_curves, boundary_points, room)
return corner_farthest_point, actual_path, actual_path_length
else:
return None, None, 0
# 重要:对于找到的最远点,使用完整的边界重新计算确切的路径
best_path, path_length = find_shortest_path_to_door(
farthest_point, door_points, room_curves, boundary_points, room)
# 返回最终结果
return farthest_point, best_path, path_length
def get_line_style_by_name(doc, name):
"""获取指定名称的线型"""
collector = FilteredElementCollector(doc)\
.OfClass(GraphicsStyle)\
.ToElements()
for style in collector:
if style.Name == name:
return style
return None
def draw_detail_line(start_point, end_point, line_style=None):
"""绘制详图线"""
length = start_point.DistanceTo(end_point)
if length <= doc.Application.ShortCurveTolerance:
return None
line = Line.CreateBound(start_point, end_point)
detail_line = doc.Create.NewDetailCurve(doc.ActiveView, line)
if line_style:
detail_line.LineStyle = line_style
return detail_line
def main():
# 选择房间
try:
# 检查是否已经选择了房间
selected_refs = uidoc.Selection.GetElementIds()
if selected_refs and len(selected_refs) > 0:
# 获取第一个选中的元素ID
first_id = list(selected_refs)[0]
selected_element = doc.GetElement(first_id)
# 如果选择的是房间
if isinstance(selected_element, SpatialElement) and \
selected_element.Category.Id.IntegerValue == int(BuiltInCategory.OST_Rooms):
room = selected_element
# 如果选择的是房间标签
elif isinstance(selected_element, SpatialElementTag):
room = selected_element.Room
if not room:
TaskDialog.Show("错误", "无法从房间标记获取房间")
return
else:
# 如果没有选择房间或房间标签,提示用户选择
try:
ref = uidoc.Selection.PickObject(ObjectType.Element, RoomSelectionFilter())
selected_element = doc.GetElement(ref.ElementId)
if isinstance(selected_element, SpatialElement) and \
selected_element.Category.Id.IntegerValue == int(BuiltInCategory.OST_Rooms):
room = selected_element
elif isinstance(selected_element, SpatialElementTag):
room = selected_element.Room
if not room:
TaskDialog.Show("错误", "无法从房间标记获取房间")
return
else:
TaskDialog.Show("错误", "请选择一个房间或房间标签")
return
except:
return
else:
# 如果没有选择任何元素,提示用户选择
try:
ref = uidoc.Selection.PickObject(ObjectType.Element, RoomSelectionFilter())
selected_element = doc.GetElement(ref.ElementId)
if isinstance(selected_element, SpatialElement) and \
selected_element.Category.Id.IntegerValue == int(BuiltInCategory.OST_Rooms):
room = selected_element
elif isinstance(selected_element, SpatialElementTag):
room = selected_element.Room
if not room:
TaskDialog.Show("错误", "无法从房间标记获取房间")
return
else:
TaskDialog.Show("错误", "请选择一个房间或房间标签")
return
except:
return
except Exception as e:
TaskDialog.Show("错误", str(e))
return
# 获取房间的门
doors = get_room_doors(room)
if not doors:
TaskDialog.Show("错误", "所选房间没有门")
return
# 获取所有门点
all_door_points = []
for door in doors:
door_points = get_door_boundary_points(door, room)
if door_points:
all_door_points.extend(door_points)
if not all_door_points:
TaskDialog.Show("错误", "无法获取门位置")
return
# 获取房间边界线段和角点(不进行额外采样)
boundary_curves = get_room_boundary(room, get_curves=True)
boundary_points = get_boundary_points(boundary_curves)
# 找到房间最远点及路径
farthest_point, best_path, path_length = find_room_farthest_point(
boundary_curves, all_door_points, boundary_points, room, 50)
if not farthest_point or not best_path:
TaskDialog.Show("错误", "无法找到有效的最远点或疏散路径")
return
# 开启事务绘制详图线
t = Transaction(doc, "绘制疏散距离线")
t.Start()
# 获取红色线型
red_line_style = get_line_style_by_name(doc, "0-红")
# 绘制路径
for i in range(len(best_path) - 1):
draw_detail_line(best_path[i], best_path[i + 1], red_line_style)
# 转换单位显示
app_version = doc.Application.VersionNumber
if int(app_version) >= 2022:
display_distance = UnitUtils.ConvertFromInternalUnits(path_length, UnitTypeId.Millimeters)
else:
display_distance = UnitUtils.ConvertFromInternalUnits(path_length, DisplayUnitType.DUT_MILLIMETERS)
t.Commit()
TaskDialog.Show("结果", "最远疏散距离: {0:.2f} 毫米".format(display_distance))
# 添加房间选择过滤器
class RoomSelectionFilter(ISelectionFilter):
def AllowElement(self, element):
if isinstance(element, SpatialElement) and \
element.Category.Id.IntegerValue == int(BuiltInCategory.OST_Rooms):
return True
if isinstance(element, SpatialElementTag):
return True
return False
def AllowReference(self, reference, point):
return False
if __name__ == '__main__':
main()

被折叠的 条评论
为什么被折叠?



