Skip to main content
Glama
pzfreo

build123d-mcp

export

Export 3D CAD models to STEP, STL, or multiple formats. Specify filename and optionally select objects by name or export all shapes as an assembly.

Instructions

Export model. format: step, stl, or comma-separated list e.g. 'step,stl'. object_name: named object from show(), '*' to export all named shapes as a combined assembly (default: current shape).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filenameYes
formatNostep
object_nameNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The @mcp.tool() decorator registers the 'export' tool in the MCP server. The function delegates to _session.export_file() which maps to the export_file module function via the Session class.
    @mcp.tool()
    def export(filename: str, format: str = "step", object_name: str = "") -> str:
        """Export model. format: step, stl, or comma-separated list e.g. 'step,stl'. object_name: named object from show(), '*' to export all named shapes as a combined assembly (default: current shape)."""
        return _session.export_file(filename, format, object_name)
  • export_file() is the core handler. It resolves the target shape, parses the format string (step, stl, or comma-separated), appends correct extensions, validates the output path, and writes each format.
    def export_file(session, filename: str, format: str = "step", object_name: str = "") -> str:
        shape = _resolve_shape(session, object_name)
    
        formats = [f.strip().lower() for f in format.split(",") if f.strip()]
        if not formats:
            raise ValueError("No format specified.")
        unknown = [f for f in formats if f not in _VALID_FORMATS]
        if unknown:
            raise ValueError(f"Unknown format(s) '{', '.join(unknown)}'. Use: step, stl")
    
        exported = []
        for fmt in formats:
            path = filename
            if fmt == "step" and not path.lower().endswith((".step", ".stp")):
                path += ".step"
            elif fmt == "stl" and not path.lower().endswith(".stl"):
                path += ".stl"
            abs_path = safe_output_path(path)
            _write_one(shape, abs_path, fmt)
            exported.append(abs_path)
    
        if len(exported) == 1:
            return f"Exported to {exported[0]}"
        return "Exported to:\n" + "\n".join(exported)
  • _resolve_shape() resolves the shape to export: '*' exports all named objects as a Compound, a specific object_name looks up by key, and empty string uses the session's current_shape.
    def _resolve_shape(session, object_name: str):
        if object_name == "*":
            if not session.objects:
                raise ValueError("No named objects in session. Use show() to register shapes first.")
            from build123d import Compound
            return Compound(children=list(session.objects.values()))
        if object_name:
            if object_name not in session.objects:
                raise ValueError(f"Unknown object '{object_name}'. Registered: {list(session.objects.keys())}")
            return session.objects[object_name]
        if session.current_shape is None:
            raise ValueError("No shape in session. Execute code to create geometry first.")
        return session.current_shape
  • _write_one() dispatches to build123d's export_step for STEP format or _stl_write for manual binary STL writing.
    def _write_one(shape, abs_path: str, fmt: str) -> None:
        if fmt == "step":
            from build123d import export_step
            export_step(shape, abs_path)
        else:
            _stl_write(shape, abs_path)
  • _stl_write() writes a binary STL file by tessellating the shape and writing vertices, normals, and triangle data with struct packing.
    def _stl_write(shape, abs_path: str) -> None:
        verts, tris = shape.tessellate(0.001, 0.1)
    
        with open(abs_path, "wb") as f:
            f.write(b"\x00" * 80)  # header
            f.write(struct.pack("<I", len(tris)))
            for tri in tris:
                v0 = verts[tri[0]]
                v1 = verts[tri[1]]
                v2 = verts[tri[2]]
                # flat normal via cross product
                ax, ay, az = v1.X - v0.X, v1.Y - v0.Y, v1.Z - v0.Z
                bx, by, bz = v2.X - v0.X, v2.Y - v0.Y, v2.Z - v0.Z
                nx, ny, nz = ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx
                length = (nx * nx + ny * ny + nz * nz) ** 0.5
                if length > 0:
                    nx, ny, nz = nx / length, ny / length, nz / length
                f.write(struct.pack("<3f", nx, ny, nz))
                for v in (v0, v1, v2):
                    f.write(struct.pack("<3f", v.X, v.Y, v.Z))
                f.write(b"\x00\x00")  # attribute byte count
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It explains format and object_name behavior (e.g., '*' exports all as assembly) but omits details like file overwrite behavior, required permissions, or whether the operation is reversible.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences efficiently describe the core functionality. The second sentence lists options in a compact manner, though it could be slightly more structured for readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Adequately covers parameter options but lacks context on usage scenarios, error handling, or output schema implications. Given the output schema exists, return values are not needed, but missing context like overwrite behavior reduces completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description effectively explains 'format' (allowed values like step, stl, comma-separated) and 'object_name' (from show() or '*'). 'filename' is self-explanatory, but overall adds meaning beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action ('Export model') with a specific verb and resource. It distinguishes from sibling tools like 'load_part' or 'save_snapshot' by focusing on exporting to file formats.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives like 'save_snapshot' or 'diff_snapshot'. The description does not provide context for appropriate usage or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pzfreo/build123d-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server