Skip to content

Scripting Basics

LoomCAD scripts are JavaScript code that automate design tasks. You can write scripts yourself or let the AI generate them from natural language descriptions.


How Scripts Work

Scripts use the runScriptCommand() function to execute drawing and editing operations:

javascript
// Create a component named "ECU1" at position (200, 300)
const pins = [];
for (let i = 1; i <= 12; i++) {
  pins.push({ name: String(i), partnumber: "" });
}

runScriptCommand("drawComponent", {
  position: { x: 200, y: 300 },
  pinNumberingOrder: "column",
  flipped: false,
  compElementData: { name: "ECU1", partnumber: "ECU-001", pinCount: 12 },
  pinData: { x: 1, y: 12, pins },
});

Each command:

  • Takes a command name (e.g., 'drawComponent')
  • Takes an options object with parameters
  • Returns a result object with success and data
  • All changes are applied after the script finishes executing

Execution Model

Scripts run synchronously. All commands are queued and executed, with changes applied only after the entire script completes. There is no await or async support.


Running Scripts

From AI Chat

When the AI generates code, click Run script:

Code Execution Buttons

Results appear below the code:

Success: Execution Success

Error: Execution Error

Saved Scripts

Run saved scripts from the Scripts tab in the side menu:

Scripts tab Scripts tab with saved scripts

  1. Open the Scripts tab
  2. Select a script source from the dropdown (project scripts or library)
  3. Click the (play) button next to any script to run it

Each script in the list shows:

  • — Run the script
  • 📋 — Copy script to clipboard
  • — View script info
  • 🗑 — Delete script

Script Output

Use scriptConsole (not console) for output:

javascript
// Available methods
scriptConsole.log("Information message");
scriptConsole.warning("Warning message");
scriptConsole.critical("Error message");
scriptConsole.table(data); // Display tabular data

Important

Regular console.log() won't display in the script results. Always use scriptConsole.


Command Categories

Draw Commands

Create new elements:

javascript
// Create a component
const pins = [];
for (let i = 1; i <= 8; i++) {
  pins.push({ name: String(i), partnumber: "" });
}

runScriptCommand("drawComponent", {
  position: { x: 100, y: 200 },
  pinNumberingOrder: "column",
  flipped: false,
  compElementData: { name: "J101", partnumber: "MOLEX-12345", pinCount: 8 },
  pinData: { x: 1, y: 8, pins },
});

// Create a wire between two pins
runScriptCommand("drawWire", {
  start: { componentName: "ECU1", pinName: "1" },
  end: { componentName: "J101", pinName: "1" },
  name: "PWR_WIRE",
  partnumber: "WIRE-18AWG-RED",
  colors: ["red"],
  coreSizeAWG: "18",
});

// Create a bundle segment
runScriptCommand("drawBundle", {
  start: { x: 100, y: 200 },
  end: { x: 300, y: 200 },
  name: "MAIN_TRUNK",
  partnumber: "BUNDLE-001",
});

Query Commands

Find and inspect elements. Queries use exact name matching (no wildcards):

javascript
// Find a component by exact name
const result = runScriptCommand("queryComponent", { query: "ECU1" });
if (result.success && result.elements.length > 0) {
  const ecu = result.elements[0];
  scriptConsole.log(`ECU has ${ecu.pinCount} pins`);
}

// Get all wires, then filter in JavaScript
const allWires = runScriptCommand("queryWire", {});
const redWires = allWires.elements.filter(
  w => w.colors && w.colors.includes("red")
);
scriptConsole.log(`Found ${redWires.length} red wires`);

// Get all components, then filter by name pattern in JavaScript
const allComponents = runScriptCommand("queryComponent", {});
const jConnectors = allComponents.elements.filter(c => c.name.startsWith("J"));
scriptConsole.log(`Found ${jConnectors.length} J-series connectors`);

No Wildcards

Queries use exact matching only. You cannot use patterns like J* or PWR*. Instead, query all elements and filter with JavaScript.

Update Commands

Modify existing elements. Requires exact name or ID:

javascript
// Update a single component by name
runScriptCommand("updateComponent", {
  query: "ECU1",
  update: { partnumber: "NEW-PART-123" },
});

// Update a wire by name
runScriptCommand("updateWire", {
  query: "POWER_WIRE",
  update: { colors: ["red"], coreSizeAWG: "14" },
});

// To update multiple elements, query first then iterate
const allWires = runScriptCommand("queryWire", {});
allWires.elements.forEach(wire => {
  if (wire.name.startsWith("PWR")) {
    runScriptCommand("updateWire", {
      query: wire.name,
      update: { coreSizeAWG: "14" },
    });
  }
});

Move Commands

Reposition elements:

javascript
// Move to absolute position
runScriptCommand("move", {
  queries: [{ type: "component", query: "J101" }],
  position: { x: 500, y: 300 },
});

// Move relatively
runScriptCommand("move", {
  queries: [{ type: "component", query: "J101" }],
  position: { dx: 100, dy: -50 },
});

Erase Commands

Delete elements:

javascript
// Delete a component by name
runScriptCommand("eraseComponent", { query: "J101" });

// Delete a wire by name
runScriptCommand("eraseWire", { query: "W001" });

Caution

Erase commands are immediate. Use Ctrl+Z to undo if needed.


Error Handling

Check the success property of command results:

javascript
const result = runScriptCommand("queryComponent", { query: "ECU1" });

if (!result.success) {
  scriptConsole.critical("Query failed: " + result.error);
} else if (result.elements.length === 0) {
  scriptConsole.warning("Component not found");
} else {
  scriptConsole.log("Found component: " + result.elements[0].name);
}

Complete Example

Here's a full script that creates a simple harness:

javascript
// Create a simple power distribution harness
// Parameters (modify these)
const FUSE_BOX_POS = { x: 200, y: 200 };
const LOAD_COUNT = 4;
const LOAD_SPACING = 150;

// Helper to create pin array
function makePins(count) {
  const pins = [];
  for (let i = 1; i <= count; i++) {
    pins.push({ name: String(i), partnumber: "" });
  }
  return pins;
}

// Create the fuse box
const fuseBoxPinCount = LOAD_COUNT + 1; // +1 for power input
runScriptCommand("drawComponent", {
  position: FUSE_BOX_POS,
  pinNumberingOrder: "column",
  flipped: false,
  compElementData: {
    name: "FUSE_BOX",
    partnumber: "FUSE-4WAY",
    pinCount: fuseBoxPinCount,
  },
  pinData: { x: 1, y: fuseBoxPinCount, pins: makePins(fuseBoxPinCount) },
});

// Create load connectors
for (let i = 1; i <= LOAD_COUNT; i++) {
  runScriptCommand("drawComponent", {
    position: {
      x: FUSE_BOX_POS.x + 400,
      y: FUSE_BOX_POS.y - 150 + i * LOAD_SPACING,
    },
    pinNumberingOrder: "column",
    flipped: false,
    compElementData: {
      name: "LOAD" + i,
      partnumber: "LOAD-CONN",
      pinCount: 2,
    },
    pinData: { x: 1, y: 2, pins: makePins(2) },
  });
}

// Create wires from fuse box to loads
for (let i = 1; i <= LOAD_COUNT; i++) {
  // Power wire
  runScriptCommand("drawWire", {
    start: { componentName: "FUSE_BOX", pinName: String(i) },
    end: { componentName: "LOAD" + i, pinName: "1" },
    name: "PWR_LOAD" + i,
    partnumber: "WIRE-16AWG-RED",
    colors: ["red"],
    coreSizeAWG: "16",
  });

  // Ground wire (from load pin 2, no destination - open end)
  runScriptCommand("drawWire", {
    start: { componentName: "LOAD" + i, pinName: "2" },
    end: { componentName: "LOAD" + i, pinName: "2" }, // Same pin for stub
    name: "GND_LOAD" + i,
    partnumber: "WIRE-16AWG-BLK",
    colors: ["black"],
    coreSizeAWG: "16",
  });
}

scriptConsole.log("Created power distribution with " + LOAD_COUNT + " loads");
// Note: All changes are applied after the script finishes

Best Practices

Use Variables for Repeated Values

javascript
// Good: Easy to modify
const WIRE_GAUGE = "18";
const SIGNAL_COLOR = "white";

runScriptCommand("drawWire", {
  name: "SIG1",
  coreSizeAWG: WIRE_GAUGE,
  colors: [SIGNAL_COLOR],
  fromComponent: "ECU",
  fromPin: 1,
  toComponent: "SENSOR",
  toPin: 1,
});

Comment Your Intent

javascript
// Create connector J1 - main power input from vehicle battery
runScriptCommand("drawComponent", {
  name: "J1",
  // Position at left edge of sheet
  x: 50,
  y: 200,
  pinCount: 4,
});

Query Before Bulk Operations

javascript
// First, query and filter to see what will be affected
const allWires = runScriptCommand("queryWire", {});
const targetWires = allWires.elements.filter(w => w.coreSizeAWG === "22");
scriptConsole.log("Will update " + targetWires.length + " wires");

// Then update each one
targetWires.forEach(wire => {
  runScriptCommand("updateWire", {
    query: wire.name,
    update: { coreSizeAWG: "20" },
  });
});

Use Exact Names

javascript
// ✅ CORRECT: Use exact component name
runScriptCommand("queryComponent", { query: "POWER_CONNECTOR" });

// ❌ WRONG: Wildcards don't work
runScriptCommand("queryComponent", { query: "POWER*" });

// ✅ CORRECT: Filter in JavaScript instead
const all = runScriptCommand("queryComponent", {});
const power = all.elements.filter(c => c.name.startsWith("POWER"));

Next Steps