dazscript-framework 0.3.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +327 -267
  3. package/package.json +9 -6
  4. package/src/Setup.dsa.ts +37 -9
  5. package/src/dialog/builders/list-view-builder.ts +6 -1
  6. package/src/dialog/builders/slider-builder.ts +1 -1
  7. package/src/examples/01-hello-world.dsa.ts +8 -0
  8. package/src/examples/02-persistence-dialog.dsa.ts +21 -0
  9. package/src/examples/02-persistence-dialog.ts +68 -0
  10. package/src/examples/03-simple-dialog.dsa.ts +23 -0
  11. package/src/examples/03-simple-dialog.ts +47 -0
  12. package/src/examples/04-settings-dialog.dsa.ts +29 -0
  13. package/src/examples/04-settings-dialog.ts +83 -0
  14. package/src/examples/05-list-dialog.dsa.ts +53 -0
  15. package/src/examples/05-list-dialog.ts +88 -0
  16. package/src/examples/06-showcase-dialog.dsa.ts +87 -0
  17. package/src/examples/06-showcase-dialog.ts +518 -0
  18. package/src/helpers/action-helper.ts +2 -2
  19. package/src/helpers/custom-action-helper.ts +5 -4
  20. package/src/helpers/custom-action-installer-helper.ts +39 -10
  21. package/src/helpers/file-helper.ts +1 -1
  22. package/src/helpers/list-view-helper.ts +1 -1
  23. package/src/helpers/message-box-helper.ts +2 -2
  24. package/src/helpers/node-helper.ts +14 -14
  25. package/src/helpers/pane-helper.ts +5 -5
  26. package/src/helpers/scene-helper.ts +9 -9
  27. package/src/helpers/viewport-helper.ts +2 -2
  28. package/src/lib/observable.test.ts +416 -0
  29. package/src/lib/observable.ts +24 -18
  30. package/src/lib/tree-node.test.ts +21 -0
  31. package/tsconfig.json +34 -109
  32. package/webpack.config.js +1 -0
  33. package/src/samples/hello-world.dsa.ts +0 -8
  34. package/src/samples/sample-dialog.dsa.ts +0 -47
  35. package/src/samples/sample-dialog.ts +0 -49
  36. /package/src/{samples → examples}/config.ts +0 -0
package/README.md CHANGED
@@ -1,441 +1,501 @@
1
1
  # DazScript Framework
2
2
 
