"""批注回写:把审查问题标到 CAD 图上(统一落 AI_REVIEW 层)。 只在问题带有 handle 时才回写。产出两类可撤销的副作用: - created :新建的批注实体(引线等)→ 用 delete_entities 删除 - highlighted :被临时改色的原实体 → 用 highlight_entities(restore=true) 还原 """ from . import config from .mcp_client import McpError, McpToolError, result_json # 引线落点相对实体定位点的偏移(图纸单位) _LEADER_OFFSET = (30.0, 20.0) def _pick_point(detail): """从实体详情中挑一个用于放引线的点(WCS)。""" if not isinstance(detail, dict): return None bbox = detail.get("bbox") if isinstance(bbox, (list, tuple)) and len(bbox) == 4: return [ (float(bbox[0]) + float(bbox[2])) / 2.0, (float(bbox[1]) + float(bbox[3])) / 2.0, 0.0, ] for key in ("position", "insert", "center", "start"): pt = detail.get(key) if isinstance(pt, (list, tuple)) and len(pt) >= 2: return [float(pt[0]), float(pt[1]), float(pt[2]) if len(pt) > 2 else 0.0] return None def annotate(ctx, issues, *, dry_run=False, log=print): """按问题列表回写批注,返回 (created, highlighted) 两个 handle 列表。""" created = [] highlighted = [] seen = set() # 同一 handle 只处理一次,避免重复批注 for issue in issues: handle = issue.get("handle") if not handle or handle in seen: continue seen.add(handle) text = f"[{issue.get('severity', '?')}] {issue.get('message', '')}" if dry_run: log(f"[dry-run] 批注 handle={handle} 层={config.ANNOTATE_LAYER}: {text}") continue # 先定位,拿不到坐标就跳过(避免产生无意义的批注) detail = result_json(ctx.tools.call("get_entity_detail", {"handle": handle}), {}) point = _pick_point(detail) if not point: log(f"[跳过] handle={handle} 无法定位坐标") continue landing = [ point[0] + _LEADER_OFFSET[0], point[1] + _LEADER_OFFSET[1], 0.0, ] try: res = result_json( ctx.tools.call( "add_leader_annotation", { "text": text, "points": [point, landing], # 至少 2 个点,首个为箭头 "text_position": landing, "layer": config.ANNOTATE_LAYER, }, ), {}, ) for item in (res or {}).get("created", []): if item.get("handle"): created.append(item["handle"]) except (McpError, McpToolError) as exc: log(f"[批注失败] handle={handle}: {exc}") continue # 引线建好后再高亮原实体(失败不影响已创建的批注) try: ctx.tools.call("highlight_entities", {"handle": handle, "color": 1}) highlighted.append(handle) except (McpError, McpToolError) as exc: log(f"[高亮失败] handle={handle}: {exc}") if not dry_run: log( f"[批注] 新建 {len(created)} 个批注实体、高亮 {len(highlighted)} 个原实体" f"(层 {config.ANNOTATE_LAYER})" ) return created, highlighted def clear(ctx, handles=None, highlighted=None, *, log=print): """撤销批注:删除批注实体 + 还原被高亮的实体。""" if handles: try: ctx.tools.call("delete_entities", {"handles": list(handles)}) log(f"[批注] 已删除 {len(handles)} 个批注实体") except (McpError, McpToolError) as exc: log(f"[撤销失败] {exc}") if highlighted: try: ctx.tools.call( "highlight_entities", {"handles": list(highlighted), "restore": True} ) log(f"[批注] 已还原 {len(highlighted)} 个实体的颜色") except (McpError, McpToolError) as exc: log(f"[还原失败] {exc}")