Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

guislice-builder-json

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.

Use case

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.

Requirements

  • Python 3.6+
  • Standard library only (no pip install needed)

Tested against GUIslice Builder 0.17.b40.

Files

File Purpose
guislice_prj_to_json.py .prj → JSON (also usable as a library)
json2prj.py JSON → .prj (also usable as a library)

Usage

Command line

# 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.prj

If no output file is given to guislice_prj_to_json.py, it prints JSON to stdout.

As a library

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)

JSON structure

{
  "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
}

Key property codes

Code Meaning
COM-001 Widget ID string, e.g. Text$53must 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

Typical modification workflow

# 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)

Finding globally unique IDs

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

After modifying the .prj

  1. Open the modified .prj in GUIslice Builder — it should load normally.
  2. Verify the layout visually.
  3. Click Generate Code to regenerate the .h file.

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.

Caveats

_class_registry is required for roundtrip

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.

Widget-type detection heuristic

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.

Builder version

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.

DRAM impact on ESP32

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.

License

MIT

About

Python tools to convert [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice) `.prj` files to JSON and back

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages