Skip to content

Creating Custom Tools

alf edited this page May 1, 2026 · 3 revisions

Creating Custom Tools

User extension tools let you register your own Python functions as tools that the LLM can call. They are also available to skills and the MCP server.


Quick Start

  1. Create a .py file in <FreeCADAI dir>/tools/:
import math

def bolt_circle(diameter: float, count: int = 8, bolt_size: float = 6.5) -> str:
    """Create a bolt hole circle pattern on the XY plane."""
    import Part
    import FreeCAD as App

    doc = App.ActiveDocument
    if doc is None:
        return "Error: no active document"

    radius = diameter / 2
    bolt_r = bolt_size / 2
    shapes = []
    for i in range(count):
        angle = math.radians(i * 360.0 / count)
        cx = radius * math.cos(angle)
        cy = radius * math.sin(angle)
        hole = Part.makeCylinder(bolt_r, 100, App.Vector(cx, cy, -50))
        shapes.append(hole)

    compound = shapes[0]
    for s in shapes[1:]:
        compound = compound.fuse(s)

    obj = doc.addObject("Part::Feature", "BoltCircle")
    obj.Shape = compound
    doc.recompute()
    return f"Created {count} x {bolt_size}mm bolt holes on {diameter}mm PCD"
  1. Restart FreeCAD (or click Reload in Settings > User Tools).
  2. In Act mode, ask the LLM to use it: "create a bolt circle with 150mm diameter and 6 holes"
  3. The LLM calls user_bolt_circle with the extracted parameters.

Function Convention

User tools use plain Python conventions -- no decorators, no special imports:

def my_tool(param1: float, param2: int = 5, param3: str = "hello") -> str:
    """Short description of what this tool does."""
    # Your code here
    return "Success message"

Rules

Rule Details
Name Function name becomes the tool name, prefixed with user_ (e.g., bolt_circleuser_bolt_circle).
Description First line of docstring. Used by the LLM to decide when to call the tool.
Parameters Must have type hints. Supported types: float, int, str, bool.
Defaults Parameters with default values become optional. Parameters without defaults are required.
Return Return a str (success message) or a dict with output and/or data keys. Exceptions are caught and returned as errors.
Private functions Functions starting with _ are skipped (use them as helpers).

Return Values

# Simple string return
def my_tool(x: float) -> str:
    return f"Created object with size {x}"

# Dict return with structured data
def my_tool(x: float) -> dict:
    return {
        "output": "Created object",   # shown to LLM
        "data": {"volume": 123.4},    # structured data
    }

# Exceptions become errors automatically
def my_tool(x: float) -> str:
    if x <= 0:
        raise ValueError("Size must be positive")
    return "OK"

Supported Parameter Types

Python Type JSON Schema Type Example
float number diameter: float
int integer count: int = 8
str string name: str = "Part"
bool boolean centered: bool = True

Other types (e.g., list, dict, tuple) are not supported and will be skipped with a warning.


File Formats

Both .py and .FCMacro files are supported. A single file can contain multiple tool functions.

<FreeCADAI dir>/tools/
  bolt_circle.py       # one tool function
  my_shapes.py         # multiple tool functions
  legacy_macro.FCMacro  # FreeCAD macro with typed functions

Validation

Files are validated using AST parsing (no execution) before being loaded:

  • Syntax check -- file must be valid Python.
  • Type hints required -- at least one public function with type-hinted parameters.
  • Supported types -- all parameter types must be float, int, str, or bool.

Warnings (non-blocking):

  • Missing docstring
  • Unsupported parameter types (function still loads if it has other valid params)

Check validation results in Settings > User Tools. Each file shows its status with details on hover.


Managing Tools

Settings Dialog

Open Settings > User Tools to:

  • Add... -- pick a .py or .FCMacro file to copy into the tools directory.
  • Remove -- delete the selected file.
  • Reload -- re-scan and re-validate all files (useful after editing a tool file externally).
  • Scan FreeCAD macros -- also discover compatible functions from FreeCAD's built-in macro directory.

Manual Management

You can also manage files directly:

# Add a tool
cp my_tool.py <FreeCADAI dir>/tools/

# Remove a tool
rm <FreeCADAI dir>/tools/my_tool.py

# Disable without removing (in config.json)
"user_tools_disabled": ["my_tool.py"]

How It Works

  1. On startup (first Act-mode message), the tool registry scans <FreeCADAI dir>/tools/.
  2. Each .py/.FCMacro file is validated with AST (no execution).
  3. Valid files are imported and functions are introspected.
  4. Each function becomes a ToolDefinition with a user_ prefix.
  5. Tools are registered alongside built-in tools in the ToolRegistry.
  6. The LLM sees them in the tool schema and can call them like any other tool.
  7. Results are wrapped in undo transactions for safe rollback.

Tips

  • Import FreeCAD modules inside the function, not at module level. The file is imported during registry setup, before FreeCAD is fully initialized.
  • Use _helper() functions (underscore prefix) for shared logic -- they won't be registered as tools.
  • Keep descriptions clear and specific -- the LLM uses the docstring to decide when to call your tool.
  • Test locally first -- run your function in FreeCAD's Python console before registering it as a tool.
  • One file per domain -- group related tools in the same file (e.g., fasteners.py with bolt_circle, nut_hole, washer_recess).

Comparison: Tools vs Skills

User Tools Skills
Invoked by LLM (automatic) User (/command)
Format Python function with type hints SKILL.md + optional handler.py
Parameters Extracted from type hints Parsed from user text
Best for Specific operations the LLM should call Multi-step workflows with instructions
Example bolt_circle(diameter=150, count=6) /mast-flange BD=250 BT=6 HD=80

Clone this wiki locally