python寻找房间内到疏散门最远点(近似解)

整体思路: ①选取房间或房间标记(点击按钮后再选择或先选择都可以) ②找到房间边界上的所有门,及门边和房间的交点(每个门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() 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值