Configuration API guide and complete example#

The pyedb.configuration package provides a Python interface for building the same configuration payload described in Configuration file architecture. Instead of manually authoring JSON, you populate a CfgData object and then pass it directly to Configuration.run with no serialization step required.

Tip

The easiest way to obtain a builder when you already have an open EDB session is edb.configuration.create_config_builder(). This keeps everything within the edb.configuration namespace and avoids a direct import of CfgData.

Why use the configuration API?#

The programmatic API is useful when you want to:

  • build configurations from templates or scripts,

  • reuse helper functions across projects,

  • validate values in Python before writing the file,

  • generate only the sections you need,

  • pass a builder directly to run() without touching the file system, and

  • move between dictionary, JSON, and TOML forms.

Two entry points#

There are two equivalent ways to start a programmatic configuration:

1. From an open EDB session (recommended)

# No extra imports needed – the builder is created inside the session.
cfg = edb.configuration.create_config_builder()
cfg.general.anti_pads_always_on = False
cfg.nets.add_signal_nets(["SIG1", "CLK"])
edb.configuration.run(cfg)  # load + apply in one call

2. Standalone (scripts, templates, CI)

from pyedb.configuration import CfgData

cfg = CfgData()
cfg.general.anti_pads_always_on = False
cfg.nets.add_signal_nets(["SIG1", "CLK"])

# Apply to an open session later:
edb.configuration.run(cfg)

# Or persist to a file for review or version control:
cfg.to_json("my_config.json")

Session-aware get() methods#

When a builder is created via edb.configuration.create_config_builder() it is bound to the live EDB session. Each section exposes a get() (or get_layer / get_material / get_definition / get_instance) helper that retrieves an existing database object and wraps it in the corresponding builder. This avoids having to define again objects that already exist in the design.

Call

Returns

cfg.components.get("U1")

ComponentConfig pre-loaded with all current EDB properties.

cfg.stackup.get_layer("top")

LayerConfig pre-loaded with current layer properties.

cfg.stackup.get_material("copper")

MaterialConfig pre-loaded with current material properties.

cfg.nets.get("GND")

CfgNet bound to the live EDB session. Attributes: name, is_power_ground (Boolean), classification ("signal" or "power_ground"). Returns False if the net does not exist.

cfg.padstacks.get_definition("via_0.2")

PadstackDefinitionConfig pre-loaded with current definition.

cfg.padstacks.get_instance("via_A1")

PadstackInstanceConfig pre-loaded with current instance data.

cfg.pin_groups.get("pg_VDD")

PinGroupConfig pre-loaded with current pin membership.

cfg.setups.get("hfss_bb")

The matching registered setup builder (HfssSetupConfig, etc.).

Each get() call caches the result—calling it twice with the same name returns the same object. If the object was already registered (for example, via add), the cached entry is returned instead.

cfg = edb.configuration.create_config_builder()

# ── Existing component ──────────────────────────────────────────────────
u1 = cfg.components.get("U1")
u1.set_solder_ball_properties("cylinder", "150um", "100um")
u1.set_ic_die_properties("flip_chip", orientation="chip_down")

# ── Existing layer ──────────────────────────────────────────────────────
top = cfg.stackup.get_layer("top")
top.set_huray_roughness("0.1um", "2.9")
top.set_etching(factor=0.4)

# ── Existing material ───────────────────────────────────────────────────
cu = cfg.stackup.get_material("copper")
cu.conductivity = 5.6e7

# ── Net classification ──────────────────────────────────────────────────
info = cfg.nets.get("GND")  # also adds GND to power_nets if it is one
print(info["classification"])  # 'power_ground'

# ── Existing padstack definition ────────────────────────────────────────
via_def = cfg.padstacks.get_definition("via_0.2")
via_def.hole_plating_thickness = "30um"

# ── Existing padstack instance ──────────────────────────────────────────
via = cfg.padstacks.get_instance("via_A1")
via.set_backdrill("L3", "0.25mm", drill_from_bottom=True)

# ── Existing pin group ──────────────────────────────────────────────────
pg = cfg.pin_groups.get("pg_VDD")
print(pg.pins)

