Skip to content

Utilities

utilities

check_cell_ports

check_cell_ports(
    p1: ProtoPort[Any], p2: ProtoPort[Any]
) -> int

Check if two ports are the same.

Returns:

Name Type Description
int int

A bitwise representation of the differences between the two ports.

Source code in kfactory/utilities.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def check_cell_ports(p1: ProtoPort[Any], p2: ProtoPort[Any]) -> int:
    """Check if two ports are the same.

    Returns:
        int: A bitwise representation of the differences between the two ports.
    """
    from .port import Port

    p1_ = Port(base=p1.base)
    p2_ = Port(base=p2.base)
    check_int = 0
    if p1_.width != p2_.width:
        check_int += 1
    if p1_.angle != p2_.angle:
        check_int += 2
    if p1_.port_type != p2_.port_type:
        check_int += 4
    return check_int

check_inst_ports

check_inst_ports(p1: Port, p2: Port) -> int

Check if two ports are the same.

Returns:

Name Type Description
int int

A bitwise representation of the differences between the two ports.

Source code in kfactory/utilities.py
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def check_inst_ports(p1: Port, p2: Port) -> int:
    """Check if two ports are the same.

    Returns:
        int: A bitwise representation of the differences between the two ports.
    """
    check_int = 0
    if p1.width != p2.width:
        check_int += 1
    if p1.angle != ((p2.angle + 2) % 4):
        check_int += 2
    if p1.port_type != p2.port_type:
        check_int += 4
    return check_int

dpolygon_from_array

dpolygon_from_array(
    array: Iterable[tuple[float, float]],
) -> kdb.DPolygon

Create a DPolygon from a 2D array-like structure. (um version).

Array-like: [[x1,y1],[x2,y2],...]

Source code in kfactory/utilities.py
75
76
77
78
79
80
def dpolygon_from_array(array: Iterable[tuple[float, float]]) -> kdb.DPolygon:
    """Create a DPolygon from a 2D array-like structure. (um version).

    Array-like: `[[x1,y1],[x2,y2],...]`
    """
    return kdb.DPolygon([kdb.DPoint(x, y) for (x, y) in array])

ensure_build_directory

ensure_build_directory(
    subdirectory: str = "mask",
    create_gitignore: bool = True,
) -> Path | None

Ensure build directory exists with proper gitignore.

This function consolidates all build directory creation logic, including git repository detection and .gitignore creation.

Parameters:

Name Type Description Default
subdirectory str

Subdirectory under build/ (e.g., 'mask', 'session/kcls').

'mask'
create_gitignore bool

Whether to create .gitignore in build directory.

True

Returns:

Type Description
Path | None

Path to the build subdirectory or None if not in a git repo.

Source code in kfactory/utilities.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
def ensure_build_directory(
    subdirectory: str = "mask", create_gitignore: bool = True
) -> Path | None:
    """Ensure build directory exists with proper gitignore.

    This function consolidates all build directory creation logic, including
    git repository detection and .gitignore creation.

    Args:
        subdirectory: Subdirectory under build/ (e.g., 'mask', 'session/kcls').
        create_gitignore: Whether to create .gitignore in build directory.

    Returns:
        Path to the build subdirectory or None if not in a git repo.
    """
    project_dir = config.project_dir
    if not project_dir:
        return None

    build_dir = Path(project_dir) / "build"
    target_dir = build_dir / subdirectory
    target_dir.mkdir(parents=True, exist_ok=True)

    if create_gitignore:
        gitignore_path = build_dir / ".gitignore"
        if not gitignore_path.exists():
            gitignore_path.write_text("*\n")

    return target_dir

get_build_path

get_build_path(
    filename: str,
    subdirectory: str = "mask",
    file_format: str = "gds",
) -> tuple[Path, bool]

Get the appropriate build path for a file.

Determines whether to use a git-tracked build directory or temp directory, and creates necessary directories.

Parameters:

Name Type Description Default
filename str

Base filename (without extension).

required
subdirectory str

Subdirectory under build/ (e.g., 'mask', 'gds', 'oas').

'mask'
file_format str

File extension/format (e.g., 'gds', 'oas', 'lyrdb').

'gds'

Returns:

Type Description
Path

Tuple of (file_path, should_delete):

bool
  • If in git repo: returns build directory path, False
tuple[Path, bool]
  • Otherwise: returns temp directory path, True
