# Inkscape

Un modelo maneja Inkscape 1.4 a través del Hub para componer una tarjeta de visita en dos capas. Inkscape es la única aplicación de aquí en la que no hay nada que iniciar: el Hub ejecuta un proceso de Python por cada script. Esta página da el texto, el script, las respuestas del Hub y la captura.

*Un dibujo hecho por un script y abierto en Inkscape: es un SVG corriente cuyos nodos XML se editan a mano.*

## Datos

- Versión probada: 1.4
- Modo: `cli`

## Activar Inkscape

No hay puente ni ventana. El Hub busca `/usr/bin/python3`, `/usr/bin/inkscape` y las extensiones de Inkscape en `/usr/share/inkscape/extensions`. Toda la preparación es una entrada en `~/.config/libremodular-mcp/config.toml`, después de `libremodular-mcp init`:

```toml
[apps.inkscape]
enabled = true
```

En un Debian 13 recién instalado, `import inkex` necesita además `python3-tinycss2`, que el paquete `inkscape` no instala: `sudo apt install inkscape python3-tinycss2`.

`get_state` sobre `hub` enumeró Inkscape como conectado en el modo `cli`, con la versión que informó Inkscape:

**Respuesta del Hub**

```json
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:13.608Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "1.4",
        "bridge_version": null,
        "connected": true,
        "mode": "cli",
        "name": "inkscape"
      }
    ]
  },
  "warnings": []
}
```


## Texto dado al modelo

```text
In Inkscape, lay out a name card, A6 landscape (148 by 105 mm). Dark blue background (#1c385c), a copper circle (#c25438) in the top-left corner, the name "Alice Martin" in white and large, a thin amber line (#f2b344) under it, then the role "Sound engineer" in light grey and smaller. Put the background shapes on a layer named "Background" and the text on a layer named "Text".
```


Antes de escribir, el modelo llamó a `get_state` sobre `inkscape`. El `state` estaba vacío y una advertencia decía `no document yet: the working document is created by the first script`.

## Script escrito

Un modelo, Claude Sonnet 5.5, escribió este script a partir de la guía y de ese estado. No es un script tomado de las pruebas del proyecto. Dos líneas vienen de la guía: el script elimina primero los hijos de la raíz, porque el documento se conserva de un script al siguiente, y fija `cx`, `cy` y `r` del círculo uno por uno, porque, como advierte la guía, los dos argumentos de `inkex.Circle.new` no forman un centro.

**Script escrito**

```python
import json
import os

import inkex

document = inkex.load_svg(os.environ["LMM_DOCUMENT"])
svg = document.getroot()
for child in list(svg):
    svg.remove(child)
svg.set("width", "148mm")
svg.set("height", "105mm")
svg.set("viewBox", "0 0 148 105")

background = svg.add(inkex.Layer.new("Background"))
panel = inkex.Rectangle.new(0, 0, 148, 105)
panel.style = {"fill": "#1c385c"}
background.add(panel)
disc = inkex.Circle()
disc.set("cx", "20")
disc.set("cy", "20")
disc.set("r", "9")
disc.style = {"fill": "#c25438"}
background.add(disc)
rule = inkex.Rectangle.new(20, 62, 108, 0.8)
rule.style = {"fill": "#f2b344"}
background.add(rule)

texts = svg.add(inkex.Layer.new("Text"))
for content, y, size, colour, weight in [("Alice Martin", 56, 12, "#ffffff", "bold"), ("Sound engineer", 76, 6, "#cfd4da", "normal")]:
    line = inkex.TextElement()
    line.text = content
    line.set("x", "20")
    line.set("y", str(y))
    line.style = {"fill": colour, "font-size": f"{size}px", "font-family": "sans-serif", "font-weight": weight}
    texts.add(line)

document.write(os.environ["LMM_DOCUMENT"])
print("LMM_RESULT: " + json.dumps({"layers": [layer.label for layer in svg.descendants().filter(inkex.Layer)], "elements": len(list(svg.descendants()))}))
```


## Respuesta del Hub

**Respuesta del Hub**

```json
{
  "app": "inkscape",
  "duration_ms": 151,
  "error": null,
  "result": {
    "elements": 8,
    "layers": [
      "Background",
      "Text"
    ]
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}
```


El script se ejecutó en 151 ms: se inició y terminó un proceso de Python. El script imprimió su `result` en la última línea, tras `LMM_RESULT: `, que es la forma en que un script de Inkscape devuelve un valor. `get_state` describió entonces el documento de trabajo y devolvió una captura:

**Respuesta del Hub**

```json
{
  "app": "inkscape",
  "app_version": "1.4",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:14.185Z",
  "connected": true,
  "content_origin": "document",
  "screenshot": "$XDG_RUNTIME_DIR/libremodular-mcp/captures/inkscape-20261021T124713832Z.png",
  "state": {
    "element_count": 8,
    "height": "105mm",
    "layers": [
      {
        "id": null,
        "label": "Background"
      },
      {
        "id": null,
        "label": "Text"
      }
    ],
    "width": "148mm"
  },
  "warnings": []
}
```


## La imagen producida

La captura es el documento de trabajo exportado por el propio `inkscape`, de 559 por 397 píxeles para una tarjeta de 148 por 105 mm. El texto se dibuja con la fuente `sans-serif` de esta máquina, así que otra máquina lo dibuja con la suya. El píxel de arriba a la izquierda de la captura es `#1C385C` y el que está dentro del círculo es `#C25438`.

![Una tarjeta azul oscuro con un círculo de cobre arriba a la izquierda, el nombre Alice Martin en blanco, una línea ámbar y el cargo Sound engineer en gris.](https://libremodular.davalan.fr/images/applications/inkscape-name-card.png)

*La captura del documento de trabajo tras el script, tal como la exportó el Hub.*


> **Límite**
>
> No se probó: una ventana de Inkscape abierta al mismo tiempo que el Hub y un documento más grande que este.

[Guía de referencia en el repositorio](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/inkscape.md)

