Skip to content

Utils

utils

Utility functions for cell factories.

boundary_from_shapes

boundary_from_shapes(c: KCell) -> kdb.DPolygon | None

Build a cell boundary from its drawn shapes.

Collects every shape on every layer, merges them, and returns the first polygon (in um) of the merged region — i.e. the outline of the cell's footprint. Returns None if the cell has no shapes.

Source code in kfactory/factories/utils.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def boundary_from_shapes(c: KCell) -> kdb.DPolygon | None:
    """Build a cell boundary from its drawn shapes.

    Collects every shape on every layer, merges them, and returns the first polygon
    (in um) of the merged region — i.e. the outline of the cell's footprint. Returns
    `None` if the cell has no shapes.
    """
    region = kdb.Region()
    for layer_index in c.kcl.layer_indexes():
        region.insert(c.shapes(layer_index))
    region.merge()
    if region.is_empty():
        return None
    return region[0].to_dtype(c.kcl.dbu)

cross_section_from_width

cross_section_from_width(
    kcl: KCLayout,
    width: int,
    layer: LayerInfo,
    enclosure: LayerEnclosure | None = None,
) -> CrossSection

Build a (dbu) symmetric cross section from legacy width/layer/enclosure args.

Source code in kfactory/factories/utils.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def cross_section_from_width(
    kcl: "KCLayout",
    width: int,
    layer: kdb.LayerInfo,
    enclosure: LayerEnclosure | None = None,
) -> CrossSection:
    """Build a (dbu) symmetric cross section from legacy width/layer/enclosure args."""
    return kcl.get_icross_section(
        CrossSectionSpecDict(
            layer=layer,
            width=width,
            unit="dbu",
            sections=layer_enclosure_to_sections(enclosure),
        ),
        symmetrical=True,
    )

extrude_backbone

extrude_backbone(
    c: VKCell,
    backbone: Sequence[DPoint],
    width: float,
    layer: LayerInfo,
    start_angle: float,
    end_angle: float,
    dbu: float,
    enclosure: LayerEnclosure | None = None,
) -> None

Extrude a backbone into a virtual cell.

Parameters:

Name Type Description Default
c VKCell

target cell

required
backbone Sequence[DPoint]

backbone to extrude

required
width float

width to extrude (main layer)

required
layer LayerInfo

main layer & reference for enclosure

required
enclosure LayerEnclosure | None

enclosure to apply

None
start_angle float

force a certain start angle

required
end_angle float

force a acertain end angle

required
dbu float

database unit to use as a reference