Source code in kfactory/utilities.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def get_build_path(
    filename: str, subdirectory: str = "mask", file_format: str = "gds"
) -> tuple[Path, bool]:
    """Get the appropriate build path for a file.

    Determines whether to use a git-tracked build directory or temp directory,
    and creates necessary directories.

    Args:
        filename: Base filename (without extension).
        subdirectory: Subdirectory under build/ (e.g., 'mask', 'gds', 'oas').
        file_format: File extension/format (e.g., 'gds', 'oas', 'lyrdb').

    Returns:
        Tuple of (file_path, should_delete):
        - If in git repo: returns build directory path, False
        - Otherwise: returns temp directory path, True
    """
    from tempfile import gettempdir

    build_dir = ensure_build_directory(subdirectory)

    if build_dir:
        filepath = build_dir / Path(filename).with_suffix(f".{file_format}")
        filepath.parent.mkdir(parents=True, exist_ok=True)
        return filepath, False
    filepath = Path(gettempdir()) / Path(filename).with_suffix(f".{file_format}")
    filepath.parent.mkdir(parents=True, exist_ok=True)
    return filepath, True

get_session_directory

get_session_directory(
    custom_dir: Path | None = None,
) -> Path

Get or create session cache directory.

Parameters:

Name Type Description Default
custom_dir Path | None

Optional custom directory override.

None

Returns:

Type Description
Path

Path to session/kcls directory.

Source code in kfactory/utilities.py
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
def get_session_directory(custom_dir: Path | None = None) -> Path:
    """Get or create session cache directory.

    Args:
        custom_dir: Optional custom directory override.

    Returns:
        Path to session/kcls directory.
    """
    if custom_dir:
        return custom_dir

    build_dir = ensure_build_directory("session/kcls", create_gitignore=True)
    if build_dir:
        return build_dir
    # Fallback to current directory
    return Path() / "build/session/kcls"

instance_port_name

instance_port_name(
    inst: Instance, port: ProtoPort[Any]
) -> str

Create a name for an instance port.

Parameters:

Name Type Description Default
inst Instance

The instance.

required
port ProtoPort[Any]

The port.

required
Source code in kfactory/utilities.py
119
120
121
122
123
124
125
126
def instance_port_name(inst: Instance, port: ProtoPort[Any]) -> str:
    """Create a name for an instance port.

    Args:
        inst: The instance.
        port: The port.
    """
    return f'{inst.name}["{port.name}"]'

load_layout_options

load_layout_options(
    **attributes: Any,
) -> kdb.LoadLayoutOptions

Default options for loading GDS/OAS.

Parameters:

Name Type Description Default
attributes Any

Set attributes of the layout load option object. E.g. to set the handling of cell name conflicts pass cell_conflict_resolution=kdb.LoadLayoutOptions.CellConflictResolution.OverwriteCell.

{}
Source code in kfactory/utilities.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def load_layout_options(**attributes: Any) -> kdb.LoadLayoutOptions:
    """Default options for loading GDS/OAS.

    Args:
        attributes: Set attributes of the layout load option object. E.g. to set the
            handling of cell name conflicts pass
            `cell_conflict_resolution=kdb.LoadLayoutOptions.CellConflictResolution.OverwriteCell`.
    """
    load = kdb.LoadLayoutOptions()

    load.cell_conflict_resolution = (
        kdb.LoadLayoutOptions.CellConflictResolution.SkipNewCell
    )
    for k, v in attributes.items():
        setattr(load, k, v)

    return load

polygon_from_array

polygon_from_array(
    array: Iterable[tuple[int, int]],
) -> kdb.Polygon

Create a DPolygon from a 2D array-like structure. (dbu version).

Array-like: [[x1,y1],[x2,y2],...]

Source code in kfactory/utilities.py
67
68
69
70
71
72
def polygon_from_array(array: Iterable[tuple[int, int]]) -> kdb.Polygon:
    """Create a DPolygon from a 2D array-like structure. (dbu version).

    Array-like: `[[x1,y1],[x2,y2],...]`
    """
    return kdb.Polygon([kdb.Point(int(x), int(y)) for (x, y) in array])

pprint_pins

pprint_pins(
    pins: Iterable[Pin] | Iterable[DPin],
    unit: Literal["dbu", "um"] | None = None,
) -> Table

Print ports as a table.

Parameters:

Name Type Description Default
pins Iterable[Pin] | Iterable[DPin]

The pins which should be printed.

required
unit Literal['dbu', 'um'] | None

Define the print type of the ports. If None, any port which can be represented accurately by a dbu representation will be printed in dbu otherwise in um. 'dbu'/'um' will force the printing to enforce one or the other representation

None
Source code in kfactory/utilities.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def pprint_pins(
    pins: Iterable[Pin] | Iterable[DPin], unit: Literal["dbu", "um"] | None = None
) -> Table:
    """Print ports as a table.

    Args:
        pins: The pins which should be printed.
        unit: Define the print type of the ports. If None, any port
            which can be represented accurately by a dbu representation
            will be printed in dbu otherwise in um. 'dbu'/'um' will force
            the printing to enforce one or the other representation
    """
    table = Table(show_lines=True)

    table.add_column("Name")
    table.add_column("Pin Type")
    table.add_column("Ports")
    table.add_column("Info")

    for pin in pins:
        ports = pprint_ports(pin.ports, unit=unit)
        ports.box = None
        table.add_row(
            str(pin.name),
            str(pin.pin_type),
            ports,
            JSON.from_data(pin.info.model_dump()),
        )

    return table

