linkcad.db
The database module provides access to LinkCAD’s in-memory drawing database. All database objects are backed by native implementations for performance.
Use the stable linkcad.v1.db namespace in scripts and plugins. It mirrors the whole public namespace of linkcad.db, so the objects are the same ones.
from linkcad.v1 import db
with db.Drawing("scratch") as dwg: cell = dwg.add_cell("TOP")Or import the names you use:
from linkcad.v1.db import ( Drawing, Cell, Layer, Object, DrawingObject, CellObject, Shape, Polygon, Polyline, Arc, Ellipse, Donut, Nurbs, Text, Ref, Color, Property, ReadLock, WriteLock, Transaction, Unit, ObjectType, CellContext, EndCap, FillRule, BooleanOperation, MergeLayerPolarityResult, TextStyle, TextStyleMask,)Rules worth knowing first
The API follows the same conventions throughout — in this module and in every other one. Knowing them means you rarely have to look anything up:
- A noun is a property, a verb is a method.
shape.area,cell.shapes,polygon.vertices,arc.radius,obj.bounds,obj.selected— no parentheses.destroy(),clone(),rotate(),reverse(),toggle_selection()keep theirs. - A getter/setter pair is one read-write property, never two names. Assign instead of calling a setter:
arc.center = Point(0, 0),layer.enabled = False,obj.selected = True. There are noset_*methods on geometry. - Geometry is created by the object that will own it. A drawing makes cells and layers (
drawing.add_cell,drawing.add_layer); a cell makes shapes (cell.add_polygon,cell.add_arc,cell.add_ref, …). There are no free-standing constructors and no static factories. - Where an accessor needs parameters, the property covers the common case and a
filtered_*method takes the rest —cell.shapesis every shape,cell.filtered_shapes(selected_only=True)narrows it.
Two more conventions save a conversion at every call site:
- A coordinate is a
Pointor an(x, y)pair, interchangeably, everywherelinkcad.dbtakes one — vertex sequences,add_arc(center=…),add_text(position=…),text.position = …. - A layer is a
Layer, a layer name, orNone.obj.layer = "METAL1"moves the object to that layer and creates it if it does not exist;obj.layer = Nonemoves it to the default layer.
Classes
Drawing
The top-level container for all layout data.
A drawing holds the whole database in memory and must be released deterministically — letting one survive to interpreter shutdown crashes the process. Use it as a context manager and that cannot be forgotten:
from linkcad.v1.db import Drawing
with Drawing("scratch") as dwg: cell = dwg.add_cell("TOP") dwg.main_cell = cell metal = dwg.add_layer("METAL1") cell.add_polygon(metal, [(0, 0), (10_000, 0), (10_000, 10_000), (0, 10_000)])Inside a tool plugin, the drawing is handed to run() and is owned by the application — do not destroy it there.
| Property / Method | Description |
|---|---|
Drawing(name="") | Create a drawing. If name is omitted, LinkCAD assigns a unique name |
with Drawing(...) as dwg: | Recommended form; releases the database on exit |
drawing.name | Drawing name |
drawing.units | Database units per metre, as a float (e.g. 1e9 for a nanometre database). 0.0 means the drawing does not declare its units. Assigning raises RuntimeError once the database holds anything, so set it first |
drawing.main_cell | Get or set the main (top) cell |
drawing.cells | All cells, as a list |
drawing.layers | All layers, as a list |
drawing.cell(name) | Find a cell by name; returns None if not found |
drawing.layer(name) | Find a layer by name; returns None if not found |
drawing.add_cell(name) | Add a cell, or return the existing cell of that name |
drawing.add_layer(name) | Add a layer, or return the existing layer of that name |
drawing.modif_time / drawing.access_time | Timestamps, as Unix seconds |
drawing.undo_enabled | Enable or disable undo recording |
drawing.begin_undo_marker(tag=0) / drawing.end_undo_marker() | Bracket an undoable transaction manually |
drawing.undo() / drawing.redo() | Undo or redo the last transaction |
drawing.can_undo / drawing.can_redo | (available, tag) tuple |
drawing.destroy_layer_by_name(name) | Delete a layer by name; returns True if it existed |
drawing.rename_layer(from_name, to_name) | Rename a layer; returns True on success |
drawing.boolean_layers_by_name(op, result_layer, operand_layer, maximum_error=100, minimum_facets=16) | Boolean two layers by name; result_layer is operand A and receives the result |
drawing.merge_layer_polarity_group(group_name, maximum_error=100, minimum_facets=16, progress_from=0, progress_to=100) | Merge one deferred-polarity layer group |
drawing.merge_all_polarity_groups(maximum_error=100, minimum_facets=16, progress_from=0, progress_to=100) | Merge every deferred-polarity layer group |
drawing.destroy() | Destroy the drawing and its contents |
drawing.memory_usage | Native database memory usage in bytes |
drawing.locked | True if any thread holds a lock on the database |
drawing.locked_by_this_thread | True if the calling thread holds the write lock |
The last three read process-wide state, so they can be read from the class as well as from an instance: Drawing.memory_usage and dwg.memory_usage give the same number.
Layer helper methods that mutate the drawing create their own undoable transaction.
from linkcad.v1.db import BooleanOperation, Drawing, MergeLayerPolarityResult
with Drawing("example") as dwg: ok = dwg.boolean_layers_by_name( BooleanOperation.Or, result_layer="metal", operand_layer="vias", )
result = dwg.merge_all_polarity_groups() if result != MergeLayerPolarityResult.Success: raise RuntimeError("Polarity merge failed")Compare enum values with ==, not is. The bindings hand back a fresh Python object for each read, so result is MergeLayerPolarityResult.Success is False even when the merge succeeded.
drawing.units reports how many database units make up one metre, which is what you need to convert a physical size into the integers the database stores:
units_per_meter = drawing.unitsif units_per_meter <= 0.0: raise ValueError("This drawing does not declare its database units")
seven_microns = 7.0 * 1e-6 * units_per_meterPlugins have an easier route: name a working unit on the DrawingContext or WriterContext and LinkCAD does the conversion for you. See linkcad.plugin.
Cell
A named container of shapes and cell references. Cells come from the drawing that owns them:
cell = drawing.add_cell("TOP") # creates, or returns the existing "TOP"cell = drawing.cell("TOP") # looks up; None if not foundCreating geometry
Every shape type follows the same shape: add_<thing>(layer, …), returning the new object.
Coordinates are Point objects or plain (x, y) tuples, interchangeably — both in a vertex sequence and as a single coordinate such as the center of an arc, circle or donut, or the position of a text. A pair is exactly two coordinates; anything else is refused rather than truncated.
| Method | Description |
|---|---|
cell.add_polygon(layer, vertices) | Add a closed polygon |
cell.add_polyline(layer, vertices, width=0, closed=False, end_cap=EndCap.Round) | Add a polyline |
cell.add_arc(layer, center, radius, width=0, start_angle=None, end_angle=None) | Add an arc, drawn counter-clockwise when start_angle <= end_angle. Omitting the angles gives a full circle outline |
cell.add_circle(layer, center, diameter) | Add a filled circle |
cell.add_donut(layer, center, mean_diameter, width) | Add a ring; mean_diameter is (outer + inner) / 2 |
cell.add_text(layer, content, position, height=1.0, font="") | Add a text shape |
cell.add_nurbs(layer, control_points, degree, knots, width=0, weights=None, periodic=False) | Add a NURBS curve; degree must be 1, 2, 3, or 5 |
cell.add_ref(cell, transformation=None, columns=1, rows=1, column_spacing=0, row_spacing=0, layer=None) | Place another cell here. Set columns/rows above 1 for an array |
from linkcad.v1.db import Drawing, EndCapfrom linkcad.v1.geom import Angle, Point, Transformation
with Drawing("shapes") as dwg: top = dwg.add_cell("TOP") dwg.main_cell = top metal = dwg.add_layer("METAL1")
top.add_polygon(metal, [(0, 0), (1000, 0), (1000, 1000), (0, 1000)]) top.add_polyline(metal, [(0, 0), (2000, 2000)], width=100, end_cap=EndCap.SquareFlat) top.add_arc(metal, Point(5000, 0), radius=1000, width=50, start_angle=Angle.from_degrees(0), end_angle=Angle.from_degrees(90)) top.add_circle(metal, Point(0, 5000), diameter=800) top.add_donut(metal, Point(3000, 5000), mean_diameter=800, width=100) top.add_text(metal, "TOP", Point(0, 8000), height=500)
stamp = dwg.add_cell("STAMP") stamp.add_circle(metal, Point(0, 0), diameter=200) top.add_ref(stamp, Transformation().translate(10_000, 0), columns=4, rows=2, column_spacing=1000, row_spacing=1000)Inspecting a cell
| Property / Method | Description |
|---|---|
cell.name | Cell name |
cell.shapes | Every shape the cell owns, each as its actual type (Polygon, Polyline, …) |
cell.filtered_shapes(selected_only=False) | Shapes, narrowed. shapes is this with the defaults |
cell.cell_objects | Everything the cell holds, including Ref objects, each as its actual type |
cell.filtered_cell_objects(selected_only=False) | Cell objects, narrowed |
cell.bounds | Bounding box of everything in this cell and its sub-cells |
cell.filtered_bounds(layer=None) | Bounding box narrowed to one layer |
cell.nesting_level | Highest nesting level (0 for the top cell) |
cell.child_levels | Number of child levels below this cell |
cell.enabled | True if the cell is enabled in any context |
cell.enabled_in_context(context_cell) | Whether the cell is enabled within another cell |
cell.enable(enabled=True, context_cell=None, context=CellContext.Descend) | Enable or disable the cell |
cell.uses_layer(layer, context=CellContext.Descend, enabled_only=False) | Whether the cell holds anything on a layer |
cell.first_shape_layer | First layer used by a contained shape |
cell.selected_count | Number of selected cell objects |
cell.first_selected | First selected cell object, or None |
cell.modif_time / cell.access_time | Timestamps, as Unix seconds |
cell.clone(name) | Copy the cell under a new name |
Because cell_objects hands back actual types, isinstance works as you would expect:
from linkcad.v1 import db
for obj in cell.cell_objects: if isinstance(obj, db.Ref): print(f"reference to {obj.ref_cell.name}") elif isinstance(obj, db.Polygon): print(f"polygon with {obj.vertex_count} vertices")Layer
A named layer with display properties. Layers come from the drawing:
metal = drawing.add_layer("METAL1") # creates, or returns the existing layermetal = drawing.layer("METAL1") # looks up; None if not found| Property / Method | Description |
|---|---|
layer.name | Layer name |
layer.color | Layer colour as a packed RGBA int |
layer.enabled | Whether the layer is enabled; assignable |
layer.hidden | Whether the layer is hidden; assignable |
layer.used | True if any enabled cell puts something on the layer |
layer.move_before(other_layer=None) | Reorder within the layer list |
layer.clone(drawing, name) | Copy the layer, optionally into another drawing. Also takes both arguments by keyword, with drawing=None meaning this drawing |
layer.destroy() | Destroy the layer |
There are no static factories: drawing.add_layer() creates a layer and drawing.layer() looks one up.
Object
Base class for every database object.
| Property / Method | Description |
|---|---|
obj.id | Object ID |
obj.valid | False once the object has been destroyed |
obj.dynamic_type | The object’s actual type, as an ObjectType |
obj.drawing | The Drawing this object belongs to |
obj.destroy() | Remove and destroy the object. It becomes invalid afterwards |
DrawingObject (base of Layer and Cell) adds nothing beyond Object.
CellObject
Base class for everything that lives inside a cell — all shapes, plus Ref.
| Property / Method | Description |
|---|---|
obj.owning_cell | The Cell containing this object |
obj.bounds | Bounding box (Bounds) |
obj.layer | The object’s Layer; assignable |
obj.layer_name | Layer name |
obj.selected | Selection state; assignable |
obj.toggle_selection() | Flip the selection state |
layer is read-write on every cell object, Ref included, and accepts a Layer, a layer name, or None:
obj.layer = drawing.layer("METAL1") # a Layerobj.layer = "METAL2" # by name; created if it does not existobj.layer = None # the default layertoggle_selection() survives the getter/setter rule because it is not selected = not selected: that would be a read and a write under two separate locks, and the database offers the flip as one operation.
Shape
Base class for geometric shapes: polygons, polylines, arcs, ellipses, donuts, NURBS, and text. Extends CellObject.
| Property / Method | Description |
|---|---|
shape.closed | True if the shape is closed |
shape.width | Trace width in database units (0 for filled shapes) |
shape.area | Enclosed area, in square database units |
shape.equivalent_to(other, ignore_sense=True) | Compare two shapes for equivalence |
shape.layer / shape.bounds | Inherited from CellObject; layer is read-write there |
shape.destroy() | Remove and destroy this shape |
vertices is not part of Shape: only Polygon, Polyline and Nurbs have one. Curves — arcs, circles, donuts — are described by centre and radius, and are tessellated on demand by the host.
total = sum(shape.area for shape in cell.shapes if shape.closed)print(f"filled area: {total}")Polygon
A closed filled shape. Extends Shape. Created with cell.add_polygon(layer, vertices).
| Property / Method | Description |
|---|---|
polygon.vertices | Vertices as a list of Point. Assign Point objects or (x, y) tuples |
polygon.vertex_count | Number of vertices |
polygon.head / polygon.tail | First and last vertex |
polygon.is_box | True if the polygon is an axis-aligned rectangle |
polygon.is_self_intersecting | True if the outline crosses itself |
polygon.has_bulges | True if any edge carries a non-zero bulge |
polygon.add_vertex(point) | Append one vertex |
polygon.add_vertices(points) | Append several vertices |
poly = cell.add_polygon(layer, [(0, 0), (1000, 0), (1000, 1000)])poly.add_vertex((0, 1000))poly.vertices = [(p.x * 2, p.y * 2) for p in poly.vertices]Polyline
An open or closed path with an optional width. Extends Shape. Created with cell.add_polyline(...).
| Property / Method | Description |
|---|---|
polyline.vertices | Vertices as a list of Point; assignable |
polyline.vertex_count | Number of vertices |
polyline.head / polyline.tail | First and last vertex |
polyline.width | Path width in database units |
polyline.closed | Whether the path is closed |
polyline.end_cap_style | EndCap value |
polyline.add_vertex(point) / polyline.add_vertices(points) | Append vertices |
polyline.reverse() | Reverse the vertex order |
Arc
A circular arc. Extends Shape. Created with cell.add_arc(...).
from linkcad.v1.geom import Angle, Point
arc = cell.add_arc( layer, center=Point(0, 0), radius=10_000, width=500, start_angle=Angle.from_degrees(0), end_angle=Angle.from_degrees(90),)
arc.center = Point(1000, 1000)arc.radius = 12_000| Property | Description |
|---|---|
arc.center | Centre point; assignable |
arc.radius | Radius in database units; assignable |
arc.width | Stroke width in database units; assignable |
arc.start_angle | Start angle as an Angle, measured counter-clockwise from the x-axis |
arc.end_angle | End angle as an Angle |
Ellipse
A circle primitive. Extends Shape. Created with cell.add_circle(layer, center, diameter).
| Property | Description |
|---|---|
ellipse.center | Centre point; assignable |
ellipse.diameter | Diameter in database units; assignable |
ellipse.radius | Radius, half the diameter (read-only) |
Donut
A ring/annulus primitive. Extends Shape. Created with cell.add_donut(layer, center, mean_diameter, width).
| Property | Description |
|---|---|
donut.center | Centre point; assignable |
donut.mean_diameter | Mean diameter, (outer + inner) / 2; assignable |
donut.width | Ring width; assignable |
donut.outer_diameter / donut.inner_diameter | Derived diameters (read-only) |
donut.mean_radius / donut.outer_radius / donut.inner_radius | Derived radii (read-only) |
Nurbs
A non-uniform B-spline curve. Extends Shape. Created with cell.add_nurbs(...).
from linkcad.v1.geom import Point
nurbs = cell.add_nurbs( layer, control_points=[Point(0, 0), Point(10, 20), Point(20, 20), Point(30, 0)], degree=3, knots=[0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0], width=5,)| Property / Method | Description |
|---|---|
nurbs.width | Stroke width; assignable |
nurbs.degree | Curve degree |
nurbs.knots / nurbs.knot_count | Knot vector and count |
nurbs.vertices / nurbs.control_points | Control points as Point objects (the two names are equivalent) |
nurbs.control_point_count | Number of control points |
nurbs.weights | Weight vector |
nurbs.rational | True when weights are present |
nurbs.periodic | True when the curve is periodic/closed; assignable |
Text
Formatted text geometry. Extends Shape. Created with cell.add_text(...).
from linkcad.v1.geom import Angle, Point
label = cell.add_text( layer, content="Hello LinkCAD", position=Point(100, 200), height=12.5, font="simplex.shx",)
label.content = "Goodbye"label.rotate(Angle.from_degrees(90))| Property / Method | Description |
|---|---|
text.content | Formatted text content; assignable. text.text is an alias |
text.font | Font name; assignable |
text.position | Attachment point; assignable |
text.height | Text height; assignable |
text.line_spacing | Line spacing; assignable |
text.box_width | Wrapping width; assignable |
text.rotation | Rotation Angle. Assigning rotates relative to any enclosing reference |
text.rotate(angle, absolute=False) | Rotate; with absolute=True, rotations applied by enclosing references are ignored |
text.width_factor | Horizontal scaling; assignable |
text.stroke_width | Stroke width; assignable |
Text.escape(content) | Escape plain text for formatted-text storage |
Ref
A placed instance of another cell, with an associated transformation. Extends CellObject. Created with cell.add_ref(...).
from linkcad.v1.geom import Transformation
xform = Transformation()xform.rotate(45.0)xform.translate(1000, 2000)
ref = parent_cell.add_ref(child_cell, xform)ref.columns = 4ref.column_spacing = 5000| Property / Method | Description |
|---|---|
ref.ref_cell | The referenced Cell; assignable |
ref.transformation | Placement Transformation (applies to the whole array); assignable |
ref.columns / ref.rows | Array size; 1 means a single instance |
ref.column_spacing / ref.row_spacing | Array pitch in database units |
ref.apply_transformation(transformation) | Apply an additional transformation |
ref.clone(cell=None, transformation=None) | Copy the reference, optionally into another cell |
ref.destroy() | Remove and destroy this reference |
cell.add_ref() raises ValueError rather than returning something unusable: for a circular hierarchy — placing a cell inside one of its own descendants — and for a missing target cell, which used to produce a reference pointing at nothing. Catch it if the cells come from user input:
try: ref = parent_cell.add_ref(child_cell, xform)except ValueError as exc: print(f"cannot place '{child_cell.name}': {exc}")Color
An RGB colour value.
| Property / Method | Description |
|---|---|
Color() / Color(red, green, blue) | Construct a colour |
color.red / color.green / color.blue | Channels (0-255); assignable |
layer.color is a packed RGBA integer rather than a Color; use Color where an API asks for one.
Property
A named property attached to a database object.
| Property / Method | Description |
|---|---|
prop.owner_type | Type of object the property is attached to, as an ObjectType |
prop.destroy() | Destroy the property |
Property.properties(drawing, holding_type) | List property names for an object type |
Property.lookup_type(drawing, name, holding_type) | Look up a property’s type by name |
from linkcad.v1.db import ObjectType, Property
for name in Property.properties(drawing, ObjectType.Cell): print(name, Property.lookup_type(drawing, name, ObjectType.Cell))Enums
ObjectType
The concrete type of a database object, returned by obj.dynamic_type and prop.owner_type and taken by the Property class methods.
| Group | Values |
|---|---|
| Containers | Drawing, Layer, Cell |
| Cell objects | Ref, Arc, Polygon, Polyline, Donut, Text, Ellipse, Nurbs |
| Base classes | Object, DrawingObject, CellObject, Shape, Property |
| Properties | BooleanProperty, IntegerProperty, RealProperty, StringProperty, each also in a Drawing… / Cell… / CellObject… / Layer… form |
| Sentinel | Invalid |
CellContext
Controls whether an operation descends into sub-cells.
| Value | Description |
|---|---|
CellContext.Descend | Include sub-cells |
CellContext.DontDescend | This cell only |
Unit
Named measurement units, used by unit-aware APIs and option dialogs.
| Value | Description |
|---|---|
Unit.Picometer, Unit.Nanometer, Unit.Micron, Unit.Millimeter, Unit.Centimeter, Unit.Meter | Metric units |
Unit.Mil, Unit.Inch, Unit.Feet | Imperial units |
Unit.Point | Typographic point (1/72 inch) |
Unit.Database | Database units (picometres) |
Unit.DotsPerInch, Unit.Facets | Resolution and count “units” used by option dialogs |
There is also a “no unit” member, but because None is a Python keyword it can only be reached as getattr(Unit, "None").
drawing.units does not return a Unit. It is a float giving database units per metre — see Drawing.
EndCap
Polyline end-cap style. Also re-exported from linkcad.v1.plugin.
| Value | Description |
|---|---|
EndCap.Round | Round cap |
EndCap.SquareExtended | Square cap extended past the endpoint |
EndCap.SquareFlat | Square cap ending at the endpoint |
FillRule
Polygon fill rule. Also re-exported from linkcad.v1.plugin for writer APIs.
| Value | Description |
|---|---|
FillRule.NonZero | Non-zero winding rule |
FillRule.EvenOdd | Even-odd fill rule |
BooleanOperation
Layer-level boolean operation for drawing.boolean_layers_by_name().
| Value | Description |
|---|---|
BooleanOperation.Or | Union operand layer into result layer |
BooleanOperation.AMinusB | Subtract operand layer from result layer |
MergeLayerPolarityResult
Result from the deferred-polarity merge helpers.
| Value | Description |
|---|---|
MergeLayerPolarityResult.Success | Merge completed successfully |
MergeLayerPolarityResult.Failure | Merge failed |
TextStyle and TextStyleMask
TextStyle contains bit flags for text alignment, orientation, and line spacing. TextStyleMask selects which groups of style bits are changed by DrawingBuilder.set_text_style().
| Enum | Common values |
|---|---|
TextStyle | Default, AlignHLeft, AlignHCenter, AlignHRight, AlignVBaseline, AlignVBottom, AlignVMiddle, AlignVMiddleAscent, AlignVTop, OrientH, OrientV, LineSpacingExact, LineSpacingCompact |
TextStyleMask | None_, AlignH, AlignV, Orient, LineSpacing |
Locking
The locking requirement depends on the execution context.
Plugin context (@tool, @format_reader, @format_writer)
The framework holds the appropriate lock before calling run(), read(), or write(). No explicit lock is needed for read operations. Write operations that should be undoable require a Transaction:
from linkcad.v1.db import Transaction
def run(self, drawing): for cell in drawing.cells: print(cell.name)
with Transaction(drawing, "My Operation"): result = drawing.add_cell("RESULT") layer = drawing.add_layer("OUTPUT") result.add_polygon(layer, [(0, 0), (100, 0), (100, 100), (0, 100)])
return {"summary": "Created RESULT"}Standalone scripts (linkcad --python-script)
Scripts run outside the plugin framework must acquire locks explicitly:
from linkcad.v1.db import Drawing, ReadLock, WriteLock
with Drawing("scratch") as dwg: with ReadLock(): for cell in dwg.cells: print(cell.name)
with WriteLock(): for layer in list(dwg.layers): if layer.name.startswith("TEMP_"): dwg.destroy_layer_by_name(layer.name)ReadLock
Context manager for read-only access in standalone scripts.
from linkcad.v1.db import ReadLock
with ReadLock(): main = dwg.main_cell for shape in main.shapes: print(shape.bounds)WriteLock
Context manager for write access in standalone scripts. Does not create an undo entry.
from linkcad.v1.db import WriteLock
with WriteLock(): for cell in dwg.cells: for obj in list(cell.cell_objects): if obj.layer_name == "SCRATCH": obj.destroy()Transaction
Context manager for write access that creates a single undoable entry in the undo history. Use it in tool plugins. It takes either a description string, shown in the Edit menu, or a numeric tag.
from linkcad.v1.db import Transaction
with Transaction(drawing, "Create Panel"): panel = drawing.add_cell("PANEL") panel.add_ref(drawing.cell("UNIT"))The transaction commits when the block exits normally and rolls back if an exception escapes it. transaction.commit() commits early.