Source code for pdkmaster.io.coriolis.export

"""Generate coriolis setup file"""
# SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
from textwrap import dedent, indent
from itertools import product
from typing import Set, Tuple, Optional, cast, overload

from ... import dispatch as _dsp
from ...typing  import GDSLayerSpecDict
from ...technology import (
    mask as _msk, primitive as _prm, geometry as _geo, technology_ as _tch,
)
from ...design import cell as _cell, routinggauge as _rg, library as _lbry
from ...design.layout import layout_ as _laylay

__all__ = ["FileExporter"]


def _str_create_basic(name, mat, *,
    minsize=None, minspace=None, minarea=None, gds_layer=None, gds_datatype=None
):
    s = "createBL(\n"
    s += f"    tech, '{name}', BasicLayer.Material.{mat},\n"
    s_args = []
    if minsize is not None:
        s_args.append(f"size=u({minsize})")
    if minspace is not None:
        s_args.append(f"spacing=u({minspace})")
    if minarea is not None:
        s_args.append(f"area={minarea}")
    if gds_layer is not None:
        s_args.append(f"gds2Layer={gds_layer}")
    if gds_datatype is not None:
        s_args.append(f"gds2DataType={gds_datatype}")
    if s_args:
        s += "    " + ", ".join(s_args) + ",\n"
    s += ")\n"
    return s


def _str_create_via(via):
    assert isinstance(via, _prm.Via)

    def _str_bottomtop(bottom, top):
        s_via = f"{bottom.name}_{via.name}_{top.name}"
        return dedent(f"""
            # {bottom.name}<>{via.name}<>{top.name}
            createVia(
                tech, '{s_via}', '{bottom.name}', '{via.name}', '{top.name}',
                u({via.width}),
            )
        """[1:])

    return "".join(_str_bottomtop(bottom, top) for bottom, top in product(
        filter(lambda p: isinstance(p, _prm.MetalWire), via.bottom),
        filter(lambda p: isinstance(p, _prm.MetalWire), via.top),
    ))


def _args_gds_layer(prim: _prm.MaskPrimitiveT, *, gds_layers: GDSLayerSpecDict):
    mask = prim.mask
    if isinstance(mask, _msk.DesignMask):
        gds_layer = gds_layers.get(mask.name, None)
        if gds_layer is not None:
            if not isinstance(gds_layer, tuple):
                gds_layer = (gds_layer, 0)
            return {"gds_layer": gds_layer[0], "gds_datatype": gds_layer[1]}
        else:
            return {}
    else:
        return {}