# ── Registered setup ────────────────────────────────────────────────────
cfg.setups.add_hfss_setup("hfss_bb", adapt_type="broadband")
setup = cfg.setups.get("hfss_bb")  # retrieve the same object
setup.add_frequency_sweep("sw2", start="1GHz", stop="20GHz", step_or_count=100)

edb.configuration.run(cfg)

Generated API reference#

Use the generated AutoAPI pages when you want full class signatures, member lists, or direct links to a specific configuration builder implementation.

Key AutoAPI links#

Page

When to use it

Configuration package overview

Browse every configuration submodule from one landing page.

Configuration runtime

Review Configuration.load(), Configuration.run(), and runtime helpers.

Setup builders

Inspect HFSS and SIwave setup classes including sweep helpers.

Ports, sources, and probes

Inspect TerminalInfo and excitation builders.

Terminal builders

Review explicit low-level terminal payload classes.

Stackup builders

Inspect material, roughness, etching, and layer builders.

Component builders

Review component, package, and model-assignment helpers.

Padstack builders

Inspect padstack definitions, instances, and backdrill payloads.

Boundaries and operations / operations

Review air-box, dielectric extent, and cutout configuration classes.

Applying a configuration#

run() is the single method to both load and apply a configuration. You can pass any of the supported input types directly:

Input type

Behaviour

CfgData

Serialized to a dict via to_dict() then loaded and applied.

dict

Merged with the existing data store then applied.

str / Path

JSON or TOML file loaded from disk then applied.

None (default)

Applies the previously loaded cfg_data as-is.

# Variant A – builder (most ergonomic)
cfg = edb.configuration.create_config_builder()
cfg.nets.add_signal_nets(["SIG"])
edb.configuration.run(cfg)

# Variant B – dictionary
edb.configuration.run({"nets": {"signal_nets": ["SIG"]}})

# Variant C – file path
edb.configuration.run("my_config.json")

# Variant D – apply previously loaded data
edb.configuration.load("base.json")
edb.configuration.load("overlay.json")  # merges on top
edb.configuration.run()

Terminal Info helpers#

For ports, sources, and probes, use TerminalInfo factory methods instead of writing raw terminal dictionaries by hand.

Factory call

Use when …

TerminalInfo.pin("A1", reference_designator="U1")

targeting a named pin on a component.

TerminalInfo.net("VDD", reference_designator="U1")

connecting to a net on a specific component.

TerminalInfo.pin_group("pg_VDD")

referencing a pre-defined pin group.

TerminalInfo.padstack("via_1")

targeting a named padstack instance (coax ports).

TerminalInfo.coordinates("top", 0.001, 0.002, "SIG")

placing a terminal at an exact XY coordinate on a layer.

TerminalInfo.nearest_pin("GND", search_radius="5mm")

auto-resolving a reference terminal from a ground net.

from pyedb.configuration import TerminalInfo

TerminalInfo.pin("A1", reference_designator="U1")
TerminalInfo.net("VDD", reference_designator="U1")
TerminalInfo.pin_group("pg_VDD")
TerminalInfo.padstack("via_1")
TerminalInfo.coordinates("top", 0.001, 0.002, "SIG")
TerminalInfo.nearest_pin("GND", search_radius="5mm")

Complete example#

The following example uses edb.configuration.create_config_builder() to obtain a builder from an open session, populates all major sections, then applies the configuration with a single run() call.

from pyedb import Edb
from pyedb.configuration import TerminalInfo

edb = Edb("my_design.aedb")

# ----------------------------------------------------------------
# Obtain a builder from the active session
# ----------------------------------------------------------------
cfg = edb.configuration.create_config_builder()

# ----------------------------------------------------------------
# General design options
# ----------------------------------------------------------------
cfg.general.spice_model_library = "/models/spice"
cfg.general.s_parameter_library = "/models/snp"
cfg.general.anti_pads_always_on = False
cfg.general.suppress_pads = True