required
Source code in kfactory/factories/utils.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def extrude_backbone(
    c: VKCell,
    backbone: Sequence[kdb.DPoint],
    width: float,
    layer: kdb.LayerInfo,
    start_angle: float,
    end_angle: float,
    dbu: float,
    enclosure: LayerEnclosure | None = None,
) -> None:
    """Extrude a backbone into a virtual cell.

    Args:
        c: target cell
        backbone: backbone to extrude
        width: width to extrude (main layer)
        layer: main layer & reference for enclosure
        enclosure: enclosure to apply
        start_angle: force a certain start angle
        end_angle: force a acertain end angle
        dbu: database unit to use as a reference
    """
    center_path_l, center_path_r = extrude_path_points(
        backbone, width=width, start_angle=start_angle, end_angle=end_angle
    )
    center_path_r.reverse()
    c.shapes(c.kcl.layer(layer)).insert(kdb.DPolygon(center_path_l + center_path_r))

    if enclosure:
        for _layer, sections in enclosure.layer_sections.items():
            _li = c.kcl.layer(_layer)
            for section in sections.sections:
                if section.d_min is not None:
                    inner_l, inner_r = extrude_path_points(
                        backbone,
                        width=width + 2 * section.d_min * dbu,
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    outer_l, outer_r = extrude_path_points(
                        backbone,
                        width=width + 2 * section.d_max * dbu,
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    inner_l.reverse()
                    outer_r.reverse()
                    c.shapes(_li).insert(kdb.DPolygon(outer_l + inner_l))
                    c.shapes(_li).insert(kdb.DPolygon(inner_r + outer_r))
                else:
                    outer_l, outer_r = extrude_path_points(
                        backbone,
                        width=width + 2 * section.d_max * dbu,
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    outer_r.reverse()
                    c.shapes(_li).insert(kdb.DPolygon(outer_l + outer_r))

extrude_backbone_cross_section

extrude_backbone_cross_section(
    c: VKCell,
    backbone: Sequence[DPoint],
    cross_section: AnyCrossSection,
    start_angle: float,
    end_angle: float,
) -> None

Extrude a (symmetric or asymmetric) cross section along a backbone (um).

The virtual-cell counterpart of extrude_path_cross_section: symmetric cross sections reproduce extrude_backbone exactly (byte-identical, centered width + enclosure annuli); asymmetric ones are extruded as one signed band [section_min, section_max] per strip (main strip + each aux section), with strips sharing a layer merged.

Parameters:

Name Type Description Default
c VKCell

target virtual cell

required
backbone Sequence[DPoint]

backbone to extrude (in um)

required
cross_section AnyCrossSection

the cross section to extrude

required
start_angle float

force a certain start angle

required
end_angle float

force a certain end angle

required
Source code in kfactory/factories/utils.py
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
def extrude_backbone_cross_section(
    c: VKCell,
    backbone: Sequence[kdb.DPoint],
    cross_section: "AnyCrossSection",
    start_angle: float,
    end_angle: float,
) -> None:
    """Extrude a (symmetric or asymmetric) cross section along a backbone (um).

    The virtual-cell counterpart of
    [`extrude_path_cross_section`][kfactory.enclosure.extrude_path_cross_section]:
    symmetric cross sections reproduce `extrude_backbone` exactly (byte-identical,
    centered width + enclosure annuli); asymmetric ones are extruded as one signed
    band ``[section_min, section_max]`` per strip (main strip + each aux section),
    with strips sharing a layer merged.

    Args:
        c: target virtual cell
        backbone: backbone to extrude (in um)
        cross_section: the cross section to extrude
        start_angle: force a certain start angle
        end_angle: force a certain end angle
    """
    from ..cross_section import AsymmetricalCrossSection

    if not isinstance(cross_section, AsymmetricalCrossSection):
        extrude_backbone(
            c,
            backbone=list(backbone),
            width=c.kcl.to_um(cross_section.width),
            layer=cross_section.main_layer,
            start_angle=start_angle,
            end_angle=end_angle,
            dbu=c.kcl.dbu,
            enclosure=cross_section.enclosure,
        )
        return

    to_um = c.kcl.to_um
    strips: dict[kdb.LayerInfo, list[tuple[float, float]]] = defaultdict(list)
    strips[cross_section.layer].append(
        (to_um(cross_section.section_min), to_um(cross_section.section_max))
    )
    for sec in cross_section.sections:
        strips[sec.layer].append((to_um(sec.section_min), to_um(sec.section_max)))

    for layer, bands in strips.items():
        region = kdb.Region()
        for lo, hi in bands:
            polygon = path_pts_to_polygon(
                *_extrude_path_band_points(
                    list(backbone), lo, hi, start_angle, end_angle
                )
            )
            region.insert(c.kcl.to_dbu(polygon))
        region.merge()
        li = c.kcl.layer(layer)
        for poly in region.each():
            c.shapes(li).insert(poly.to_dtype(c.kcl.dbu))

extrude_backbone_dynamic

extrude_backbone_dynamic(
    c: VKCell,
    backbone: list[DPoint],
    width1: float,
    width2: float,
    layer: LayerInfo,
    start_angle: float,
    end_angle: float,
    dbu: float,
    enclosure: LayerEnclosure | None = None,
) -> None

Extrude a backbone into a virtual cell.

Parameters:

Name Type Description Default
c VKCell

target cell

required
backbone list[DPoint]

backbone to extrude

required
width1 float

start width to extrude (main layer)

required
width2 float

end width to extrude (main layer)

required
layer LayerInfo

main layer & reference for enclosure

required
enclosure LayerEnclosure | None

enclosure to apply

None
start_angle float

force a certain start angle

required
end_angle float

force a acertain end angle

required
dbu float

database unit to use as a reference

required
Source code in kfactory/factories/utils.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def extrude_backbone_dynamic(
    c: VKCell,
    backbone: list[kdb.DPoint],
    width1: float,
    width2: float,
    layer: kdb.LayerInfo,
    start_angle: float,
    end_angle: float,
    dbu: float,
    enclosure: LayerEnclosure | None = None,
) -> None:
    """Extrude a backbone into a virtual cell.

    Args:
        c: target cell
        backbone: backbone to extrude
        width1: start width to extrude (main layer)
        width2: end width to extrude (main layer)
        layer: main layer & reference for enclosure
        enclosure: enclosure to apply
        start_angle: force a certain start angle
        end_angle: force a acertain end angle
        dbu: database unit to use as a reference
    """

    def width_f(x: float, a: float) -> float:
        return (width1 - width2) * (1 - x) + width2 + a

    center_path_l, center_path_r = extrude_path_dynamic_points(
        backbone,
        widths=partial(width_f, a=0),
        start_angle=start_angle,
        end_angle=end_angle,
    )
    center_path_r.reverse()
    c.shapes(c.kcl.layer(layer)).insert(kdb.DPolygon(center_path_l + center_path_r))

    if enclosure:
        for _layer, sections in enclosure.layer_sections.items():
            _li = c.kcl.layer(_layer)
            for section in sections.sections:
                if section.d_min is not None:
                    inner_l, inner_r = extrude_path_dynamic_points(
                        backbone,
                        widths=partial(width_f, a=2 * section.d_min * dbu),
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    outer_l, outer_r = extrude_path_dynamic_points(
                        backbone,
                        widths=partial(width_f, a=2 * section.d_max * dbu),
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    inner_l.reverse()
                    outer_r.reverse()
                    c.shapes(_li).insert(kdb.DPolygon(outer_l + inner_l))
                    c.shapes(_li).insert(kdb.DPolygon(inner_r + outer_r))
                else:
                    outer_l, outer_r = extrude_path_dynamic_points(
                        backbone,
                        widths=partial(width_f, a=2 * section.d_max * dbu),
                        start_angle=start_angle,
                        end_angle=end_angle,
                    )
                    outer_r.reverse()
                    c.shapes(_li).insert(kdb.DPolygon(outer_l + outer_r))

layer_enclosure_to_sections

layer_enclosure_to_sections(
    enclosure: LayerEnclosure | None,
) -> list[
    tuple[kdb.LayerInfo, int]
    | tuple[kdb.LayerInfo, int, int]
]

Flatten a LayerEnclosure into CrossSectionSpec sections (dbu).

Source code in kfactory/factories/utils.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def layer_enclosure_to_sections(
    enclosure: LayerEnclosure | None,
) -> list[tuple[kdb.LayerInfo, int] | tuple[kdb.LayerInfo, int, int]]:
    """Flatten a `LayerEnclosure` into `CrossSectionSpec` ``sections`` (dbu)."""
    if enclosure is None:
        return []
    sections: list[tuple[kdb.LayerInfo, int] | tuple[kdb.LayerInfo, int, int]] = []
    for layer, layer_section in enclosure.layer_sections.items():
        for section in layer_section.sections:
            if section.d_min is None:
                sections.append((layer, section.d_max))
            else:
                sections.append((layer, section.d_min, section.d_max))
    return sections