KLayout is a high performance layout viewer and editor that supports GDS and OASIS files and more formats
—
Geometric transformations including rotation, mirroring, scaling, and translation operations for precise layout manipulation and coordinate system conversions in IC design workflows.
class Trans:
def __init__(self, rotation: int = 0, mirror: bool = False, displacement: Point = None):
"""
Create an integer coordinate transformation.
Parameters:
- rotation: Rotation in 90-degree increments (0, 1, 2, 3)
- mirror: Mirror across X-axis before rotation
- displacement: Translation vector
"""
@property
def rot(self) -> int:
"""Get rotation value (0-3 for 0°, 90°, 180°, 270°)."""
@property
def is_mirror(self) -> bool:
"""Check if transformation includes mirroring."""
@property
def disp(self) -> Point:
"""Get displacement vector."""
def inverted(self) -> Trans:
"""Return the inverse transformation."""
def __mul__(self, other: Trans) -> Trans:
"""Combine transformations (self * other)."""
def transform_point(self, point: Point) -> Point:
"""Transform a point."""
def transform_box(self, box: Box) -> Box:
"""Transform a bounding box."""
class DTrans:
def __init__(self, rotation: int = 0, mirror: bool = False, displacement: DPoint = None):
"""Create a double precision transformation."""
@property
def rot(self) -> int:
"""Get rotation value."""
@property
def is_mirror(self) -> bool:
"""Check if transformation includes mirroring."""
@property
def disp(self) -> DPoint:
"""Get displacement vector."""class CplxTrans:
def __init__(self, mag: float = 1.0, rotation: float = 0.0, mirror: bool = False, displacement: DPoint = None):
"""
Create a complex transformation with scaling and arbitrary rotation.
Parameters:
- mag: Magnification factor
- rotation: Rotation angle in degrees
- mirror: Mirror across X-axis before rotation
- displacement: Translation vector
"""
@property
def mag(self) -> float:
"""Get magnification factor."""
@property
def angle(self) -> float:
"""Get rotation angle in degrees."""
@property
def is_mirror(self) -> bool:
"""Check if transformation includes mirroring."""
@property
def disp(self) -> DPoint:
"""Get displacement vector."""
def inverted(self) -> CplxTrans:
"""Return the inverse transformation."""
def __mul__(self, other: CplxTrans) -> CplxTrans:
"""Combine complex transformations."""
def transform_point(self, point: DPoint) -> DPoint:
"""Transform a double precision point."""
def transform_box(self, box: DBox) -> DBox:
"""Transform a double precision bounding box."""
class ICplxTrans:
def __init__(self, mag: float = 1.0, rotation: float = 0.0, mirror: bool = False, displacement: Point = None):
"""Create an integer complex transformation."""
def to_trans(self) -> Trans:
"""Convert to simple transformation (if no scaling)."""
class DCplxTrans:
def __init__(self, mag: float = 1.0, rotation: float = 0.0, mirror: bool = False, displacement: DPoint = None):
"""Create a double precision complex transformation."""class Matrix2d:
def __init__(self, m11: float = 1.0, m12: float = 0.0, m21: float = 0.0, m22: float = 1.0):
"""
Create a 2x2 transformation matrix.
Parameters:
- m11, m12, m21, m22: Matrix elements
"""
def determinant(self) -> float:
"""Calculate matrix determinant."""
def inverted(self) -> Matrix2d:
"""Return inverted matrix."""
def __mul__(self, other: Matrix2d) -> Matrix2d:
"""Matrix multiplication."""
def transform_point(self, point: DPoint) -> DPoint:
"""Transform a point using the matrix."""
class Matrix3d:
def __init__(self):
"""Create a 3x3 transformation matrix."""
def translate(self, dx: float, dy: float) -> Matrix3d:
"""Add translation to the transformation."""
def rotate(self, angle: float) -> Matrix3d:
"""Add rotation to the transformation (in radians)."""
def scale(self, sx: float, sy: float = None) -> Matrix3d:
"""
Add scaling to the transformation.
Parameters:
- sx: X scale factor
- sy: Y scale factor (defaults to sx if not provided)
"""
def shear(self, shx: float, shy: float) -> Matrix3d:
"""Add shear transformation."""
def inverted(self) -> Matrix3d:
"""Return inverted matrix."""
def transform_point(self, point: DPoint) -> DPoint:
"""Transform a point."""def coord_from_dbu(coord_dbu: int, dbu: float) -> float:
"""
Convert coordinate from database units to user units.
Parameters:
- coord_dbu: Coordinate in database units
- dbu: Database unit in user units
Returns:
float: Coordinate in user units
"""
def coord_to_dbu(coord: float, dbu: float) -> int:
"""
Convert coordinate from user units to database units.
Parameters:
- coord: Coordinate in user units
- dbu: Database unit in user units
Returns:
int: Coordinate in database units
"""
class Layout:
@property
def dbu(self) -> float:
"""Get database unit in micrometers."""
def set_dbu(self, dbu: float) -> None:
"""Set database unit."""
def scale_and_snap(self, layout_target: Layout, cell_mapping, layers, factor: float) -> None:
"""Scale layout and snap to grid."""import klayout.db as db
# Create basic transformations
trans1 = db.Trans(1, False, db.Point(100, 200)) # 90° rotation + translation
trans2 = db.Trans(0, True, db.Point(0, 0)) # Mirror only
trans3 = db.Trans(db.Point(50, 75)) # Translation only
# Apply transformations to shapes
original_box = db.Box(0, 0, 100, 50)
rotated_box = trans1.transform_box(original_box)
mirrored_box = trans2.transform_box(original_box)
# Combine transformations
combined = trans1 * trans2 # Apply trans2, then trans1
print(f"Original: {original_box}")
print(f"Rotated: {rotated_box}")
print(f"Combined transformation: {combined}")import klayout.db as db
import math
# Create complex transformation
angle = 45.0 # 45 degrees
scale = 1.5 # 150% scaling
displacement = db.DPoint(100.0, 200.0)
ctrans = db.CplxTrans(scale, angle, False, displacement)
# Transform double precision shapes
dbox = db.DBox(0.0, 0.0, 10.0, 5.0)
transformed_dbox = ctrans.transform_box(dbox)
print(f"Original: {dbox}")
print(f"Transformed: {transformed_dbox}")
print(f"Scale: {ctrans.mag}, Angle: {ctrans.angle}°")import klayout.db as db
import math
# Create transformation using matrices
matrix = db.Matrix3d()
matrix.rotate(math.pi / 4) # 45 degrees in radians
matrix.scale(2.0, 1.5) # Scale X by 2, Y by 1.5
matrix.translate(100.0, 50.0)
# Apply to points
points = [db.DPoint(0, 0), db.DPoint(10, 0), db.DPoint(10, 10), db.DPoint(0, 10)]
transformed_points = [matrix.transform_point(p) for p in points]
print("Original points:", points)
print("Transformed points:", transformed_points)import klayout.db as db
layout = db.Layout()
top_cell = layout.create_cell("TOP")
sub_cell = layout.create_cell("SUB")
# Add content to subcell
layer = layout.layer(db.LayerInfo(1, 0))
sub_cell.shapes(layer).insert(db.Box(0, 0, 100, 100))
# Create instances with various transformations
transformations = [
db.Trans(0, False, db.Point(0, 0)), # Original position
db.Trans(1, False, db.Point(200, 0)), # 90° rotation
db.Trans(2, False, db.Point(200, 200)), # 180° rotation
db.Trans(3, False, db.Point(0, 200)), # 270° rotation
db.Trans(0, True, db.Point(400, 0)), # Mirrored
]
for i, trans in enumerate(transformations):
instance = db.CellInstArray(sub_cell.cell_index, trans)
top_cell.insert(instance)
layout.write("transformed_instances.gds")import klayout.db as db
layout = db.Layout()
print(f"Default DBU: {layout.dbu} micrometers")
# Work with different coordinate systems
# Coordinates in micrometers
user_coord = 10.5 # 10.5 μm
# Convert to database units
dbu_coord = db.coord_to_dbu(user_coord, layout.dbu)
print(f"{user_coord} μm = {dbu_coord} DBU")
# Convert back to user units
recovered_coord = db.coord_from_dbu(dbu_coord, layout.dbu)
print(f"{dbu_coord} DBU = {recovered_coord} μm")
# Create shapes using converted coordinates
box_in_um = db.Box(
db.coord_to_dbu(0.0, layout.dbu),
db.coord_to_dbu(0.0, layout.dbu),
db.coord_to_dbu(10.5, layout.dbu),
db.coord_to_dbu(5.25, layout.dbu)
)
cell = layout.create_cell("TOP")
layer = layout.layer(db.LayerInfo(1, 0))
cell.shapes(layer).insert(box_in_um)import klayout.db as db
# Create a chain of transformations
base_trans = db.Trans(db.Point(100, 100)) # Base translation
rotate_90 = db.Trans(1, False, db.Point(0, 0)) # 90° rotation
mirror_x = db.Trans(0, True, db.Point(0, 0)) # Mirror
# Build transformation chain
step1 = base_trans * rotate_90
step2 = step1 * mirror_x
# Apply to create pattern
layout = db.Layout()
cell = layout.create_cell("PATTERN")
layer = layout.layer(db.LayerInfo(1, 0))
# Original shape
original = db.Box(0, 0, 50, 25)
cell.shapes(layer).insert(original)
# Transformed copies
cell.shapes(layer).insert(step1.transform_box(original))
cell.shapes(layer).insert(step2.transform_box(original))
# Verify inverse transformations
inverse_step2 = step2.inverted()
recovered = inverse_step2.transform_box(step2.transform_box(original))
print(f"Original: {original}")
print(f"Recovered: {recovered}")
print(f"Match: {original == recovered}")
layout.write("transformation_chain.gds")import klayout.db as db
import math
# For arbitrary angles, use CplxTrans
angles = [15, 30, 45, 60, 75] # Degrees
center = db.DPoint(50, 50)
layout = db.Layout()
cell = layout.create_cell("ROTATIONS")
layer = layout.layer(db.LayerInfo(1, 0))
# Create base shape
base_points = [
db.Point(0, 0), db.Point(20, 0),
db.Point(20, 10), db.Point(0, 10)
]
base_polygon = db.Polygon(base_points)
for angle in angles:
# Create transformation: translate to origin, rotate, translate back
to_origin = db.CplxTrans(1.0, 0.0, False, db.DPoint(-center.x, -center.y))
rotate = db.CplxTrans(1.0, angle, False, db.DPoint(0, 0))
from_origin = db.CplxTrans(1.0, 0.0, False, center)
# Combine transformations
full_trans = from_origin * rotate * to_origin
# Convert to integer transformation for polygon
# (Note: This approximates arbitrary angles to nearest valid Trans rotation)
# For exact arbitrary angles, work with DPolygon instead
approx_trans = db.ICplxTrans(full_trans).to_trans()
rotated_polygon = base_polygon.transformed(approx_trans)
cell.shapes(layer).insert(rotated_polygon)
layout.write("arbitrary_rotations.gds")Install with Tessl CLI
npx tessl i tessl/pypi-klayout