dazscript-framework 0.3.2 → 1.0.2

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.
package/README.md CHANGED
@@ -1,441 +1,537 @@
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`:
44
39
 
45
- The generated package scripts use the framework CLI directly, so consumer projects do not need their own webpack or Babel setup.
40
+ ```typescript
41
+ import { action } from '@dsf/core/action';
42
+ import { info } from '@dsf/helpers/message-box-helper';
46
43
 
47
- You can customize the generated defaults:
44
+ action({ text: 'Hello World' }, () => {
45
+ info('Hello World!');
46
+ });
47
+ ```
48
+
49
+ Files ending in `.dsa.ts` are compiled as runnable Daz Studio entry points.
50
+
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
51
55
  ```
52
56
 
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.
57
+ Output lands in `./out/`.
57
58
 
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.
59
+ ### 5. Load in Daz Studio
59
60
 
60
- Set `appDataPath` explicitly in `dazscript.config.ts` for every project. It is required for builds that generate launcher shims:
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
71
+ ```
72
+
73
+ Then in Daz Studio: **Scripts > MyScripts > Hello World**. A message box appears.
74
+
75
+ ---
76
+
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
120
+
121
+ ### Installation & Setup
122
+
123
+ Install the framework and its peer dependency:
124
+
125
+ ```bash
126
+ npm install dazscript-framework dazscript-types
127
+ ```
128
+
129
+ Scaffold a new project:
75
130
 
76
- Built action outputs now use stable launcher shims by default:
131
+ ```bash
132
+ npx dazscript init
133
+ ```
77
134
 
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
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.
80
136
 
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.
137
+ This generates:
138
+ - `dazscript.config.ts`
139
+ - `tsconfig.json`
140
+ - `package.json` script wiring for `build`, `watch`, `icons`, and `installer`
141
+
142
+ Available `init` flags:
82
143
 
83
- ## Usage
144
+ ```bash
145
+ npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out --app-data-path YourName/my-project
146
+ ```
84
147
 
85
- ### Quick Start: Hello World
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) |
86
154
 
87
- Create a simple script that logs to the console:
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.
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.
228
+
229
+ At runtime the launcher looks for the local `lib/` bundle first, then falls back to `App.getAppDataPath()/<appDataPath>`.
230
+
231
+ ---
147
232
 
148
233
  ### Generated Setup Script
149
234
 
150
- Running `npm run installer` generates `src/Setup.dsa.ts` for the project.
235
+ ```bash
236
+ npm run installer
237
+ ```
238
+
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.
240
+
241
+ The generated setup dialog:
242
+
243
+ - Shows an install checkbox per action with columns for Action, Shortcut, Description, Menu, and Toolbar
244
+ - Adds a `Keyboard Shortcuts` tab when a project defines shortcut JSON
245
+ - Includes a search box that filters across all columns
246
+ - Supports Select All / Deselect All on the visible rows
247
+ - Lets the user right-click to set or reset a shortcut (overrides shown with `[ovr]`)
248
+ - Initializes from the current Daz Studio install state — already-installed actions show as checked
249
+ - Uses `bundleName` from `dazscript.config.ts` in the window title
151
250
 
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.
251
+ Applying the dialog:
252
+ - Checked rows are installed or updated
253
+ - Unchecked rows are removed from their menu and toolbar targets
254
+ - Affected toolbars are rebuilt; empty framework-created toolbars are removed
255
+ - Selected keyboard shortcut rows are applied after actions are installed
153
256
 
154
- The generated setup script:
257
+ This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist, except when shortcut restoration is needed.
155
258
 
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
259
+ ### Setup Keyboard Shortcuts
163
260
 
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.
261
+ Projects can define keyboard shortcuts for both framework custom actions and built-in Daz Studio actions. The installer generator looks for shortcut JSON in this order:
165
262
 
166
- Current setup dialog behavior:
263
+ - `keyboardShortcutsPath`, `shortcutsPath`, or `actionAcceleratorsPath` in `dazscript.config.ts`
264
+ - `src/keyboard-shortcuts.json`
265
+ - `src/action-accelerators.json`
266
+ - `keyboard-shortcuts.json`
267
+ - `action-accelerators.json`
167
268
 
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
269
+ The JSON can be an array or an object containing `actions`, `shortcuts`, or `accelerators`. Each entry can use the Action Accelerator Finder style fields:
175
270
 
176
- Applying the setup dialog does both install and cleanup work:
271
+ ```json
272
+ [
273
+ {
274
+ "name": "DzRenderAction",
275
+ "text": "Render",
276
+ "shortcut": "CTRL+R"
277
+ }
278
+ ]
279
+ ```
280
+
281
+ Accepted shortcut fields are `shortcut`, `accelerator`, or `key`. Accepted action-name fields are `name` or `action`.
177
282
 
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
283
+ At build time the JSON is embedded into generated `Setup.dsa.ts`; Daz Studio does not need to read the original JSON file at setup time. During setup, the `Keyboard Shortcuts` tab shows the action label, current shortcut, new shortcut, action type, and conflicts. The user chooses which shortcut rows to apply.
182
284
 
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.
285
+ Before changing a non-custom Daz Studio action shortcut, setup writes the original value to:
286
+
287
+ ```text
288
+ App.getAppDataPath()/<appDataPath>/Installer/keyboard-shortcuts-backup.json
289
+ ```
290
+
291
+ When shortcut JSON exists, the generator also writes `src/Uninstall.dsa.ts`. Running that uninstall script restores backed-up non-custom shortcuts. Custom action shortcuts are not backed up because uninstalling the custom action removes the shortcut with the action.
292
+
293
+ ---
184
294
 
185
295
  ### Action-Level Bundles
186
296
 
187
- The `bundle` property on `action(...)` is separate from project `bundleName`.
297
+ The `bundle` property on `action(...)` is separate from the project-level `bundleName` in config.
188
298
 
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
299
+ When `bundle` is set, the installer generator also writes a setup script beside that action:
191
300
 
192
- When `bundle` is set on an action, the installer generator also writes a setup script beside that action:
301
+ - `bundle: true` writes `Setup.dsa.ts`
302
+ - `bundle: 'Utilities'` → writes `Setup Utilities.dsa.ts`
193
303
 
194
- - `bundle: true` writes `Setup.dsa.ts`
195
- - `bundle: 'Utilities'` writes `Setup Utilities.dsa.ts`
304
+ Those bundle-scoped setup files use the same setup dialog helper and also receive the project `bundleName`.
196
305
 
197
- Those bundle-generated setup files use the same setup dialog helper and now also receive the project `bundleName`.
306
+ ---
198
307
 
199
- ### Building UIs with Observables & Dialogs
308
+ ### Building UIs: Dialogs & Observables
200
309
 
201
- The framework uses a **Model-View pattern** with reactive data bindings:
310
+ The framework uses a **Model-View pattern** with reactive bindings.
202
311
 
203
- #### 1. Define Your Model
312
+ #### 1. Define a model
204
313
 
205
314
  ```typescript
