- annotator.annotate:同一 handle 只批注一次(修复 created 无去重的既有 gap, 同 handle 出现两次不再产生重复引线) - 删除 test_screenshot.png(已删除脚本 test_autoagent.py 的残留产物)
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
"""批注回写:把审查问题标到 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}")
|