DBU vs µm — coordinate systems
kfactory inherits two parallel coordinate systems from KLayout:
| System | Unit | Type | KLayout prefix |
|---|---|---|---|
| DBU — database units | 1 nm (by default) | int |
no prefix / I |
| µm — micrometres | 1 µm | float |
D |
Every geometry class exists in both flavours:
| DBU (integer) | µm (float) |
|---|---|
kdb.Box |
kdb.DBox |
kdb.Polygon |
kdb.DPolygon |
kdb.Point |
kdb.DPoint |
kdb.Vector |
kdb.DVector |
kdb.Trans |
kdb.DTrans |
kdb.Edge |
kdb.DEdge |
The relationship is fixed: 1 µm = 1 000 DBU (because kf.kcl.dbu = 0.001 µm).
This page shows how to use both, convert between them, and choose the right one.
import kfactory as kf
class LAYER(kf.LayerInfos):
WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0)
WGEX: kf.kdb.LayerInfo = kf.kdb.LayerInfo(2, 0)
SLAB: kf.kdb.LayerInfo = kf.kdb.LayerInfo(3, 0)
L = LAYER()
kf.kcl.infos = L
The dbu constant
kf.kcl.dbu is the size of one DBU expressed in µm. The default is 0.001,
meaning 1 DBU = 0.001 µm = 1 nm.
print(f"dbu = {kf.kcl.dbu} µm/DBU")
print(f"1 µm = {kf.kcl.to_dbu(1.0)} DBU")
print(f"1000 DBU = {kf.kcl.to_um(1000)} µm")
dbu = 0.001 µm/DBU
1 µm = 1000 DBU
1000 DBU = 1.0 µm
Geometry objects
Box vs DBox
# DBU: integer coordinates, 1 unit = 1 nm
box_dbu = kf.kdb.Box(2000, 1000) # 2 µm × 1 µm
print(f"Box (DBU): {box_dbu}")
# µm: float coordinates
box_um = kf.kdb.DBox(2.0, 1.0) # same size
print(f"DBox (µm): {box_um}")
Box (DBU): (-1000,-500;1000,500)
DBox (µm): (-1,-0.5;1,0.5)
Both represent the same physical area. KLayout centres boxes at the origin by
default, so the corners are (±half_width, ±half_height).
Converting between DBU and µm
# DBU → µm
box_um_from_dbu = box_dbu.to_dtype(kf.kcl.dbu)
print(f"Box → DBox: {box_um_from_dbu}")
# µm → DBU
box_dbu_from_um = box_um.to_itype(kf.kcl.dbu)
print(f"DBox → Box: {box_dbu_from_um}")
Box → DBox: (-1,-0.5;1,0.5)
DBox → Box: (-1000,-500;1000,500)
Trans vs DTrans
Trans / DTrans encode a rotation + optional mirror + translation. The
displacement part uses DBU integers / µm floats respectively.
# DBU: displacement in integer DBU
t_dbu = kf.kdb.Trans(0, False, 5000, 0) # 5 µm to the right
print(f"Trans (DBU): {t_dbu}")
# µm: displacement in float µm
t_um = kf.kdb.DTrans(0, False, 5.0, 0.0)
print(f"DTrans (µm): {t_um}")
# Convert
print(f"Trans → DTrans: {t_dbu.to_dtype(kf.kcl.dbu)}")
print(f"DTrans → Trans: {t_um.to_itype(kf.kcl.dbu)}")
Trans (DBU): r0 5000,0
DTrans (µm): r0 5,0
Trans → DTrans: r0 5,0
DTrans → Trans: r0 5000,0
Cell bounding boxes
KCell exposes bounding boxes in both systems:
| Method | Returns |
|---|---|
cell.bbox() |
kdb.Box in DBU |
cell.dbbox() |
kdb.DBox in µm |
li = kf.kcl.find_layer(L.WG)
example = kf.KCell("example_bbox")
example.shapes(li).insert(kf.kdb.Box(4000, 2000)) # 4 µm × 2 µm
print(f"bbox (DBU): {example.bbox()}") # integer coords
print(f"dbbox (µm): {example.dbbox()}") # float coords
bbox (DBU): (-2000,-1000;2000,1000)
dbbox (µm): (-2,-1;2,1)
Ports: width vs dwidth
Port width is stored internally in DBU (int). The .dwidth property
converts on-the-fly to µm:
p = kf.Port(
name="o1",
trans=kf.kdb.Trans(0, False, 2000, 0),
width=500, # 500 DBU = 0.5 µm
layer=li,
port_type="optical",
)
print(f"Port width (DBU): {p.width}") # 500
print(f"Port dwidth (µm): {p.dwidth}") # 0.5
Port width (DBU): 500
Port dwidth (µm): 0.5
Shapes: inserting DBU and µm geometry
cell.shapes(layer_index).insert(...) accepts both DBU and µm objects directly —
KLayout converts D-prefixed shapes automatically using the layout's dbu.
mixed = kf.KCell("shapes_mixed")
# Insert a DBU box
mixed.shapes(li).insert(kf.kdb.Box(3000, 500))
# Insert a µm DPolygon — automatically converted
dpoly = kf.kdb.DPolygon(kf.kdb.DBox(1.0, 0.25))
mixed.shapes(li).insert(dpoly)
print(f"Shapes count: {mixed.shapes(li).size()}")
print(f"Total bbox (DBU): {mixed.bbox()}")
print(f"Total dbbox (µm): {mixed.dbbox()}")
Shapes count: 2
Total bbox (DBU): (-1500,-250;1500,250)
Total dbbox (µm): (-1.5,-0.25;1.5,0.25)
Choosing the right system
Use DBU (int) when:
- Writing internal cell logic — integer arithmetic avoids floating-point drift.
- Defining port width and trans — the API stores these as integers.
- Doing boolean operations (Region) — Region always works in DBU.
Use µm (float) when:
- Accepting user-facing parameters in a factory function — width=0.5 reads more
naturally than width=500.
- Reading back coordinates for display or export.
- Constructing paths or references from physical dimensions.
A common idiom is to accept µm at the function boundary and convert immediately:
@kf.cell
def waveguide(width: float = 0.5, length: float = 10.0) -> kf.KCell:
"""Simple waveguide with µm parameters converted to DBU internally."""
c = kf.KCell()
w = kf.kcl.to_dbu(width) # → int DBU
ll = kf.kcl.to_dbu(length) # → int DBU
c.shapes(li).insert(kf.kdb.Box(ll, w))
c.add_port(
port=kf.Port(
name="o1",
trans=kf.kdb.Trans(2, False, -ll // 2, 0),
width=w,
layer=li,
port_type="optical",
)
)
c.add_port(
port=kf.Port(
name="o2",
trans=kf.kdb.Trans(0, False, ll // 2, 0),
width=w,
layer=li,
port_type="optical",
)
)
return c
wg = waveguide(width=0.5, length=10.0)
print(f"bbox (DBU): {wg.bbox()}")
print(f"dbbox (µm): {wg.dbbox()}")
print(f"Port o1 dwidth: {wg.ports['o1'].dwidth} µm")
bbox (DBU): (-5000,-250;5000,250)
dbbox (µm): (-5,-0.25;5,0.25)
Port o1 dwidth: 0.5 µm
Quick-reference table
| Task | DBU expression | µm expression |
|---|---|---|
| Rectangle | kdb.Box(w, h) |
kdb.DBox(w, h) |
| Point | kdb.Point(x, y) |
kdb.DPoint(x, y) |
| Translation | kdb.Trans(rot, mir, x, y) |
kdb.DTrans(rot, mir, x, y) |
| Cell bbox | cell.bbox() |
cell.dbbox() |
| Port width | port.width |
port.dwidth |
| Convert DBU→µm | kf.kcl.to_um(n) |
— |
| Convert µm→DBU | kf.kcl.to_dbu(x) |
— |
| Shape convert | dshape.to_itype(kf.kcl.dbu) |
shape.to_dtype(kf.kcl.dbu) |
See Also
| Topic | Where |
|---|---|
| KCell and DKCell side-by-side | Core Concepts: KCell |
| Port width and position in DBU | Core Concepts: Ports |
| Factory functions and their unit conventions | Components: Factory Functions |
| FAQ — when to use DBU vs µm | How-To: FAQ |