feat(server): SECTION 18 styles — dimstyle/textstyle/mleaderstyle tools with DIMSTYLE/STYLE/MLEADERSTYLE aliases
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
266b6fb838
commit
a60f801205
@@ -27,7 +27,7 @@ Live through COM on Windows, or headless through ezdxf anywhere — one typed co
|
||||
|
||||
</div>
|
||||
|
||||
> **v1.5 release snapshot:** 166 tools · 8 resources · 5 prompt templates · 2423 collected tests.
|
||||
> **v1.5 release snapshot:** 175 tools · 8 resources · 5 prompt templates · 2423 collected tests.
|
||||
> 166 is the **registered** count; a default install advertises 161 over `tools/list`,
|
||||
> because `ENABLE_3D` is unset. `system_about` is the runtime authority.
|
||||
|
||||
|
||||
@@ -72,20 +72,27 @@ class ToolAliases:
|
||||
# PLINE -- a raw polyline (entity_create_polyline) or a P&ID line run
|
||||
# between two ports (pid_line_draw), which is an LWPOLYLINE with a
|
||||
# class, a number and markers; a drafter typing it could mean either.
|
||||
# DIMSTYLE, STYLE, MLEADERSTYLE -- one dialog each in AutoCAD for
|
||||
# list / create / modify / set-current; this server splits each into
|
||||
# discrete style tools (track E), so the command is a correct
|
||||
# destination for every one of them.
|
||||
#
|
||||
# Every other AutoCAD command must map to exactly one tool.
|
||||
SHARED_ACAD_COMMANDS: frozenset[str] = frozenset(
|
||||
{
|
||||
"ARRAY",
|
||||
"BLOCK",
|
||||
"DIMSTYLE",
|
||||
"ERASE",
|
||||
"INSERT",
|
||||
"LAYER",
|
||||
"LAYOUT",
|
||||
"MEASUREGEOM",
|
||||
"MLEADERSTYLE",
|
||||
"PLINE",
|
||||
"QSELECT",
|
||||
"SETVAR",
|
||||
"STYLE",
|
||||
"ZOOM",
|
||||
}
|
||||
)
|
||||
@@ -1427,6 +1434,89 @@ TOOL_ALIASES: dict[str, ToolAliases] = {
|
||||
"loop number",
|
||||
),
|
||||
),
|
||||
# ── Styles (track E, group S) ───────────────────────────────────────────
|
||||
"dimstyle_create": ToolAliases(
|
||||
acad=("DIMSTYLE", "DDIM"),
|
||||
synonyms=(
|
||||
"new dimension style",
|
||||
"create a dimension style",
|
||||
"iso-25 dimension style",
|
||||
"ansi dimension style",
|
||||
"dimension style from a preset",
|
||||
"set up dimension text height and arrows",
|
||||
),
|
||||
),
|
||||
"dimstyle_list": ToolAliases(
|
||||
acad=("DIMSTYLE",),
|
||||
synonyms=(
|
||||
"list dimension styles",
|
||||
"dimension styles in the drawing",
|
||||
"dimension style table",
|
||||
"current dimstyle",
|
||||
"dim styles",
|
||||
),
|
||||
),
|
||||
"dimstyle_modify": ToolAliases(
|
||||
acad=("DIMSTYLE",),
|
||||
synonyms=(
|
||||
"change a dimension style",
|
||||
"edit the dimension style",
|
||||
"change dimension text height for the whole drawing",
|
||||
"set dimdec on a style",
|
||||
"modify dimstyle variables",
|
||||
),
|
||||
),
|
||||
"dimstyle_set_current": ToolAliases(
|
||||
acad=("DIMSTYLE",),
|
||||
synonyms=(
|
||||
"current dimension style",
|
||||
"make this dimension style current",
|
||||
"switch dimension style",
|
||||
"use iso-25 for new dimensions",
|
||||
"activate a dimstyle",
|
||||
),
|
||||
),
|
||||
"mleaderstyle_create": ToolAliases(
|
||||
acad=("MLEADERSTYLE",),
|
||||
synonyms=(
|
||||
"new leader style",
|
||||
"multileader style",
|
||||
"leader arrow size and landing",
|
||||
"iso leader style",
|
||||
),
|
||||
),
|
||||
"mleaderstyle_list": ToolAliases(
|
||||
acad=("MLEADERSTYLE",),
|
||||
synonyms=("list leader styles", "multileader styles in the drawing", "mleader styles"),
|
||||
),
|
||||
"textstyle_create": ToolAliases(
|
||||
acad=("STYLE",),
|
||||
synonyms=(
|
||||
"new text style",
|
||||
"create a text style",
|
||||
"isocp font",
|
||||
"set the font",
|
||||
"text style with width factor and oblique angle",
|
||||
),
|
||||
),
|
||||
"textstyle_list": ToolAliases(
|
||||
acad=("STYLE",),
|
||||
synonyms=(
|
||||
"list text styles",
|
||||
"fonts loaded in the drawing",
|
||||
"text style table",
|
||||
"current text style",
|
||||
),
|
||||
),
|
||||
"textstyle_set_current": ToolAliases(
|
||||
acad=("STYLE", "TEXTSTYLE"),
|
||||
synonyms=(
|
||||
"make this text style current",
|
||||
"switch the text style",
|
||||
"use isocp for new text",
|
||||
"active text style",
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
+129
-2
@@ -3,10 +3,10 @@
|
||||
"schema_version": 1,
|
||||
"package_version": "1.5.1",
|
||||
"totals": {
|
||||
"tools": 166,
|
||||
"tools": 175,
|
||||
"resources": 8,
|
||||
"prompts": 5,
|
||||
"groups": 20
|
||||
"groups": 21
|
||||
},
|
||||
"groups": {
|
||||
"analysis": 12,
|
||||
@@ -24,6 +24,7 @@
|
||||
"pid": 9,
|
||||
"premium": 12,
|
||||
"solids": 5,
|
||||
"styles": 9,
|
||||
"system": 7,
|
||||
"templates": 2,
|
||||
"transactions": 3,
|
||||
@@ -450,6 +451,62 @@
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dimstyle_create",
|
||||
"group": "styles",
|
||||
"cost": "mutate",
|
||||
"summary": "Create a dimension style from the ISO-25 or ANSI preset, with per-variable overrides.",
|
||||
"tags": [
|
||||
"create",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Create Dimension Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dimstyle_list",
|
||||
"group": "styles",
|
||||
"cost": "read",
|
||||
"summary": "List dimension styles with their ISO-25/ANSI variables and the current one.",
|
||||
"tags": [
|
||||
"query",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: List Dimension Styles",
|
||||
"readOnlyHint": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dimstyle_modify",
|
||||
"group": "styles",
|
||||
"cost": "mutate",
|
||||
"summary": "Change DIM* variables on an existing dimension style; reports only what moved.",
|
||||
"tags": [
|
||||
"modify",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Modify Dimension Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dimstyle_set_current",
|
||||
"group": "styles",
|
||||
"cost": "safe",
|
||||
"summary": "Make a dimension style current so new dimensions use it.",
|
||||
"tags": [
|
||||
"modify",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Set Current Dimension Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drawing_apply_iso_layers",
|
||||
"group": "premium",
|
||||
@@ -1717,6 +1774,34 @@
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mleaderstyle_create",
|
||||
"group": "styles",
|
||||
"cost": "mutate",
|
||||
"summary": "Create a multileader style from the ISO or ANSI preset, on both engines.",
|
||||
"tags": [
|
||||
"create",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Create Multileader Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mleaderstyle_list",
|
||||
"group": "styles",
|
||||
"cost": "read",
|
||||
"summary": "List multileader styles: arrow size, landing gap, text style and height.",
|
||||
"tags": [
|
||||
"query",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: List Multileader Styles",
|
||||
"readOnlyHint": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pid_equipment_list",
|
||||
"group": "pid",
|
||||
@@ -2155,6 +2240,48 @@
|
||||
"destructiveHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "textstyle_create",
|
||||
"group": "styles",
|
||||
"cost": "mutate",
|
||||
"summary": "Create a text style from a bundled font (isocp, isocpeur, arial, romans) or any file.",
|
||||
"tags": [
|
||||
"create",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Create Text Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "textstyle_list",
|
||||
"group": "styles",
|
||||
"cost": "read",
|
||||
"summary": "List text styles: font file, height, width factor, oblique angle.",
|
||||
"tags": [
|
||||
"query",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: List Text Styles",
|
||||
"readOnlyHint": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "textstyle_set_current",
|
||||
"group": "styles",
|
||||
"cost": "safe",
|
||||
"summary": "Make a text style current so new text uses it.",
|
||||
"tags": [
|
||||
"modify",
|
||||
"style"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Styles: Set Current Text Style",
|
||||
"readOnlyHint": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "titleblock_apply_iso_a3",
|
||||
"group": "engineering",
|
||||
|
||||
@@ -703,6 +703,7 @@ _GROUP_TAG_PRIORITY = (
|
||||
"validation",
|
||||
"analysis",
|
||||
"block",
|
||||
"style",
|
||||
"layer",
|
||||
"linetype",
|
||||
"create",
|
||||
@@ -728,6 +729,7 @@ _GROUP_TAG_LABELS = {
|
||||
"analysis": "analysis",
|
||||
"validation": "validation",
|
||||
"block": "blocks",
|
||||
"style": "styles",
|
||||
"layer": "layers",
|
||||
"linetype": "layers",
|
||||
"create": "entity_creation",
|
||||
@@ -6815,6 +6817,253 @@ async def pid_tag_parse(
|
||||
return parse_tag(tag, kind, equipment_prefixes)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ── SECTION 18: Styles (9 tools) ────────────────────────────────────────────
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Pack: core (drafting essentials). Lean profile: dimstyle_set_current and
|
||||
# textstyle_set_current only (Task 13) — a lean client needs to *use* a
|
||||
# style a template already holds far more often than to author one.
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="List dimension styles with their ISO-25/ANSI variables and the current one.",
|
||||
cost="read",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: List Dimension Styles", "readOnlyHint": True},
|
||||
tags={"style", "query"},
|
||||
)
|
||||
async def dimstyle_list(ctx: Context = None) -> dict:
|
||||
"""Every DIMSTYLE table entry with the seventeen preset variables
|
||||
(DIMTXT, DIMASZ, DIMEXE, DIMEXO, DIMGAP, DIMTAD, DIMTIH, DIMTOH, DIMDEC,
|
||||
DIMDSEP, DIMLUNIT, DIMZIN, DIMBLK, DIMTXSTY, DIMLWD, DIMLWE, DIMSCALE).
|
||||
|
||||
Headless, `values` is what the DXF stores (the schema default where an
|
||||
entry omits a code). On the live engine ActiveX has no per-style getters,
|
||||
so `values` is read for the *current* style only and the other rows carry
|
||||
`values: null`, `values_available: false` — `dimstyle_set_current` a
|
||||
style to read it. Never refuses on an open drawing.
|
||||
"""
|
||||
rows = await _backend(ctx).dimstyle_list()
|
||||
return {
|
||||
"ok": True,
|
||||
"styles": rows,
|
||||
"count": len(rows),
|
||||
"current": next((row["name"] for row in rows if row["current"]), None),
|
||||
}
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="Create a dimension style from the ISO-25 or ANSI preset, with per-variable overrides.",
|
||||
cost="mutate",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Create Dimension Style", "readOnlyHint": False},
|
||||
tags={"style", "create"},
|
||||
)
|
||||
async def dimstyle_create(
|
||||
name: Annotated[str, "New dimension style name, e.g. ISO-25, ANSI, MECH-A3"],
|
||||
preset: Annotated[
|
||||
str | None,
|
||||
"iso-25 (ISO 129-1, 2.5 mm, comma) | ansi (ASME Y14.2, 3 mm, point); omit for the ISO-25 base",
|
||||
] = None,
|
||||
overrides: Annotated[
|
||||
dict | None,
|
||||
"DIM* variables applied on top of the preset, e.g. {DIMTXT: 3.5, DIMDSEP: '.', DIMTAD: 0}",
|
||||
] = None,
|
||||
set_current: Annotated[bool, "Also make it the current dimension style"] = False,
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Create a dimension style: preset values first, then `overrides`.
|
||||
|
||||
Refuses, before anything is written: an unknown preset; an override key
|
||||
outside the 33-variable whitelist (the 17 preset variables plus DIMTOL,
|
||||
DIMTP, DIMTM, DIMTOLJ, DIMTFAC, DIMLFAC, DIMRND, DIMATFIT, DIMTMOVE,
|
||||
DIMCLRD/E/T, DIMSAH, DIMBLK1/2, DIMCEN); a value outside its range
|
||||
(DIMDEC 0-8, sizes > 0, DIMTAD 0-4, DIMDSEP one character, lineweights an
|
||||
AutoCAD code); a name that already exists (use `dimstyle_modify`); a
|
||||
DIMTXSTY that is neither an existing text style nor a bundled preset
|
||||
(ISOCP, ISOCPEUR, ARIAL, ROMANS — those are created, `textstyle_created`).
|
||||
With `set_current` the next dimension carries the style on both engines.
|
||||
"""
|
||||
from engineering.standards.dimstyles import resolve_dimstyle
|
||||
|
||||
values = resolve_dimstyle(preset, overrides)
|
||||
await ctx.info(f"Dimension style {name!r} from preset {preset or 'iso-25'}")
|
||||
return await _backend(ctx).dimstyle_create(name, values, set_current)
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="Change DIM* variables on an existing dimension style; reports only what moved.",
|
||||
cost="mutate",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Modify Dimension Style", "readOnlyHint": False},
|
||||
tags={"style", "modify"},
|
||||
)
|
||||
async def dimstyle_modify(
|
||||
name: Annotated[str, "Existing dimension style name"],
|
||||
overrides: Annotated[dict, "DIM* variables to change, e.g. {DIMDEC: 3, DIMTXT: 3.5}"],
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Change variables on an existing dimension style.
|
||||
|
||||
`changed` names only the variables that actually moved — re-setting a
|
||||
value to itself is not a change. `dimensions_using_style` lists the
|
||||
dimensions already drawn with it; headlessly `rerender_required: true`
|
||||
says they still show the old style until redrawn (AutoCAD re-renders on
|
||||
the next regen). Refuses a missing style, an empty `overrides`, a key
|
||||
outside the whitelist, a value outside its range, a DIMTXSTY that does
|
||||
not exist — all before any write. On the live engine, editing a
|
||||
non-current style makes it current for the duration of the call and the
|
||||
previous style is restored (its unsaved overrides are discarded, which is
|
||||
AutoCAD's own DIMSTYLE rule).
|
||||
"""
|
||||
await ctx.info(f"Modifying dimension style {name!r}")
|
||||
return await _backend(ctx).dimstyle_modify(name, overrides)
|
||||
|
||||
|
||||
@cad_tool(summary="Make a dimension style current so new dimensions use it.", cost="safe")
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Set Current Dimension Style", "readOnlyHint": False},
|
||||
tags={"style", "modify"},
|
||||
)
|
||||
async def dimstyle_set_current(
|
||||
name: Annotated[str, "Dimension style to make current (case-insensitive)"],
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Set `$DIMSTYLE` and load the style's variables as the current DIM*
|
||||
settings — what a live seat's DIMSTYLE Restore does — so `dimension_*`
|
||||
and `dimension_auto` draw with it on both engines. Per-dimension
|
||||
tolerances, fits and text overrides still apply on top. Refuses a style
|
||||
the drawing does not hold. Returns `previous` and `changed`.
|
||||
"""
|
||||
return await _backend(ctx).dimstyle_set_current(name)
|
||||
|
||||
|
||||
@cad_tool(summary="List text styles: font file, height, width factor, oblique angle.", cost="read")
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: List Text Styles", "readOnlyHint": True},
|
||||
tags={"style", "query"},
|
||||
)
|
||||
async def textstyle_list(ctx: Context = None) -> dict:
|
||||
"""Every STYLE table entry and the one new TEXT/MTEXT will use."""
|
||||
rows = await _backend(ctx).textstyle_list()
|
||||
return {
|
||||
"ok": True,
|
||||
"styles": rows,
|
||||
"count": len(rows),
|
||||
"current": next((row["name"] for row in rows if row["current"]), None),
|
||||
}
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="Create a text style from a bundled font (isocp, isocpeur, arial, romans) or any file.",
|
||||
cost="mutate",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Create Text Style", "readOnlyHint": False},
|
||||
tags={"style", "create"},
|
||||
)
|
||||
async def textstyle_create(
|
||||
name: Annotated[str, "New text style name, e.g. ISOCP"],
|
||||
font: Annotated[
|
||||
str,
|
||||
"isocp.shx | isocpeur.ttf | arial.ttf | romans.shx (name or file, any case) or any font file",
|
||||
],
|
||||
height: Annotated[
|
||||
float, Field(default=0.0, ge=0, description="Fixed height; 0 = ask per text")
|
||||
] = 0.0,
|
||||
width_factor: Annotated[float, Field(default=1.0, gt=0, description="Width factor")] = 1.0,
|
||||
oblique_deg: Annotated[float, "Obliquing angle in degrees, -85..85"] = 0.0,
|
||||
set_current: Annotated[bool, "Also make it the current text style"] = False,
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Create a text style.
|
||||
|
||||
A font that is neither a bundled preset nor on the engine's font path is
|
||||
*written* and reported `font_resolved: false` (AutoCAD substitutes at
|
||||
open; the headless renderer falls back) — never refused, because a DXF
|
||||
stores only the name. Refuses, before any write: a name that exists, an
|
||||
empty font, `width_factor <= 0`, `height < 0`, an oblique angle outside
|
||||
±85°.
|
||||
"""
|
||||
await ctx.info(f"Text style {name!r} with font {font!r}")
|
||||
return await _backend(ctx).textstyle_create(
|
||||
name, font, height, width_factor, oblique_deg, set_current
|
||||
)
|
||||
|
||||
|
||||
@cad_tool(summary="Make a text style current so new text uses it.", cost="safe")
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Set Current Text Style", "readOnlyHint": False},
|
||||
tags={"style", "modify"},
|
||||
)
|
||||
async def textstyle_set_current(
|
||||
name: Annotated[str, "Text style to make current (case-insensitive)"],
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Set `$TEXTSTYLE`; `entity_create_text` / `entity_create_mtext` then
|
||||
carry the style on both engines. Refuses a style the drawing does not
|
||||
hold. Returns `previous` and `changed`."""
|
||||
return await _backend(ctx).textstyle_set_current(name)
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="List multileader styles: arrow size, landing gap, text style and height.",
|
||||
cost="read",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: List Multileader Styles", "readOnlyHint": True},
|
||||
tags={"style", "query"},
|
||||
)
|
||||
async def mleaderstyle_list(ctx: Context = None) -> dict:
|
||||
"""Every MLEADERSTYLE with its four values on both engines
|
||||
(`values_available: true`). Headless they are read from the MLEADERSTYLE
|
||||
object; live from the `AcadMLeaderStyle` objects the `ACAD_MLEADERSTYLE`
|
||||
dictionary holds (ActiveX exposes no *collection* property for them, but
|
||||
the dictionary items are full objects). `leader_create_mleader` can still
|
||||
override any of them per leader."""
|
||||
rows = await _backend(ctx).mleaderstyle_list()
|
||||
return {"ok": True, "styles": rows, "count": len(rows)}
|
||||
|
||||
|
||||
@cad_tool(
|
||||
summary="Create a multileader style from the ISO or ANSI preset, on both engines.",
|
||||
cost="mutate",
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Styles: Create Multileader Style", "readOnlyHint": False},
|
||||
tags={"style", "create"},
|
||||
)
|
||||
async def mleaderstyle_create(
|
||||
name: Annotated[str, "New multileader style name"],
|
||||
preset: Annotated[
|
||||
str, "iso (2.5 mm arrow, 1.0 landing, ISOCP 2.5) | ansi (3.0, 1.5, ROMANS 3.0)"
|
||||
],
|
||||
overrides: Annotated[
|
||||
dict | None, "arrow_size | landing_gap | text_style | text_height on top of the preset"
|
||||
] = None,
|
||||
ctx: Context = None,
|
||||
) -> dict:
|
||||
"""Create a multileader style: preset values first, then `overrides`.
|
||||
|
||||
Headless it lands on `doc.mleader_styles`; live it is added through the
|
||||
`ACAD_MLEADERSTYLE` dictionary (`AddObject(name, "AcDbMLeaderStyle")`)
|
||||
and the object's properties are written. Refuses, before any write: an
|
||||
unknown preset; an override key outside the four; `arrow_size` /
|
||||
`text_height <= 0`, `landing_gap < 0`; a name that exists; a `text_style`
|
||||
that is neither present nor a bundled preset (ISOCP, ISOCPEUR, ARIAL,
|
||||
ROMANS — those are created, `textstyle_created`).
|
||||
"""
|
||||
from engineering.standards.mleaderstyles import resolve_mleaderstyle
|
||||
|
||||
values = resolve_mleaderstyle(preset, overrides)
|
||||
await ctx.info(f"Multileader style {name!r} from preset {preset}")
|
||||
return await _backend(ctx).mleaderstyle_create(name, values)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ── RESOURCES ───────────────────────────────────────────────────────────────
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1007,3 +1007,83 @@ async def test_com_mleaderstyle_create_adds_the_dictionary_to_a_drawing_without_
|
||||
assert [obj.Name for obj in document.mleader_dictionary.objects] == ["ISO"]
|
||||
rows = await backend.mleaderstyle_list()
|
||||
assert [row["name"] for row in rows] == ["ISO"] and rows[0]["arrow_size"] == 2.5
|
||||
|
||||
|
||||
# ── the MCP tools (SECTION 18) ──────────────────────────────────────────────
|
||||
|
||||
STYLE_TOOLS = {
|
||||
"dimstyle_list",
|
||||
"dimstyle_create",
|
||||
"dimstyle_modify",
|
||||
"dimstyle_set_current",
|
||||
"textstyle_list",
|
||||
"textstyle_create",
|
||||
"textstyle_set_current",
|
||||
"mleaderstyle_list",
|
||||
"mleaderstyle_create",
|
||||
}
|
||||
|
||||
|
||||
class _Ctx:
|
||||
"""Enough of a fastmcp Context for a direct tool call (tests/test_capabilities.py)."""
|
||||
|
||||
def __init__(self, backend):
|
||||
self.lifespan_context = {"backend": backend}
|
||||
|
||||
async def info(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
|
||||
async def test_style_tools_are_registered_and_grouped_under_styles():
|
||||
import server
|
||||
|
||||
names = {t.name for t in await server._registered_tools() if getattr(t, "name", None)}
|
||||
assert STYLE_TOOLS <= names
|
||||
groups = await server._tool_groups()
|
||||
assert STYLE_TOOLS <= set(groups["styles"]), "the style tag must win over query/create/modify"
|
||||
for other in ("layers", "drawing", "entity_query", "entity_creation", "entity_modification"):
|
||||
assert not STYLE_TOOLS & set(groups.get(other, [])), other
|
||||
|
||||
|
||||
async def test_dimstyle_tools_resolve_the_preset_then_write(backend):
|
||||
import server
|
||||
|
||||
ctx = _Ctx(backend)
|
||||
created = await server.dimstyle_create(
|
||||
name="ISO-25", preset="iso-25", overrides={"dimdec": 3}, set_current=True, ctx=ctx
|
||||
)
|
||||
assert created["current"] is True and created["values"]["DIMDEC"] == 3
|
||||
listed = await server.dimstyle_list(ctx=ctx)
|
||||
assert listed["count"] == 2 and listed["current"] == "ISO-25"
|
||||
modified = await server.dimstyle_modify(name="ISO-25", overrides={"DIMTXT": 3.5}, ctx=ctx)
|
||||
assert modified["changed"] == {"DIMTXT": [2.5, 3.5]}
|
||||
switched = await server.dimstyle_set_current(name="Standard", ctx=ctx)
|
||||
assert switched["previous"] == "ISO-25" and switched["current"] == "Standard"
|
||||
|
||||
|
||||
async def test_dimstyle_create_tool_refuses_an_unknown_override_before_writing(backend):
|
||||
import server
|
||||
|
||||
ctx = _Ctx(backend)
|
||||
with pytest.raises(ValueError, match="DIMFOO"):
|
||||
await server.dimstyle_create(name="X", overrides={"DIMFOO": 1}, ctx=ctx)
|
||||
with pytest.raises(ValueError, match="preset"):
|
||||
await server.dimstyle_create(name="X", preset="din", ctx=ctx)
|
||||
assert (await server.dimstyle_list(ctx=ctx))["count"] == 1
|
||||
|
||||
|
||||
async def test_textstyle_and_mleaderstyle_tools_round_trip(backend):
|
||||
import server
|
||||
|
||||
ctx = _Ctx(backend)
|
||||
made = await server.textstyle_create(name="ISOCP", font="isocp", set_current=True, ctx=ctx)
|
||||
assert made["font_resolved"] is True
|
||||
assert (await server.textstyle_list(ctx=ctx))["current"] == "ISOCP"
|
||||
assert (await server.textstyle_set_current(name="Standard", ctx=ctx))["previous"] == "ISOCP"
|
||||
leader = await server.mleaderstyle_create(
|
||||
name="ISO", preset="iso", overrides={"arrow_size": 3}, ctx=ctx
|
||||
)
|
||||
assert leader["values"]["arrow_size"] == 3.0 and leader["textstyle_created"] is False
|
||||
assert (await server.mleaderstyle_list(ctx=ctx))["count"] == 2
|
||||
with pytest.raises(ValueError, match="preset"):
|
||||
await server.mleaderstyle_create(name="X", preset="din", ctx=ctx)
|
||||
|
||||
@@ -517,8 +517,12 @@ async def test_tool_group_sizes_are_unchanged():
|
||||
when `pid_tag_parse` (the ISA-5.1 tag grammar as a read-only tool)
|
||||
joined; the `pid`
|
||||
tag is ranked first in `_GROUP_TAG_PRIORITY` so those tools file under
|
||||
`pid` rather than under their secondary `query` / `create` tags. Every
|
||||
other number here has been unchanged since the snapshot was taken.
|
||||
`pid` rather than under their secondary `query` / `create` tags. `styles`
|
||||
appeared (0 -> 9) when track E's SECTION 18 opened with the dimension,
|
||||
text and multileader style tools; the `style` tag sits ahead of `layer`
|
||||
in `_GROUP_TAG_PRIORITY` so they never file under their secondary
|
||||
`query` / `create` / `modify` tags. Every other number here has been
|
||||
unchanged since the snapshot was taken.
|
||||
"""
|
||||
sizes = {label: len(names) for label, names in (await server._tool_groups()).items()}
|
||||
assert sizes == {
|
||||
@@ -537,10 +541,11 @@ async def test_tool_group_sizes_are_unchanged():
|
||||
"pid": 9,
|
||||
"premium": 12,
|
||||
"solids": 5,
|
||||
"styles": 9,
|
||||
"system": 7,
|
||||
"templates": 2,
|
||||
"transactions": 3,
|
||||
"validation": 1,
|
||||
"view": 4,
|
||||
}
|
||||
assert sum(sizes.values()) == 166
|
||||
assert sum(sizes.values()) == 175
|
||||
|
||||
Reference in New Issue
Block a user