Skip to content

← Blog

Automate DWG drawings with Python (and with AutoLISP or MCP)

· Guides

Drawing the same structural grid in every project, counting blocks in a hundred drawings or putting on the right layer what someone left on layer 0 are tasks that repeat. LinceCAD can be driven from outside through a local API: a Python script opens drawings, creates entities, changes layers, runs commands and saves to DWG. Everything goes through channels on your own computer; nothing goes out to the internet.

Four routes, depending on what you need

RouteWhat it isWhen to use it
Local API (JSON-RPC)One JSON message per line over a named pipe (Windows) or a socket (Linux). Works from any language.Driving LinceCAD, with its window open or without it, from your own programs.
Python lince clientA dependency-free library on top of that API: dib.linea(…), dib.guardar_como(…)…Shorter, more readable Python scripts.
lince_nucleo wheelA compiled Python module that reads, modifies and saves DWG/DXF without opening LinceCAD.Batches of files, servers or Jupyter.
AutoLISPThe built-in interpreter, with about 200 functions.Your own commands inside the program, or LISP routines you already have.

LinceCAD also includes an MCP server for connecting AI assistants, which we cover at the end.

Step 1: enable automation

The API is disabled by default. There are two ways to use it:

Programs identify themselves with a token (a 64-character password) stored in api.toml, in %APPDATA%\lince on Windows or ~/.config/lince on Linux. Only your user can read it.

Step 2: a script with the standard library

This example needs nothing installed but Python. First start lince --servidor --canal blogpy (or use the channel of the open window). The script authenticates with the token, creates a drawing, a layer and a 30 × 20 parcel, and saves it as DWG:

import json, os

# The token (the API password) is in %APPDATA%\lince\api.toml
toml = os.path.join(os.environ["APPDATA"], "lince", "api.toml")
ficha = next(l.split("=", 1)[1].strip().strip('"')
             for l in open(toml, encoding="utf-8") if l.startswith("ficha"))

canal = open(r"\\.\pipe\blogpy", "r+b", buffering=0)  # lince --servidor --canal blogpy
n = 0

def llamar(metodo, **params):
    global n
    n += 1
    msg = {"jsonrpc": "2.0", "id": n, "method": metodo, "params": params}
    canal.write((json.dumps(msg) + "\n").encode("utf-8"))
    r = json.loads(canal.readline())
    if "error" in r:
        raise RuntimeError(r["error"]["message"])
    return r["result"]

llamar("session.hello", token=ficha)
llamar("document.new")
llamar("layer.create", name="PARCELA", color=1)
llamar("entity.create", entity={"type": "LWPOLYLINE", "layer": "PARCELA", "closed": True,
                                "vertices": [[0, 0], [30, 0], [30, 20], [0, 20]]})
print(llamar("document.summary")["byType"])         # {'LWPOLYLINE': 1}
print(llamar("document.saveAs", path=r"C:\planos\parcela.dwg"))

The first request on each connection must be session.hello; otherwise the connection is closed. On Linux, instead of open(), connect a socket.AF_UNIX to the socket path and use s.makefile("rwb", buffering=0); the rest is the same.

The API has methods for drawings (document.open, save, saveAs, summary…), entities (entity.query, get, create, set, delete, as objects or as entget DXF lists), layers, blocks, styles, variables, selection, undo, commands (command.run), AutoLISP (lisp.eval), plotting to PDF, PNG and SVG, view capture and events. document.saveAs saves to DWG or DXF depending on the extension, in version 2018 by default or in 2013, 2010, 2007, 2004, 2000 and R14.

Step 3: the same with the Python client

The lince client wraps that API in objects. This script, with lince.iniciar(), starts LinceCAD without a window, draws a structural grid with its labels and twelve 30 × 30 cm columns, and saves the DWG, a PDF and an image:

import lince

ejes_x = [0, 5.0, 10.5, 16.0]      # grid line positions, in meters
ejes_y = [0, 4.5, 9.0]
h = 0.15                            # half the side of a 30 x 30 cm column