# ----------------------------------------------------------------
# Stackup
# ----------------------------------------------------------------
cfg.stackup.add_material("copper", conductivity=5.8e7)
cfg.stackup.add_material("fr4", permittivity=4.4, dielectric_loss_tangent=0.02)
cfg.stackup.add_signal_layer(
    "top", material="copper", fill_material="fr4", thickness="35um"
)
cfg.stackup.add_dielectric_layer("diel1", material="fr4", thickness="100um")
cfg.stackup.add_signal_layer(
    "bot", material="copper", fill_material="fr4", thickness="35um"
)

# ----------------------------------------------------------------
# Nets
# ----------------------------------------------------------------
cfg.nets.add_signal_nets(["DDR4_DQ0", "DDR4_DQ1", "CLK"])
cfg.nets.add_power_ground_nets(["VDD", "VCC", "GND"])
# Optional: store reference nets so they can be forwarded to add_cutout
cfg.nets.add_reference_nets(["GND"])

# ----------------------------------------------------------------
# Components
# ----------------------------------------------------------------
r1 = cfg.components.add("R1", part_type="resistor", enabled=True)
r1.add_pin_pair_rlc("1", "2", resistance="100ohm", resistance_enabled=True)

c1 = cfg.components.add("C1", part_type="capacitor")
c1.add_pin_pair_rlc("1", "2", capacitance="100nF", capacitance_enabled=True)

u1 = cfg.components.add("U1", part_type="ic")
u1.set_ic_die_properties("flip_chip", orientation="chip_down")
u1.set_solder_ball_properties("cylinder", "150um", "100um")
u1.set_port_properties(reference_height="50um")

# ----------------------------------------------------------------
# Padstacks
# ----------------------------------------------------------------
cfg.padstacks.add_definition(
    "via_0.2", material="copper", hole_plating_thickness="25um"
)
via = cfg.padstacks.add_instance(name="v1", net_name="GND", layer_range=["top", "bot"])
via.set_backdrill("L3", "0.25mm", drill_from_bottom=True)

# ----------------------------------------------------------------
# Pin groups
# ----------------------------------------------------------------
cfg.pin_groups.add("pg_VDD", "U1", net="VDD")
cfg.pin_groups.add("pg_GND", "U1", pins=["A1", "A2", "B1"])

# ----------------------------------------------------------------
# Explicit low-level terminals (optional; most users use ports/sources)
# ----------------------------------------------------------------
cfg.terminals.add_pin_group_terminal("t_vdd", "pg_VDD", 50, "port")
cfg.terminals.add_pin_group_terminal(
    "t_gnd", "pg_GND", 50, "port", reference_terminal="t_vdd"
)
cfg.terminals.add_bundle_terminal("bundle_demo", ["t_vdd", "t_gnd"])

# ----------------------------------------------------------------
# Ports
# ----------------------------------------------------------------
cfg.ports.add_circuit_port(
    "port_U1",
    positive_terminal=TerminalInfo.pin_group("pg_VDD"),
    negative_terminal=TerminalInfo.pin_group("pg_GND"),
    impedance=50,
)
cfg.ports.add_wave_port(
    "wport1",
    primitive_name="trace1",
    point_on_edge=[0.001, 0.002],
    horizontal_extent_factor=6,
)
# Coax port – padstack shortcut (most common)
cfg.ports.add_coax_port("coax1", padstack="v1")
# Coax port – net shortcut (distributed if >1 matching pin)
cfg.ports.add_coax_port("coax_vdd", net="VDD", reference_designator="U1")
# Coax port – pin shortcut
cfg.ports.add_coax_port("coax_a1", pin="A1", reference_designator="U1", impedance=50)

# ----------------------------------------------------------------
# Sources and probes
# ----------------------------------------------------------------
cfg.sources.add_current_source(
    "isrc1",
    positive_terminal=TerminalInfo.pin_group("pg_VDD"),
    negative_terminal=TerminalInfo.pin_group("pg_GND"),
    magnitude=0.5,
)
cfg.sources.add_voltage_source(
    "vsrc1",
    positive_terminal=TerminalInfo.net("VDD"),
    negative_terminal=TerminalInfo.net("GND"),
    magnitude=1.0,
)
cfg.probes.add(
    "probe1",
    positive_terminal=TerminalInfo.net("DDR4_DQ0"),
    negative_terminal=TerminalInfo.net("GND"),
)

