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>
|
</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`,
|
> 166 is the **registered** count; a default install advertises 161 over `tools/list`,
|
||||||
> because `ENABLE_3D` is unset. `system_about` is the runtime authority.
|
> 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
|
# 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
|
# between two ports (pid_line_draw), which is an LWPOLYLINE with a
|
||||||
# class, a number and markers; a drafter typing it could mean either.
|
# 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.
|
# Every other AutoCAD command must map to exactly one tool.
|
||||||
SHARED_ACAD_COMMANDS: frozenset[str] = frozenset(
|
SHARED_ACAD_COMMANDS: frozenset[str] = frozenset(
|
||||||
{
|
{
|
||||||
"ARRAY",
|
"ARRAY",
|
||||||
"BLOCK",
|
"BLOCK",
|
||||||
|
"DIMSTYLE",
|
||||||
"ERASE",
|
"ERASE",
|
||||||
"INSERT",
|
"INSERT",
|
||||||
"LAYER",
|
"LAYER",
|
||||||
"LAYOUT",
|
"LAYOUT",
|
||||||
"MEASUREGEOM",
|
"MEASUREGEOM",
|
||||||
|
"MLEADERSTYLE",
|
||||||
"PLINE",
|
"PLINE",
|
||||||
"QSELECT",
|
"QSELECT",
|
||||||
"SETVAR",
|
"SETVAR",
|
||||||
|
"STYLE",
|
||||||
"ZOOM",
|
"ZOOM",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1427,6 +1434,89 @@ TOOL_ALIASES: dict[str, ToolAliases] = {
|
|||||||
"loop number",
|
"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,
|
"schema_version": 1,
|
||||||
"package_version": "1.5.1",
|
"package_version": "1.5.1",
|
||||||
"totals": {
|
"totals": {
|
||||||
"tools": 166,
|
"tools": 175,
|
||||||
"resources": 8,
|
"resources": 8,
|
||||||
"prompts": 5,
|
"prompts": 5,
|
||||||
"groups": 20
|
"groups": 21
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"analysis": 12,
|
"analysis": 12,
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"pid": 9,
|
"pid": 9,
|
||||||
"premium": 12,
|
"premium": 12,
|
||||||
"solids": 5,
|
"solids": 5,
|
||||||
|
"styles": 9,
|
||||||
"system": 7,
|
"system": 7,
|
||||||
"templates": 2,
|
"templates": 2,
|
||||||
"transactions": 3,
|
"transactions": 3,
|
||||||
@@ -450,6 +451,62 @@
|
|||||||
"readOnlyHint": false
|
"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",
|
"name": "drawing_apply_iso_layers",
|
||||||
"group": "premium",
|
"group": "premium",
|
||||||
@@ -1717,6 +1774,34 @@
|
|||||||
"readOnlyHint": false
|
"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",
|
"name": "pid_equipment_list",
|
||||||
"group": "pid",
|
"group": "pid",
|
||||||
@@ -2155,6 +2240,48 @@
|
|||||||
"destructiveHint": false
|
"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",
|
"name": "titleblock_apply_iso_a3",
|
||||||
"group": "engineering",
|
"group": "engineering",
|
||||||
|
|||||||
@@ -703,6 +703,7 @@ _GROUP_TAG_PRIORITY = (
|
|||||||
"validation",
|
"validation",
|
||||||
"analysis",
|
"analysis",
|
||||||
"block",
|
"block",
|
||||||
|
"style",
|
||||||
"layer",
|
"layer",
|
||||||
"linetype",
|
"linetype",
|
||||||
"create",
|
"create",
|
||||||
@@ -728,6 +729,7 @@ _GROUP_TAG_LABELS = {
|
|||||||
"analysis": "analysis",
|
"analysis": "analysis",
|
||||||
"validation": "validation",
|
"validation": "validation",
|
||||||
"block": "blocks",
|
"block": "blocks",
|
||||||
|
"style": "styles",
|
||||||
"layer": "layers",
|
"layer": "layers",
|
||||||
"linetype": "layers",
|
"linetype": "layers",
|
||||||
"create": "entity_creation",
|
"create": "entity_creation",
|
||||||
@@ -6815,6 +6817,253 @@ async def pid_tag_parse(
|
|||||||
return parse_tag(tag, kind, equipment_prefixes)
|
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 ───────────────────────────────────────────────────────────────
|
# ── 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"]
|
assert [obj.Name for obj in document.mleader_dictionary.objects] == ["ISO"]
|
||||||
rows = await backend.mleaderstyle_list()
|
rows = await backend.mleaderstyle_list()
|
||||||
assert [row["name"] for row in rows] == ["ISO"] and rows[0]["arrow_size"] == 2.5
|
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)
|
when `pid_tag_parse` (the ISA-5.1 tag grammar as a read-only tool)
|
||||||
joined; the `pid`
|
joined; the `pid`
|
||||||
tag is ranked first in `_GROUP_TAG_PRIORITY` so those tools file under
|
tag is ranked first in `_GROUP_TAG_PRIORITY` so those tools file under
|
||||||
`pid` rather than under their secondary `query` / `create` tags. Every
|
`pid` rather than under their secondary `query` / `create` tags. `styles`
|
||||||
other number here has been unchanged since the snapshot was taken.
|
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()}
|
sizes = {label: len(names) for label, names in (await server._tool_groups()).items()}
|
||||||
assert sizes == {
|
assert sizes == {
|
||||||
@@ -537,10 +541,11 @@ async def test_tool_group_sizes_are_unchanged():
|
|||||||
"pid": 9,
|
"pid": 9,
|
||||||
"premium": 12,
|
"premium": 12,
|
||||||
"solids": 5,
|
"solids": 5,
|
||||||
|
"styles": 9,
|
||||||
"system": 7,
|
"system": 7,
|
||||||
"templates": 2,
|
"templates": 2,
|
||||||
"transactions": 3,
|
"transactions": 3,
|
||||||
"validation": 1,
|
"validation": 1,
|
||||||
"view": 4,
|
"view": 4,
|
||||||
}
|
}
|
||||||
assert sum(sizes.values()) == 166
|
assert sum(sizes.values()) == 175
|
||||||
|
|||||||
Reference in New Issue
Block a user