API Reference¶
MCPClient¶
Context-managed TCP client for the Vexy Lines MCP server. Speaks JSON-RPC 2.0 over newline-delimited TCP to the server embedded in the Vexy Lines desktop app.
from vexy_lines_api import MCPClient
with MCPClient(host="127.0.0.1", port=47384, timeout=30.0, auto_launch=True) as vl:
info = vl.get_document_info()
Constructor:
MCPClient(
host: str = "127.0.0.1",
port: int = 47384,
timeout: float = 30.0,
*,
auto_launch: bool = True,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
host |
str |
"127.0.0.1" |
Server address |
port |
int |
47384 |
Server TCP port |
timeout |
float |
30.0 |
Socket timeout in seconds for all operations |
auto_launch |
bool |
True |
Launch the Vexy Lines app if connection fails |
Connection lifecycle¶
__enter__calls_connect()then_handshake()._connect()opens a TCP socket. On failure, ifauto_launch=True, launches the app and polls with exponential back-off (0.5 s initial, 1.2x growth, 2.0 s cap) for up to 30 seconds._handshake()sendsinitializewith protocol version"2024-11-05"and client info{"name": "vexy-lines-apy", "version": "1.0.0"}. Validates the server returns the same protocol version. Sendsnotifications/initializednotification.- Tool calls use
_send_request("tools/call", ...)which assigns an auto-incrementing integer request ID. __exit__shuts down and closes the socket.
Auto-launch behaviour¶
When auto_launch=True and the initial connection fails:
- macOS: runs
open -a "Vexy Lines"via subprocess. - Windows: searches
C:\Program Files\Vexy Lines\,C:\Program Files (x86)\Vexy Lines\, and%LOCALAPPDATA%\Programs\Vexy Lines\forVexy Lines.exe. RaisesMCPErrorif not found. - Other platforms: raises
MCPError(auto-launch unsupported).
After launching, polls the TCP port for up to 30 seconds before giving up.
Document methods¶
new_document(width, height, dpi, source_image) -> NewDocumentResult¶
Create a new document. Width and height are inferred from the source image when omitted.
def new_document(
self,
width: float | None = None,
height: float | None = None,
dpi: float = 300,
source_image: str | None = None,
) -> NewDocumentResult
| Parameter | Type | Default | Description |
|---|---|---|---|
width |
float \| None |
None |
Width in pixels (inferred from source image if omitted) |
height |
float \| None |
None |
Height in pixels (inferred from source image if omitted) |
dpi |
float |
300 |
Document resolution |
source_image |
str \| None |
None |
Path to source image file (resolved to absolute) |
Returns a NewDocumentResult with the created document's metadata.
open_document(path) -> str¶
Open a .lines file. The path is resolved to absolute before sending.
def open_document(self, path: str) -> str
Returns server status string.
save_document(path=None) -> str¶
Save the current document. Pass a path for Save As.
def save_document(self, path: str | None = None) -> str
export_document(path, dpi=None, format=None) -> str¶
Export to file. Format is one of "svg", "pdf", "png", "jpg", "eps".
def export_document(
self,
path: str,
dpi: int | None = None,
format: str | None = None,
) -> str
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
str |
required | Output file path (resolved to absolute) |
dpi |
int \| None |
None |
Override document DPI for export |
format |
str \| None |
None |
"svg", "pdf", "png", "jpg", or "eps" |
get_document_info() -> DocumentInfo¶
Returns a DocumentInfo with dimensions, DPI, units, and unsaved changes flag.
def get_document_info(self) -> DocumentInfo
Structure methods¶
get_layer_tree() -> LayerNode¶
Returns the full document tree as a recursive LayerNode. The root node has type == "document".
def get_layer_tree(self) -> LayerNode
add_group(parent_id=None, caption=None, source_image_path=None) -> dict¶
Add a group to the document.
def add_group(
self,
parent_id: int | None = None,
caption: str | None = None,
source_image_path: str | None = None,
) -> dict[str, object]
| Parameter | Type | Default | Description |
|---|---|---|---|
parent_id |
int \| None |
None |
Parent object ID (defaults to document root) |
caption |
str \| None |
None |
Group display name |
source_image_path |
str \| None |
None |
Source image for the group |
Returns a dict containing "id" of the created group.
add_layer(group_id) -> dict¶
Add a layer to a group.
def add_layer(self, group_id: int) -> dict[str, object]
Returns a dict containing "id" of the created layer.
add_fill(layer_id, fill_type, color=None, params=None) -> dict¶
Add a fill to a layer.
def add_fill(
self,
layer_id: int,
fill_type: str,
color: str | None = None,
params: dict[str, object] | None = None,
) -> dict[str, object]
| Parameter | Type | Default | Description |
|---|---|---|---|
layer_id |
int |
required | Parent layer object ID |
fill_type |
str |
required | One of FILL_TYPES: "linear", "wave", "circular", "radial", "spiral", "scribble", "halftone", "handmade", "fractals", "trace" |
color |
str \| None |
None |
Hex colour string ("#RRGGBB" or "#RRGGBBAA") |
params |
dict \| None |
None |
Fill parameters dict (see FILL_TYPE_PARAMS) |
Returns a dict containing "id" of the created fill.
delete_object(object_id) -> str¶
Delete any object by ID (group, layer, or fill).
def delete_object(self, object_id: int) -> str
Note: the wire argument key is "id", not "object_id".
Fill parameter methods¶
get_fill_params(fill_id) -> dict¶
Get all parameters of a fill as a dict. The wire argument key is "id".
def get_fill_params(self, fill_id: int) -> dict[str, object]
Returns a dict of parameter names to current values.
set_fill_params(fill_id, **params) -> str¶
Set fill parameters by keyword. The wire format wraps kwargs inside a "params" dict alongside "id".
def set_fill_params(self, fill_id: int, **params: object) -> str
vl.set_fill_params(42, color="#ff0000", interval=3.0, angle=45)
All spatial values (interval, thickness, dispersion) are in pixels. The server converts internally to mm or points based on the document DPI. See FILL_TYPE_PARAMS for valid parameter names per fill type.
Visual methods¶
set_source_image(image_path, group_id=None) -> str¶
Set the source image for a group.
def set_source_image(self, image_path: str, group_id: int | None = None) -> str
set_caption(object_id, caption) -> str¶
Rename any object. Wire argument key is "id".
def set_caption(self, object_id: int, caption: str) -> str
set_visible(object_id, visible) -> str¶
Toggle visibility. The visible parameter is keyword-only.
def set_visible(self, object_id: int, *, visible: bool) -> str
set_layer_mask(layer_id, paths, mode="create") -> str¶
Set an SVG vector mask on a layer.
def set_layer_mask(
self,
layer_id: int,
paths: list[str],
mode: str = "create",
) -> str
| Parameter | Type | Default | Description |
|---|---|---|---|
layer_id |
int |
required | Target layer object ID |
paths |
list[str] |
required | SVG path data strings (e.g. ["M 0 0 L 100 100 Z"]) |
mode |
str |
"create" |
"create" (replace), "add" (union), "subtract" (difference) |
get_layer_mask(layer_id) -> dict¶
Get mask data for a layer.
def get_layer_mask(self, layer_id: int) -> dict[str, object]
transform_layer(layer_id, ...) -> str¶
Apply a 2D transform to a layer.
def transform_layer(
self,
layer_id: int,
translate_x: float = 0,
translate_y: float = 0,
rotate_deg: float = 0,
scale_x: float = 1,
scale_y: float = 1,
) -> str
All positional values are in pixels. Wire argument key for the layer is "id".
set_layer_warp(layer_id, top_left, top_right, bottom_right, bottom_left) -> str¶
Set perspective warp corners. Each corner is [x, y] in pixels. Wire argument key for the layer is "id".
def set_layer_warp(
self,
layer_id: int,
top_left: list[float],
top_right: list[float],
bottom_right: list[float],
bottom_left: list[float],
) -> str
Control methods¶
render(timeout=120.0) -> bool¶
Render all layers and wait for completion. Combines render_all() + wait_for_render().
def render(self, timeout: float = 120.0) -> bool
Returns True when rendering completed, False if timed out. The app continues rendering in the background on timeout.
render_all() -> str¶
Trigger a render without waiting.
def render_all(self) -> str
wait_for_render(timeout=120.0, poll_interval=0.5) -> bool¶
Poll until rendering finishes. Waits 0.5 s before the first check to let the render thread set its flag. Handles two scenarios:
- Render started and finished: detected by a
True -> Falsetransition inget_render_status. - Render already finished: detected by four consecutive
Falsereadings without ever seeingTrue.
Adds a 0.5 s settling delay after detecting completion.
def wait_for_render(self, timeout: float = 120.0, poll_interval: float = 0.5) -> bool
get_render_status() -> RenderStatus¶
Check whether the document is currently rendering.
def get_render_status(self) -> RenderStatus
undo() -> str / redo() -> str¶
Undo or redo the last action.
get_selection() -> dict | str¶
Get the currently selected objects.
def get_selection(self) -> dict[str, object] | str
select_object(object_id) -> str¶
Select an object by ID. Wire argument key is "id".
def select_object(self, object_id: int) -> str
Export shortcuts¶
Convenience methods that call export_document with the appropriate format and return the resolved absolute path. The dpi parameter is keyword-only.
def export_svg(self, path: str, *, dpi: int | None = None) -> Path
def export_pdf(self, path: str, *, dpi: int | None = None) -> Path
def export_png(self, path: str, *, dpi: int | None = None) -> Path
def export_jpeg(self, path: str, *, dpi: int | None = None) -> Path
def export_eps(self, path: str, *, dpi: int | None = None) -> Path
| Method | Format | Notes |
|---|---|---|
export_svg(path, *, dpi=None) |
"svg" |
Vector output |
export_pdf(path, *, dpi=None) |
"pdf" |
Vector output |
export_png(path, *, dpi=None) |
"png" |
Raster; lower DPI = faster export |
export_jpeg(path, *, dpi=None) |
"jpg" |
Raster; lower DPI = faster export |
export_eps(path, *, dpi=None) |
"eps" |
Encapsulated PostScript |
All return the resolved Path of the exported file.
svg() -> str¶
Export as SVG to a temporary file, read it, clean up, and return the SVG content as a string. Useful when you pipe SVG into other tools or return it from an API.
def svg(self) -> str
svg_parsed() -> svglab.Svg¶
Export as SVG and return a parsed svglab.Svg DOM object for traversal, editing, and rasterisation. Requires the [svg] extra (pip install vexy-lines-apy[svg]).
def svg_parsed(self) -> object # svglab.Svg at runtime
Raises ImportError if svglab is not installed.
Low-level method¶
call_tool(name, arguments=None) -> dict | str¶
Call any MCP tool by name. The server wraps results in content[0].text which may be JSON or plain text. This method attempts JSON parse first, then falls back to returning the raw string.
def call_tool(
self,
name: str,
arguments: dict[str, object] | None = None,
) -> dict[str, object] | str
Use this to call tools not yet wrapped as typed methods, or to pass through raw arguments:
# Direct tool call
result = vl.call_tool("get_document_info")
# With arguments
result = vl.call_tool("set_fill_params", {"id": 42, "params": {"color": "#ff0000"}})
MCPError¶
Raised when the MCP server returns an error, connection fails, or protocol validation fails.
class MCPError(Exception):
message: str # Human-readable error description
Common scenarios that raise MCPError:
| Cause | Example message |
|---|---|
| Connection refused | Cannot connect to Vexy Lines MCP server at 127.0.0.1:47384: ... |
| App not found (Windows) | Vexy Lines not found. Install it or pass auto_launch=False... |
| Unsupported platform | Auto-launch not supported on linux... |
| Server timeout | Vexy Lines launched but MCP server not ready after 30s... |
| Protocol mismatch | Protocol mismatch: client=2024-11-05, server=... |
| Server error | MCP error -32000: No document open |
| Connection closed | Connection closed by server |
| Invalid JSON | Invalid JSON from server: ... |
| Unexpected response | Unexpected response from new_document: ... |
from vexy_lines_api import MCPClient, MCPError
try:
with MCPClient(auto_launch=False) as vl:
vl.get_document_info()
except MCPError as e:
print(f"MCP failed: {e.message}")
Style functions¶
extract_style(path) -> Style¶
Parse a .lines file and return a Style containing the full group/layer/fill tree and document properties. Does not need the app: it works offline via the vexy-lines-py parser.
If Pillow is available, also extracts the pixel dimensions of the embedded source image into style.source_image_size.
def extract_style(path: str | Path) -> Style
apply_style(client, style, source_image, ...) -> str¶
Apply a style to a source image via MCP. Returns the SVG string.
def apply_style(
client: MCPClient,
style: Style,
source_image: str | Path,
*,
dpi: int = 72,
relative: bool = False,
render_timeout: float = 300.0,
style_mode: Literal["auto", "fast", "slow"] = "fast",
save_lines_to: str | Path | None = None,
) -> str
| Parameter | Type | Default | Description |
|---|---|---|---|
client |
MCPClient |
required | Connected MCP client |
style |
Style |
required | Style to apply |
source_image |
str \| Path |
required | Path to source image |
dpi |
int |
72 |
Document DPI (lower = faster; 72 good for video) |
relative |
bool |
False |
Scale spatial params to match target size |
render_timeout |
float |
300.0 |
Max seconds to wait for render (Fractals at high res may need 120-300 s) |
style_mode |
"auto" \| "fast" \| "slow" |
"fast" |
Transfer strategy (see below) |
save_lines_to |
str \| Path \| None |
None |
Save intermediate .lines to this path |
Style modes:
| Mode | Strategy |
|---|---|
"fast" |
XML source-image swap in the .lines file, then open in app. Preserves all fill params losslessly. If the source image has different dimensions, it is resized to match. Falls back to "slow" if the original .lines file is not available. |
"slow" |
Creates a new document via MCP, replicates the style tree by calling add_group / add_layer / add_fill / set_fill_params for every node. |
"auto" |
Uses "fast" if the target image pixel dimensions exactly match the embedded source image; otherwise "slow". |
create_styled_document(client, style, source_image, ...) -> None¶
Create a styled document in Vexy Lines without rendering or exporting. The document remains open in the app.
def create_styled_document(
client: MCPClient,
style: Style,
source_image: str | Path,
*,
dpi: int = 72,
relative: bool = False,
) -> None
save_and_consolidate(client, path, ...) -> None¶
Save, reopen, render, and save again. This improves .lines reliability after programmatic edits.
def save_and_consolidate(
client: MCPClient,
path: str | Path,
*,
render_timeout: float = 300.0,
) -> None
Sequence: save -> reopen -> render -> save.
interpolate_style(a, b, t) -> Style¶
Blend two compatible styles. t=0 returns style A, t=1 returns style B. Numeric fill parameters and hex colours interpolate linearly.
def interpolate_style(a: Style, b: Style, t: float) -> Style
The t value is clamped to [0.0, 1.0]. If styles are incompatible, logs a warning and returns a deep copy of style A.
styles_compatible(a, b) -> bool¶
Check whether two styles have matching tree structures. Required before interpolation.
def styles_compatible(a: Style, b: Style) -> bool
JobFolder¶
Persistent job folder for resumable export pipelines. Stores intermediate artifacts (.lines, .svg, frames) alongside the final output instead of using temporary directories that vanish on crash.
from vexy_lines_api.export import JobFolder
jf = JobFolder("output.mp4", force=False)
print(jf.path) # /absolute/path/to/output-vljob/
print(jf.output_stem) # "output"
print(jf.src_path) # /absolute/path/to/output-vljob/src/
Constructor:
JobFolder(output_path: str | Path, *, force: bool = False)
| Parameter | Type | Default | Description |
|---|---|---|---|
output_path |
str \| Path |
required | Final output destination (file or directory) |
force |
bool |
False |
Delete existing job folder and start fresh |
Path resolution:
| Output type | Example output path | Computed job folder |
|---|---|---|
File (.mp4, .png, .jpg, .jpeg, .svg, .lines) |
./out/video.mp4 |
./out/video-vljob/ |
| Directory | ./output/ |
./output-vljob/ |
Override with the VEXY_LINES_JOB_FOLDER environment variable.
Properties:
| Property | Type | Description |
|---|---|---|
path |
Path |
The job folder directory |
output_stem |
str |
Base name for naming intermediate files |
src_path |
Path |
{job_folder}/src/ directory for raw source frames |
Methods:
| Method | Signature | Returns | Description |
|---|---|---|---|
asset_path |
(name: str, ext: str) |
Path |
{job_folder}/{name}.{ext} |
frame_path |
(name: str, frame_num: int, ext: str, *, pad_width: int) |
Path |
{job_folder}/{name}--{NNN}.{ext} (zero-padded) |
frame_src_path |
(name: str, frame_num: int, ext: str, *, pad_width: int) |
Path |
{job_folder}/src/src--{name}--{NNN}.{ext} |
existing_frames |
(name: str, ext: str) |
set[int] |
Frame numbers already on disk |
existing_src_frames |
(name: str, ext: str) |
set[int] |
Source frame numbers already on disk |
copy_to_output |
(src_name: str, dest: str \| Path) |
Path |
Copy file from job folder to destination |
cleanup |
() |
None |
Delete the entire job folder |
Export pipeline¶
process_export(request, ...) -> None¶
Unified entry point for all export modes. Accepts either an ExportRequest dataclass or legacy positional arguments. Creates a JobFolder, dispatches to the appropriate processor, and optionally cleans up.
def process_export(
request: ExportRequest | str,
input_paths: list[str] | None = None,
style_path: str | None = None,
end_style_path: str | None = None,
output_path: str | None = None,
fmt: str | None = None,
size: str | None = None,
*,
audio: bool = True,
frame_range: tuple[int, int] | None = None,
relative_style: bool = False,
force: bool = False,
cleanup: bool = False,
abort_event: threading.Event | None = None,
on_progress: ProgressCallback | None = None,
on_complete: CompleteCallback | None = None,
on_error: ErrorCallback | None = None,
on_preview: PreviewCallback | None = None,
) -> None
ExportRequest¶
Canonical export request dataclass. Frozen (immutable after creation).
@dataclass(frozen=True)
class ExportRequest:
mode: ExportMode # "lines" | "images" | "video"
input_paths: list[str]
style_path: str | None
end_style_path: str | None
output_path: str
format: ExportFormat # "LINES" | "SVG" | "PNG" | "JPG" | "MP4"
size: str
audio: bool = True
frame_range: tuple[int, int] | None = None
relative_style: bool = False
style_mode: str = "fast"
force: bool = False # Force-clean job folder
cleanup: bool = False # Delete job folder after success
Callback types¶
ProgressCallback = Callable[[int, int, str], None] # (current, total, message)
CompleteCallback = Callable[[str], None] # (message)
ErrorCallback = Callable[[str], None] # (message)
PreviewCallback = Callable[[bytes], None] # (image_bytes)
Export errors¶
| Exception | When raised |
|---|---|
ExportAborted |
Abort event was set during processing |
ExportValidationError |
Missing required fields in legacy argument mode |
Video utilities¶
VideoInfo¶
Frozen dataclass with video metadata.
| Field | Type | Description |
|---|---|---|
width |
int |
Frame width in pixels |
height |
int |
Frame height in pixels |
fps |
float |
Frames per second |
total_frames |
int |
Total number of frames |
duration |
float |
Duration in seconds |
has_audio |
bool |
Whether the file contains an audio stream (requires ffprobe) |
probe(path) -> VideoInfo¶
Read video metadata using OpenCV. Audio detection requires ffprobe on PATH.
def probe(path: str) -> VideoInfo
svg_to_pil(svg_string, width, height) -> Image.Image¶
Rasterise an SVG string to a PIL Image via resvg_py. Patches mm dimensions to px before rasterising. Falls back to a blank white image if resvg_py is not installed.
def svg_to_pil(svg_string: str, width: int, height: int) -> Image.Image
process_video(input_path, output_path, ...) -> VideoInfo¶
Re-encode a video with optional trim and scale. No style transfer.
def process_video(
input_path: str,
output_path: str,
*,
start_frame: int = 0,
end_frame: int | None = None,
include_audio: bool = True,
size_multiplier: int = 1,
abort_event: Any = None,
on_frame_image: Any = None,
) -> VideoInfo
process_video_with_style(input_path, output_path, ...) -> VideoInfo¶
Per-frame style transfer. Each frame is: decoded -> saved as PNG -> styled via MCP -> rasterised via resvg_py -> encoded to output video. If end_style is provided, the style interpolates linearly across the frame range.
def process_video_with_style(
input_path: str,
output_path: str,
*,
style: Style | None = None,
end_style: Style | None = None,
start_frame: int = 0,
end_frame: int | None = None,
include_audio: bool = True,
size_multiplier: int = 1,
relative: bool = False,
style_mode: str = "auto",
abort_event: Any = None,
on_progress: Callable[[int, int], None] | None = None,
on_frame_image: Callable[[Image.Image], None] | None = None,
) -> VideoInfo
Media utilities¶
extract_preview_from_lines(filepath) -> bytes | None¶
Extract the embedded preview or source image from a .lines file. Returns PNG or JPEG bytes, or None on failure.
extract_frame(video_path, frame_number=1) -> Image.Image | None¶
Extract a single frame from a video file as a PIL Image. frame_number is 1-based.
fit_image_to_box(image, width, height) -> Image.Image¶
Scale a PIL Image to fit within width x height while preserving aspect ratio. Composites RGBA onto white, returns RGB.
truncate_start(text, max_chars=60) -> str¶
Truncate a string from the start, prepending "..." if truncated. For display in progress messages.
Dataclasses¶
Style¶
A transferable style extracted from a .lines document.
| Field | Type | Description |
|---|---|---|
groups |
list[GroupInfo \| LayerInfo] |
Top-level fill tree (from vexy_lines.types) |
props |
DocumentProps |
Document dimensions, DPI, and thickness/interval ranges |
source_path |
str \| None |
Path of the .lines file this style was extracted from |
source_image_size |
tuple[int, int] \| None |
(width, height) of the embedded source image in pixels |
StyleMode = Literal["auto", "fast", "slow"]: type alias for the style_mode parameter.
DocumentInfo¶
MCP response from get_document_info.
| Field | Type | Description |
|---|---|---|
width_mm |
float |
Document width in mm |
height_mm |
float |
Document height in mm |
resolution |
float |
DPI |
units |
str |
Measurement units (e.g. "mm") |
has_changes |
bool |
Unsaved changes flag |
LayerNode¶
Recursive tree node from get_layer_tree().
| Field | Type | Description |
|---|---|---|
id |
int |
Object ID for MCP calls |
type |
str |
"document", "group", "layer", or "fill" |
caption |
str |
Display name |
visible |
bool |
Visibility state |
fill_type |
str \| None |
Fill algorithm name (only when type == "fill") |
children |
list[LayerNode] |
Child nodes (empty for fills) |
Class method LayerNode.from_dict(d) constructs the tree from nested dicts returned by the server.
NewDocumentResult¶
MCP response from new_document.
| Field | Type | Description |
|---|---|---|
status |
str |
Status string (e.g. "ok") |
width |
float |
Width in pixels |
height |
float |
Height in pixels |
dpi |
float |
Resolution |
root_id |
int |
Root node object ID (use as parent_id for add_group) |
RenderStatus¶
MCP response from get_render_status.
| Field | Type | Description |
|---|---|---|
rendering |
bool |
Whether the document is currently rendering |
Constants¶
FILL_TYPES¶
FILL_TYPES: frozenset[str] = frozenset({
"linear", "wave", "circular", "radial", "spiral",
"scribble", "halftone", "handmade", "fractals", "trace",
})
All valid fill type names accepted by add_fill.
FILL_TYPE_PARAMS¶
FILL_TYPE_PARAMS: dict[str, tuple[str, ...]]
Maps each fill type to its valid parameter names for set_fill_params. Derived from the C++ paramsForType() in mcptools.cpp.
Base parameters (shared by all stroke-based fills except trace):
interval, angle, thickness, thickness_min, contrast, smoothness, break_up, break_down, dispersion, vdisp, color_mode, color_seg_len, color_seg_disp
Per-type additions:
| Fill type | Extra parameters |
|---|---|
linear |
(base only) |
wave |
wave_height, wave_length, wave_fade, phase, curviness |
circular |
x0, y0 |
radial |
x0, y0, r0, auto_distance, auto_randomize |
spiral |
x0, y0, direction_ccw |
scribble |
scribble_length, curviness, variety, complexity, rotation, scribble_pattern |
halftone |
cell_size, rotation, halftone_mode, rotation_mode, morphing, randomization |
handmade |
mode, parity_mode, is_filled, expand_lines, averaging |
fractals |
depth, kind |
trace |
smoothness, clearing_level, detailing, color_mode (no base params) |
Parameter name mapping (parser to MCP)¶
The .lines XML uses different attribute names from the MCP server. The style engine handles this translation internally via PARSER_TO_MCP_PARAMS:
| Parser field (XML) | MCP parameter | Notes |
|---|---|---|
interval |
interval |
Line spacing, stored in points |
angle |
angle |
Degrees |
multiplier |
thickness |
Stroke width multiplier, stored in mm |
thickness_min |
thickness_min |
Min stroke width, stored in mm |
smoothness |
contrast |
Tone-mapping curve |
uplimit |
break_up |
Upper brightness threshold |
downlimit |
break_down |
Lower brightness threshold |
dispersion |
dispersion |
Random offset, stored in points |
Note: XML thick_gap has no MCP equivalent and is not settable via the API.