# ----------------------------------------------------------------
# Simulation setups
# ----------------------------------------------------------------
# ----- HFSS setup -----
hfss = cfg.setups.add_hfss_setup(
    "hfss_bb",
    adapt_type="broadband",  # "single" | "broadband" | "multi_frequencies"
)

# Adaptive refinement – only one of these three is active at a time:
hfss.set_broadband_adaptive(
    low_freq="1GHz",
    high_freq="20GHz",
    max_passes=20,
    max_delta=0.02,
)
# hfss.set_single_frequency_adaptive(freq="5GHz", max_passes=20, max_delta=0.02)
# hfss.add_multi_frequency_adaptive("5GHz", max_passes=20, max_delta=0.02)
# hfss.add_multi_frequency_adaptive("10GHz")

# Automatic mesh seeding
hfss.set_auto_mesh_operation(
    enabled=True,
    trace_ratio_seeding=3.0,
    signal_via_side_number=12,
)

# Length-based mesh operation
hfss.add_length_mesh_operation(
    name="mesh1",
    nets_layers_list={"DDR4_DQ0": ["top"]},
    max_length="0.5mm",
    max_elements=1000,
    restrict_length=True,
    refine_inside=False,
)

# Frequency sweep – Option A: inline (start/stop/step_or_count in one call)
hfss.add_frequency_sweep(
    "sweep1",
    start="1GHz",
    stop="20GHz",
    step_or_count=100,
    distribution="linear_count",  # or "log_count" | "linear_scale" | "log_scale" | "single"
    sweep_type="interpolation",
    enforce_passivity=True,
)

# Frequency sweep – Option B: chained (add multiple ranges on one sweep)
sweep2 = hfss.add_frequency_sweep(
    "sweep2",
    sweep_type="interpolation",
    use_q3d_for_dc=False,
    compute_dc_point=False,
    enforce_causality=False,
    enforce_passivity=True,
    adv_dc_extrapolation=False,
)
sweep2.add_linear_count_frequencies("1GHz", "20GHz", 100)
sweep2.add_single_frequency("5GHz")

# ----- SIwave AC setup -----
siwave_ac = cfg.setups.add_siwave_ac_setup(
    "siw_ac",
    si_slider_position=2,  # 0=Speed | 1=Balanced | 2=Accuracy
    pi_slider_position=1,
    use_si_settings=True,
)
# Inline sweep (start/stop/step_or_count) – no separate chaining call needed
siwave_ac.add_frequency_sweep(
    "siw_sw1",
    start="1kHz",
    stop="1GHz",
    step_or_count=100,
    distribution="log_count",
    compute_dc_point=False,
    enforce_passivity=True,
)

# ----- SIwave DC setup -----
cfg.setups.add_siwave_dc_setup(
    "siw_dc",
    dc_slider_position=1,  # 0=Speed | 1=Balanced | 2=Accuracy
    export_dc_thermal_data=True,
)

# ----------------------------------------------------------------
# Boundaries
# ----------------------------------------------------------------
cfg.boundaries.set_radiation_boundary()
cfg.boundaries.set_air_box_extents(0.15, truncate_at_ground=True)
cfg.boundaries.set_dielectric_extent(
    "BoundingBox", expansion_size=0.001, honor_user_dielectric=True
)

# ----------------------------------------------------------------
# Operations (cutout)
# ----------------------------------------------------------------
# Option A: explicit lists
cfg.operations.add_cutout(
    signal_nets=["DDR4_DQ0", "CLK"],
    reference_nets=["GND"],
    extent_type="ConvexHull",  # case-insensitive
    expansion_size=0.002,
    auto_identify_nets_enabled=True,
)
# Option B: reuse previously stored net lists (no duplication)
cfg.operations.add_cutout(
    signal_nets=cfg.nets.signal_nets,
    reference_nets=cfg.nets.reference_nets,
    extent_type="ConvexHull",
)
cfg.operations.generate_auto_hfss_regions = True

