DEV Community

vjmap
vjmap

Posted on

A Browser-Based CAD Editor with AI Agent Support - vjmap webcad

Desktop CAD software has been the norm for decades — you buy a license, install a 2GB executable, and edit drawings on a dedicated workstation. That model works, but it creates real friction: version conflicts when files are emailed around, no collaboration, no browser access, and expensive per-seat licensing.

The web has changed nearly every other tool category. It's taken longer with CAD, because the problem is genuinely hard: CAD geometry is complex, files are large and binary, rendering precision matters, and users expect the same commands they've used for 20 years.

This post covers VJCAD — a web-based CAD editor built on TypeScript, WebGL, and WebAssembly — and walks through how to embed it in your own application. It also covers the AI agent integration via MCP, which lets you drive the CAD engine with natural language.


What's in the WebCAD


Before getting into code, here's what the platform actually provides:

Drawing engine: 20+ entity types (Line, Circle, Arc, Polyline, Spline, Text, MText, Insert/Block, Hatch, Dimension, etc.), 80+ commands (COPY, MOVE, ROTATE, SCALE, TRIM, EXTEND, OFFSET, MIRROR…), full Undo/Redo stack, layer management, linetype and lineweight support, TrueType and SHX font rendering.

DWG compatibility: Native read/write of DWG and DXF formats across multiple AutoCAD versions. Layers, blocks, external references, text styles, and linetypes all round-trip correctly. Export back to standard DWG for use in desktop AutoCAD.

Tile editing mode: For very large drawings (city-scale site plans, large infrastructure drawings), the server pre-renders the file into a multi-resolution tile pyramid. The browser loads only the tiles in the current viewport. You then use TILEEDITAREA or TILEEDITLAYER commands to pull down the entity data for just the region or layer you need to edit.

Version control: Branching and patch-based versioning inspired by Git. Each save creates an incremental patch recording added, modified, and deleted entities. Multiple team members can work on branches and merge with conflict detection.

Plugin system: Highly modular architecture. The built-in AI, 3D view, annotation, table extraction, and industry-specific features are all plugins. Developers can write and distribute their own.

AI agent via MCP: Plug the official MCP server into Cursor, Trae, or any MCP-compatible client and drive the CAD engine with natural language instructions.


Getting Started

Requirements

  • Node.js 16+
  • npm, yarn, or pnpm
  • A modern browser with WebGL support

Install

# New project with Vite + TypeScript (recommended)
npm create vite@latest my-webcad-app -- --template vanilla-ts
cd my-webcad-app
npm install vjcad
npm run dev

# Or add to an existing project
npm install vjcad
Enter fullscreen mode Exit fullscreen mode

Minimal Setup

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebCAD App</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; overflow: hidden; }
    #cad-app { width: 100%; height: 100%; }
  </style>
</head>
<body>
  <div id="cad-app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

src/main.ts:

import { MainView, initCadContainer } from 'vjcad';

const cadView = new MainView({
  appname: "My CAD App",
  version: "v1.0.0"
});

initCadContainer("cad-app", cadView);
Enter fullscreen mode Exit fullscreen mode

That's it. initCadContainer mounts the full CAD editor — Ribbon toolbar, command line, canvas, layer panel — into the #cad-app div.


Full Configuration

MainView accepts a configuration object. Here's the complete interface:

import { MainView, initCadContainer } from 'vjcad';

const cadView = new MainView({
  // Basic info
  appname: "My CAD App",
  version: "v1.0.0",

  // Backend service (for file operations, tile generation, version control)
  serviceUrl: "http://127.0.0.1:27660/api/v1",
  accessToken: "your-token",
  accessKey: "",                          // for encrypted drawings

  // UI layout
  sidebarStyle: "right",                  // "both" | "left" | "right" | "none"
  locale: "en-US",                        // "en-US" | "zh-CN"
                                          // omit to auto-detect from browser

  // Plugin marketplace
  pluginMarketUrl: "./plugins/plugins.json",

  // Font files (for rendering text entities in DWG)
  fonts: [
    { path: "./fonts/simkai.woff", name: "simkai", kind: "woff" },
    { path: "./fonts/_default.shx", name: "_default", kind: "shx" }
  ]
});

initCadContainer("cad-app", cadView);
Enter fullscreen mode Exit fullscreen mode

Locale auto-detection priority order: URL ?lang=localStoragenavigator.languagezh-CN.


Drawing Entities Programmatically

