tweakpane: `exportPreset` doesn't capture pane

I have a large root Pane with many folders and parameters nested inside. But when I call pane.exportPreset() on the root, it returns what appears to be a random folder from the middle of the tree.

screenshot Screenshot 2022-09-30 at 9 58 21 PM

I’m not sure if this is intended functionality or not. Is this because I neglected to specify the presetKey for each controller? (I’m just now reading about presetKey 🤦‍♂️

As I try to figure it out, it appears there is no API exposed at all for things like recursive children, controller values / titles / etc.

I got the folders recursively in the screenshot like so:
const walk = (gui: FolderApi) => {
	for (const child of gui.children) {
		console.log(child)
		const isFolder = child.rackApi_ !== undefined
		if (isFolder) {
			console.log('🗂')
			walk(child as FolderApi)
		}
	}
}

But now, getting all the targets / titles / values doesn’t seem to be very straightforward.

Unless I’m missing something big, it seems like my best bet is to build a system over top of tweakpane to expose important information like values, names, targets, default values, etc in order to implement a preset system that functions like gui.save() in lil-gui or dat.gui

Sorry for the long post! I’m knee deep in a huge project and was tasked with implementing presets so I’m a bit scatter brained 😅

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (9 by maintainers)

Commits related to this issue

Most upvoted comments

After considering, preset will be replaced with “blade state” in v4.

image

// v3: preset
{
  "name": "exported json",
  "size": 10,
  "color": "#00ffd6",
}
// v4: blade state
{
  "disabled": false,
  "hidden": false,
  "children": [
    {
      "disabled": false,
      "hidden": false,
      "label": "name",
      "value": "exported json",
      "key": "name"
    },
    {
      "disabled": false,
      "hidden": false,
      "label": "size",
      "value": 10,
      "max": 100,
      "min": 0,
      "key": "size"
    },
    {
      "disabled": false,
      "hidden": false,
      "label": "color",
      "value": "#00ffd6",
      "key": "color"
    }
  ],
  "expanded": true,
  "title": "Values"
}

Every blade has the method importState() and exportState(), and the pane uses them recursively to create a whole state.

Blade state contains more information about blades and it can convert into classic preset easily with small utility function.

Would an additional generic data field make sense here for arbitrary user data?

Blade state is literally a state of the blade, so I think related data is out of scope of the state. All binding blades have a string field tag and you can use it as a key for the data like this:

const dataMap = {
  foo: data1,
  bar: data2,
};

pane.addInput(PARAMS, 'param1', {tag: 'foo'});
pane.addInput(PARAMS, 'param2', {tag: 'bar'});

Blade state of the pane:

{
  "disabled": false,
  "hidden": false,
  "children": [
    {
      "disabled": false,
      "hidden": false,
      "label": "param1",
      "binding": {
        "key": "param1",
        "value": 1
      },
      "tag": "foo"
    },
    {
      "disabled": false,
      "hidden": false,
      "label": "param2",
      "binding": {
        "key": "param2",
        "value": 2
      },
      "tag": "bar"
    }
  ],
  "expanded": true
}

Generally you can determine input blades by binding key and value, unless someone creates a blade plugin that exports these fields.

And here is an example of a folder state:

console.log(folder.exportState());

// {
//   disabled: false,
//   expanded: false,
//   hidden: false,
//   title: 'folder',
//   children: [...],
// }

I also found that I needed the ability to exclude certain folders from the export for things like monitor bindings and other settings since there was no use for them when re-importing.

You’ll be able to except monitor bindings by readonly: true.

image

{
  "disabled": false,
  "hidden": false,
  "children": [
    {
      "disabled": false,
      "hidden": false,
      "label": "foo",
      "value": 1,
      "key": "foo"
    },
    {
      "disabled": false,
      "hidden": false,
      "label": "bar",
      "value": 1,
      "key": "bar",
      "readonly": true  // <----
    }
  ],
  "expanded": true
}

Big +1 for this feature! I’ve actually recently ended up hacking together my own version of this which works nicely but required overwriting core methods so it isn’t future-proofed.

What’s nice about the ability to export all data is that you can place this in localStorage and retrieve it on refresh so you can persist your settings. It might be nice to have this as a built-in option on tweakpane 😃

I also found that I needed the ability to exclude certain folders from the export for things like monitor bindings and other settings since there was no use for them when re-importing.

Haha thanks, I love hacking on tweakpane! 🙏 Let me know if there is anything I can do to help.

There are several possibilities for the preset format.

image

1. Flatten object (current format)

{
  size: 16,
  weight: 'Normal',
  name: 'Sketch',
  active: true,
  color: '#ff0055',
  offset: {x: 0, y: 0},
  point3d: {x: 0, y: 0, z: 0},
  point4d: {x: 0, y: 0, z: 0, w: 0},
}

Pros:

  • Looks clean.
  • Not depend on the UI structure.

Cons:

  • The output can be ambiguous if the pane contains bindings with duplicate keys.
    • presetKey can solve this problem, but not smart a bit…

2. Nested object

{
  size: 16,
  weight: 'Normal',
  name: 'Sketch',
  active: true,
  color: '#ff0055',
  tab: {
    offset: {x: 0, y: 0},
    point3d: {x: 0, y: 0, z: 0},
    point4d: {x: 0, y: 0, z: 0, w: 0},
  },
}

Pros:

  • Easy to understand the structure.

Cons:

  • The output can also be ambiguous.
  • How to decide a container name like tabs…?
  • Exported object depends on the UI structure. If user changes the structure, importing the preset can be failed.

3. Nested array

[
  {key: 'size', value: 16},
  {key: 'weight', value: 'Normal'},
  {key: 'name', value: 'Sketch'},
  {key: 'active', value: true},
  {key: 'color', value: '#ff0055'},
  [  // tab
    [  // tabpage[0]
      {key: 'offset', value: {x: 0, y: 0}},
    ],
    [  // tabpage[1]
      {key: 'point3d', value: {x: 0, y: 0, z: 0}},
      {key: 'point4d', value: {x: 0, y: 0, z: 0, w: 0}},
    ],
  ],
]

Pros:

  • The output is simple and unique.
  • User can convert it to the current format with the small utility function.

Cons:

  • Exported object depends on the UI structure. If user changes the structure, importing the preset can be failed.