Adding a New CAD Kernel
Step-by-step guide to integrating a new CAD kernel into Tau.
This guide explains how to add a new CAD kernel to Tau. It's based on the JSCAD kernel integration and serves as a template for future kernels.
Overview
Tau's multi-kernel architecture allows different CAD engines to be plugged in as Web Workers. Each kernel follows the same interface pattern, making it straightforward to add new ones.
What You'll Create
- Worker — Runs in a Web Worker, handles geometry computation
- Type Export — Prevents Vite from bundling the worker twice
- Kernel Registry Entry — Configuration in the types library
- Machine Wiring — Integration with the kernel state machine
- Examples (optional) — Sample builds to showcase the kernel
- AI Configuration (optional) — Prompts for AI-assisted modeling
Step 1: Create the Worker
Create your worker at:
apps/ui/app/components/geometry/kernel/<kernel>/<kernel>.worker.tsExtend KernelWorker
Import and extend the base KernelWorker class:
import { expose } from 'comlink';
import type { ComputeGeometryResult, ExportFormat, ExportGeometryResult, ExtractParametersResult } from '@taucad/types';
import { createKernelSuccess, createKernelError } from '@taucad/types/guards';
import { KernelWorker } from '#components/geometry/kernel/utils/kernel-worker.js';
class MyKernelWorker extends KernelWorker {
protected readonly name = 'MyKernelWorker';
// Define supported export formats
protected static override readonly supportedExportFormats: ExportFormat[] = ['stl', 'glb'];
// Implement required abstract methods...
}
// Expose via Comlink
const service = new MyKernelWorker();
expose(service);
export type MyKernelWorkerInterface = typeof service;Implement Required Methods
canHandle(filename, extension)
Quick check to determine if this worker can process the file. Must be fast — don't initialize heavy runtimes here.
protected async canHandle(filename: string, extension: string): Promise<boolean> {
// Check file extension
if (extension !== 'myext') {
return false;
}
// Optionally check file content for specific patterns
const code = await this.readFile(filename, 'utf8');
return code.includes('mykernel-signature');
}extractParameters(path)
Parse the file and extract parameters with their JSON Schema.
protected async extractParameters(path: string): Promise<ExtractParametersResult> {
try {
const code = await this.readFile(path, 'utf8');
const { defaultParameters, jsonSchema } = parseParameters(code);
return createKernelSuccess({
defaultParameters,
jsonSchema,
});
} catch (error) {
return createKernelError({
message: `Failed to extract parameters: ${error}`,
type: 'code',
});
}
}computeGeometry(path, parameters, geometryId?)
Execute the code and generate geometry (typically as glTF).
protected async computeGeometry(
path: string,
parameters: Record<string, unknown>,
geometryId?: string,
): Promise<ComputeGeometryResult> {
try {
const code = await this.readFile(path, 'utf8');
const geometry = await executeCode(code, parameters);
const gltfBlob = await convertToGltf(geometry);
return createKernelSuccess({
geometries: [{
id: geometryId ?? 'default',
type: 'model/gltf-binary',
blob: gltfBlob,
}],
});
} catch (error) {
return createKernelError({
message: `Geometry computation failed: ${error}`,
type: 'kernel',
});
}
}exportGeometry(fileType, geometryId?, meshConfig?)
Export geometry to the requested format.
protected async exportGeometry(
fileType: ExportFormat,
geometryId?: string,
meshConfig?: { linearTolerance: number; angularTolerance: number },
): Promise<ExportGeometryResult> {
// Export from cached geometry
const blob = await this.exportToFormat(fileType);
return createKernelSuccess({
files: [{ name: `model.${fileType}`, blob }],
});
}Create the Types File
Create a sibling file to prevent Vite double-bundling:
apps/ui/app/components/geometry/kernel/<kernel>/<kernel>.worker.types.ts// eslint-disable-next-line no-barrel-files/no-barrel-files -- Type re-export prevents Vite from bundling worker twice
export type { MyKernelWorkerInterface } from '#components/geometry/kernel/<kernel>/<kernel>.worker.js';Step 2: Register in Kernel Machine
Edit apps/ui/app/machines/kernel.machine.ts:
Add to Provider Union
type KernelProvider = CadKernelProvider | 'tau' | 'jscad' | 'mykernel';Import the Worker
import type { MyKernelWorkerInterface as MyKernelWorker } from '#components/geometry/kernel/mykernel/mykernel.worker.types.js';
import MyKernelBuilderWorker from '#components/geometry/kernel/mykernel/mykernel.worker.js?worker';Add to Workers Map
const workers = {
// ... existing workers
mykernel: MyKernelBuilderWorker,
} as const satisfies Partial<Record<KernelProvider, new () => Worker>>;Add to Priority Array
const workerPriority: KernelProvider[] = ['openscad', 'zoo', 'replicad', 'jscad', 'mykernel', 'tau'];Wire Up in createWorkersActor
Add worker creation, wrapping, and initialization in the createWorkersActor promise actor.
Add Cleanup in destroyWorkers
Ensure the worker is terminated in the cleanup action.
Step 3: Add Kernel Configuration
Edit libs/types/src/constants/kernel.constants.ts:
export const kernelConfigurations = [
// ... existing kernels
{
id: 'mykernel',
name: 'My Kernel',
dimensions: [3],
language: 'typescript', // or 'javascript', 'openscad', 'kcl', etc.
description: 'Brief description for UI',
mainFile: 'main.ts',
backendProvider: 'mybackend',
longDescription: 'Detailed description explaining the kernel strengths and use cases.',
emptyCode: `// Starter template
export default function main() {
return createGeometry();
}
`,
recommended: 'Use Case Category',
tags: ['Tag1', 'Tag2'],
features: ['Feature 1', 'Feature 2'],
},
] as const satisfies KernelConfiguration[];Step 4: Add Examples (Optional)
Create Examples in Library
Edit libs/tau-examples/src/build.examples.ts:
export const myKernelExamples = [
{
id: 'my-example-1',
name: 'Example Name',
code: `// Example code...`,
thumbnail: 'data:image/png;base64,...', // Optional
},
];Export from Index
Edit libs/tau-examples/src/index.ts:
export { myKernelExamples } from './build.examples.js';Add to Sample Builds
Edit apps/ui/app/constants/build-examples.ts:
import { myKernelExamples } from '@taucad/tau-examples';
// Map to Build objects and add to sampleBuildsStep 5: Configure AI Prompts (Optional)
Edit apps/api/app/api/chat/prompts/chat-prompt-cad.ts:
Add a KernelConfig entry to cadKernelConfigs with:
fileExtension— File extension (e.g.,.ts,.scad)languageName— Human-readable nameroleDescription— Brief purpose descriptiontechnicalContext— Strengths and use casescodeStandards— Syntax and formatting rulesmodelingStrategy— Design philosophytechnicalResources— Available APIs and examplescommonErrorPatterns— Typical issues and solutions
Step 6: Verify
Run the following Nx tasks to verify your integration:
# Typecheck
pnpm nx typecheck ui
# Lint
pnpm nx lint ui
# Test
pnpm nx test ui --watch=falseBest Practices
Result Pattern
Always return result objects using the helpers:
import { createKernelSuccess, createKernelError } from '@taucad/types/guards';
// Success
return createKernelSuccess({ data });
// Error
return createKernelError({
message: 'Human-readable error',
type: 'code' | 'kernel', // code = syntax error, kernel = runtime error
});Keep canHandle Fast
The canHandle method is called for every file to determine which kernel to use. Don't:
- Initialize heavy runtimes
- Parse entire files
- Make network requests
Do:
- Check file extensions
- Look for quick signature patterns
Export Formats
Define supported export formats as a static property:
protected static override readonly supportedExportFormats: ExportFormat[] = ['stl', 'glb', 'step'];Use glTF-Transform for Output
For consistent 3D output, use the @gltf-transform/core library:
import { Document, NodeIO } from '@gltf-transform/core';
const document = new Document();
// Build scene...
const io = new NodeIO();
const glb = await io.writeBinary(document);File System Access
Use the provided fileReader for filesystem operations:
// Read file content
const content = await this.readFile('path/to/file.txt', 'utf8');
// Check if file exists
const exists = await this.exists('path/to/file.txt');
// List directory
const files = await this.readdir('path/to/dir');Logging
Use the built-in logging methods:
this.debug('Processing file', { operation: 'computeGeometry' });
this.warn('Deprecated feature used');
this.error('Failed to parse', { data: { line: 42 } });Existing Kernels Reference
| Kernel | Language | Backend | Description |
|---|---|---|---|
openscad | OpenSCAD | Manifold | CSG for 3D printing |
replicad | TypeScript | OpenCascade | Precise BRep engineering |
zoo | KCL | Zoo | Cloud-native CAD with AI |
jscad | TypeScript | JSCAD | Parametric CSG modeling |
tau | TypeScript | Replicad | File conversion kernel |
See the existing implementations in apps/ui/app/components/geometry/kernel/ for reference.