Initialization is asynchronous (canvas setup, WASM loading, plugin loading). Always await cadView.onLoad() before doing anything with the engine:

import { MainView, initCadContainer, Engine, LineEnt, CircleEnt, ArcEnt, PolylineEnt, TextEnt } from 'vjcad';

const cadView = new MainView({ appname: "WebCAD", version: "v1.0.0" });
initCadContainer("cad-app", cadView);

await cadView.onLoad();

// Draw a line from (0,0) to (100,100)
// Arrays are shorthand for Point2D objects
const line = new LineEnt([0, 0], [100, 100]);
line.setDefaults();
Engine.addEnt(line);

// Draw a circle: center (50,50), radius 30
const circle = new CircleEnt([50, 50], 30);
circle.setDefaults();
Engine.addEnt(circle);

// Draw an arc: center, radius, start angle, end angle (degrees)
const arc = new ArcEnt([0, 0], 40, 0, 270);
arc.setDefaults();
Engine.addEnt(arc);

// Draw a polyline
const polyline = new PolylineEnt([[0,0], [100,0], [100,100], [0,100]]);
polyline.closed = true;
polyline.setDefaults();
Engine.addEnt(polyline);

// Add single-line text
const text = new TextEnt("Hello WebCAD", [10, 10], 5);
text.setDefaults();
Engine.addEnt(text);

Engine.updateScreen();
Enter fullscreen mode Exit fullscreen mode

Setting Entity Properties

import { Engine, LineEnt, McColor } from 'vjcad';

await cadView.onLoad();

const line = new LineEnt([0, 0], [200, 0]);
line.setDefaults();

// Color: use McColor or a CSS color string
line.color = new McColor(255, 0, 0);    // red
// or: line.color = McColor.fromCssColor("#FF0000");

// Layer: assign to an existing layer
line.layer = "WALLS";

// Linetype
line.linetype = "DASHED";

// Lineweight (in mm × 100, e.g. 25 = 0.25mm)
line.lineweight = 25;

Engine.addEnt(line);
Engine.updateScreen();
Enter fullscreen mode Exit fullscreen mode

Getting User Input

The input methods are async and resolve when the user completes the action in the canvas:

import { Engine } from 'vjcad';

await cadView.onLoad();

// Prompt user to pick a point on the canvas
const ptResult = await Engine.getPoint({ prompt: "Pick start point" });
if (ptResult.isOk()) {
  const pt = ptResult.val();
  console.log(`User picked: ${pt.x}, ${pt.y}`);
}

// Prompt for a selection set (the user draws a selection box or clicks entities)
const ssResult = await Engine.getSelectSet({ prompt: "Select entities" });
if (ssResult.isOk()) {
  const ss = ssResult.val();
  console.log(`Selected ${ss.count()} entities`);
}

// Prompt for a numeric value
const distResult = await Engine.getReal({ prompt: "Enter distance", default: 100 });
if (distResult.isOk()) {
  console.log(`Distance: ${distResult.val()}`);
}

// Prompt with keyword options (like AutoCAD command keywords)
const kwResult = await Engine.getKeywords({
  prompt: "Select mode [Rect/Circle/Polygon]",
  keywords: "Rect Circle Polygon",
  default: "Rect"
});
if (kwResult.isOk()) {
  console.log(`User chose: ${kwResult.val()}`);
}
Enter fullscreen mode Exit fullscreen mode

Writing a Custom Command

VJCAD's command system uses the same pattern as AutoCAD: commands are named strings that users type at the command line, and they can prompt for user input mid-execution.

import { Engine, McCommand, LineEnt, McColor } from 'vjcad';

// Register a custom command called "REDLINE"
Engine.addCommand("REDLINE", async () => {
  // Ask user for start point
  const startResult = await Engine.getPoint({ prompt: "Pick start point" });
  if (!startResult.isOk()) return;

  // Ask user for end point, showing a rubber-band preview line
  const endResult = await Engine.getPoint({
    prompt: "Pick end point",
    basePoint: startResult.val()
  });
  if (!endResult.isOk()) return;

  // Draw a red line between the two points
  const line = new LineEnt(startResult.val(), endResult.val());
  line.setDefaults();
  line.color = new McColor(255, 0, 0);
  Engine.addEnt(line);
  Engine.updateScreen();
});

// The user can now type "REDLINE" in the command bar to trigger this
Enter fullscreen mode Exit fullscreen mode

Writing a Plugin

Plugins are the primary extension mechanism. They hook into the Ribbon toolbar, command system, and event lifecycle:

import { PluginBase, Engine, LineEnt } from 'vjcad';

export class MyPlugin extends PluginBase {
  // Unique plugin ID
  id = "my-plugin";
  name = "My Plugin";

  // Called when the plugin is loaded
  async onLoad() {
    // Register commands
    Engine.addCommand("MY_DRAW_GRID", () => this.drawGrid());

    // Add a Ribbon button
    this.addRibbonButton({
      tabName: "Tools",
      groupName: "My Plugin",
      label: "Draw Grid",
      icon: "grid",
      command: "MY_DRAW_GRID"
    });
  }

  private async drawGrid() {
    const spacing = 50;
    const count = 10;
    for (let i = 0; i <= count; i++) {
      // Horizontal lines
      const h = new LineEnt([0, i * spacing], [count * spacing, i * spacing]);
      h.setDefaults();
      Engine.addEnt(h);
      // Vertical lines
      const v = new LineEnt([i * spacing, 0], [i * spacing, count * spacing]);
      v.setDefaults();
      Engine.addEnt(v);
    }
    Engine.updateScreen();
  }
}
Enter fullscreen mode Exit fullscreen mode

Register the plugin in your main.ts:

import { MainView, initCadContainer } from 'vjcad';
import { MyPlugin } from './plugins/MyPlugin';

const cadView = new MainView({ appname: "WebCAD", version: "v1.0.0" });

// Register plugins before mounting
cadView.registerPlugin(new MyPlugin());

initCadContainer("cad-app", cadView);
Enter fullscreen mode Exit fullscreen mode

The official plugins use the same API — you can inspect their source code in the GitHub repository as reference.


Version Control (Git-Style for Drawings)

Every save creates a Patch — an incremental record of added, modified, and deleted entities. Patches chain together into a version history, and you can create branches:

main  ──●──●──●──────────────●  (merge)
              \              /
 feature        ●──●──●──●
Enter fullscreen mode Exit fullscreen mode

Multiple team members work on separate branches. When merging, the system detects conflicts (two people edited the same entity) and offers three resolution strategies: server-priority, local-priority, or manual.

This is managed through the backend service API, and VJCAD's built-in version panel provides a UI for it without any custom code.


AI Agent via MCP

This is the most forward-looking part of the platform. VJCAD exposes an official MCP (Model Context Protocol) server, which you can connect to any MCP-compatible AI client.

Connect Cursor or Trae

Add this to your MCP client configuration:

{
  "mcpServers": {
    "vjcad": {
      "url": "https://vjmap.com/server/aicad/mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Once connected, your AI assistant can read and modify the open drawing through three interaction modes:

Ask — Read-only. The AI queries drawing data, counts entities, extracts layer info, reads attribute values. No modifications. Good for drawing review and reporting.

Agent — Default execution mode. The AI receives an instruction and calls the VJCAD API directly to draw or modify. Good for concrete, well-defined tasks.

Plan — For complex tasks. The AI decomposes the instruction into numbered steps, shows you the plan, then executes step by step. For example, "Draw a floor plan":

  1. Create layers (WALLS, DOORS, WINDOWS, DIMS)
  2. Draw outer wall perimeter
  3. Draw interior walls
  4. Insert door/window blocks
  5. Add dimension annotations

What you can ask it to do

The AI's capability depends on what "skills" (system prompts + API docs) are loaded. With the base SDK skill:

  • "Draw a 10×10 grid with 50mm spacing."
  • "Change all red lines to blue dashed lines."
  • "Add a text label 'Entry' at coordinate (200, 100), height 5mm."
  • "Select all entities on the DIMENSIONS layer and delete them."

With a domain-specific skill (e.g., floor plan rules), you can go further:

  • "Draw a 3-bedroom apartment, approximately 120 sq meters."

The AI generates and executes the API calls. Non-CAD users can accomplish tasks that would otherwise require hours of manual work by someone with CAD training.

Build your own skill

The skill mechanism is open. You write a skill as a structured prompt that teaches the AI your domain rules and the relevant API methods. This is how you build a vertical industry platform on top of VJCAD:

  • Electrical schematic rules → electrical diagram CAD tool
  • Pipe network topology → utility/infrastructure CAD tool
  • Structural detailing standards → structural engineering CAD tool

The platform provides the engine; you bring the domain knowledge.


Source Code and Resources

The example source code (HTML, Vue, and React scaffolds) is available on both GitHub and Gitee:

Other links:


Top comments (0)