# ----------------------------------------------------------------
# Model assignments
# ----------------------------------------------------------------
cfg.s_parameters.add(
    "cap_model",
    component_definition="CAP_100nF",
    file_path="/snp/cap.s2p",
    reference_net="GND",
)
cfg.spice_models.add(
    "ic_spice",
    component_definition="IC_U1",
    file_path="/spice/ic.sp",
    sub_circuit_name="IC_TOP",
)

# ----------------------------------------------------------------
# Thermal package definitions
# ----------------------------------------------------------------
pkg = cfg.package_definitions.add(
    "PKG_U1",
    component_definition="IC_U1",
    apply_to_all=True,
    maximum_power="5W",
    theta_jb="10C/W",
    theta_jc="5C/W",
    height="1mm",
)
pkg.set_heatsink(
    fin_base_height="0.5mm",
    fin_height="3mm",
    fin_orientation="x_oriented",
    fin_spacing="1mm",
    fin_thickness="0.2mm",
)

# ----------------------------------------------------------------
# Variables
# ----------------------------------------------------------------
cfg.variables.add("trace_width", "0.15mm", "Default trace width")
cfg.variables.add("$project_temp", "25cel")

# ----------------------------------------------------------------
# Modeler geometry
# ----------------------------------------------------------------
cfg.modeler.add_trace(
    "trace1", "top", "0.15mm", net_name="DDR4_DQ0", path=[[0.0, 0.0], [0.01, 0.0]]
)
cfg.modeler.add_rectangular_plane(
    "bot",
    "gnd_plane",
    "GND",
    lower_left_point=[-0.05, -0.05],
    upper_right_point=[0.05, 0.05],
)
cfg.modeler.delete_primitives_by_layer(["old_layer"])

# ----------------------------------------------------------------
# Apply everything to the open EDB design in one call
# ----------------------------------------------------------------
edb.configuration.run(cfg)

Persisting the configuration#

CfgData can be passed directly to edb.configuration.run(), no .to_dict() call is required:

cfg = edb.configuration.create_config_builder()
cfg.nets.add_signal_nets(["SIG1", "CLK"])
cfg.nets.add_power_ground_nets(["VDD", "GND"])

# Pass the builder directly — to_dict() is called internally.
edb.configuration.run(cfg)

If you also want to save the configuration for review, archiving, or reuse in another project, serialize the builder before (or instead of) calling run():

# Inspect as a plain dict (optional — not needed to call run())
payload = cfg.to_dict()

# Write to disk
cfg.to_json("my_project_config.json")
cfg.to_toml("my_project_config.toml")

# Apply the saved file later via file path
edb.configuration.run("my_project_config.json")

Note

edb.configuration.run(cfg) accepts a CfgData instance directly and handles the serialization step internally. You only need cfg.to_dict() when you want to inspect the payload programmatically or pass it to another API that expects a plain dictionary.

Round-trip helpers#

The root builder supports round-tripping from existing dictionaries, JSON, or TOML files. This is useful for loading a template, tweaking specific values, and re-exporting.

from pyedb.configuration import CfgData

# Load from a file, modify, and save back
cfg = CfgData.from_json("base_config.json")
cfg.general.suppress_pads = True
cfg.stackup.add_material("silver", conductivity=6.3e7)
cfg.to_json("modified_config.json")

# Load from a dict
cfg2 = CfgData.from_dict({"nets": {"signal_nets": ["CLK"]}})
cfg2.to_toml("nets_only.toml")

Practical recommendations#

  • Use edb.configuration.create_config_builder() when working inside an active EDB session. It avoids the extra import and keeps calling conventions consistent.

  • Call edb.configuration.run(cfg) to load and apply in a single statement. Pass None (the default) to apply previously loaded data again.

  • Prefer TerminalInfo factory methods over hand-written terminal dictionaries.

  • Build only the sections you need: empty sections are omitted by CfgData.to_dict() so the serialized payload stays minimal.

  • Persist to JSON / TOML when you want a reviewed artifact kept in version control and applied without a Python script.

  • Store reusable snippets as plain Python functions that accept and return a CfgData instance. Composing builders is straightforward.