Render C struct / JSON schema as editable PySide6 UI, export to C/JSON/bin
Project description
struct2ui
Render C structs / JSON schemas as an editable PySide6 UI, and convert freely between C / JSON / bin. Built for algorithm parameter tuning: describe a C interface in JSON, auto-generate a Qt form, edit it, then export back to C source, JSON, or binary.
Features
- JSON → UI: describe C structs / enums / arrays with minimal JSON and auto-render Qt widgets (int→QSpinBox, float→QDoubleSpinBox, enum→QComboBox, etc.).
- Embeddable:
StructEditoris a plainQWidgetthat drops into any PySide6 / PyQt UI. - Multi-format export: edited results export to C source, JSON, or binary; C source can also be parsed back into a schema.
- Validation: keyword spell-checking (with “Did you mean X?” hints), semantic audits (
min<=max,step>0), and pipeline cross-validation. - Qt-binding agnostic: built on Qt.py — works with PySide6 / PyQt6 / PySide2 / PyQt5.
Installation
pip install struct2ui
# Pick a Qt binding (choose one)
pip install "struct2ui[pyside6]"
pip install "struct2ui[pyqt6]"
# Enable ELF layout verification (optional)
pip install "struct2ui[elf]"
The library itself only depends on
Qt.py; you must install a Qt binding (PySide6 / PyQt6 / etc.) yourself. ELF verification depends onpyelftools, installed via the[elf]extra.
Quick Start
from Qt import QtWidgets
from struct2ui import StructEditor
app = QtWidgets.QApplication([])
editor = StructEditor(
flow_file="abc.json", # pipeline definition file (optional)
cfg_dir="cfg_t", # modules dir: *.json holding struct/enum/typedef (optional)
)
editor.resize(480, 600)
editor.show()
app.exec_()
JSON Schema Reference
This is the core of the library: you describe your C types in JSON and the UI is
generated from it. Files live in the modules directory (cfg_dir), one
*.json per logical group. Each file is a flat object whose keys are either
type names (your structs / enums) or the reserved key typedefs.
File layout
{
// optional free-form metadata (ignored by the loader, allowed anywhere)
"version": "1.0",
"description": "audio EQ parameters",
// type aliases: map a custom C type to a primitive
"typedefs": { "gain_t": "int32_t", "freq_t": "float" },
// a struct definition
"eq_cfg_t": {
"type": "struct",
"items": [
{ "name": "enabled", "type": "uint8_t", "value": 1 },
{ "name": "gain", "type": "gain_t", "value": 0,
"min": -12, "max": 12, "step": 1, "unit": "dB",
"tip": "output gain", "when": { "enabled": 1 } }
]
},
// an enum definition
"mode_t": {
"type": "enum",
"items": { "OFF": 0, "LOW": 1, "HIGH": 2 }
}
}
Top-level blocks
| Block type | Keys | Notes |
|---|---|---|
struct |
type, items (list of field specs) |
items is required |
enum |
type, items (object {NAME: value}) |
reads back the enumerator name |
typedefs |
object {alias: real_type} |
resolved before field building |
Free-form metadata keys are allowed and ignored at any level: version,
description, author, comment, note.
Field properties (inside a struct's items)
Each entry in items is a field spec. Only the following keys are
recognized — any other key triggers an "unknown keyword" error (with a
"Did you mean …?" hint):
| Property | Applies to | Description |
|---|---|---|
name |
all | Required. C field name. |
type |
all | Required. C type or a typedefs alias (e.g. int32_t, float, char, an enum/struct name). |
value |
all | Default value. For arrays, a list (per element) or a scalar (applied to every element). |
count |
arrays | Makes the field an array. Integer, a #define name, an expression (N - 1), or a list for multi-dim ([3, 4], stored flat as 12). |
min / max |
int, float | Numeric bounds. Validated min <= max. Drive spin-box / slider / dial ranges. |
step |
int, float | Increment, must be > 0. |
decimals |
float | Number of fractional digits shown/stored. Non-negative integer; defaults to digits implied by step. |
unit |
all | Unit suffix shown in the label, e.g. gain (dB). |
tip |
all | Tooltip text. |
choices |
int, float, char[N] |
Discrete value set rendered as a combo (see below). |
when |
all | Conditional enable/disable (see below). |
widget |
all | Override the auto-picked editor (see table below). |
widget values
When omitted, the widget is inferred from type (and from min/max,
choices). Override it explicitly with widget:
widget |
Valid for | Renders as |
|---|---|---|
checkbox |
int | Check box (also auto-picked when min:0, max:1). |
toggle |
bool/int | Toggle push-button. |
combo |
int, float, char[N] |
Combo box; pair with choices. |
slider |
int | Slider with min/max labels (requires min & max). |
dial |
int | Rotary dial popup (requires min & max). |
file |
array | Path label + Browse button; loads array values from a text file. |
multiline |
array | Single-line shell that pops up a multi-line editor. |
table |
struct array | Force grid layout (one column per struct member). |
Numeric fields without an explicit widget become a QSpinBox /
QDoubleSpinBox; scalar arrays become a comma-separated line edit.
choices (discrete values)
A non-enum integer / float / char[N] field can be constrained to a fixed set.
Two forms are accepted:
"choices": [0, 1, 2, 3] // label = str(value)
"choices": [{ "Off": 0 }, { "Low": 1 }, { "High": 2 }] // explicit labels
The combo reads back the underlying value (not the label), so C / JSON / bin
export still emits the number (or string for char[N]). Audits enforce: each
value within [min, max] when declared, and value (the default) must be one
of the choices.
when (conditional enable/disable)
A field can be greyed out unless other fields hold specific values. when is an
object mapping a sibling field name to its required value; all entries must
match (logical AND):
{ "name": "cutoff", "type": "float", "when": { "enabled": 1, "mode": "HIGH" } }
The dependency name is matched against the dotted leaf name in scope. When a dependency is not found, the field stays enabled.
count expressions & multi-dimensional arrays
count may be an integer, a #define constant name, or an integer expression
of those using + - * / % () (e.g. MAX_BAND_NUM - 1). A list makes a
multi-dimensional array that is stored flat to match the C ABI:
{ "name": "matrix", "type": "float", "count": [3, 4] } // 12 contiguous floats, shown as [3][4]
char[N] is treated as a C string by default (use widget to override).
Validation
Loading runs three layers of checks, surfaced in the load report panel:
- Keyword spelling — unknown keys report "Did you mean
tip?" style hints. - Semantic audits —
min <= max,step > 0,count > 0, choices within bounds, default is one of the choices. - Pipeline cross-validation — values in the pipeline file are checked against the schema (range, choices, enum membership).
Embedding into an Existing UI
StructEditor is a regular QWidget; just put it into a layout:
from Qt import QtWidgets
from struct2ui import StructEditor
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
editor = StructEditor(
"abc.json", "cfg_t",
settings_org="MyCompany", # custom QSettings scope to
settings_app="MyApp", # avoid clashing with the host app
)
self.setCentralWidget(editor)
Constructor Parameters
| Parameter | Description |
|---|---|
flow_file |
Path to the pipeline JSON file (pipeline definition), defaults to None; pick it in the UI when omitted |
cfg_dir |
Modules directory holding *.json (struct / enum / typedef definitions), defaults to None; pick it in the UI when omitted |
parent |
Qt parent object, defaults to None |
elf_path |
Path to an ELF for layout verification, defaults to None; pick it in the UI when omitted |
appearance |
Button appearance overrides, {key: {'mode': ..., 'icon': ..., 'text': ...}} |
settings_org |
QSettings organization name, defaults to 'struct2ui' |
settings_app |
QSettings application name, defaults to 'StructEditor' |
toolbar_orientation |
Toolbar layout, 'horizontal' (default) or 'vertical' |
custom_button_position |
Where add_custom_button() drops buttons relative to the built-ins: 'end' (after, default) or 'start' (before / far left) |
Custom Toolbar Buttons
Host applications can add their own buttons to the top path bar via
add_custom_button(). All custom buttons form a single group, separated from
the built-in buttons by a divider that is added automatically — you only choose
which side they sit on (once, via the custom_button_position constructor arg).
editor = StructEditor("abc.json", "cfg_t", custom_button_position="end")
def on_send():
print(editor.current_values())
btn = editor.add_custom_button(
"Send", # tooltip / accessible label (shown when no icon)
on_send, # callable invoked on click (no arguments)
icon="icons/send.png", # optional; falls back to text when missing
checkable=False, # optional; make the button toggleable
)
# `btn` is the created QPushButton, returned so you can tweak it further.
| Parameter | Description |
|---|---|
text |
Tooltip / accessible label, also shown when no icon is given. Required. |
on_click |
Callable invoked on click, takes no arguments. Required. |
icon |
Path to an icon file; falls back to text when the path is missing/empty. |
checkable |
Make the button toggleable, defaults to False. |
Returns the created QPushButton.
Programmatic API
Beyond the UI, StructEditor exposes methods to read state and drive exports
from code (e.g. wired to a custom button):
| Method | Description |
|---|---|
current_values() -> dict |
Live editor values of the active section, keyed by field name (empty dict when no parameter section is shown). |
current_paths() -> dict |
Current {'modules', 'pipeline', 'elf'} file paths; an empty string means that slot is unset. |
export_bin(path, show_dialogs=False) -> bool |
Write the current pipeline to a .bin at path — the programmatic equivalent of clicking Export → Binary. Returns True when there are no ELF layout errors (or no ELF is selected), False otherwise. |
export_bin verifies the ELF layout first when an ELF is selected (the file is
still written, matching the UI flow). With show_dialogs=True it pops the same
success / ELF-mismatch / failure dialogs the UI shows; with the default
show_dialogs=False no dialogs appear and render/write failures are raised as
exceptions for the caller to handle.
paths = editor.current_paths() # {'modules': ..., 'pipeline': ..., 'elf': ...}
name = paths['pipeline'] # derive an output name from the pipeline
ok = editor.export_bin("out/speech.bin") # True when ELF layout is clean
Export API
The low-level export functions can be used standalone, without any UI:
from struct2ui.schema import SchemaRegistry
from struct2ui.exporters import (
emit_c, # sections + registry -> C source string
dumps_json, # -> JSON string
emit_bin, # -> binary bytes
merge_abi, # merge ABI info
verify_sections, # verify .bin layout against an ELF
parse_c_source, # C source -> parse result
build_schema_dict,
)
Architecture
| Layer | Module | Responsibility |
|---|---|---|
| Schema (pure data, no Qt) | struct2ui.schema |
Parse *.json into a Field tree; spell-checking, semantic audits, pipeline cross-validation |
| UI rendering | struct2ui.ui |
WidgetFactory, FormRenderer/TreeRenderer, array tables, when conditional binding |
| Export | struct2ui.exporters |
C / JSON / bin export, C source reverse parsing, ELF verification |
| Top-level widget | struct2ui.StructEditor |
Path bar / action buttons / content area; load report panel; QSettings path memory |
Development
pip install -e ".[dev]" --no-build-isolation
python -m pytest
Tests live under tests/ and drive real Qt widgets headlessly on the offscreen platform.
License
MIT © Jay
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file struct2ui-0.3.0.tar.gz.
File metadata
- Download URL: struct2ui-0.3.0.tar.gz
- Upload date:
- Size: 91.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b43e98481e922c924ae47b35482429c34d09f880c157144ab61ec58a61cc57b
|
|
| MD5 |
abe130a5ab8ae2e795a3176c68d9acd9
|
|
| BLAKE2b-256 |
4350920c81fbab871359a4e18fa481b31f904297d6e7e169c2fe3e2500ae0157
|
File details
Details for the file struct2ui-0.3.0-py3-none-any.whl.
File metadata
- Download URL: struct2ui-0.3.0-py3-none-any.whl
- Upload date:
- Size: 79.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d262e5312f0d6bdf19eb0409019abd69d2adc38c072f355e9e2bd43d11aefbb4
|
|
| MD5 |
d7304d89b00194e2cf653483a4b0fdb2
|
|
| BLAKE2b-256 |
44e6866ad6d679dead8264ece0dedc6efe5883d5e411362ecc25e1ba54a0b82a
|