pprint_ports

pprint_ports(
    ports: Iterable[ProtoPort[Any]],
    unit: Literal["dbu", "um"] | None = None,
) -> Table

Print ports as a table.

Parameters:

Name Type Description Default
ports Iterable[ProtoPort[Any]]

The ports which should be printed.

required
unit Literal['dbu', 'um'] | None

Define the print type of the ports. If None, any port which can be represented accurately by a dbu representation will be printed in dbu otherwise in um. 'dbu'/'um' will force the printing to enforce one or the other representation

None
Source code in kfactory/utilities.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def pprint_ports(
    ports: Iterable[ProtoPort[Any]], unit: Literal["dbu", "um"] | None = None
) -> Table:
    """Print ports as a table.

    Args:
        ports: The ports which should be printed.
        unit: Define the print type of the ports. If None, any port
            which can be represented accurately by a dbu representation
            will be printed in dbu otherwise in um. 'dbu'/'um' will force
            the printing to enforce one or the other representation
    """
    table = Table(show_lines=True)

    table.add_column("Name")
    table.add_column("Width")
    table.add_column("Layer")
    table.add_column("Type")
    table.add_column("X")
    table.add_column("Y")
    table.add_column("Angle")
    table.add_column("Mirror")
    table.add_column("Info")

    match unit:
        case None:
            for port in ports:
                if port.base.trans is not None:
                    table.add_row(
                        str(port.name) + " [dbu]",
                        f"{port.width:_}",
                        port.kcl.get_info(port.layer).to_s(),
                        port.port_type,
                        f"{port.x:_}",
                        f"{port.y:_}",
                        str(port.angle),
                        str(port.mirror),
                        JSON.from_data(port.info.model_dump()),
                    )
                else:
                    t = port.dcplx_trans
                    dx = t.disp.x
                    dy = t.disp.y
                    dwidth = port.kcl.to_um(port.cross_section.width)
                    angle = t.angle
                    mirror = t.mirror
                    table.add_row(
                        str(port.name) + " [um]",
                        f"{dwidth:_}",
                        port.kcl.get_info(port.layer).to_s(),
                        port.port_type,
                        f"{dx:_}",
                        f"{dy:_}",
                        str(angle),
                        str(mirror),
                        JSON.from_data(port.info.model_dump()),
                    )
        case "um":
            for port in ports:
                dport = port.to_dtype()
                t = dport.dcplx_trans
                dx = t.disp.x
                dy = t.disp.y
                dwidth = dport.cross_section.width
                angle = t.angle
                mirror = t.mirror
                table.add_row(
                    str(dport.name) + " [um]",
                    f"{dwidth:_}",
                    dport.kcl.get_info(dport.layer).to_s(),
                    dport.port_type,
                    f"{dx:_}",
                    f"{dy:_}",
                    str(angle),
                    str(mirror),
                    JSON.from_data(dport.info.model_dump()),
                )
        case "dbu":
            for port in ports:
                iport = port.to_itype()
                table.add_row(
                    str(iport.name) + " [dbu]",
                    f"{iport.width:_}",
                    iport.kcl.get_info(iport.layer).to_s(),
                    iport.port_type,
                    f"{iport.x:_}",
                    f"{iport.y:_}",
                    str(iport.angle),
                    str(iport.mirror),
                    JSON.from_data(iport.info.model_dump()),
                )

    return table

save_layout_options

save_layout_options(
    **attributes: Any,
) -> kdb.SaveLayoutOptions

Default options for saving GDS/OAS.

Parameters:

Name Type Description Default
attributes Any

Set attributes of the layout save option object. E.g. to save the gds without metadata pass write_context_info=False

{}
Source code in kfactory/utilities.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def save_layout_options(**attributes: Any) -> kdb.SaveLayoutOptions:
    """Default options for saving GDS/OAS.

    Args:
        attributes: Set attributes of the layout save option object. E.g. to save the
            gds without metadata pass `write_context_info=False`
    """
    save = kdb.SaveLayoutOptions()
    save.write_context_info = config.write_context_info
    save.gds2_write_cell_properties = config.write_cell_properties
    save.gds2_write_file_properties = config.write_file_properties
    save.gds2_write_timestamps = config.write_timestamps
    save.gds2_max_cellname_length = config.max_cellname_length

    for k, v in attributes.items():
        setattr(save, k, v)

    return save

update_default_trans

update_default_trans(
    new_trans: dict[
        str,
        str | int | float | dict[str, str | int | float],
    ],
) -> None

Allows to change the default transformation for reading a yaml file.

Source code in kfactory/utilities.py
60
61
62
63
64
def update_default_trans(
    new_trans: dict[str, str | int | float | dict[str, str | int | float]],
) -> None:
    """Allows to change the default transformation for reading a yaml file."""
    DEFAULT_TRANS.update(new_trans)