Skip to content

Format Decorators

The @format_reader() and @format_writer() decorators register classes as custom import/export formats in LinkCAD.

@format_reader()

Signature

@format_reader(
name: str,
extensions: list[str],
description: str = "",
)
class MyReader(FormatReader):
def read(self, path: Path, drawing: DrawingContext) -> None:
...

Parameters

ParameterTypeDefaultDescription
namestrrequiredFormat display name
extensionslist[str]requiredFile patterns (e.g. ["*.gds", "*.gdsii"])
descriptionstr""Format description

FormatReader Base Class

class FormatReader:
def read(self, path: Path, drawing: DrawingContext) -> None:
"""Override to implement file import logic."""
...
def post_process(self, phase, drawing, resolution) -> bool:
"""Optional. Take part in the phases LinkCAD runs after parsing."""
return True

The DrawingContext provides a builder API for constructing the drawing. Name your working unit once and LinkCAD converts everything you hand it — a reader should not carry its own scale table:

def read(self, path: Path, drawing: DrawingContext) -> None:
drawing.units = "um" # coordinates below are microns
with drawing.cell("main", main=True) as cell:
with cell.layer("metal1") as layer:
layer.polygon([(0, 0), (100, 0), (100, 100), (0, 100)])
layer.polyline(10, [(0, 0), (200, 200)], closed=False)
layer.circle((500, 500), 200)

Accepted units are nm, um, mil, mm, cm, inch, and m, plus aliases such as micron and millimetre. Leaving units unset means raw database units. An unknown name, or a drawing that does not declare its database units, raises ValidationError.

post_process()

post_process() is optional — the same hook the built-in native readers use. It is called once per phase after parsing, on the same instance as read(), so anything you recorded there is still available. Return False to abort the import.

from linkcad.v1.plugin import Phase
class MyReader(FormatReader):
def post_process(self, phase, drawing, resolution) -> bool:
if phase == Phase.ResolvedRefs:
... # every reference now points at a real cell
return True
PhaseReached when
Phase.ParsedFileOne input file has been parsed
Phase.ParsedAllEvery input file has been parsed
Phase.ClosedOpenCellsCells left open by the parser have been closed
Phase.ResolvedRefsEvery reference points at a real cell
Phase.SelectedMainCellThe main cell has been chosen
Phase.ResolvedLayersByBlockByBlock layer inheritance has been resolved
Phase.AutoNumberedZLayer Z ordering has been assigned

@format_writer()

Signature

@format_writer(
name: str,
extensions: list[str],
description: str = "",
)
class MyWriter(FormatWriter):
def write(self, path: Path, drawing: WriterContext) -> None:
...

Parameters

ParameterTypeDefaultDescription
namestrrequiredFormat display name
extensionslist[str]requiredFile patterns
descriptionstr""Format description

FormatWriter Base Class

class FormatWriter:
def write(self, path: Path, drawing: WriterContext) -> None:
"""Override to implement file export logic."""
...

The WriterContext provides read access to the drawing. As with readers, name your working unit and the geometry arrives already converted:

def write(self, path: Path, drawing: WriterContext) -> None:
drawing.units = "mm" # coordinates below are millimetres
drawing.flatten = True # resolve cell references into placed shapes
with open(path, "w") as f:
for shape in drawing.shapes():
f.write(f"{shape.layer_name}: {len(shape.vertices)} vertices\n")

shapes() is the single traversal for geometry. Set flatten before iterating: with flatten = False you get each cell’s own shapes and handle references yourself; with flatten = True the host renders the hierarchy, applies the transformation stack, and tessellates curves along the way.


FormatInfo

Both decorators create a FormatInfo object attached to the class, and replace info() with a static method returning it:

FieldDescription
nameFormat display name
extensionsList of file patterns
descriptionFormat description
optionsThe Option attributes collected from the class

Registration

Registered formats automatically appear in LinkCAD’s Open/Save dialogs:

  • Readers are listed in the File → Open format dropdown
  • Writers are listed in the File → Save As format dropdown

The extensions list determines file type association. For example:

@format_reader(name="My Format", extensions=["*.myf", "*.myfx"])

This adds “My Format (*.myf, *.myfx)” to the Open dialog.