206
- import { BasicDialog } from '@dsf/dialog/basic-dialog';
207
- import { Observable } from '@dsf/lib/observable';
208
315
  import { AppSettings } from '@dsf/lib/settings';
316
+ import { Observable } from '@dsf/lib/observable';
209
317
 
210
- // Model extends AppSettings for automatic persistence
211
- export class MyDialogModel extends AppSettings {
318
+ // AppSettings adds automatic persistence under the given namespace
319
+ class MyModel extends AppSettings {
212
320
  constructor() {
213
- super('MyAuthor/MyDialog'); // Namespace for saved settings
321
+ super('YourName/MyDialog');
214
322
  }
215
323
 
216
- selectedNode$ = new Observable<DzNode>();
217
- nodeLabel$ = new Observable<string>();
324
+ name$ = new Observable<string>();
325
+ enabled$ = new Observable<boolean>(false);
218
326
  }
219
327
  ```
220
328
 
221
- #### 2. Build Your Dialog
329
+ #### 2. Build the dialog
222
330
 
223
331
  ```typescript
224
332
  import { BasicDialog } from '@dsf/dialog/basic-dialog';
225
- import { MyDialogModel } from './my-dialog-model';
226
333
 
227
- export class MyDialog extends BasicDialog {
228
- constructor(private readonly model: MyDialogModel) {
334
+ class MyDialog extends BasicDialog {
335
+ constructor(private readonly model: MyModel) {
229
336
  super('My Dialog');
230
337
  }
231
338
 
232
339
  protected build(): void {
233
- const add = this.add; // Fluent builder API
234
- const model = this.model;
340
+ const { add, model } = this;
341
+
342
+ add.group('Settings').build(() => {
343
+ add.label('Name:');
344
+ add.edit().value(model.name$);
235
345
 
236
- add.group('Node Properties').build(() => {
237
- add.label('Label:');
238
- add.edit().value(model.nodeLabel$); // Two-way binding
346
+ add.checkbox('Enabled').value(model.enabled$);
239
347
  });
240
348
  }
241
349
  }
242
350
  ```
243
351
 
244
- #### 3. Connect & Use in Your Script
352
+ #### 3. Show it from a script
245
353
 
246
354
  ```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
355
  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
- }
259
-
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
- });
356
+ const model = new MyModel();
357
+ const dialog = new MyDialog(model);
268
358
 
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
- }
359
+ if (dialog.ok()) {
360
+ // model.name$.value holds whatever the user typed
361
+ }
276
362
  });
