mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
feat(agent): share Web UI compatible drawing builders between MCP and copilot
The text drawing tool was MCP-only; the copilot needs the same capability and its area-annotation helper carried the same Web UI compat bugs. Split into three layers with a single implementation: - gns3server/utils/drawings.py: pure SVG builders (text, rectangle, ellipse) with full input validation (ValueError) — the Web UI format knowledge lives in core utils next to picture.py - api_handlers.py: shared REST handlers (create_text_drawing_handler moved here, create_rectangle/ellipse_drawing_handler added) — the layer both consumers already share - thin shells: MCP tools (drawing_create_text unchanged, plus new drawing_create_rectangle/_ellipse) and three LangChain tools (tools_v2/gns3_create_drawing.py) registered in both copilot mode lists Shape builder details: - rectangle: solid fill or "none", stroke block emitted only when a stroke is given, rx corner radius, dasharray validated against the Web UI's Qt remap table (QtDasharrayFixer rewrites those patterns) - ellipse: bounding-box API; cx/cy derived as offsets inside the drawing box (the Web UI treats them as box-relative, not canvas coordinates); odd sizes round down and the actual size is returned gns3_drawing_utils now delegates to the shared builders, fixing the px-blind sizing, the ignored text-anchor/x/y centering and the missing XML escaping in the area-annotation path.
This commit is contained in:
13 files changed
+1003
-265
No files matched your search
@@ -33,6 +33,8 @@ Supports ellipse and rectangle shapes for two-node annotations.
|
||||
import math
|
||||
from typing import Any, Literal
|
||||
|
||||
from gns3server.utils.drawings import build_ellipse_svg, build_rectangle_svg, build_text_svg
|
||||
|
||||
# Default parameters
|
||||
DEFAULT_DEVICE_WIDTH = 50
|
||||
DEFAULT_DEVICE_HEIGHT = 50
|
||||
@@ -144,10 +146,16 @@ def calculate_two_node_shape(
|
||||
center_y = (node1_center_y + node2_center_y) / 2
|
||||
|
||||
color_scheme = _get_color_scheme(area_name)
|
||||
text_svg = generate_text_svg(area_name, color_scheme)
|
||||
|
||||
text_svg_width = len(area_name) * 8 + 20
|
||||
text_svg_height = DEFAULT_FONT_SIZE + 16
|
||||
# Shared builder: Web UI compatible SVG plus the computed label size
|
||||
# (the old len * 8 + 20 estimate ignored the Web UI's pt-based rendering).
|
||||
text_svg, text_svg_width, text_svg_height, _ = build_text_svg(
|
||||
area_name,
|
||||
font_size=DEFAULT_FONT_SIZE,
|
||||
color=color_scheme["stroke"],
|
||||
bold=True,
|
||||
font_family="TypeWriter",
|
||||
)
|
||||
|
||||
if shape_type == "ellipse":
|
||||
rx = distance / 2
|
||||
@@ -236,8 +244,11 @@ def generate_ellipse_svg(
|
||||
svg_width: int,
|
||||
svg_height: int,
|
||||
) -> str:
|
||||
"""Generate SVG for ellipse."""
|
||||
return f"""<svg width="{svg_width}" height="{svg_height}"><ellipse cx="{rx}" cy="{ry}" rx="{rx}" ry="{ry}" fill="{color_scheme["fill"]}" fill-opacity="{color_scheme["fill_opacity"]}"/></svg>"""
|
||||
"""Generate SVG for ellipse (delegates to the shared Web UI compatible builder)."""
|
||||
svg, _, _ = build_ellipse_svg(
|
||||
rx * 2, ry * 2, fill=color_scheme["fill"], fill_opacity=color_scheme["fill_opacity"]
|
||||
)
|
||||
return svg
|
||||
|
||||
|
||||
def generate_rectangle_svg(
|
||||
@@ -245,16 +256,23 @@ def generate_rectangle_svg(
|
||||
height: int,
|
||||
color_scheme: dict[str, Any],
|
||||
) -> str:
|
||||
"""Generate SVG for rectangle."""
|
||||
return f"""<svg width="{width}" height="{height}"><rect x="0" y="0" width="{width}" height="{height}" fill="{color_scheme["fill"]}" fill-opacity="{color_scheme["fill_opacity"]}"/></svg>"""
|
||||
"""Generate SVG for rectangle (delegates to the shared Web UI compatible builder)."""
|
||||
svg, _, _ = build_rectangle_svg(
|
||||
width, height, fill=color_scheme["fill"], fill_opacity=color_scheme["fill_opacity"]
|
||||
)
|
||||
return svg
|
||||
|
||||
|
||||
def generate_text_svg(text: str, color_scheme: dict[str, Any]) -> str:
|
||||
"""Generate SVG for text label."""
|
||||
text_width = len(text) * 8 + 20
|
||||
text_height = DEFAULT_FONT_SIZE + 16
|
||||
|
||||
return f"""<svg width="{text_width}" height="{text_height}"><text font-family="TypeWriter" font-size="{DEFAULT_FONT_SIZE}.0" font-weight="bold" fill="{color_scheme["stroke"]}" text-anchor="middle" x="{text_width / 2}" y="{text_height / 2 + 4}">{text}</text></svg>"""
|
||||
"""Generate SVG for text label (delegates to the shared Web UI compatible builder)."""
|
||||
svg, _, _, _ = build_text_svg(
|
||||
text,
|
||||
font_size=DEFAULT_FONT_SIZE,
|
||||
color=color_scheme["stroke"],
|
||||
bold=True,
|
||||
font_family="TypeWriter",
|
||||
)
|
||||
return svg
|
||||
|
||||
|
||||
def _hsv_to_hex(h: int, s: int, v: int) -> str:
|
||||
|
||||
Reference in new issue
Block a user