开发一款工程图编号标注自动排布软件, 用于生成从部件点或线段到多边形边界的一组引出线段, 确保编号在视图中的位置合理、均匀分布且不与障碍物(如标高线、文字框)发生碰撞, 同时满足几何和拓扑约束, 优化线段长度和布局美观性。
-
输入数据处理
- 接收 JSON 格式的输入数据, 包括:
- 多边形包围盒边缘(闭合多边形路径)。
- 部件编号信息(点候选集合
PartPointCandidate和线候选集合PartLineCandidate, 包含可能的起始点或线段)。 - 矩形障碍物集合
ObstacleBox。 - 线段障碍物集合
BarrierLine(如标高线与定位线)。
- 解析输入数据为适合几何运算的结构(如
Point、LineString、Polygon), 验证输入合法性(如检查缺失字段、点集是否构成有效多边形)。 - 将线候选转换为点候选(通过固定步长采样线段), 统一为点候选进行后续处理。
- 接收 JSON 格式的输入数据, 包括:
-
终点线生成与筛选
- 根据多边形边界生成候选终点集合, 通过固定步长(或动态调整步长)均匀采样边界点。
- 为每个点候选(或从线候选转换的点)计算与边界采样点的连接线, 筛选满足以下条件的候选线段:
- 不穿越多边形边界(从内部连出)。
- 不穿越任何矩形障碍物。
- 与线段障碍物保持指定距离(默认 5mm)。
- 终点集合相互远离一定距离(默认 5mm)。
- 线段与多边形外包围盒法线的夹角在5°到65°之间。
- 线段不得水平(与水平线夹角 ≠ 0°)或竖直(与垂直线夹角 ≠ 0°)。
-
编号排布
- 贪心策略:
- 为每个点候选选择最短且满足约束的线段(不与已有线段相交、不穿越障碍物、远离线段障碍物、满足夹角和非水平/竖直约束)。
- 逐一确定每个候选的引出线, 更新已使用线段集合以避免后续交叉。
- 回溯策略:
- 若贪心策略无法为所有候选生成有效线段, 采用回溯算法, 尝试不同线段组合以最大化成功连接的候选数量。
- 递归探索所有可能线段, 记录全局最优解(连接最多候选的方案)。
- 若某些候选无法连接, 跳过并继续处理其他候选。
- 贪心策略:
-
布局优化
- 在初始排布基础上, 通过动态调整步长优化线段布局, 减少重叠并提高均匀性。
- 使用二分法调整采样步长(建议基于多边形周长动态调整, 如周长的 1/100), 记录每轮的最佳结果。
- 优化线段夹角分布, 确保夹角均匀分布于5°到65°, 避免过于集中。
-
排布结果量化
- 评估引出线段的布局质量, 基于以下指标:
- 线段终点在边界上的分布均匀程度(以终点间距离标准差衡量)。
- 线段长度优化程度(优先选择较短线段)。
- 与线段障碍物的距离(避免靠近以提升美观性)。
- 线段夹角的合规性(5°到65°)和分布均匀性。
- 计算每条边界线的量化得分, 按候选数量加权平均, 得出整体布局评分。
- 评估引出线段的布局质量, 基于以下指标:
- 输入数据(基于 JSON 格式):
- 多边形边界(
target_exterior):点集描述的闭合多边形(shapely.Polygon)。 - 部件编号信息:
- 点候选(
PartPointCandidate):包含部件名称和点集(shapely.Point)。 - 线候选(
PartLineCandidate):包含部件名称和线段集(MyLine), 可转换为点候选。
- 点候选(
- 障碍物信息:
- 矩形障碍物(
ObstacleBox):由四个顶点构成的矩形(shapely.Polygon)。 - 线段障碍物(
BarrierLine):线段集合(MyLine), 用于避免靠近。
- 矩形障碍物(
- 多边形边界(
- 几何约束:
- 引出线段起点为部件点候选或线候选的采样点, 终点位于多边形边界。
- 线段不得穿越多边形边界(仅从内部连出)。
- 线段不得穿越矩形障碍物。
- 线段终点与线段障碍物保持最小距离(默认 5mm)。
- 线段与多边形外包围盒法线的夹角在5°到65°之间。
- 线段不得水平(与水平线夹角 ≠ 0°)或竖直(与垂直线夹角 ≠ 0°)。
- 拓扑约束:
- 所有引出线段互不相交。
- 优化目标:
- 尽量缩短每条引出线段的长度。
- 确保终点在边界上分布均匀。
- 确保线段夹角均匀分布于5°到65°。
- 提高布局美观性(通过与线段障碍物保持距离)。
- 健壮性:
- 验证输入数据的合法性(如点集是否构成有效多边形、是否存在缺失字段)。
- 处理非法输入, 抛出
SchemaParseError异常。
- 功能测试:验证软件是否满足所有几何和拓扑约束, 包括夹角(5°到65°)和非水平/竖直约束, 覆盖所有合法输入场景。
- 边界测试:测试边界值(如最小/最大采样步长、点集数量、边界点密度、夹角边界值5°和65°)。
- 异常处理测试:验证软件在非法输入(如缺失字段、无效多边形)下的异常处理能力。
- 提供命令行接口, 支持输入文件解析与完整生成流程。
- 输入输出数据结构与 JSON 格式兼容, 方便与外部系统(如
C#程序)集成。 - 使用
CLR(Common Language Runtime)封装成C#可以调用的形式。
- 语言:Python 3.13.6
- 几何运算:
shapely:处理点、线、多边形及其几何操作(交集、距离计算、夹角计算等)。numpy:支持高效数组和向量计算。
- 数据结构:
heapq:实现最小堆, 用于按线段长度排序。
- 可视化:
matplotlib:用于绘制几何图形和验证算法效果(包括夹角分布)。
- 开发环境:
- 使用虚拟环境(
python -m venv)隔离依赖。 - 通过
pip install -r requirements-dev.txt安装依赖。
- 使用虚拟环境(
- 软件支持单视图处理(三视图、展开图或向视图), 每次处理一个视图的编号排布。
- 采样步长动态调整, 建议基于多边形周长(如周长的 1/100)。
- 线候选采样规则:
- 短线段视为单点(仅取中点)。
- 中等长度线段按固定步长采样。
- 长线段按固定数量采样。
- 优化建议:
- 按几何中心到边界的距离对候选排序以提高效率。
- 使用向量运算验证线段夹角, 优先选择满足5°到65°约束的线段。
- 在回溯算法中优先探索夹角分布均匀的方案。
下图与步骤描述展示了回溯版排布算法的整体流程,已包含“优先级”策略:
- 候选优先级说明:
priority从 1 开始,数值越小优先级越高;数值越大优先级越低。 - 候选排序规则:按“候选内点的最小 priority”升序排序(高优先更先处理)。
- 候选内连线排序:以“点的 priority”优先,再以“线段长度”优先(更小更优)。
- 边界采样:对外轮廓
exterior以固定步长采样边界点集合。 - 候选统一:将线候选
PartLineCandidate通过 v2 规则采样为点候选,与原有点候选合并。 - 候选排序(按优先级):对合并后的候选按最小点优先级升序排序。
- 预生成可行连线:
- 对每个候选,枚举其每个点与全部边界采样点,构造连线;
- 过滤不合法连线(不穿越外轮廓、不穿越障碍物的快速几何筛选);
- 将合格连线按键
(点priority, 线长)排序,得到该候选的“候选线列表”。
- 回溯搜索(DFS)与剪枝:
- 递归遍历候选序列,尝试为当前候选选择一条与既有选择不冲突的连线;
- 使用
is_valid_line严格校验(不与已有连线相交、满足距离/夹角等约束、避开驱散线dispel_lines); - 维护全局最优(连接数量最多)。
- 入口剪枝:若“当前已选数量 + 剩余候选数 ≤ 已知最优条数”,直接回退。
- 循环层剪枝:在当前候选的候选线循环中,若“本层上界 = 当前已选 + 剩余候选数 == 已知最优”,剪掉本层剩余线的尝试。
- 跳过分支剪枝:若“当前已选 + 剩余候选数 - 1 ≤ 已知最优”,则无需探索“跳过当前候选”的分支。
6)日志记录
- 当找到“全覆盖”(全部候选均成功连接)时,输出一次 INFO 日志(控制台可见)。
- DFS 过程细节(进入、剪枝、尝试、选择、回溯、跳过、更新最优)以 DEBUG 写入
backtracking_dfs.log文件。
flowchart TD
A["开始"] --> B["采样外轮廓点<br/>generate_sampled_points_by_length"]
B --> C["统一候选<br/>point_candidates + line_candidates.to_point_candidate_v2"]
C --> D["为每个候选预生成候选线列表"]
D --> D1["枚举 点 x 边界采样点"]
D1 --> D2["构造 LineString pt->boundary_pt"]
D2 --> D3{"是否穿越<br/>外轮廓 或 障碍物?"}
D3 -- 是 --> D1
D3 -- 否 --> D4["按 (点优先级, 线长, 序号) 入堆"]
D4 --> D5["堆排序 -> 候选线列表"]
D5 --> E["DFS 启动: idx=0, selected=∅"]
E --> F{"idx 是否等于候选数?"}
F -- 是 --> G{"selected 是否更优?"}
G -- 是 --> H["best_solution <- selected 拷贝"]
G -- 否 --> I["返回"]
H --> J{"是否全覆盖 且 未记录?"}
J -- 是 --> K["INFO: 全覆盖方案"]
J -- 否 --> I
F -- 否 --> L["remaining = 候选数 - idx"]
L --> M{"selected + remaining <= best ?"}
M -- 是 --> I
M -- 否 --> N["遍历 当前候选 的候选线"]
N --> O["取一条候选线"]
O --> P{"is_valid_line 合法?"}
P -- 否 --> S["尝试下一条"]
P -- 是 --> Q["选择该线 push"]
Q --> R["递归: DFS(idx+1, selected)"]
R --> T["回溯 pop"]
T --> U{"本层上界 == 已知最优?"}
U -- 是 --> V["剪掉本层剩余线"]
U -- 否 --> S
V --> W
S --> W
W["评估跳过分支"] --> X{"selected + remaining - 1 <= best?"}
X -- 是 --> I
X -- 否 --> Y["跳过当前候选 -> DFS(idx+1, selected)"]
Y --> I
-
输入:
point_candidates: list[PartPointCandidate]line_candidates: list[PartLineCandidate]exterior: Polygon,obstacles: list[Polygon],dispel_lines: list[LineString]samples_distance: int
-
输出:
list[LineString](最佳方案中的连线集合)
-
核心逻辑:
- 采样
exterior的边界点。 - 将线候选转点候选(v2),与点候选合并,按候选优先级排序。
- 为每个候选生成“候选线列表”,按
(点priority, 线长)排序。 - DFS:按顺序尝试为每个候选选线,满足
is_valid_line则继续;维护best_solution,并应用三类剪枝(入口剪枝、循环层剪枝、跳过分支剪枝)。 - 日志:全覆盖以 INFO 输出到控制台;DFS 细节以 DEBUG 写入
backtracking_dfs.log;最终返回best_solution。
- 采样
提示:优先级越小越先被处理;在候选内部也优先使用来自高优先级点的连线,并倾向更短的连线,这会引导回溯更早满足高优先级部件的布线需求。
结果演示