277
363
  ```
278
364
 
279
- ### Core Concepts
365
+ ---
280
366
 
281
- #### Observables (`Observable<T>`)
367
+ ### Observables
282
368
 
283
- Reactive state management with change notifications:
369
+ `Observable<T>` is lightweight reactive state. Controls bound with `.value(observable)` stay in sync automatically.
284
370
 
285
371
  ```typescript
286
- const name = new Observable<string>('John');
372
+ const name$ = new Observable<string>('initial');
287
373
 
288
374
  // Subscribe to changes
289
- name.connect((value) => console.log(`Name: ${value}`));
375
+ name$.connect((value) => console.log(value));
290
376
 
291
- // Set value (triggers callbacks)
292
- name.value = 'Jane'; // Logs: "Name: Jane"
377
+ // Set value fires all subscribers
378
+ name$.value = 'updated';
293
379
 
294
- // Intercept/validate before change
295
- name.intercept(
296
- (prev, current) => current.toUpperCase() // Transform
297
- );
380
+ // Transform values before they are applied
381
+ name$.intercept((prev, next) => next.trim());
298
382
 
299
- // Pause/resume notifications
300
- name.pause(() => {
301
- name.value = 'A';
302
- name.value = 'B'; // Won't trigger callbacks
383
+ // Batch updates without firing subscribers mid-batch
384
+ name$.pause(() => {
385
+ name$.value = 'a';
386
+ name$.value = 'b'; // only 'b' fires after the pause block
303
387
  });
304
388
  ```
305
389
 
306
- #### Available Helpers
390
+ ---
307
391
 
308
- The framework includes 29 helper modules for common Daz Studio tasks:
392
+ ### Dialog Builder Reference
309
393
 
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
394
+ Use `this.add` inside `build()` to construct the UI declaratively.
318
395
 
319
- Example:
396
+ **Widgets**
320
397
 
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';
398
+ | Builder | Description |
399
+ |---|---|
400
+ | `add.label(text)` | Static text label |
401
+ | `add.edit()` | Single-line text input |
402
+ | `add.button(text)` | Push button |
403
+ | `add.checkbox(text)` | Checkbox |
404
+ | `add.radio(text)` | Radio button |
405
+ | `add.comboBox()` | Drop-down list |
406
+ | `add.listBox()` | Scrollable list |
407
+ | `add.slider(min, max)` | Numeric slider |
408
+ | `add.colorPicker()` | Color picker |
409
+ | `add.nodeSelection()` | Daz Studio node selector |
325
410
 
326
- const allNodes = SceneHelper.getNodes();
327
- const figures = allNodes.filter(n => NodeHelper.isFigure(n));
328
- const unique = ArrayHelper.distinct(figures);
329
- ```
411
+ **Layout**
330
412
 
331
- #### Builder Pattern for UIs
413
+ | Builder | Description |
414
+ |---|---|
415
+ | `add.group(text)` | Group box |
416
+ | `add.tab(text)` | Tab page |
417
+ | `add.horizontal(fn)` | Horizontal layout row |
418
+ | `add.splitter()` | Resizable splitter |
332
419
 
333
- Fluent, chainable API for rapid dialog construction:
420
+ Most widget builders expose a fluent chain:
334
421
 
335
422
  ```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
- });
423
+ add.edit()
424
+ .value(model.name$) // two-way binding
425
+ .toolTip('Enter your name')
426
+ .readOnly(false);
342
427
 
343
- add.horizontal((layout) => {
344
- add.label('Name:');
345
- add.edit().value(nameObservable);
346
- });
428
+ add.button('Apply')
429
+ .clicked(() => applyChanges());
347
430
 
348
- add.listView()
349
- .items(itemsObservable)
350
- .value(selectedItemObservable)
351
- .changed((item) => console.log(`Selected: ${item}`));
431
+ add.tab('Options').build(() => {
432
+ add.group('Colors').build(() => {
433
+ add.colorPicker().value(model.color$);
434
+ });
352
435
  });
353
436
  ```
354
437
 
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
438
+ ---
360
439
 
361
- ### Two-Way Data Binding
440
+ ### Available Helpers
362
441
 
363
- The power of this framework is reactive data binding between models and UI:
442
+ The framework ships helpers for common Daz Studio tasks, all importable from `@dsf/helpers/*`.
364
443
 
365
- ```typescript
366
- // User types in UI → updates model → triggers logic
367
- model.nodeLabel$ = new Observable<string>();
444
+ | Module | Key functions |
445
+ |---|---|
446
+ | `scene-helper` | `getRoot()`, `getSelectedNode()`, `getNodes()` |
447
+ | `node-helper` | Type checks (`isFigure`, `isBone`), transforms, visibility |
448
+ | `property-helper` | Find, read, and adjust node properties |
449
+ | `array-helper` | `distinct()`, `flatten()`, `groupBy()` |
450
+ | `string-helper` | Case, trimming, splitting |
451
+ | `directory-helper` | File and path operations |
452
+ | `message-box-helper` | `info()`, `warn()`, `error()` message boxes |
453
+ | `progress-helper` | Progress dialogs |
454
+ | `menu-helper` | Custom menus |
455
+ | `undo-helper` | Undo stack integration |
368
456
 
369
- model.nodeLabel$.connect((newLabel) => {
370
- // React to UI changes
371
- selectedNode.setLabel(newLabel);
372
- });
457
+ ```typescript
458
+ import * as SceneHelper from '@dsf/helpers/scene-helper';
459
+ import * as NodeHelper from '@dsf/helpers/node-helper';
373
460
 
374
- // Code updates model UI automatically reflects
375
- model.nodeLabel$.value = 'New Label'; // UI edit box updates
461
+ const figures = SceneHelper.getNodes().filter(n => NodeHelper.isFigure(n));
376
462
  ```
377
463
 
378
- This eliminates the need for manual synchronization between UI and data.
464
+ ---
379
465
 
380
466
  ### Directory Structure
381
467
 
382
- For a typical project using this framework:
383
-
384
468
  ```
385
469
  my-daz-scripts/
386
470
  ├── 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
471
+ │ ├── hello-world.dsa.ts # runnable entry point → compiles to .dsa
472
+ │ ├── my-dialog-model.ts # plain TypeScript — model or helper class
473
+ │ ├── my-dialog.ts
474
+ └── my-dialog-script.dsa.ts # runnable entry point
475
+ ├── out/ # build output — launchers, bundles, icons
476
+ ├── dazscript.config.ts
395
477
  ├── tsconfig.json
396
- └── dazscript.config.ts
478
+ └── package.json
397
479
  ```
398
480
 
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
481
+ 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.
482
+
483
+ **Common commands**
406
484
 
407
- ## Development & Publishing
485
+ | Command | What it does |
486
+ |---|---|
487
+ | `npm run build` | Compile TypeScript → Daz Script |
488
+ | `npm run watch` | Recompile on every save |
489
+ | `npm run installer` | Generate the setup dialog |
490
+ | `npm run icons` | Copy icon assets to the output folder |
408
491
 
409
- This project uses **semantic-release** for automatic versioning and npm publishing.
492
+ ---
410
493
 
411
- ### Commit Message Conventions
494
+ ### Development & Publishing
495
+
496
+ This package uses **semantic-release** for automatic versioning and npm publishing.
497
+
498
+ #### Commit message conventions
499
+
500
+ | Prefix | Effect |
501
+ |---|---|
502
+ | `fix: ...` | Patch bump (`1.0.0` → `1.0.1`) |
503
+ | `feat: ...` | Minor bump (`1.0.0` → `1.1.0`) |
504
+ | `BREAKING CHANGE: ...` in commit body | Major bump (`1.0.0` → `2.0.0`) |
505
+ | No prefix | No version bump |
506
+
507
+ Examples:
508
+ ```
509
+ fix: resolve layout overflow in group builder
510
+ feat: add tree view builder
511
+ feat: refactor action entrypoint
512
+
513
+ BREAKING CHANGE: action() now requires an explicit menuPath
514
+ ```
412
515
 
413
- Use conventional commit messages to control version bumping:
516
+ #### Publishing
414
517
 
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`
518
+ Every push to `master` automatically:
418
519
 
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`
520
+ 1. Analyzes commit messages since the last release
521
+ 2. Updates the version in `package.json`
522
+ 3. Builds the project
523
+ 4. Creates a GitHub release with changelog
524
+ 5. Publishes to npm
422
525
 
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`
526
+ No manual steps required.
426
527
 
427
- - **No prefix** → No version bump
428
- - Documentation, style, or non-publishing changes
429
- - Example: `update README examples`
528
+ ---
430
529
 
431
- ### Publishing
530
+ ## Resources
432
531
 
433
- Every push to the `master` branch automatically triggers:
532
+ - [DAZ Script Reference](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/start) official Daz Studio scripting documentation
533
+ - [dazscript-types](https://www.npmjs.com/package/dazscript-types) — TypeScript type definitions for the Daz Studio API
434
534
 
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
535
+ ## Examples
440
536
 
441
- No manual steps required—just commit with proper conventions and push!
537
+ The `src/examples/` folder contains ready-to-run scripts demonstrating common patterns and fuller reference implementations for common Daz Studio workflows.