Cross sections and extrusion
gf.CrossSection is the union of gf.SymmetricCrossSection
(kfactory.DCrossSection) and gf.AsymmetricCrossSection
(kfactory.DAsymmetricCrossSection). Both use micrometers. Factories and
gf.get_cross_section() return these objects directly. Their profiles travel
with ports through GDS and OAS metadata; no separate gdsfactory profile registry
is needed to recover them.
Create a profile with gf.cross_section.cross_section(). Auxiliary strips use
absolute signed bounds, or kfactory.DCrossSectionLayer:
import gdsfactory as gf
gf.gpdk.PDK.activate()
xs = gf.cross_section.cross_section(
width=0.5, layer="WG",
sections=[("SLAB90", -3.25, 3.25)],
bbox_layers=["DEVREC"], bbox_offsets=[2.0],
)
sections = xs.get_sections() # main strip first, absolute micrometer bounds
component = gf.path.straight(10).extrude(xs)
Each bound is snapped independently with kcl.to_dbu(). KLayout rounds halfway
values away from zero: at 1 nm DBU, ±0.2505 µm becomes ±251 DBU. A centered
nominal width of 0.501 µm therefore becomes 0.502 µm and remains symmetric.
Odd realized spans are represented by the asymmetric type. Symmetry is
tested after snapping and merging auxiliary overlaps, with the main strip kept
separate. Layer names participate in automatic names; use consistent layer
names across layouts for stable names.
Symmetric profiles store auxiliary bands relative to the core edge. A 3 µm
enclosure reaches ±3.25 µm around a 0.5 µm core and ±3.55 µm around a 1.1 µm
core. get_sections() resolves these bands into absolute strips. Ring bands
produce two strips; touching strips merge; fully shrunken bands disappear.
gf.cross_section.with_width(xs, width) explicitly preserves the auxiliary
strips' absolute bounds while replacing the main width.
Port names, port types, variable widths and offsets, simplification, insets,
and hidden strips belong to extrusion, not to a profile. Dictionaries below
select indices in get_sections():
component = gf.path.straight(10, npoints=101).extrude(
xs,
width_function=lambda t: 0.5 + t,
ports={0: ("input", "output", "optical")},
insets={1: (1.0, 2.0)},
simplify=0.01,
)
ports={} suppresses all ports. Without an explicit map, the PDK's
layer_port_types chooses the main port type and auxiliary_port_types chooses
auxiliary layers with ports, keyed by (main_layer, auxiliary_layer) physical
layer tuples. The generic PDK enables heater contacts this way.
Names use per-type counters (o1, o2, e1, e2, …).
Use an explicit map for device-specific contacts. An auxiliary port carries
only its own recentered strip, with its offset in the port transform. An odd
strip span uses asymmetric bounds so the port origin stays on the grid.
Transitions still use gf.path.transition() or transition_asymmetric().
Extrusion pairs the main strips and matches auxiliary strips by layer and
signed-bound order. Use section_pairs for a different pairing and
skip_transition or hidden to omit selected geometry. Attach repeated
components separately with gf.path.along_path().
Dynamic-width and offset extrusions attach the realized endpoint profile,
including auxiliary strips, to each main port. A taper may end at zero width
when that endpoint has no port: use a None endpoint name or ports={}.
Copying an asymmetric port
preserves its mirror state by default; connections between identical asymmetric
profiles use mirror=True, use_mirror=True.
Regression changes
No GDS geometry goldens were regenerated. Settings/netlist snapshots changed
for canonical cross-section names, physical LayerInfo tuples, the removal of
strip_no_ports, and explicit extrusion parameters. Instance placements and
netlist connectivity were compared before accepting those changes.
The rib preset no longer stores its former 0.05 µm slab simplification.
For ring_single(cross_section="rib"), comparison with the original layout
found identical WG geometry and a 3.43365 µm² SLAB90 XOR. The slab area changes
from 1066.994205 to 1067.644168 µm²; after fix_spacing(min_space=1) the tested
area is 1070.968887 µm². ring_single_pn explicitly supplies the former slab
tolerance at extrusion time and matches its existing geometry golden.
Bounding-box layers
Both kfactory cross-section types store bbox_sections as a mapping from
LayerInfo to padding in micrometers. To draw them during extrusion, use
path.extrude(xs, add_bbox=True) or
gf.path.extrude(path, xs, add_bbox=True). The flag defaults to False and does
not change the profile or its port metadata. Padding surrounds all emitted
geometry, including auxiliary strips, after applying extrusion controls.
An empty extrusion produces no bbox geometry.
For a manually drawn component, use kfactory's xs.add_bbox() directly:
xs.add_bbox(component) # all geometry, including child instances
xs.add_bbox(component, ref=xs.layer) # only the main layer's bounds
xs.add_bbox(component, ref=instance) # bounds after instance placement
xs.add_bbox(component, ref=gf.kdb.DBox(0, -1, 10, 1), top=0)
These are alternative ways to select the reference bounds, not calls to stack.
ref also accepts a layer index or an integer Box in the target layout's dbu.
Instances (Instance, DInstance, or VInstance) use their transformed bounds
in their parent's coordinates; these must match the target component's coordinate
system. Cells are not accepted as ref; pass cell.dbbox() explicitly instead.
The cross section, target, and instance reference must share the same KCLayout
object. Equal DBUs do not make different layouts interchangeable: their layer
indices can differ. A mismatch raises ValueError before drawing or layer lookup.
Real cells use integer-DBU geometry; virtual cells use a separate micrometer
implementation that preserves off-grid bounds and padding overrides.
The method snapshots the reference bounds so padding does not accumulate
between layers. Optional top, bottom, left, and right override padding
for that edge on every bbox layer, in the cross section's units. Bend factories
use these overrides to retain their existing clipping. The separate
gf.cross_section.add_bbox() helper is removed.
For a Component with pending vinsts, bounds include approximate virtual-instance
geometry and xs.add_bbox() logs a warning, even with an explicit reference.
Call component.insert_vinsts() before xs.add_bbox(component) for bounds based
on materialized geometry. Adding bbox layers does not insert virtual instances
automatically. ComponentAllAngle uses virtual instances normally and does not
emit this warning merely because it contains them.
Radius defaults
validate_radius(xs, radius) remains a function in gf.cross_section.
Bend and route radius overrides belong on the bend or route
call. A layout has one canonical name per profile, and different explicit
radius defaults for the same profile conflict. metal_routing is an alias of
the metal3 factory. strip_no_ports is replaced by strip and ports={}.
Define radius and radius_min at first creation: a profile registered without
them cannot acquire them later. Width/layer-only ports and auxiliary ports use
the PDK's port_cross_sections mapping (physical layer tuple → factory) to
establish their process defaults immediately. Unconfigured layers produce bare
profiles with no radii. Explicit cross-section arguments are used as supplied;
the low-level cross_section() factory does not infer process defaults.
An intentional abrupt interface between symmetric and asymmetric doping or
cladding profiles can be placed using gf.port.core_port() on both connection
ports. This selects the physical core for that placement; the original ports
retain their complete profiles.