Python tools to convert GUIslice Builder .prj files to JSON and back.
GUIslice Builder saves projects as Java ObjectOutputStream binary files. These scripts parse that format at the protocol level — no Java required — enabling you to read, modify, and rewrite .prj files with plain Python.
The GUIslice Builder UI is the right tool for designing layouts. These scripts are for programmatic post-processing: reorganising widgets across pages, bulk repositioning, adding or removing elements while preserving all metadata, or automating repetitive layout changes that would be tedious to do by hand.
- Python 3.6+
- Standard library only (no
pip installneeded)
Tested against GUIslice Builder 0.17.b40.
| File | Purpose |
|---|---|
guislice_prj_to_json.py |
.prj → JSON (also usable as a library) |
json2prj.py |
JSON → .prj (also usable as a library) |
# Export to JSON
python guislice_prj_to_json.py MyProject.prj output.json
# Import back (after modifying output.json)
python json2prj.py output.json MyProject.prjIf no output file is given to guislice_prj_to_json.py, it prints JSON to stdout.
import json, subprocess
import json2prj as W
# Parse .prj into a Python dict
result = subprocess.run(
['python', 'guislice_prj_to_json.py', 'MyProject.prj'],
capture_output=True, text=True, encoding='utf-8'
)
d = json.loads(result.stdout)
# ... modify d ...
# Write back
data = W.write_prj(d)
with open('MyProject.prj', 'wb') as f:
f.write(data){
"file_version": "17",
"active_page": "Page$4",
"project": { "bSendEvents": true, "properties": { ... } },
"zoom": 1.0,
"page_count": 7,
"pages": [
{
"key": "Page$4",
"enum": "E_PG4",
"type": "Page",
"content": {
"widget_count": 73,
"widgets": [
{
"type": "Text",
"bSendEvents": false,
"properties": { "COM-001": "Text$53", "COM-002": "E_ELEM_TEXT53", ... }
},
...
]
}
}
],
"_class_registry": { ... } ← internal Java class metadata, do not edit
}
| Code | Meaning |
|---|---|
COM-001 |
Widget ID string, e.g. Text$53 — must be globally unique across all pages |
COM-002 |
C enum name, e.g. E_ELEM_TEXT53 |
COM-003 |
X position (pixels) |
COM-004 |
Y position (pixels) |
COM-005 |
Width (pixels) |
COM-006 |
Height (pixels) |
COM-019 |
C member name (pointer variable), e.g. m_pElemFuse1Text |
TXT-201 |
Label / default text |
TXT-213 |
Text alignment, e.g. GSLC_ALIGN_MID_LEFT |
# 1. Find the page you want to change
pg4 = next(p for p in d['pages'] if p['enum'] == 'E_PG4')
widgets = pg4['content']['widgets']
# 2. Find a widget by its ID
def get_w(name):
for w in widgets:
if w['properties'].get('COM-001') == name:
return w
raise KeyError(name)
# 3. Reposition it
w = get_w('Text$53')
w['properties']['COM-003'] = 10 # x
w['properties']['COM-004'] = 20 # y
w['properties']['COM-005'] = 80 # width
w['properties']['COM-006'] = 14 # height
# 4. Remove widgets
widgets[:] = [w for w in widgets if w['properties'].get('COM-001') not in ('Text$174', 'Text$175')]
# 5. Clone a widget with a new ID
import copy
new_w = copy.deepcopy(get_w('Text$53'))
new_w['properties']['COM-001'] = 'Text$276'
new_w['properties']['COM-002'] = 'E_ELEM_TEXT276'
new_w['properties']['COM-019'] = 'm_pElemFuse10Text'
widgets.append(new_w)
# 6. Update widget count
pg4['content']['widget_count'] = len(widgets)Widget IDs (COM-001) must be unique across all pages, not just the page you are editing. The Builder assigns C enum values (COM-002) based on these IDs, and duplicates cause compile errors.
def find_max_id(prefix):
max_n = 0
for page in d['pages']:
for w in page['content']['widgets']:
name = w['properties'].get('COM-001', '')
if name.startswith(prefix + '$'):
try:
n = int(name.split('$')[1])
max_n = max(max_n, n)
except (ValueError, IndexError):
pass
return max_n
next_text_id = find_max_id('Text') + 1
next_toggle_id = find_max_id('ToggleButton') + 1
next_image_id = find_max_id('Image') + 1- Open the modified
.prjin GUIslice Builder — it should load normally. - Verify the layout visually.
- Click Generate Code to regenerate the
.hfile.
The generated .h file name matches the .prj file name. If you rename the .prj, the generated header changes name too — update any scripts or pre-compile steps that reference it by name.
The JSON contains a _class_registry key with Java class metadata extracted from the original file. json2prj.py needs this to reconstruct the correct binary encoding. Always generate JSON from an existing .prj (even an empty project) rather than writing it from scratch.
The parser detects widget-type boundaries using a simple heuristic: a string that is 3–30 characters, starts with an uppercase letter, and contains only letters. This is reliable for all widget types in GUIslice Builder 0.17.b40. If a future Builder version introduces a widget type name containing digits or underscores, the parser would misidentify the boundary. The fix is to relax the _is_widget_type function in guislice_prj_to_json.py.
The binary format is tied to the Java serialVersionUID of each Builder class. These scripts have been tested on Builder 0.17.b40. Other versions may have different field layouts; the _class_registry in the JSON will reflect whatever version generated the original file, so roundtripping the same file should always work. Cross-version editing (modifying a file from version A and loading it in version B) has not been tested.
Each GUIslice element added to a page increases the size of the static element array (m_asPageNElem[MAX_ELEM_PGN_RAM]) stored in DRAM. On ESP32 this array lives in the dram0_0_seg linker segment, which has a fixed upper bound independent of total heap space. Adding many elements to one page can overflow this segment while total free RAM still appears large. Monitor the linker output for dram0_0_seg overflowed by N bytes.
MIT