3
- > ⚠️ This framework is under active development. The API may still evolve between releases. If you need a stable long-term surface, wait for `v1.0`.
3
+ **DazScript Framework** is a TypeScript toolkit for writing [Daz Studio](https://www.daz3d.com/daz-studio) scripts. It layers a full TypeScript development experience on top of [DAZ Script](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/start) (Qt Script / ECMAScript 5.1), and ships a fluent dialog builder so you can build UIs in code without touching the Qt widget API directly.
4
4
 
5
- The **DazScript Framework** is a TypeScript-based framework for writing Daz Studio scripts. It provides all the advantages of a typed language such as autocompletion, error checking, and method parameter documentation and hinting. The framework also includes a set of dialog helpers for rapid UI development.
5
+ ## Why use it?
6
6
 
7
- ## Benefits
7
+ DAZ Script gives you direct access to the entire Daz Studio API. The DazScript Framework builds on that foundation and adds:
8
8
 
9
- - **Autocompletion:** Take advantage of IDE autocompletion for faster and more efficient script development.
10
- - **Error Checking:** Catch potential errors early in the development process with TypeScript's static analysis.
11
- - **Method Documentation & Hinting:** Get contextual documentation and hints for methods, classes, and parameters.
9
+ - **TypeScript everywhere** full autocompletion and type checking for every Daz Studio API, your own models, and every helper in the framework.
10
+ - **Fast UI development** a fluent builder API lets you describe dialogs declaratively without touching the Qt widget API by hand.
11
+ - **Two-way data binding** link your data model to UI controls so they stay in sync automatically. The user types in a field and your model updates; you update the model in code and the UI reflects it instantly. No manual synchronization needed.
12
+ - **One-command build** — `npm run build` compiles TypeScript to `.dsa` files that Daz Studio runs directly.
13
+ - **Stable launcher shims** — built scripts use a two-level layout so iterating on your code never requires reinstalling actions in Daz Studio.
14
+ - **Automated installer generation** — `npm run installer` produces a full setup dialog by reading action metadata from your source code.
12
15
 
13
- ## Features
16
+ ---
14
17
 
15
- - TypeScript support with full IntelliSense.
16
- - A lightweight `action(...)` entrypoint plus helper methods for building interactive scripts.
17
- - A generated setup dialog for installing, updating, and removing custom action registrations.
18
- - Setup generation is fully automated from `action(...)` metadata, including menu path, toolbar, shortcut, description, grouping, icons, and bundle-based setup outputs.
19
- - Easy integration with Daz Studio for quick script deployment.
18
+ ## Quick Start: Hello World
20
19
 
21
- ## Installation
20
+ A script that shows a message box in Daz Studio.
22
21
 
23
- To install the **DazScript Framework**, run the following command:
22
+ ### 1. Install
24
23
 
25
24
  ```bash
26
25
  npm install dazscript-framework dazscript-types
27
26
  ```
28
27
 
29
- ## Setup
30
-
31
- After installing the package, scaffold the project files:
28
+ ### 2. Scaffold the project
32
29
 
33
30
  ```bash
34
31
  npx dazscript init
35
32
  ```
36
33
 
37
- If `--app-data-path` is not provided, `init` prompts for the AppData author namespace up front and uses the current folder name as the default product segment.
34
+ Follow the prompt for your AppData author namespace (e.g. `YourName/my-project`). This creates `dazscript.config.ts`, `tsconfig.json`, and wires the `build`, `watch`, `icons`, and `installer` scripts into `package.json`.
38
35
 
39
- This generates:
36
+ ### 3. Write the script
40
37
 
41
- - `dazscript.config.ts`
42
- - `tsconfig.json`
43
- - `package.json` script wiring for `build`, `watch`, `icons`, and `installer`
38
+ Create `src/hello-world.dsa.ts`:
39
+
40
+ ```typescript
41
+ import { action } from '@dsf/core/action';
42
+ import { info } from '@dsf/helpers/message-box-helper';
43
+
44
+ action({ text: 'Hello World' }, () => {
45
+ info('Hello World!');
46
+ });
47
+ ```
44
48
 
45
- The generated package scripts use the framework CLI directly, so consumer projects do not need their own webpack or Babel setup.
49
+ Files ending in `.dsa.ts` are compiled as runnable Daz Studio entry points.
46
50
 
47
- You can customize the generated defaults:
51
+ ### 4. Build
48
52
 
49
53
  ```bash
50
- npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out --app-data-path YourName/my-project
54
+ npm run build
55
+ ```
56
+
57
+ Output lands in `./out/`.
58
+
59
+ ### 5. Load in Daz Studio
60
+
61
+ **Option A — Copy:** copy the `out/` folder into your Daz Studio scripts directory.
62
+
63
+ **Option B — Symlink** (recommended for development, re-runs pick up the latest build automatically):
64
+
65
+ ```bash
66
+ # Windows — run as Administrator
67
+ mklink /D "C:\Users\[Username]\Documents\DAZ 3D\Studio\My Library\Scripts\MyScripts" "C:\path\to\project\out"
68
+
69
+ # macOS
70
+ ln -s /path/to/project/out ~/Documents/DAZ\ 3D/Studio/My\ Library/Scripts/MyScripts
51
71
  ```
52
72
 
53
- - `--menu-path` sets which Daz Studio menu the scripts are added to by default. See [The `action(...)` Entrypoint](#the-action-entrypoint) for how a script can override that with `menuPath`.
54
- - `--scripts-path` tells the installer generator where to scan for runnable `.dsa.ts` entry files.
55
- - `--out-dir` sets the webpack build output directory for generated `.dsa` files and copied icons.
56
- - `--app-data-path` sets the AppData namespace used by launcher fallback resolution. Use a unique `Author/Product` path.
73
+ Then in Daz Studio: **Scripts > MyScripts > Hello World**. A message box appears.
57
74
 
58
- Use `--scripts-path ./src/scripts` for projects shaped like `scripts/common`, where runnable `.dsa.ts` files live under `src/scripts/`. Use `--scripts-path ./src` for packages shaped like `scripts/power-menu`, where runnable `.dsa.ts` files live at the source root.
75
+ ---
59
76
 
60
- Set `appDataPath` explicitly in `dazscript.config.ts` for every project. It is required for builds that generate launcher shims:
77
+ ## Quick Start: A Simple Dialog
78
+
79
+ A dialog with a name input and an OK/Cancel button pair.
61
80
 
62
81
  ```typescript
63
- import { defineConfig } from 'dazscript-framework/config';
82
+ import { action } from '@dsf/core/action';
83
+ import { BasicDialog } from '@dsf/dialog/basic-dialog';
84
+ import { Observable } from '@dsf/lib/observable';
85
+ import { info } from '@dsf/helpers/message-box-helper';
64
86
 
65
- export default defineConfig({
66
- scriptsPath: './src',
67
- outDir: './out',
68
- defaultMenuPath: '/MyScripts',
69
- appDataPath: 'YourName/my-project',
70
- bundleName: 'My Project',
87
+ // The model holds state
88
+ class GreetModel {
89
+ name$ = new Observable<string>('World');
90
+ }
91
+
92
+ // The dialog describes the UI
93
+ class GreetDialog extends BasicDialog {
94
+ constructor(private model: GreetModel) {
95
+ super('Greet');
96
+ }
97
+
98
+ protected build(): void {
99
+ this.add.label('Enter your name:');
100
+ this.add.edit().value(this.model.name$);
101
+ this.add.button('Say Hello').clicked(() => this.dialog.accept());
102
+ }
103
+ }
104
+
105
+ action({ text: 'Greet Dialog' }, () => {
106
+ const model = new GreetModel();
107
+ const dialog = new GreetDialog(model);
108
+
109
+ if (dialog.ok()) {
110
+ info(`Hello, ${model.name$.value}!`);
111
+ }
71
112
  });
72
113
  ```
73
114
 
74
- `bundleName` is optional display metadata for generated setup dialogs. If omitted, the dialog falls back to `Setup Scripts`. `dazscript init` now scaffolds it automatically from the project folder name.
115
+ `add.edit().value(observable)` creates a two-way binding: typing in the field updates `name$.value`, and assigning `name$.value` in code updates the field.
116
+
117
+ ---
118
+
119
+ ## Documentation
75
120
 
76
- Built action outputs now use stable launcher shims by default:
121
+ ### Installation & Setup
77
122
 
78
- - `out/<script>.dsa` is the stable launcher registered with Daz Studio menus, toolbars, and shortcuts
79
- - `out/<folder>/lib/<script-name>/script.dsa` is the current implementation bundle that the launcher executes
123
+ Install the framework and its peer dependency:
80
124
 
81
- Rebuilding updates the implementation bundle under the shim's sibling `lib/` folder. At runtime, each launcher checks that local `lib/` path first and falls back to `App.getAppDataPath()/...` second. Because the registered launcher path stays stable, action updates normally do not require reinstalling the action in Daz Studio.
125
+ ```bash
126
+ npm install dazscript-framework dazscript-types
127
+ ```
128
+
129
+ Scaffold a new project:
130
+
131
+ ```bash
132
+ npx dazscript init
133
+ ```
82
134
 
83
- ## Usage
135
+ If `--app-data-path` is not provided, `init` prompts for the AppData author namespace and uses the current folder name as the product segment.
84
136
 
85
- ### Quick Start: Hello World
137
+ This generates:
138
+ - `dazscript.config.ts`
139
+ - `tsconfig.json`
140
+ - `package.json` script wiring for `build`, `watch`, `icons`, and `installer`
86
141
 
87
- Create a simple script that logs to the console:
142
+ Available `init` flags:
143
+
144
+ ```bash
145
+ npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out --app-data-path YourName/my-project
146
+ ```
147
+
148
+ | Flag | Description |
149
+ |---|---|
150
+ | `--menu-path` | Default menu where scripts appear in Daz Studio |
151
+ | `--scripts-path` | Where the generator scans for runnable `.dsa.ts` entry files |
152
+ | `--out-dir` | Where `build` writes `.dsa` files and copies icons |
153
+ | `--app-data-path` | AppData namespace for launcher fallback (`Author/Product` format) |
154
+
155
+ Use `--scripts-path ./src/scripts` when runnable files live under a subfolder; use `--scripts-path ./src` when they are at the source root.
156
+
157
+ ---
158
+
159
+ ### Project Configuration
160
+
161
+ `dazscript.config.ts` is the single configuration file for a project:
88
162
 
89
163
  ```typescript
90
- import { debug } from '@dsf/common/log';
91
- import { action } from '@dsf/core/action';
92
- import { info } from '@dsf/helpers/message-box-helper';
164
+ import { defineConfig } from 'dazscript-framework/config';
93
165
 
94
- action({ text: 'Hello World' }, () => {
95
- debug('Hello World!');
96
- info('Hello World!');
166
+ export default defineConfig({
167
+ scriptsPath: './src',
168
+ outDir: './out',
169
+ defaultMenuPath: '/MyScripts',
170
+ appDataPath: 'YourName/my-project', // required
171
+ bundleName: 'My Project', // optional — used in the setup dialog title
97
172
  });
98
173
  ```
99
174
 
175
+ `appDataPath` is required for builds that generate launcher shims and must be unique across your projects.
176
+
177
+ ---
178
+
100
179
  ### The `action(...)` Entrypoint
101
180
 
102
- Use `action(...)` at module scope to define how a runnable `.dsa.ts` file should appear in Daz Studio and what it should execute.
181
+ Every runnable `.dsa.ts` file calls `action(...)` at module scope. This defines how the script registers in Daz Studio and what it executes.
103
182
 
104
183
  ```typescript
105
184
  action({
106
- text: 'Hello World',
107
- menuPath: '#{defaultMenuPath}/Examples',
108
- shortcut: 'CTRL+SHIFT+H',
109
- toolbar: 'MyToolbar',
110
- group: 'Examples',
111
- description: 'Runs the Hello World script',
185
+ text: 'My Script',
186
+ menuPath: '#{defaultMenuPath}/Tools',
187
+ shortcut: 'CTRL+SHIFT+M',
188
+ toolbar: 'MyToolbar',
189
+ group: 'Tools',
190
+ description: 'Does something useful',
112
191
  }, () => {
113
- info('Hello World!');
192
+ info('Running!');
114
193
  });
115
194
  ```
116
195
 
117
- `action(...)` also accepts a reusable class with a `run()` method:
196
+ `action(...)` also accepts a class with a `run()` method:
118
197
 
119
198
  ```typescript
120
- class HelloWorldScript {
199
+ class MyScript {
121
200
  run(): void {
122
- info('Hello World!');
201
+ info('Running!');
123
202
  }
124
203
  }
125
204
 
126
- action({ text: 'Hello World' }, HelloWorldScript);
205
+ action({ text: 'My Script' }, MyScript);
127
206
  ```
128
207
 
129
- Common `action(...)` parameters:
208
+ | Parameter | Description |
209
+ |---|---|
210
+ | `text` | Label shown in Daz Studio |
211
+ | `menuPath` | Menu path where the action is registered. Set to `false` to skip. Defaults to `defaultMenuPath` from config. |
212
+ | `shortcut` | Keyboard shortcut (e.g. `CTRL+SHIFT+H`) |
213
+ | `toolbar` | Toolbar name the action should appear on |
214
+ | `group` | Grouping label for related actions in Daz Studio |
215
+ | `description` | Longer description for the action |
216
+ | `bundle` | Generates a setup script beside the action. `true` → `Setup.dsa.ts`, a string → `Setup <name>.dsa.ts` |
130
217
 
131
- - `text`: the label shown for the script in Daz Studio.
132
- - `menuPath`: the menu path where the script should be added. Set it to `false` to skip adding the script to a menu. If omitted, the default menu from `--menu-path` is used.
133
- - `shortcut`: the keyboard shortcut for the action.
134
- - `toolbar`: the toolbar name used when the action should appear on a toolbar.
135
- - `group`: an optional grouping label used by Daz Studio for related actions.
136
- - `description`: a longer description for the action.
137
- - `bundle`: generates an additional setup script next to the action file. Use `true` for `Setup.dsa.ts` or a string for `Setup <bundle>.dsa.ts`.
218
+ ---
138
219
 
139
- When an action is built, the framework emits two files for it:
220
+ ### Build Output: Launcher Shims
140
221
 
141
- - the stable launcher at the original output path
142
- - the implementation bundle under a sibling `lib/<script-name>/script.dsa` path
222
+ Each built action produces two files:
143
223
 
144
- Generated installers register the launcher path, so menu placement, toolbars, shortcuts, and icons keep pointing at a stable target across rebuilds.
224
+ - `out/<script>.dsa` the stable **launcher** registered with Daz Studio (menus, toolbars, shortcuts)
225
+ - `out/<folder>/lib/<script-name>/script.dsa` — the **implementation bundle** the launcher executes
145
226
 
146
- If the local `lib/` implementation is missing, the launcher falls back to the configured `appDataPath`. Builds now require this value and validate it as a unique `Author/Product` style path.
147
-
148
- ### Generated Setup Script
227
+ When you rebuild, only the implementation bundle changes. The launcher path stays stable, so re-registering the action in Daz Studio is normally not required.
149
228
 
150
- Running `npm run installer` generates `src/Setup.dsa.ts` for the project.
229
+ At runtime the launcher looks for the local `lib/` bundle first, then falls back to `App.getAppDataPath()/<appDataPath>`.
151
230
 
152
- This flow is completely automated. The installer generator scans runnable `.dsa.ts` entry files, reads the top-level `action(...)` call, and derives the setup dialog rows and registration behavior directly from that metadata. In practice, the menu path, toolbar target, shortcut, description, grouping, icon usage, and bundle-specific setup outputs all come from the action definition rather than from separate installer code you have to maintain by hand.
231
+ ---
153
232
 
154
- The generated setup script:
233
+ ### Generated Setup Script
155
234
 
156
- - Scans all runnable top-level `.dsa.ts` files under `scriptsPath`
157
- - Reads `action(...)` metadata directly from the source
158
- - Normalizes default menu paths relative to `defaultMenuPath`
159
- - Derives action labels, descriptions, shortcuts, toolbar targets, grouping, and icons from the action definition
160
- - Writes one searchable setup entry per discovered action
161
- - Uses `appDataPath/Installer` as the installer settings namespace
162
- - Passes `bundleName` through so the dialog title can be project-specific
235
+ ```bash
236
+ npm run installer
237
+ ```
163
238
 
164
- The setup dialog initializes from the current Daz Studio install state rather than assuming a clean install. It checks which actions are already installed, which ones are present in menus or toolbars, and what shortcut is currently assigned.
239
+ This scans all `.dsa.ts` entry files, reads each top-level `action(...)` call, and generates `src/Setup.dsa.ts` automatically. No installer code to maintain by hand.
165
240
 
166
- Current setup dialog behavior:
241
+ The generated setup dialog:
167
242
 
168
- - Shows an install checkbox plus the columns `Action`, `Shortcut`, `Description`, `Menu`, and `Toolbar`
169
- - Includes a search box that filters by action name, shortcut, description, menu path, and toolbar
170
- - Supports `Select All` and `Deselect All` for the currently visible rows
171
- - Lets the user right-click an action to set a shortcut or reset it to the default shortcut
172
- - Shows shortcut overrides with an `[ovr]` marker
173
- - Displays the configured toolbar name directly instead of a generic yes/no flag
174
- - Uses the configured `bundleName` in the window title when available
243
+ - Shows an install checkbox per action with columns for Action, Shortcut, Description, Menu, and Toolbar
244
+ - Includes a search box that filters across all columns
245
+ - Supports Select All / Deselect All on the visible rows
246
+ - Lets the user right-click to set or reset a shortcut (overrides shown with `[ovr]`)
247
+ - Initializes from the current Daz Studio install state — already-installed actions show as checked
248
+ - Uses `bundleName` from `dazscript.config.ts` in the window title
175
249
 
176
- Applying the setup dialog does both install and cleanup work:
250
+ Applying the dialog:
251
+ - Checked rows are installed or updated
252
+ - Unchecked rows are removed from their menu and toolbar targets
253
+ - Affected toolbars are rebuilt; empty framework-created toolbars are removed
177
254
 
178
- - Selected rows are installed or updated through the framework custom action helpers
179
- - Unselected rows are removed from supported menu and toolbar targets
180
- - Affected toolbars are rebuilt after removal so remaining selected actions stay grouped correctly
181
- - Empty toolbars created by the framework are cleaned up automatically
255
+ This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist.
182
256
 
183
- The generated project-level setup file replaces the older generated `Install.dsa.ts` and `Uninstall.dsa.ts` flow. The installer generator now removes those legacy files if they still exist.
257
+ ---
184
258
 
185
259
  ### Action-Level Bundles
186
260
 
187
- The `bundle` property on `action(...)` is separate from project `bundleName`.
261
+ The `bundle` property on `action(...)` is separate from the project-level `bundleName` in config.
188
262
 
189
- - `bundleName` in `dazscript.config.ts` is project metadata used for the setup dialog title
190
- - `bundle` in an action definition changes installer generation behavior for that action
263
+ When `bundle` is set, the installer generator also writes a setup script beside that action:
191
264
 
192
- When `bundle` is set on an action, the installer generator also writes a setup script beside that action:
265
+ - `bundle: true` writes `Setup.dsa.ts`
266
+ - `bundle: 'Utilities'` → writes `Setup Utilities.dsa.ts`
193
267
 
194
- - `bundle: true` writes `Setup.dsa.ts`
195
- - `bundle: 'Utilities'` writes `Setup Utilities.dsa.ts`
268
+ Those bundle-scoped setup files use the same setup dialog helper and also receive the project `bundleName`.
196
269
 
197
- Those bundle-generated setup files use the same setup dialog helper and now also receive the project `bundleName`.
270
+ ---
198
271
 
199
- ### Building UIs with Observables & Dialogs
272
+ ### Building UIs: Dialogs & Observables
200
273
 
201
- The framework uses a **Model-View pattern** with reactive data bindings:
274
+ The framework uses a **Model-View pattern** with reactive bindings.
202
275
 
203
- #### 1. Define Your Model
276
+ #### 1. Define a model
204
277
 
205
278
  ```typescript
206
- import { BasicDialog } from '@dsf/dialog/basic-dialog';
207
- import { Observable } from '@dsf/lib/observable';
208
279
  import { AppSettings } from '@dsf/lib/settings';
280
+ import { Observable } from '@dsf/lib/observable';
209
281
 
210
- // Model extends AppSettings for automatic persistence
211
- export class MyDialogModel extends AppSettings {
282
+ // AppSettings adds automatic persistence under the given namespace
283
+ class MyModel extends AppSettings {
212
284
  constructor() {
213
- super('MyAuthor/MyDialog'); // Namespace for saved settings
285
+ super('YourName/MyDialog');
214
286
  }
215
287
 
216
- selectedNode$ = new Observable<DzNode>();
217
- nodeLabel$ = new Observable<string>();
288
+ name$ = new Observable<string>();
289
+ enabled$ = new Observable<boolean>(false);
218
290
  }
219
291
  ```
220
292
 
221
- #### 2. Build Your Dialog
293
+ #### 2. Build the dialog
222
294
 
223
295
  ```typescript
224
296
  import { BasicDialog } from '@dsf/dialog/basic-dialog';
225
- import { MyDialogModel } from './my-dialog-model';
226
297
 
227
- export class MyDialog extends BasicDialog {
228
- constructor(private readonly model: MyDialogModel) {
298
+ class MyDialog extends BasicDialog {
299
+ constructor(private readonly model: MyModel) {
229
300
  super('My Dialog');
230
301
  }
231
302
 
232
303
  protected build(): void {
233
- const add = this.add; // Fluent builder API
234
- const model = this.model;
304
+ const { add, model } = this;
305
+
306
+ add.group('Settings').build(() => {
307
+ add.label('Name:');
308
+ add.edit().value(model.name$);
235
309
 
236
- add.group('Node Properties').build(() => {
237
- add.label('Label:');
238
- add.edit().value(model.nodeLabel$); // Two-way binding
310
+ add.checkbox('Enabled').value(model.enabled$);
239
311
  });
240
312
  }
241
313
  }
242
314
  ```
243
315
 
244
- #### 3. Connect & Use in Your Script
316
+ #### 3. Show it from a script
245
317
 
246
318
  ```typescript
247
- import { action } from '@dsf/core/action';
248
- import { getSelectedNode } from '@dsf/helpers/scene-helper';
249
- import { MyDialog, MyDialogModel } from './my-dialog';
250
-
251
319
  action({ text: 'My Dialog Script' }, () => {
252
- const model = new MyDialogModel();
253
- const selectedNode = getSelectedNode();
254
-
255
- if (!selectedNode) {
256
- console.error('Please select a node');
257
- return;
258
- }
320
+ const model = new MyModel();
321
+ const dialog = new MyDialog(model);
259
322
 
260
- // Set initial model values
261
- model.selectedNode$.value = selectedNode;
262
- model.nodeLabel$.value = selectedNode.getLabel();
263
-
264
- // React to model changes (two-way binding)
265
- model.nodeLabel$.connect((label) => {
266
- selectedNode.setLabel(label);
267
- });
268
-
269
- // Build and show dialog
270
- const dialog = new MyDialog(model);
271
- if (dialog.run()) {
272
- console.log('Dialog accepted');
273
- } else {
274
- console.log('Dialog cancelled');
275
- }
323
+ if (dialog.ok()) {
324
+ // model.name$.value holds whatever the user typed
325
+ }
276
326
  });
277
327
  ```
278
328
 
279
- ### Core Concepts
329
+ ---
280
330
 
281
- #### Observables (`Observable<T>`)
331
+ ### Observables
282
332
 
283
- Reactive state management with change notifications:
333
+ `Observable<T>` is lightweight reactive state. Controls bound with `.value(observable)` stay in sync automatically.
284
334
 
285
335
  ```typescript
286
- const name = new Observable<string>('John');
336
+ const name$ = new Observable<string>('initial');
287
337
 
288
338
  // Subscribe to changes
289
- name.connect((value) => console.log(`Name: ${value}`));
339
+ name$.connect((value) => console.log(value));
290
340
 
291
- // Set value (triggers callbacks)
292
- name.value = 'Jane'; // Logs: "Name: Jane"
341
+ // Set value fires all subscribers
342
+ name$.value = 'updated';
293
343
 
294
- // Intercept/validate before change
295
- name.intercept(
296
- (prev, current) => current.toUpperCase() // Transform
297
- );
344
+ // Transform values before they are applied
345
+ name$.intercept((prev, next) => next.trim());
298
346
 
299
- // Pause/resume notifications
300
- name.pause(() => {
301
- name.value = 'A';
302
- name.value = 'B'; // Won't trigger callbacks
347
+ // Batch updates without firing subscribers mid-batch
348
+ name$.pause(() => {
349
+ name$.value = 'a';
350
+ name$.value = 'b'; // only 'b' fires after the pause block
303
351
  });
304
352
  ```
305
353
 
306
- #### Available Helpers
354
+ ---
307
355
 
308
- The framework includes 29 helper modules for common Daz Studio tasks:
356
+ ### Dialog Builder Reference
309
357
 
310
- - **Scene**: `getRoot()`, `getSelectedNode()`, `getNodes()`, scene modification
311
- - **Nodes**: Type checking (figure, bone, etc.), transforms, visibility, selection
312
- - **Properties**: Finding, adjusting, interpolating property values
313
- - **Dialogs**: `BasicDialog`, `InputDialog`, `SelectionDialog`
314
- - **Arrays**: `distinct()`, `flatten()`, `groupBy()`, unique operations
315
- - **Strings**: Upper/lowercase, trimming, splitting
316
- - **Files & Paths**: Reading, writing, directory operations
317
- - **UI Helpers**: Message boxes, progress dialogs, menus, keyboard shortcuts
358
+ Use `this.add` inside `build()` to construct the UI declaratively.
318
359
 
319
- Example:
360
+ **Widgets**
320
361
 
321
- ```typescript
322
- import * as SceneHelper from '@dsf/helpers/scene-helper';
323
- import * as NodeHelper from '@dsf/helpers/node-helper';
324
- import * as ArrayHelper from '@dsf/helpers/array-helper';
362
+ | Builder | Description |
363
+ |---|---|
364
+ | `add.label(text)` | Static text label |
365
+ | `add.edit()` | Single-line text input |
366
+ | `add.button(text)` | Push button |
367
+ | `add.checkbox(text)` | Checkbox |
368
+ | `add.radio(text)` | Radio button |
369
+ | `add.comboBox()` | Drop-down list |
370
+ | `add.listBox()` | Scrollable list |
371
+ | `add.slider(min, max)` | Numeric slider |
372
+ | `add.colorPicker()` | Color picker |
373
+ | `add.nodeSelection()` | Daz Studio node selector |
325
374
 
326
- const allNodes = SceneHelper.getNodes();
327
- const figures = allNodes.filter(n => NodeHelper.isFigure(n));
328
- const unique = ArrayHelper.distinct(figures);
329
- ```
375
+ **Layout**
330
376
 
331
- #### Builder Pattern for UIs
377
+ | Builder | Description |
378
+ |---|---|
379
+ | `add.group(text)` | Group box |
380
+ | `add.tab(text)` | Tab page |
381
+ | `add.horizontal(fn)` | Horizontal layout row |
382
+ | `add.splitter()` | Resizable splitter |
332
383
 
333
- Fluent, chainable API for rapid dialog construction:
384
+ Most widget builders expose a fluent chain:
334
385
 
335
386
  ```typescript
336
- add.tab('Settings').build(() => {
337
- add.group('Colors').build(() => {
338
- add.colorPicker().value(colorObservable);
339
- add.label('Opacity:');
340
- add.slider(0, 100).value(opacityObservable);
341
- });
387
+ add.edit()
388
+ .value(model.name$) // two-way binding
389
+ .toolTip('Enter your name')
390
+ .readOnly(false);
342
391
 
343
- add.horizontal((layout) => {
344
- add.label('Name:');
345
- add.edit().value(nameObservable);
346
- });
392
+ add.button('Apply')
393
+ .clicked(() => applyChanges());
347
394
 
348
- add.listView()
349
- .items(itemsObservable)
350
- .value(selectedItemObservable)
351
- .changed((item) => console.log(`Selected: ${item}`));
395
+ add.tab('Options').build(() => {
396
+ add.group('Colors').build(() => {
397
+ add.colorPicker().value(model.color$);
398
+ });
352
399
  });
353
400
  ```
354
401
 
355
- Supported widgets:
356
- - Basic: Label, Edit (text input), Button, Checkbox, Radio
357
- - Selection: ComboBox, ListBox, Slider, ColorPicker
358
- - Layout: Tab, Group, Horizontal, Vertical, Splitter
359
- - Advanced: ListView, TreeView, Popup Menu
402
+ ---
360
403
 
361
- ### Two-Way Data Binding
404
+ ### Available Helpers
362
405
 
363
- The power of this framework is reactive data binding between models and UI:
406
+ The framework ships helpers for common Daz Studio tasks, all importable from `@dsf/helpers/*`.
364
407
 
365
- ```typescript
366
- // User types in UI → updates model → triggers logic
367
- model.nodeLabel$ = new Observable<string>();
408
+ | Module | Key functions |
409
+ |---|---|
410
+ | `scene-helper` | `getRoot()`, `getSelectedNode()`, `getNodes()` |
411
+ | `node-helper` | Type checks (`isFigure`, `isBone`), transforms, visibility |
412
+ | `property-helper` | Find, read, and adjust node properties |
413
+ | `array-helper` | `distinct()`, `flatten()`, `groupBy()` |
414
+ | `string-helper` | Case, trimming, splitting |
415
+ | `directory-helper` | File and path operations |
416
+ | `message-box-helper` | `info()`, `warn()`, `error()` message boxes |
417
+ | `progress-helper` | Progress dialogs |
418
+ | `menu-helper` | Custom menus |
419
+ | `undo-helper` | Undo stack integration |
368
420
 
369
- model.nodeLabel$.connect((newLabel) => {
370
- // React to UI changes
371
- selectedNode.setLabel(newLabel);
372
- });
421
+ ```typescript
422
+ import * as SceneHelper from '@dsf/helpers/scene-helper';
423
+ import * as NodeHelper from '@dsf/helpers/node-helper';
373
424
 
374
- // Code updates model UI automatically reflects
375
- model.nodeLabel$.value = 'New Label'; // UI edit box updates
425
+ const figures = SceneHelper.getNodes().filter(n => NodeHelper.isFigure(n));
376
426
  ```
377
427
 
378
- This eliminates the need for manual synchronization between UI and data.
428
+ ---
379
429
 
380
430
  ### Directory Structure
381
431
 
382
- For a typical project using this framework:
383
-
384
432
  ```
385
433
  my-daz-scripts/
386
434
  ├── src/
387
- │ ├── scripts/
388
- ├── my-first-script.dsa.ts
389
- ├── my-dialog-model.ts
390
- │ ├── my-dialog.ts
391
- │ │ └── my-dialog-script.dsa.ts
392
- │ └── config.ts
393
- ├── out/ # Generated launchers, implementations, and copied icons
394
- ├── package.json
435
+ │ ├── hello-world.dsa.ts # runnable entry point → compiles to .dsa
436
+ │ ├── my-dialog-model.ts # plain TypeScript — model or helper class
437
+ │ ├── my-dialog.ts
438
+ └── my-dialog-script.dsa.ts # runnable entry point
439
+ ├── out/ # build output — launchers, bundles, icons
440
+ ├── dazscript.config.ts
395
441
  ├── tsconfig.json
396
- └── dazscript.config.ts
442
+ └── package.json
397
443
  ```
398
444
 
399
- **Key points:**
400
- - Scripts ending in `.dsa.ts` compile to `.dsa` files for Daz Studio
401
- - Regular `.ts` files are utility, model, or helper classes
402
- - Built action outputs are split into stable launchers plus sibling `lib/<script-name>/script.dsa` implementations
403
- - Run `npm run build` to compile TypeScript → Daz Scripts
404
- - Run `npm run watch` during development for live rebuild
405
- - Rebuild after script changes; reinstalling Daz actions is usually not required because the launcher path stays stable
445
+ Files ending in `.dsa.ts` are treated as runnable entry points and compiled to `.dsa`. Plain `.ts` files are modules — imported by entry points but not compiled independently.
446
+
447
+ **Common commands**
448
+
449
+ | Command | What it does |
450
+ |---|---|
451
+ | `npm run build` | Compile TypeScript Daz Script |
452
+ | `npm run watch` | Recompile on every save |
453
+ | `npm run installer` | Generate the setup dialog |
454
+ | `npm run icons` | Copy icon assets to the output folder |
406
455
 
407
- ## Development & Publishing
456
+ ---
408
457
 
409
- This project uses **semantic-release** for automatic versioning and npm publishing.
458
+ ### Development & Publishing
410
459
 
411
- ### Commit Message Conventions
460
+ This package uses **semantic-release** for automatic versioning and npm publishing.
461
+
462
+ #### Commit message conventions
463
+
464
+ | Prefix | Effect |
465
+ |---|---|
466
+ | `fix: ...` | Patch bump (`1.0.0` → `1.0.1`) |
467
+ | `feat: ...` | Minor bump (`1.0.0` → `1.1.0`) |
468
+ | `BREAKING CHANGE: ...` in commit body | Major bump (`1.0.0` → `2.0.0`) |
469
+ | No prefix | No version bump |
470
+
471
+ Examples:
472
+ ```
473
+ fix: resolve layout overflow in group builder
474
+ feat: add tree view builder
475
+ feat: refactor action entrypoint
476
+
477
+ BREAKING CHANGE: action() now requires an explicit menuPath
478
+ ```
412
479
 
413
- Use conventional commit messages to control version bumping:
480
+ #### Publishing
414
481
 
415
- - **`fix: description`** → Patch version bump (`x.y.z` → `x.y.(z+1)`)
416
- - Bug fixes, patches, or minor improvements
417
- - Example: `fix: resolve dialog builder layout issue`
482
+ Every push to `master` automatically:
418
483
 
419
- - **`feat: description`** Minor version bump (`x.y.z` → `x.(y+1).0`)
420
- - New features or significant enhancements
421
- - Example: `feat: add tree view builder component`
484
+ 1. Analyzes commit messages since the last release
485
+ 2. Updates the version in `package.json`
486
+ 3. Builds the project
487
+ 4. Creates a GitHub release with changelog
488
+ 5. Publishes to npm
422
489
 
423
- - **`BREAKING CHANGE: description`** → Major version bump (`x.y.z` → `(x+1).0.0`)
424
- - Add to commit body for breaking changes
425
- - Example: `feat: refactor action decorator API\n\nBREAKING CHANGE: action() now requires explicit menu path`
490
+ No manual steps required.
426
491
 
427
- - **No prefix** → No version bump
428
- - Documentation, style, or non-publishing changes
429
- - Example: `update README examples`
492
+ ---
430
493
 
431
- ### Publishing
494
+ ## Resources
432
495
 
433
- Every push to the `master` branch automatically triggers:
496
+ - [DAZ Script Reference](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/start) official Daz Studio scripting documentation
497
+ - [dazscript-types](https://www.npmjs.com/package/dazscript-types) — TypeScript type definitions for the Daz Studio API
434
498
 
435
- 1. **Analyze** commit messages since last release
436
- 2. **Update** version in `package.json`
437
- 3. **Build** the project (`npm run build`)
438
- 4. **Create** a GitHub release with changelog
439
- 5. **Publish** to npm
499
+ ## Examples
440
500
 
441
- No manual steps required—just commit with proper conventions and push!
501
+ The `src/examples/` folder contains ready-to-run scripts demonstrating common patterns and fuller reference implementations for common Daz Studio workflows.