with lince.iniciar() as s:          # starts LinceCAD without a window
    dib = s.nuevo()
    dib.crear_capa("EJES", color=1)
    dib.crear_capa("PILARES", color=7)
    dib.crear_capa("ROTULOS", color=3)
    with dib.transaccion():         # everything is a single undo step
        for i, x in enumerate(ejes_x):
            dib.linea([x, -1.5], [x, ejes_y[-1] + 1.5], layer="EJES")
            dib.texto([x - 0.15, ejes_y[-1] + 2], str(i + 1), altura=0.4, layer="ROTULOS")
        for j, y in enumerate(ejes_y):
            dib.linea([-1.5, y], [ejes_x[-1] + 1.5, y], layer="EJES")
            dib.texto([-2.3, y - 0.2], "ABC"[j], altura=0.4, layer="ROTULOS")
        for x in ejes_x:
            for y in ejes_y:
                dib.polilinea([[x - h, y - h], [x + h, y - h], [x + h, y + h], [x - h, y + h]],
                              cerrada=True, layer="PILARES")
    print(dib.resumen()["byType"])  # {'LINE': 7, 'LWPOLYLINE': 12, 'TEXT': 7}
    dib.trazar_pdf(r"C:\planos\ejes.pdf")
    dib.guardar_como(r"C:\planos\ejes.dwg")
    dib.captura(r"C:\planos\ejes.png", ancho=1280)
Grid of four vertical axes (1 to 4) and three horizontal axes (A to C) in red, with twelve square columns at the intersections and the axis labels in green, on a white background.
The image the script saves with dib.captura.

To work with the window already open, use lince.conectar() and s.dibujo (the active drawing). There are also .circulo, .arco, .mtexto, .insertar (blocks), .consultar (by type, layer, box or filter), .comando, .lisp and .trazar_png/.trazar_svg.

Without opening LinceCAD: the lince_nucleo wheel

To process many files, the lince_nucleo wheel works inside the Python process itself. This snippet, from the documentation, lists the circles in a drawing and saves it as DWG 2018:

import lince_nucleo as ln
dib = ln.abrir("plano.dwg")
for e in dib.consultar(tipo="CIRCLE"):
    print(e["handle"], e["radius"])
dib.guardar("salida.dwg", "2018")

The alternative without Python: AutoLISP

If you already work with LISP, you can keep doing so. The LinceCAD interpreter supports defun C:, ssget with filters, entget, entmod, entmake and command, among others; routines are loaded with APPLOAD or automatically from acaddoc.lsp. This command counts the columns in the previous drawing:

(defun C:CONTARPILARES (/ ss)
  (setq ss (ssget "X" '((0 . "LWPOLYLINE") (8 . "PILARES"))))
  (princ (strcat "\nPilares: " (itoa (if ss (sslength ss) 0))))
  (princ)
)

On ejes.dwg it replies “Pilares: 12” (columns: 12). For fixed sequences of commands there are also scripts (SCRIPT).

AI assistants with MCP

lince --mcp is an MCP (Model Context Protocol) server over standard input and output, for assistants that launch it themselves. With the window open, you can also enable an MCP server over HTTP, only on 127.0.0.1. The assistant sees tools to open, query, create, dimension, insert blocks, plot to PDF, capture the view and undo, and the LinceCAD help as documentation. For example, to add it to Claude Code:

claude mcp add --scope project lince -- "C:\Program Files\lince\lince.exe" --mcp

With confirmation turned on, each change the assistant asks for waits in the AIACTIONS palette until you accept or reject it, and everything a call does is undone with a single UNDO.

Limitations

Frequently asked questions

What if the starting drawing is a scan? It first has to be turned into vectors: we explain it in how to vectorize a scanned drawing and convert it to DWG.

And on Linux? The API works the same way, with a Unix socket. Read how to open and edit DWG files on Linux.

And for surveying? The workflow with the Topography tab is in contour lines from a LAS/LAZ point cloud.

The local API, AutoLISP and the MCP server come with LinceCAD, with a perpetual license. Download · See pricing · AutoLISP, automation and all other features.