class _LayerGenerator(_dsp.PrimitiveDispatcher):
    def __init__(self, *, tech: _tch.Technology, gds_layers: GDSLayerSpecDict):
        self.gds_layers = gds_layers
        # TODO: get the poly layers
        self.poly_layers = {
            gate.poly for gate in tech.primitives.__iter_type__(_prm.MOSFETGate)
        }
        self.via_conns = via_conns = cast(Set[_prm.PrimitiveT], set())
        for via in tech.primitives.__iter_type__(_prm.Via):
            via_conns.update(via.bottom)
            via_conns.update(via.top)
        self.blockages = {prim.blockage for prim in filter(
            lambda p: p.blockage is not None,
            tech.primitives.__iter_type__(_prm.BlockageAttrPrimitiveT),
        )}

    def _Primitive(self, prim: _prm.PrimitiveT):
        raise NotImplementedError(
            f"layer code generation for '{prim.__class__.__name__}'"
        )

    def Marker(self, prim: _prm.Marker):
        type_ = "blockage" if prim in self.blockages else "other"
        return _str_create_basic(prim.name, type_, **_args_gds_layer(
            prim, gds_layers=self.gds_layers,
        ))

    def Auxiliary(self, prim):
        return _str_create_basic(prim.name, "other", **_args_gds_layer(
            prim, gds_layers=self.gds_layers,
        ))

    def ExtraProcess(self, prim: _prm.ExtraProcess):
        return _str_create_basic(prim.name, "other", **_args_gds_layer(
            prim, gds_layers=self.gds_layers,
        ))

    def Implant(self, prim: _prm.Implant):
        return _str_create_basic(
            prim.name,
            (
                f"{prim.type_.value}Implant"
                if prim.type_ in (_prm.nImplT, _prm.pImplT)
                else "other"
            ),
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def Insulator(self, prim: _prm.Insulator):
        return _str_create_basic(prim.name, "other", **_args_gds_layer(
            prim, gds_layers=self.gds_layers,
        ))

    def Well(self, prim: _prm.Well):
        return _str_create_basic(
            prim.name, prim.type_.value+"Well",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def DeepWell(self, prim: _prm.DeepWell):
        # Treat it as a regular well
        return _str_create_basic(
            prim.name, prim.type_.value+"Well",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def WaferWire(self, prim: _prm.WaferWire):
        return _str_create_basic(
            prim.name, "active",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def GateWire(self, prim: _prm.GateWire):
        return _str_create_basic(
            prim.name, "poly",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def MetalWire(self, prim: _prm.MetalWire):
        return _str_create_basic(
            prim.name, "metal",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def Via(self, prim: _prm.Via, *, via_layer: bool=False):
        if via_layer:
            return _str_create_via(prim)
        else:
            return _str_create_basic(
                prim.name, "cut",
                minsize=prim.width, minspace=prim.min_space,
                **_args_gds_layer(prim, gds_layers=self.gds_layers),
            )

    def PadOpening(self, prim: _prm.PadOpening):
        return _str_create_basic(
            prim.name, "cut",
            minsize=prim.min_width, minspace=prim.min_space,
            minarea=prim.min_area,
            **_args_gds_layer(prim, gds_layers=self.gds_layers),
        )

    def Resistor(self, prim: _prm.Resistor):
        if len(prim.indicator) == 1:
            s_indicator = f"'{prim.indicator[0].name}'"
        else:
            s_indicator = str(tuple(ind.name for ind in prim.indicator))
        return (
            f"# ResistorLayer.create(tech, '{prim.name}', '{prim.wire.name}', "
            f"{s_indicator})\n"
        )

    def MIMCapacitor(self, prim: _prm.MIMCapacitor):
        return f"# MIMCAP '{prim.name}': '{prim.top}' over '{prim.bottom}'"

    def Diode(self, prim: _prm.Diode):
        if len(prim.indicator) == 1:
            s_indicator = f"'{prim.indicator[0].name}'"
        else:
            s_indicator = str(tuple(ind.name for ind in prim.indicator))
        return (
            f"# DiodeLayer.create(tech, '{prim.name}', '{prim.wire.name}', "
            f"{s_indicator})\n"
        )

    def MOSFETGate(self, prim: _prm.MOSFETGate):
        s_oxide = f", '{prim.oxide.name}'" if prim.oxide is not None else ""
        return (
            f"# GateLayer.create(tech, '{prim.name}', '{prim.active.name}', "
            f"'{prim.poly.name}'{s_oxide})\n"
        )

    def MOSFET(self, prim: _prm.MOSFET):
        impl_names = tuple(impl.name for impl in prim.implant)
        s_impl = f"'{impl_names[0]}'" if len(impl_names) == 1 else str(impl_names)
        s_well = f", '{prim.well.name}'" if prim.well is not None else ""
        return (
            f"# TransistorLayer.create(tech, '{prim.name}', '{prim.gate.name}', "
            f"{s_impl}{s_well})\n"
        )

    def Bipolar(self, prim: _prm.Bipolar):
        return f"# Not implemented: Bipolar '{prim.name}'"


class _AnalogGenerator(_dsp.PrimitiveDispatcher):
    def __init__(self, tech):
        self.tech = tech

    def _Primitive(self, prim: _prm.PrimitiveT):
        raise NotImplementedError(
            f"analog code generation for '{prim.__class__.__name__}'"
        )

    def _DesignMaskPrimitive(self, prim: _prm.DesignMaskPrimitiveT):
        s = ""
        if prim.grid is not None:
            s += f"('grid', '{prim.name}', {prim.grid}, Length, ''),\n"
        return s

    def _WidthSpacePrimitive(self, prim: _prm.WidthSpacePrimitiveT):
        s = f"('minWidth', '{prim.name}', {prim.min_width}, Length, ''),\n"
        s += f"('minSpacing', '{prim.name}', {prim.min_space}, Length, ''),\n"
        if isinstance(prim, _prm.DesignMaskPrimitiveT):
            s += self._DesignMaskPrimitive(prim)
        if prim.min_area is not None:
            s += f"('minArea', '{prim.name}', {prim.min_area}, Area, ''),\n"
        if prim.min_density is not None:
            s += f"('minDensity', '{prim.name}', {prim.min_density}, Unit, ''),\n"
        if prim.max_density is not None:
            s += f"('maxDensity', '{prim.name}', {prim.max_density}, Unit, ''),\n"
        return s

    # primitives handled by base classes of hierarchy:
    # Marker, Auxiliary, ExtraProcess, Implant, Insulator, GateWire, (Top)MetalWire

    def Base(self, prim: _prm.Base):
        return ""

    def Well(self, prim: _prm.Well):
        s = self._WidthSpacePrimitive(prim)
        if prim.min_space_samenet is not None:
            s += f"('minSpacingSameNet', '{prim.name}', {prim.min_space_samenet}, Length, ''),\n"
        return s

    def WaferWire(self, prim: _prm.WaferWire):
        s = self._WidthSpacePrimitive(prim)
        for i in range(len(prim.well)):
            well = prim.well[i]
            enc = prim.min_well_enclosure[i].spec
            s += (
                f"('minEnclosure', '{well.name}', '{prim.name}', {enc},"
                " Length|Asymmetric, ''),\n"
            )
        if prim.min_substrate_enclosure is not None:
            for well in self.tech.primitives.__iter_type__(_prm.Well):
                s += (
                    f"('minSpacing', '{well.name}', '{prim.name}', "
                    f" {prim.min_substrate_enclosure.spec}, Length|Asymmetric, ''),\n"
                )
        s += (
            f"# TODO for {prim.name}:\n"
            "#    allow_in_substrate, implant_abut, allow_contactless_implant, allow_well_crossing\n"
        )
        return s

    def Via(self, prim: _prm.Via):
        s = self._DesignMaskPrimitive(prim)
        s += f"('minWidth', '{prim.name}', {prim.width}, Length, ''),\n"
        s += f"('maxWidth', '{prim.name}', {prim.width}, Length, ''),\n"
        s += f"('minSpacing', '{prim.name}', {prim.min_space}, Length, ''),\n"
        for i in range(len(prim.bottom)):
            bottom = prim.bottom[i]
            enc = prim.min_bottom_enclosure[i].spec
            s += (
                f"('minEnclosure', '{bottom.name}', '{prim.name}', {enc}, "
                "Length|Asymmetric, ''),\n"
            )
        for i in range(len(prim.top)):
            top = prim.top[i]
            enc = prim.min_top_enclosure[i].spec
            s += (
                f"('minEnclosure', '{top.name}', '{prim.name}', {enc}, "
                "Length|Asymmetric, ''),\n"
            )
        return s

    def PadOpening(self, prim: _prm.PadOpening):
        s = self._WidthSpacePrimitive(prim)
        s += (
            f"('minEnclosure', '{prim.bottom.name}', '{prim.name}', "
            f"{prim.min_bottom_enclosure.spec}, Length|Asymmetric, ''),\n"
        )
        return s

    def Resistor(self, prim: _prm.Resistor):
        s = f"('minWidth', '{prim.name}', {prim.min_width}, Length, ''),\n"
        s += f"('minSpacing', '{prim.name}', {prim.min_space}, Length, ''),\n"
        for i in range(len(prim.indicator)):
            ind = prim.indicator[i]
            enc = prim.min_indicator_extension[i]
            s += (
                f"('minEnclosure', '{ind.name}', '{prim.wire.name}', {enc}, "
                "Length|Asymmetric, ''),\n"
            )
        s = indent(s, prefix="# ")
        return s

    def MIMCapacitor(self, prim: _prm.Diode):
        s = f"('minWidth', '{prim.name}', {prim.min_width}, Length, ''),\n"
        s += "# TODO: MIMCapacitor rules\n"

        return s

    def Diode(self, prim: _prm.Diode):
        s = f"('minWidth', '{prim.name}', {prim.min_width}, Length, ''),\n"
        for i in range(len(prim.indicator)):
            ind = prim.indicator[i]
            enc = prim.min_indicator_enclosure[i]
            s += (
                f"('minEnclosure', '{ind.name}', '{prim.wire.name}', {enc.spec}, "
                "Length|Asymmetric, ''),\n"
            )
        s = indent(s, prefix="# ")
        return s

    def MOSFETGate(self, prim: _prm.MOSFETGate):
        s = ""
        if prim.min_l is not None:
            s += f"# ('minTransistorL', '{prim.name}', {prim.min_l}, Length, ''),\n"
        if prim.min_w is not None:
            s += f"# ('minTransistorW', '{prim.name}', {prim.min_w}, Length, ''),\n"
        if prim.min_sd_width is not None:
            s += (
                f"# ('minGateExtension', '{prim.active.name}', '{prim.name}', "
                f"{prim.min_sd_width}, Length|Asymmetric, ''),\n"
            )
        if prim.min_polyactive_extension is not None:
            s += (
                f"# ('minGateExtension', '{prim.poly.name}', '{prim.name}', "
                f"{prim.min_polyactive_extension}, Length|Asymmetric, ''),\n"
            )
        if prim.min_gate_space is not None:
            s += (
                f"# ('minGateSpacing', '{prim.name}', {prim.min_gate_space}, "
                "Length, ''),\n"
            )
        if prim.contact is not None:
            s += (
                f"# ('minGateSpacing', '{prim.contact.name}', '{prim.name}', "
                f"{prim.min_contactgate_space}, Length|Asymmetric, ''),\n"
            )
        return s

    def MOSFET(self, prim: _prm.MOSFET):
        s = ""
        if prim.min_l is not None:
            s += f"# ('minTransistorL', '{prim.name}', {prim.min_l}, Length, ''),\n"
        if prim.min_w is not None:
            s += f"# ('minTransistorW', '{prim.name}', {prim.min_w}, Length, ''),\n"
        if prim.min_sd_width is not None:
            s += (
                f"# ('minGateExtension', '{prim.gate.active.name}', '{prim.name}', "
                f"{prim.min_sd_width}, Length|Asymmetric, ''),\n"
            )
        if prim.min_polyactive_extension is not None:
            s += (
                f"# ('minGateExtension', '{prim.gate.poly.name}', '{prim.name}', "
                f"{prim.min_polyactive_extension}, Length|Asymmetric, ''),\n"
            )
        for i in range(len(prim.implant)):
            impl = prim.implant[i]
            enc = prim.min_gateimplant_enclosure[i].spec
            s += (
                f"# ('minGateEnclosure', '{impl.name}', '{prim.name}', {enc}, "
                "Length|Asymmetric, ''),\n"
            )
        if prim.min_gate_space is not None:
            s += (
                f"# ('minGateSpacing', '{prim.name}', {prim.min_gate_space}, "
                "Length, ''),\n"
            )
        if prim.contact is not None:
            s += (
                f"# ('minGateSpacing', '{prim.contact.name}', '{prim.name}', "
                f"{prim.min_gate_space}, Length, ''),\n"
            )
        return s

    def Bipolar(self, prim: _prm.Bipolar):
        return f"# Not implemented: Bipolar '{prim.name}'\n"

    def MinWidth(self, prim: _prm.MinWidth):
        return f"# ('minWidth', '{prim.prim.name}', '{prim.min_width}', Length, '')"

    def Spacing(self, prim: _prm.Spacing):
        if prim.primitives2 is None:
            return "".join(
                f"('minSpacing', '{prim1.name}', {prim.min_space}, Length, ''),\n"
                for prim1 in prim.primitives1
            )
        else:
            return "".join(
                f"('minSpacing', '{prim1.name}', '{prim2.name}', {prim.min_space}, "
                "Length|Asymmetric, ''),\n"
                for prim1, prim2 in product(prim.primitives1, prim.primitives2)
            )

    def Enclosure(self, prim: _prm.Enclosure):
        return (
            f"('minEnclosure', '{prim.by.name}', '{prim.prim.name}', {prim.min_enclosure.spec}, "
            "Length|Asymmetric, ''),\n"
        )

    def NoOverlap(self, prim: _prm.NoOverlap):
        return f"# ('noOverlap', '{prim.prim1.name}', '{prim.prim2.name}'),\n"


class _LibraryGenerator:
    def __init__(self, *, tech: _tch.Technology):
        self.tech = tech
        self.metals: Tuple[_prm.MetalWire, ...] = tuple(filter(
            lambda m: not isinstance(m, _prm.MIMTop),
            tech.primitives.__iter_type__(_prm.MetalWire),
        ))
        self.vias: Tuple[_prm.Via, ...] = tuple(tech.primitives.__iter_type__(_prm.Via))
        assert len(self.metals) == len(self.vias)
        self.pinmasks = pinmasks = {}
        for prim in tech.primitives.__iter_type__(_prm.PinAttrPrimitiveT):
            if prim.pin is not None:
                assert isinstance(prim, _prm.DesignMaskPrimitiveT)
                pinmasks[prim.pin.mask] = prim.mask

        self.metalsdir: Optional[Tuple[Optional[str], ...]] = None

    def __call__(self, lib: _lbry.Library, *, routinggauge: Optional[_rg.RoutingGauge]):
        def otherdir(dir_):
            return "vertical" if dir_ == "horizontal" else "horizontal"

        if isinstance(lib, _lbry.RoutingGaugeLibrary):
            assert routinggauge is None
            routinggauge = lib.routinggauge[0]

        if routinggauge is not None:
            bottom_idx = self.metals.index(routinggauge.bottom)
            bottom_dir = routinggauge.bottom_direction
            self.metalsdir = tuple(
                None if i < (bottom_idx - 1)
                else bottom_dir if ((i - bottom_idx)%2) == 0
                else otherdir(bottom_dir) # This is also for bottom_idx - 1
                for i in range(len(self.metals))
            )


        s = "\n".join((
            self._s_head(), self._s_routing(lib), self._s_load(lib),
            self._s_setup(lib),
        ))

        self.metalsdir = None

        return s

    def _s_head(self):
        return dedent(f"""
            # Autogenerated file. Changes will be overwritten.

            import CRL, Hurricane, Viewer, Cfg
            from Hurricane import (
                Technology, DataBase, DbU, Library,
                Layer, BasicLayer,
                Cell, Net, Horizontal, Vertical, Rectilinear, Box, Point,
                Instance, Transformation,
                NetExternalComponents,
            )
            from common.colors import toRGB
            from common.patterns import toHexa
            from helpers import u, l
            from helpers.technology import setEnclosures
            from helpers.overlay import CfgCache, UpdateSession

            __all__ = ["setup"]

            def createRL(tech, net, layer, coords):
                coords = [Point(u(x), u(y)) for x,y in coords]
                Rectilinear.create(net, tech.getLayer(layer), coords)
        """[1:])

    def _s_setup(self, lib):
        return dedent(f"""
            def setup():
                lib = _load()
                _routing()
                try:
                    from .{lib.name}_fix import fix
                except:
                    pass
                else:
                    fix(lib)

                return lib
        """[1:])

    def _s_routing(self, lib):
        s = dedent(f"""
            def _routing():
                af = CRL.AllianceFramework.get()
                db = DataBase.getDB()
                tech = db.getTechnology()

        """[1:])

        if isinstance(lib, _lbry.RoutingGaugeLibrary):
            s += indent(
                "\n".join(
                    (self._s_gauge(lib), self._s_pnr(lib), self._s_plugins())
                ),
                prefix="    ",
            )
        else:
            s += "    # No standard cell library\n    pass\n"

        return s

    def _s_gauge(self, lib):
        def s_cordir(dir_):
            return (
                "CRL.RoutingLayerGauge.Horizontal" if dir_ == "horizontal"
                else "CRL.RoutingLayerGauge.Vertical"
            )

        assert len(lib.routinggauge) == 1
        rg = lib.routinggauge[0]
        s = dedent(f"""
            rg = CRL.RoutingGauge.create('{lib.name}')
            rg.setSymbolic(False)
        """[1:])
        bottom_idx = self.metals.index(rg.bottom)
        top_idx = self.metals.index(rg.top)

        depth = 0
        mwidths = tuple(
            self.tech.computed.min_width(
                metal, up=True, down=True, min_enclosure=(i < bottom_idx),
            )
            for i, metal in enumerate(self.metals)
        )
        for i in range(max(bottom_idx - 1, 0), top_idx+1):
            assert self.metalsdir is not None
            routedir = self.metalsdir[i]
            assert routedir is not None
            s_usage = (
                "CRL.RoutingLayerGauge.PinOnly" if i < bottom_idx
                else "CRL.RoutingLayerGauge.Default"
            )

            metal = self.metals[i]
            mwidth = round(mwidths[i], 6)
            s += f"metal = tech.getLayer('{metal.name}')\n"
            mpwidth = round(
                self.tech.computed.min_width(
                    metal, up=False, down=True, min_enclosure=True
                ), 6,
            )
            if i >= bottom_idx:
                mpitch = round(
                    rg.pitches.get(
                        metal,
                        self.tech.computed.min_pitch(metal, up=True, down=True),
                    ),
                    6,
                )
                offset = rg.offsets.get(metal, 0.0)
            else:
                # For pin only layer take pitch/offset of two levels up
                altmetal = self.metals[i+2]
                mpitch = round(
                    rg.pitches.get(
                        altmetal,
                        self.tech.computed.min_pitch(altmetal, up=True, down=True),
                    ),
                    6,
                )
                offset = rg.offsets.get(altmetal, 0.0)
            s_pindir = s_cordir(routedir)
            dw = metal.min_space

            vwidth = None

            # Via below
            if i > 0:
                mwidth_below = mwidths[i - 1]
                via = self.vias[i]
                metal_idx = via.top.index(metal)
                enc = via.min_top_enclosure[metal_idx]
                metal2 = self.metals[i-1]
                via_name = f"{metal2.name}_{via.name}_{metal.name}"
                if routedir == "horizontal":
                    henc = max(enc.min(), 0.5*(mwidth_below - via.width))
                    venc = max(enc.max(), 0.5*(mwidth - via.width))
                else:
                    henc = max(enc.max(), 0.5*(mwidth - via.width))
                    venc = max(enc.min(), 0.5*(mwidth_below - via.width))
                s += dedent(f"""
                    via = tech.getLayer('{via_name}')
                    setEnclosures(via, metal, (u({henc:.6}), u({venc:.6})))
                """[1:])
                vwidth = via.width

            # Via above (only if it exists)
            if i < (len(self.vias) - 1):
                mwidth_above = mwidths[i + 1]
                via = self.vias[i + 1]
                metal_idx = via.bottom.index(metal)
                enc = via.min_bottom_enclosure[metal_idx]
                metal2 = self.metals[i+1]
                via_name = f"{metal.name}_{via.name}_{metal2.name}"
                if routedir == "horizontal":
                    henc = max(enc.max(), 0.5*(mwidth_above - via.width))
                    venc = max(enc.min(), 0.5*(mwidth - via.width))
                else:
                    henc = max(enc.min(), 0.5*(mwidth - via.width))
                    venc = max(enc.max(), 0.5*(mwidth_above - via.width))
                s += dedent(f"""
                    via = tech.getLayer('{via_name}')
                    setEnclosures(via, metal, (u({henc:.6}), u({venc:.6})))
                """[1:])
                vwidth = via.width

            assert vwidth is not None

            s += dedent(f"""
                rg.addLayerGauge(CRL.RoutingLayerGauge.create(
                    metal, {s_pindir}, {s_usage}, {depth}, 0.0,
                    u({offset:.6}), u({mpitch:.6}), u({mwidth:.6}), u({mpwidth:.6}), u({vwidth:.6}), u({dw:.6}),
                ))
            """[1:])

            depth += 1

        s += dedent(f"""
            af.addRoutingGauge(rg)
            af.setRoutingGauge('{lib.name}')

            cg = CRL.CellGauge.create(
                '{lib.name}', '{self.metals[1].name}',
                u({lib.pingrid_pitch}), u({lib.row_height}), u({lib.pingrid_pitch}),
            )
            af.addCellGauge(cg)
            af.setCellGauge('{lib.name}')
        """[1:])

        return s

    def _s_pnr(self, lib):
        topmetal = tuple(self.tech.primitives.__iter_type__(_prm.MetalWire))[-1]
        return dedent(f"""
            # Place & Route setup
            with CfgCache(priority=Cfg.Parameter.Priority.ConfigurationFile) as cfg:
                cfg.lefImport.minTerminalWidth = 0.0
                cfg.crlcore.groundName = 'vss'
                cfg.crlcore.powerName = 'vdd'
                cfg.etesian.aspectRatio = 1.00
                cfg.etesian.aspectRatio = [10, 1000]
                cfg.etesian.spaceMargin = 0.10
                cfg.etesian.uniformDensity = True
                cfg.etesian.routingDriven = False
                cfg.etesian.latchUpDistance = u(30.0 - 1.0)
                cfg.etesian.diodeName = 'diode_w1'
                cfg.etesian.antennaInsertThreshold = 0.50
                cfg.etesian.antennaMaxWL = u(250.0)
                cfg.etesian.feedNames = 'tie,decap_w0'
                cfg.etesian.cell.zero = 'zero_x1'
                cfg.etesian.cell.one = 'one_x1'
                cfg.etesian.bloat = 'disabled'
                cfg.etesian.effort = 2
                cfg.etesian.effort = (
                    ('Fast', 1),
                    ('Standard', 2),
                    ('High', 3 ),
                    ('Extreme', 4 ),
                )
                cfg.etesian.graphics = 2
                cfg.etesian.graphics = (
                    ('Show every step', 1),
                    ('Show lower bound', 2),
                    ('Show result only', 3),
                )
                cfg.anabatic.routingGauge = '{lib.name}'
                cfg.anabatic.globalLengthThreshold = 1450
                cfg.anabatic.saturateRatio = 0.90
                cfg.anabatic.saturateRp = 10
                cfg.anabatic.topRoutingLayer = '{topmetal.name}'
                cfg.anabatic.edgeLength = 48
                cfg.anabatic.edgeWidth = 8
                cfg.anabatic.edgeCostH = 9.0
                cfg.anabatic.edgeCostK = -10.0
                cfg.anabatic.edgeHInc = 1.0
                cfg.anabatic.edgeHScaling = 1.0
                cfg.anabatic.globalIterations = 10
                cfg.anabatic.globalIterations = [ 1, 100 ]
                cfg.anabatic.gcell.displayMode = 1
                cfg.anabatic.gcell.displayMode = (("Boundary", 1), ("Density", 2))
                cfg.katana.hTracksReservedLocal = 4
                cfg.katana.hTracksReservedLocal = [0, 20]
                cfg.katana.vTracksReservedLocal = 3
                cfg.katana.vTracksReservedLocal = [0, 20]
                cfg.katana.termSatReservedLocal = 8
                cfg.katana.termSatThreshold = 9
                cfg.katana.eventsLimit = 4000002
                cfg.katana.ripupCost = 3
                cfg.katana.ripupCost = [0, None]
                cfg.katana.strapRipupLimit = 16
                cfg.katana.strapRipupLimit = [1, None]
                cfg.katana.localRipupLimit = 9
                cfg.katana.localRipupLimit = [1, None]
                cfg.katana.globalRipupLimit = 5
                cfg.katana.globalRipupLimit = [1, None]
                cfg.katana.longGlobalRipupLimit = 5
                cfg.chip.padCoreSide = 'South'
        """[1:])

    def _s_plugins(self):
        return dedent(f"""
            # Plugins setup
            with CfgCache(priority=Cfg.Parameter.Priority.ConfigurationFile) as cfg:
                cfg.viewer.minimumSize = 500
                cfg.viewer.pixelThreshold = 10
                cfg.chip.block.rails.count = 5
                cfg.chip.block.rails.hWidth = u(2.68)
                cfg.chip.block.rails.vWidth = u(2.68)
                cfg.chip.block.rails.hSpacing = u(0.7)
                cfg.chip.block.rails.vSpacing = u(0.7)
                cfg.clockTree.minimumSide = l(600)
                cfg.clockTree.buffer = 'buf_x1'
                cfg.clockTree.placerEngine = 'Etesian'
                cfg.block.spareSide = 10
                cfg.spares.buffer = 'buf_x4'
                cfg.spares.maxSinks = 31
        """[1:])

    def _s_load(self, lib):
        s = dedent(f"""
            def _load():
                af = CRL.AllianceFramework.get()
                db = DataBase.getDB()
                tech = db.getTechnology()
                rootlib = db.getRootLibrary()

                lib = Library.create(rootlib, '{lib.name}')
        """)

        # Trigger layout generation
        for cell in lib.cells:
            l = cell.layout

        s += "    new_cells = {\n"
        s += "".join(
            f"        '{cell.name}': Cell.create(lib, '{cell.name}'),\n"
            for cell in lib.cells
        )
        s += "    }\n"

        s += indent(
            "".join(self._s_cell(lib, cell) for cell in lib.cells),
            prefix="    ",
        )

        s += indent(dedent("""
            af.wrapLibrary(lib, 0)

            return lib
        """), prefix="    ")

        return s

    def _s_cell(self, lib: _lbry.Library, cell: _cell.Cell):
        try:
            s = dedent(f"""
                cell = new_cells['{cell.name}']
                with UpdateSession():
            """)

            if hasattr(cell, "layout"):
                layout = cell.layout
                bnd = layout.boundary
                assert bnd is not None, f"Cell boundary needed for {cell.name}"

                pls = tuple(layout._sublayouts.__iter_type__(_laylay._MaskShapesSubLayout))
                def get_netname(sl: _laylay._MaskShapesSubLayout):
                    return "*" if sl.net is None else sl.net.name

                netnames = {get_netname(sl) for sl in pls}

                s += (
                    "    cell.setAbutmentBox(Box(\n"
                    f"        u({bnd.left}), u({bnd.bottom}), u({bnd.right}), u({bnd.top}),\n"
                    "    ))\n"
                    "    nets = {\n"
                    + "\n".join(
                        f"        '{net}': Net.create(cell, '{net}'),"
                        for net in sorted(netnames)
                    )
                    + "\n    }\n"
                )

                for sl in pls:
                    s += indent(
                        f"net = nets['{get_netname(sl)}']\n" +
                        "".join(self._s_shape(ms) for ms in sl.shapes),
                        prefix="    ",
                    )

                for sl in layout._sublayouts.__iter_type__(_laylay._InstanceSubLayout):
                    # Currently usage of af.getCell() may not work as intended when
                    # two libraries have a cell with the same name.
                    # TODO: support libraries with cells with same name properly
                    r = {
                        _geo.Rotation.R0: "ID",
                        _geo.Rotation.R90: "R1",
                        _geo.Rotation.R180: "R2",
                        _geo.Rotation.R270: "R3",
                        _geo.Rotation.MX: "MX",
                        _geo.Rotation.MX90: "XR",
                        _geo.Rotation.MY: "MY",
                        _geo.Rotation.MY90: "YR",
                    }[sl.rotation]
                    s += indent(
                        dedent(f"""
                            try:
                                subcell = new_cells['{sl.inst.cell.name}']
                            except:
                                subcell = af.getCell('{sl.inst.cell.name}', 0)
                            trans = Transformation(
                                u({sl.origin.x}), u({sl.origin.y}), Transformation.Orientation.{r},
                            )
                            Instance.create(
                                cell, '{sl.inst.name}', subcell, trans,
                                Instance.PlacementStatus.PLACED,
                            )
                        """[1:]),
                        prefix = "    "
                    )
            return s
        except NotImplementedError:
            return f"# Export failed for cell '{cell.name}'"

    def _s_shape(self, shape: _geo.MaskShape) -> str:
        metalmasks = tuple(metal.mask for metal in self.metals)

        mask = shape.mask
        s = ""

        for ps in shape.shape.pointsshapes:
            coords = tuple(ps.points)

            if mask in self.pinmasks:
                metalmask = self.pinmasks[mask]
                metalidx = metalmasks.index(metalmask)
                if len(coords) != 5:
                    raise NotImplementedError(
                        f"Non-rectangular pin with coords '{coords}'"
                    )
                xs = tuple(coord.x for coord in coords)
                ys = tuple(coord.y for coord in coords)
                left = min(xs)
                right = max(xs)
                bottom = round(min(ys), 6)
                top = round(max(ys), 6)
                width = round(right - left, 6)
                height = round(top - bottom, 6)

                routingdir = (
                    None if self.metalsdir is None
                    else self.metalsdir[metalidx]
                )
                if routingdir is None:
                    routingdir = (
                        "vertical" if height > width
                        else "horizontal"
                    )

                if routingdir == "vertical":
                    x = round(0.5*(left + right), 6)
                    s += dedent(f"""
                        Vertical.create(
                            net, tech.getLayer('{mask.name}'),
                            u({x}), u({width}), u({bottom}), u({top}),
                        )
                        pin = Vertical.create(
                            net, tech.getLayer('{metalmask.name}'),
                            u({x}), u({width}), u({bottom}), u({top}),
                        )
                        net.setExternal(True)
                        NetExternalComponents.setExternal(pin)
                    """[1:])
                else:
                    assert routingdir == "horizontal"
                    y = round(0.5*(bottom + top), 6)
                    s += dedent(f"""
                        Horizontal.create(
                            net, tech.getLayer('{mask.name}'),
                            u({y}), u({height}), u({left}), u({right}),
                        )
                        pin = Horizontal.create(
                            net, tech.getLayer('{metalmask.name}'),
                            u({y}), u({height}), u({left}), u({right}),
                        )
                        net.setExternal(True)
                        NetExternalComponents.setExternal(pin)
                    """[1:])

            else:
                s += dedent(f"""
                    createRL(
                        tech, net, '{mask.name}',
                        ({",".join(self._s_point(point) for point in coords)}),
                    )
                """[1:])

        return s

    def _s_point(self, point: _geo.Point):
        # TODO: put on grid
        x = round(point.x, 6)
        y = round(point.y, 6)

        return f"({x},{y})"


class _TechnologyGenerator:
    def __call__(self, *, tech: _tch.Technology, gds_layers: GDSLayerSpecDict):
        self.tech = tech
        self.gds_layers = gds_layers

        return "\n".join((
            self._s_head(), self._s_analog(), self._s_technology(),
            self._s_display(), self._s_setup(),
        ))

    def _s_head(self):
        return dedent(f"""
            # Autogenerated file. Changes will be overwritten.

            import CRL, Hurricane, Viewer, Cfg
            from Hurricane import (
                Technology, DataBase, DbU, Library,
                Layer, BasicLayer,
                Cell, Net, Horizontal, Vertical, Rectilinear, Box, Point,
                NetExternalComponents,
            )
            from common.colors import toRGB
            from common.patterns import toHexa
            from helpers import u
            from helpers.technology import createBL, createVia
            from helpers.overlay import CfgCache
            from helpers.analogtechno import Length, Area, Unit, Asymmetric, loadAnalogTechno

            __all__ = ["analogTechnologyTable", "setup"]
        """[1:])

    def _s_setup(self):
        return dedent(f"""
            def setup():
                _setup_techno()
                _setup_display()
                loadAnalogTechno(analogTechnologyTable, __file__)
                try:
                    from .techno_fix import fix
                except:
                    pass
                else:
                    fix()
        """[1:])

    def _s_technology(self):
        gen = _LayerGenerator(tech=self.tech, gds_layers=self.gds_layers)

        # Take smallest transistor length as lambda
        lambda_ = min(
            trans.computed.min_l
            for trans in self.tech.primitives.__iter_type__(_prm.MOSFET)
        )

        assert (self.tech.grid % 1e-6) < 1e-9, "Unsupported grid"

        s_head = dedent(f"""
            def _setup_techno():
                db = DataBase.create()
                CRL.System.get()

                tech = Technology.create(db, '{self.tech.name}')

                DbU.setPrecision(2)
                DbU.setPhysicalsPerGrid({self.tech.grid}, DbU.UnitPowerMicro)
                with CfgCache(priority=Cfg.Parameter.Priority.ConfigurationFile) as cfg:
                    cfg.gdsDriver.metricDbu = {1e-6*self.tech.dbu}
                    cfg.gdsDriver.dbuPerUu = {self.tech.dbu}
                DbU.setGridsPerLambda({round(lambda_/self.tech.grid)})
                DbU.setSymbolicSnapGridStep(DbU.fromGrid(1.0))
                DbU.setPolygonStep(DbU.fromGrid(1.0))
                DbU.setStringMode(DbU.StringModePhysical, DbU.UnitPowerMicro)

        """[1:])

        s_prims = ""
        written_prims = set()
        vias = tuple(self.tech.primitives.__iter_type__(_prm.Via))

        for prim in self.tech.primitives:
            # Some primitives are handled later or don't need to be handled
            if isinstance(prim, (
                # Handled by Via
                _prm.WaferWire, _prm.GateWire, _prm.MetalWire,
                # Handled later
                _prm.Resistor, _prm.MIMCapacitor, _prm.Diode,
                _prm.MOSFETGate, _prm.MOSFET, _prm.Bipolar,
                # Not exported
                _prm.Base, _prm.RulePrimitiveT,
            )):
                continue

            # We have to make sure via layers are defined in between top and bottom
            # metal layers
            if isinstance(prim, _prm.Via):
                for prim2 in prim.bottom:
                    s_prims += gen(prim2)
                    written_prims.add(prim2)

            # Do not generate layer for Auxiliary layers to avoid having too many
            # layer definitions. Still mark the layer as written.
            # if not isinstance(prim, prm.Auxiliary):
                # s_prims += gen(prim)
            s_prims += gen(prim)
            written_prims.add(prim)

            # For top via also do the top layers
            if isinstance(prim, _prm.Via) and prim == vias[-1]:
                for prim2 in prim.top:
                    s_prims += gen(prim2)
                    written_prims.add(prim2)

        # Check if all basic layers were included
        unhandled_masks = (
            {prim.name for prim in written_prims}
            - {mask.name for mask in self.tech.designmasks}
        )
        if unhandled_masks:
            raise NotImplementedError(
                f"Layer generation for masks {unhandled_masks} not implemented",
            )

        s_prims += "\n# ViaLayers\n"
        for via in vias:
            s_prims += gen(via, via_layer=True)

        s_prims += "\n# Blockages\n"
        for prim in filter(
            lambda p: p.blockage is not None,
            self.tech.primitives.__iter_type__(_prm.BlockageAttrPrimitiveT),
        ):
            s_prims += dedent(f"""
                tech.getLayer('{prim.name}').setBlockageLayer(
                    tech.getLayer('{cast(_prm.Marker, prim.blockage).name}')
                )
            """[1:])

        s_prims += "\n# Coriolis internal layers\n"
        for name, mat in (
            ("text.cell", "other"),
            ("text.instance", "other"),
            ("SPL1", "other"),
            ("AutoLayer", "other"),
            ("gmetalh", "metal"),
            ("gcontact", "cut"),
            ("gmetalv", "metal"),
        ):
            s_prims += _str_create_basic(name, mat)

        s_prims += "\n# Resistors\n"
        for prim in self.tech.primitives.__iter_type__(_prm.Resistor):
            assert prim not in written_prims
            s_prims += gen(prim)
            written_prims.add(prim)

        s_prims += "\n# Capacitors\n"
        for prim in self.tech.primitives.__iter_type__(_prm.MIMCapacitor):
            assert prim not in written_prims
            s_prims += gen(prim)
            written_prims.add(prim)

        s_prims += "\n# Transistors\n"
        for prim in self.tech.primitives.__iter_type__((_prm.MOSFETGate, _prm.MOSFET)):
            assert prim not in written_prims
            s_prims += gen(prim)
            written_prims.add(prim)

        s_prims += "\n# Bipolars\n"
        for prim in self.tech.primitives.__iter_type__(_prm.Bipolar):
            assert prim not in written_prims
            s_prims += gen(prim)
            written_prims.add(prim)

        return s_head + indent(s_prims, prefix="    ")

    def _s_analog(self):
        gen = _AnalogGenerator(self.tech)

        s = dedent(f"""
            analogTechnologyTable = (
                ('Header', '{self.tech.name}', DbU.UnitPowerMicro, 'alpha'),
                ('PhysicalGrid', {self.tech.grid}, Length, ''),

            """[1:]
        )
        s += indent(
            "".join(gen(prim) for prim in self.tech.primitives),
            prefix="    ",
        )
        s += ")\n"

        return s

    def _s_display(self):
        s = dedent("""
            def _setup_display():
                # ----------------------------------------------------------------------
                # Style: Alliance.Classic [black]

                threshold = 0.2 if Viewer.Graphics.isHighDpi() else 0.1

                style = Viewer.DisplayStyle( 'Alliance.Classic [black]' )
                style.setDescription( 'Alliance Classic Look - black background' )
                style.setDarkening  ( Viewer.DisplayStyle.HSVr(1.0, 3.0, 2.5) )

                # Viewer.
                style.addDrawingStyle( group='Viewer', name='fallback'      , color=toRGB('Gray238'    ), border=1, pattern='55AA55AA55AA55AA' )
                style.addDrawingStyle( group='Viewer', name='background'    , color=toRGB('Gray50'     ), border=1 )
                style.addDrawingStyle( group='Viewer', name='foreground'    , color=toRGB('White'      ), border=1 )
                style.addDrawingStyle( group='Viewer', name='rubber'        , color=toRGB('192,0,192'  ), border=4, threshold=0.02 )
                style.addDrawingStyle( group='Viewer', name='phantom'       , color=toRGB('Seashell4'  ), border=1 )
                style.addDrawingStyle( group='Viewer', name='boundaries'    , color=toRGB('wheat1'     ), border=2, pattern='0000000000000000', threshold=0 )
                style.addDrawingStyle( group='Viewer', name='marker'        , color=toRGB('80,250,80'  ), border=1 )
                style.addDrawingStyle( group='Viewer', name='selectionDraw' , color=toRGB('White'      ), border=1 )
                style.addDrawingStyle( group='Viewer', name='selectionFill' , color=toRGB('White'      ), border=1 )
                style.addDrawingStyle( group='Viewer', name='grid'          , color=toRGB('White'      ), border=1, threshold=2.0 )
                style.addDrawingStyle( group='Viewer', name='spot'          , color=toRGB('White'      ), border=2, threshold=6.0 )
                style.addDrawingStyle( group='Viewer', name='ghost'         , color=toRGB('White'      ), border=1 )
                style.addDrawingStyle( group='Viewer', name='text.ruler'    , color=toRGB('White'      ), border=1, threshold=  0.0 )
                style.addDrawingStyle( group='Viewer', name='text.instance' , color=toRGB('White'      ), border=1, threshold=400.0 )
                style.addDrawingStyle( group='Viewer', name='text.reference', color=toRGB('White'      ), border=1, threshold=200.0 )
                style.addDrawingStyle( group='Viewer', name='undef'         , color=toRGB('Violet'     ), border=0, pattern='2244118822441188' )
        """[1:])

        clrs = ("Blue", "Aqua", "LightPink", "Green", "Yellow", "Violet", "Red")

        s += "\n    # Active Layers.\n"
        for prim in self.tech.primitives.__iter_type__(_prm.Well):
            rgb = "Tan" if prim.type_ == _prm.nImplT else "LightYellow"
            s += (
                f"    style.addDrawingStyle(group='Active Layers', name='{prim.name}'"
                f", color=toRGB('{rgb}'), pattern=toHexa('urgo.8'), border=1"
                ", threshold=threshold)\n"
            )
        for prim in filter(
            lambda p: not isinstance(p, _prm.Well),
            self.tech.primitives.__iter_type__(_prm.Implant),
        ):
            rgb = "LawnGreen" if prim.type_ == _prm.nImplT else "Yellow"
            s += (
                f"    style.addDrawingStyle(group='Active Layers', name='{prim.name}'"
                f", color=toRGB('{rgb}'), pattern=toHexa('antihash0.8'), border=1"
                ", threshold=threshold)\n"
            )
        for prim in self.tech.primitives.__iter_type__(_prm.WaferWire):
            s += (
                f"    style.addDrawingStyle(group='Active Layers', name='{prim.name}'"
                ", color=toRGB('White'), pattern=toHexa('antihash0.8'), border=1"
                ", threshold=threshold)\n"
            )
            if prim.pin is not None:
                s += (
                    "    style.addDrawingStyle(group='Active Layers'"
                    f", name='{prim.pin.name}', color=toRGB('White')"
                    ", pattern=toHexa('antihash0.8'), border=2"
                    ", threshold=threshold)\n"
                )
        for i, prim in enumerate(self.tech.primitives.__iter_type__(_prm.GateWire)):
            rgb = "Red" if i == 0 else "Orange"
            s += (
                f"    style.addDrawingStyle(group='Active Layers', name='{prim.name}'"
                f", color=toRGB('{rgb}'), pattern=toHexa('antihash0.8'), border=1"
                ", threshold=threshold)\n"
            )
            if prim.pin is not None:
                s += (
                    "    style.addDrawingStyle(group='Active Layers'"
                    f", name='{prim.pin.name}', color=toRGB('{rgb}')"
                    ", pattern=toHexa('antihash0.8'), border=2"
                    ", threshold=threshold)\n"
                )

        s += "\n    # Routing Layers.\n"
        for i, prim in enumerate(self.tech.primitives.__iter_type__(_prm.MetalWire)):
            rgb = clrs[i%len(clrs)]
            hexa = "slash.8" if i == 0 else "poids4.8"
            s += (
                f"    style.addDrawingStyle(group='Routing Layers', name='{prim.name}'"
                f", color=toRGB('{rgb}'), pattern=toHexa('{hexa}'), border=1"
                ", threshold=threshold)\n"
            )
            if prim.pin is not None:
                s += (
                    f"    style.addDrawingStyle(group='Routing Layers'"
                    f", name='{prim.pin.name}', color=toRGB('{rgb}'), pattern=toHexa('{hexa}')"
                    ", border=2, threshold=threshold)\n"
                )

        s += "\n    # Cuts (VIA holes).\n"
        for i, prim in enumerate(
            self.tech.primitives.__iter_type__((_prm.Via, _prm.PadOpening)),
        ):
            rgb = clrs[i%len(clrs)] if i > 0 else "0,150,150"
            s += (
                f"    style.addDrawingStyle(group='Cuts (VIA holes', name='{prim.name}'"
                f", color=toRGB('{rgb}'), threshold=threshold)\n"
            )

        s += "\n    # Blockages.\n"
        blockages = {prim.blockage for prim in filter(
            lambda p: p.blockage is not None,
            self.tech.primitives.__iter_type__(_prm.BlockageAttrPrimitiveT),
        )}
        for i, prim in enumerate(filter(
            lambda p: p in blockages, self.tech.primitives.__iter_type__(_prm.Marker)
        )):
            rgb = clrs[i%len(clrs)]
            hexa = "slash.8" if i == 0 else "poids4.8"
            s += (
                f"    style.addDrawingStyle(group='Blockages', name='{prim.name}'"
                f", color=toRGB('{rgb}'), pattern=toHexa('{hexa}')"
                ", border=4, threshold=threshold)\n"
            )

        s += indent(dedent("""

            # Knick & Kite.
            style.addDrawingStyle( group='Knik & Kite', name='SPL1'           , color=toRGB('Red'        ) )
            style.addDrawingStyle( group='Knik & Kite', name='AutoLayer'      , color=toRGB('Magenta'    ) )
            style.addDrawingStyle( group='Knik & Kite', name='gmetalh'        , color=toRGB('128,255,200'), pattern=toHexa('antislash2.32'    ), border=1 )
            style.addDrawingStyle( group='Knik & Kite', name='gmetalv'        , color=toRGB('200,200,255'), pattern=toHexa('light_antihash1.8'), border=1 )
            style.addDrawingStyle( group='Knik & Kite', name='gcontact'       , color=toRGB('255,255,190'),                                      border=1 )
            style.addDrawingStyle( group='Knik & Kite', name='Anabatic::Edge' , color=toRGB('255,255,190'), pattern='0000000000000000'         , border=4, threshold=0.02 )
            style.addDrawingStyle( group='Knik & Kite', name='Anabatic::GCell', color=toRGB('255,255,190'), pattern='0000000000000000'         , border=2, threshold=threshold )

            Viewer.Graphics.addStyle( style )

            # ----------------------------------------------------------------------
            # Style: Alliance.Classic [white].

            style = Viewer.DisplayStyle( 'Alliance.Classic [white]' )
            style.inheritFrom( 'Alliance.Classic [black]' )
            style.setDescription( 'Alliance Classic Look - white background' )
            style.setDarkening  ( Viewer.DisplayStyle.HSVr(1.0, 3.0, 2.5) )

            style.addDrawingStyle( group='Viewer', name='background', color=toRGB('White'), border=1 )
            style.addDrawingStyle( group='Viewer', name='foreground', color=toRGB('Black'), border=1 )
            style.addDrawingStyle( group='Viewer', name='boundaries', color=toRGB('Black'), border=1, pattern='0000000000000000' )
            Viewer.Graphics.addStyle( style )

            Viewer.Graphics.setStyle( 'Alliance.Classic [black]' )
        """[1:]), prefix="    ")

        return s


[docs]class FileExporter: def __init__(self, *, tech: _tch.Technology, gds_layers: GDSLayerSpecDict): self.tech = tech self.gds_layers = gds_layers @overload def __call__(self, obj: None, *, routinggauge: None=None, ) -> str: ... @overload def __call__(self, obj: _lbry.RoutingGaugeLibrary, *, routinggauge: None=None, ) -> str: ... @overload def __call__(self, obj: _lbry.Library, *, routinggauge: Optional[_rg.RoutingGauge]=None, ) -> str: ... def __call__(self, obj: Optional[_lbry.Library]=None, *, routinggauge: Optional[_rg.RoutingGauge]=None, ) -> str: if obj is None: s = self._s_tech() elif isinstance(obj, _lbry.Library): s = self._s_lib(obj, routinggauge=routinggauge) else: raise TypeError("object has to be None or of type 'Library'") return s def _s_tech(self): gen = _TechnologyGenerator() return gen(tech=self.tech, gds_layers=self.gds_layers) def _s_lib(self, lib: _lbry.Library, *, routinggauge: Optional[_rg.RoutingGauge]): gen = _LibraryGenerator(tech=self.tech) return gen(lib, routinggauge=routinggauge)