|
|
|
|
@ -46,6 +46,8 @@ DEFAULT_OPTIONS = {
|
|
|
|
|
"allow_floating_fallback": False,
|
|
|
|
|
# 障碍包围盒会按这个距离膨胀,用于提前发现贴碰风险。
|
|
|
|
|
"obstacle_clearance": 5.0,
|
|
|
|
|
# 端子出线/入线段通常会贴近端子塑壳或设备外壳,不作为主路径碰撞判定依据。
|
|
|
|
|
"ignore_endpoint_collision_segments": True,
|
|
|
|
|
# 防止坐标异常或端子离路由网络过远时生成超长接入线,把 FreeCAD
|
|
|
|
|
# 视图包围盒拉得过大,导致旋转时模型被裁剪到看不见。
|
|
|
|
|
"terminal_access_max_distance": 1000.0,
|
|
|
|
|
@ -800,10 +802,32 @@ def _expanded_obstacle_exclusion_ids(doc, exclude):
|
|
|
|
|
return excluded
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _distance_point_to_bbox(point, bbox):
|
|
|
|
|
squared = 0.0
|
|
|
|
|
for axis, min_key, max_key in (
|
|
|
|
|
("x", "xmin", "xmax"),
|
|
|
|
|
("y", "ymin", "ymax"),
|
|
|
|
|
("z", "zmin", "zmax"),
|
|
|
|
|
):
|
|
|
|
|
value = _axis_value(point, axis)
|
|
|
|
|
low = float(bbox[min_key])
|
|
|
|
|
high = float(bbox[max_key])
|
|
|
|
|
if value < low:
|
|
|
|
|
squared += (low - value) * (low - value)
|
|
|
|
|
elif value > high:
|
|
|
|
|
squared += (value - high) * (value - high)
|
|
|
|
|
return math.sqrt(squared)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def collect_obstacles(doc, exclude=None, options=None):
|
|
|
|
|
opts = _merged_options(options)
|
|
|
|
|
excluded = _expanded_obstacle_exclusion_ids(doc, exclude)
|
|
|
|
|
clearance = float(opts.get("obstacle_clearance", 0.0) or 0.0)
|
|
|
|
|
endpoint_clearance = max(float(opts.get("terminal_exit_length", 0.0) or 0.0), 0.0) + clearance
|
|
|
|
|
endpoint_points = []
|
|
|
|
|
for obj in exclude or []:
|
|
|
|
|
if obj is not None and TerminalObjects.is_terminal_object(obj):
|
|
|
|
|
endpoint_points.append(_terminal_origin(obj))
|
|
|
|
|
obstacles = []
|
|
|
|
|
for obj in list(getattr(doc, "Objects", []) or []):
|
|
|
|
|
if id(obj) in excluded:
|
|
|
|
|
@ -820,6 +844,11 @@ def collect_obstacles(doc, exclude=None, options=None):
|
|
|
|
|
bbox = _bbox_payload(obj, clearance=clearance)
|
|
|
|
|
if bbox is None:
|
|
|
|
|
continue
|
|
|
|
|
if endpoint_points and any(
|
|
|
|
|
_distance_point_to_bbox(point, bbox) <= endpoint_clearance
|
|
|
|
|
for point in endpoint_points
|
|
|
|
|
):
|
|
|
|
|
continue
|
|
|
|
|
obstacles.append(
|
|
|
|
|
{
|
|
|
|
|
"name": getattr(obj, "Name", ""),
|
|
|
|
|
@ -861,9 +890,12 @@ def _segment_intersects_bbox(start, end, bbox):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detect_collisions(points, obstacles):
|
|
|
|
|
def detect_collisions(points, obstacles, ignored_segment_indices=None):
|
|
|
|
|
ignored = set(ignored_segment_indices or [])
|
|
|
|
|
collisions = []
|
|
|
|
|
for index in range(max(len(points) - 1, 0)):
|
|
|
|
|
if index in ignored:
|
|
|
|
|
continue
|
|
|
|
|
start = points[index]
|
|
|
|
|
end = points[index + 1]
|
|
|
|
|
for obstacle in obstacles:
|
|
|
|
|
@ -878,6 +910,16 @@ def detect_collisions(points, obstacles):
|
|
|
|
|
return collisions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _endpoint_collision_segment_indices(points):
|
|
|
|
|
segment_count = max(len(points or []) - 1, 0)
|
|
|
|
|
if segment_count <= 0:
|
|
|
|
|
return set()
|
|
|
|
|
ignored = {0}
|
|
|
|
|
if segment_count > 1:
|
|
|
|
|
ignored.add(segment_count - 1)
|
|
|
|
|
return ignored
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _detach_object_from_groups(doc, obj):
|
|
|
|
|
parents = list(getattr(obj, "InList", []) or [])
|
|
|
|
|
parents.extend(list(getattr(doc, "Objects", []) or []))
|
|
|
|
|
@ -1026,7 +1068,10 @@ def route_between_terminals(
|
|
|
|
|
raise AutoRoutingError("Auto-routing produced fewer than two points.")
|
|
|
|
|
|
|
|
|
|
obstacles = collect_obstacles(doc, exclude=[start_terminal, end_terminal], options=opts)
|
|
|
|
|
collisions = detect_collisions(points, obstacles)
|
|
|
|
|
ignored_collision_segments = set()
|
|
|
|
|
if opts.get("ignore_endpoint_collision_segments", True):
|
|
|
|
|
ignored_collision_segments = _endpoint_collision_segment_indices(points)
|
|
|
|
|
collisions = detect_collisions(points, obstacles, ignored_segment_indices=ignored_collision_segments)
|
|
|
|
|
status = "CollisionWarning" if collisions else "Routed"
|
|
|
|
|
|
|
|
|
|
wire_name = _unique_name(doc, _wire_object_name(start_terminal, end_terminal, wire_uuid))
|
|
|
|
|
@ -1161,6 +1206,7 @@ def route_all_from_payload(doc, payload, options=None, prepared_layout=None):
|
|
|
|
|
"skipped_invalid": 0,
|
|
|
|
|
"missing_endpoint_uuids": [],
|
|
|
|
|
"missing_endpoint_samples": [],
|
|
|
|
|
"collision_samples": [],
|
|
|
|
|
"errors": [],
|
|
|
|
|
"routes": [],
|
|
|
|
|
}
|
|
|
|
|
@ -1218,6 +1264,20 @@ def route_all_from_payload(doc, payload, options=None, prepared_layout=None):
|
|
|
|
|
continue
|
|
|
|
|
if result["route_status"] == "CollisionWarning":
|
|
|
|
|
report["collision_warnings"] += 1
|
|
|
|
|
route_collision_samples = []
|
|
|
|
|
for collision in list(result.get("collisions", []) or [])[:3]:
|
|
|
|
|
sample = dict(collision)
|
|
|
|
|
sample.update(
|
|
|
|
|
{
|
|
|
|
|
"wire_uuid": _wire_item_value(item, "wire_id", "wire_uuid", "id"),
|
|
|
|
|
"wire_label": _wire_item_value(item, "wire_label", "wire_mark"),
|
|
|
|
|
"start_terminal_uuid": start_uuid,
|
|
|
|
|
"end_terminal_uuid": end_uuid,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
route_collision_samples.append(sample)
|
|
|
|
|
if len(report["collision_samples"]) < 8:
|
|
|
|
|
report["collision_samples"].append(sample)
|
|
|
|
|
report["routed"] += 1
|
|
|
|
|
route_length = float(result.get("length_mm", 0.0) or 0.0)
|
|
|
|
|
report["total_length_mm"] += route_length
|
|
|
|
|
@ -1234,6 +1294,7 @@ def route_all_from_payload(doc, payload, options=None, prepared_layout=None):
|
|
|
|
|
"lane": result.get("lane", {}),
|
|
|
|
|
"network": result.get("network", {}),
|
|
|
|
|
"collision_count": result["collision_count"],
|
|
|
|
|
"collision_samples": route_collision_samples,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
report["missing_endpoint_uuids"] = sorted(missing_endpoint_uuids)
|
|
|
|
|
@ -1260,6 +1321,19 @@ def format_route_all_report(report):
|
|
|
|
|
errors = report.get("errors", []) or []
|
|
|
|
|
if errors:
|
|
|
|
|
message += "\n首个错误:{0}".format(str(errors[0]))
|
|
|
|
|
collision_sample = (report.get("collision_samples") or [None])[0]
|
|
|
|
|
if collision_sample:
|
|
|
|
|
obstacle_text = (
|
|
|
|
|
collision_sample.get("obstacle_label")
|
|
|
|
|
or collision_sample.get("obstacle_name")
|
|
|
|
|
or "未知对象"
|
|
|
|
|
)
|
|
|
|
|
message += "\n碰撞示例:导线 {0} 碰到 {1}。".format(
|
|
|
|
|
collision_sample.get("wire_label")
|
|
|
|
|
or collision_sample.get("wire_uuid")
|
|
|
|
|
or "未知导线",
|
|
|
|
|
obstacle_text,
|
|
|
|
|
)
|
|
|
|
|
auto_bound = report.get("auto_bound_terminals", 0)
|
|
|
|
|
auto_created = report.get("auto_created_terminals", 0)
|
|
|
|
|
if auto_bound or auto_created:
|
|
|
|
|
|