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 +361 -265
- package/dist/scripts/install-generator.js +85 -14
- package/package.json +7 -4
- package/src/Setup.dsa.ts +37 -9
- package/src/dialog/builders/list-view-builder.ts +5 -0
- package/src/examples/01-hello-world.dsa.ts +8 -0
- package/src/examples/02-persistence-dialog.dsa.ts +21 -0
- package/src/examples/02-persistence-dialog.ts +68 -0
- package/src/examples/03-simple-dialog.dsa.ts +23 -0
- package/src/examples/03-simple-dialog.ts +47 -0
- package/src/examples/04-settings-dialog.dsa.ts +29 -0
- package/src/examples/04-settings-dialog.ts +83 -0
- package/src/examples/05-list-dialog.dsa.ts +53 -0
- package/src/examples/05-list-dialog.ts +88 -0
- package/src/examples/06-showcase-dialog.dsa.ts +87 -0
- package/src/examples/06-showcase-dialog.ts +518 -0
- package/src/helpers/custom-action-helper.ts +2 -1
- package/src/helpers/custom-action-installer-helper.ts +306 -20
- package/src/lib/observable.test.ts +416 -0
- package/src/lib/observable.ts +24 -18
- package/src/lib/tree-node.test.ts +21 -0
- package/tsconfig.json +28 -112
- package/webpack.config.js +7 -0
- package/src/samples/hello-world.dsa.ts +0 -8
- package/src/samples/sample-dialog.dsa.ts +0 -47
- package/src/samples/sample-dialog.ts +0 -49
- /package/src/{samples → examples}/config.ts +0 -0
package/README.md
CHANGED
|
@@ -1,441 +1,537 @@
|
|
|
1
1
|
# DazScript Framework
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
## Why use it?
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
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
|
-
|
|
16
|
+
---
|
|
14
17
|
|
|
15
|
-
|
|
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
|
-
|
|
20
|
+
A script that shows a message box in Daz Studio.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
### 1. Install
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
25
|
npm install dazscript-framework dazscript-types
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
### 3. Write the script
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
- `tsconfig.json`
|
|
43
|
-
- `package.json` script wiring for `build`, `watch`, `icons`, and `installer`
|
|
38
|
+
Create `src/hello-world.dsa.ts`:
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
```typescript
|
|
41
|
+
import { action } from '@dsf/core/action';
|
|
42
|
+
import { info } from '@dsf/helpers/message-box-helper';
|
|
46
43
|
|
|
47
|
-
|
|
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
|
-
|
|
54
|
+
npm run build
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
|
|
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
|
-
|
|
59
|
+
### 5. Load in Daz Studio
|
|
59
60
|
|
|
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
|
|
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 {
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
131
|
+
```bash
|
|
132
|
+
npx dazscript init
|
|
133
|
+
```
|
|
77
134
|
|
|
78
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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('
|
|
192
|
+
info('Running!');
|
|
114
193
|
});
|
|
115
194
|
```
|
|
116
195
|
|
|
117
|
-
`action(...)` also accepts a
|
|
196
|
+
`action(...)` also accepts a class with a `run()` method:
|
|
118
197
|
|
|
119
198
|
```typescript
|
|
120
|
-
class
|
|
199
|
+
class MyScript {
|
|
121
200
|
run(): void {
|
|
122
|
-
info('
|
|
201
|
+
info('Running!');
|
|
123
202
|
}
|
|
124
203
|
}
|
|
125
204
|
|
|
126
|
-
action({ text: '
|
|
205
|
+
action({ text: 'My Script' }, MyScript);
|
|
127
206
|
```
|
|
128
207
|
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
+
### Build Output: Launcher Shims
|
|
140
221
|
|
|
141
|
-
|
|
142
|
-
- the implementation bundle under a sibling `lib/<script-name>/script.dsa` path
|
|
222
|
+
Each built action produces two files:
|
|
143
223
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
+
- `bundle: true` → writes `Setup.dsa.ts`
|
|
302
|
+
- `bundle: 'Utilities'` → writes `Setup Utilities.dsa.ts`
|
|
193
303
|
|
|
194
|
-
|
|
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
|
-
|
|
306
|
+
---
|
|
198
307
|
|
|
199
|
-
### Building UIs
|
|
308
|
+
### Building UIs: Dialogs & Observables
|
|
200
309
|
|
|
201
|
-
The framework uses a **Model-View pattern** with reactive
|
|
310
|
+
The framework uses a **Model-View pattern** with reactive bindings.
|
|
202
311
|
|
|
203
|
-
#### 1. Define
|
|
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
|
-
//
|
|
211
|
-
|
|
318
|
+
// AppSettings adds automatic persistence under the given namespace
|
|
319
|
+
class MyModel extends AppSettings {
|
|
212
320
|
constructor() {
|
|
213
|
-
super('
|
|
321
|
+
super('YourName/MyDialog');
|
|
214
322
|
}
|
|
215
323
|
|
|
216
|
-
|
|
217
|
-
|
|
324
|
+
name$ = new Observable<string>();
|
|
325
|
+
enabled$ = new Observable<boolean>(false);
|
|
218
326
|
}
|
|
219
327
|
```
|
|
220
328
|
|
|
221
|
-
#### 2. Build
|
|
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
|
-
|
|
228
|
-
constructor(private readonly model:
|
|
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
|
|
234
|
-
|
|
340
|
+
const { add, model } = this;
|
|
341
|
+
|
|
342
|
+
add.group('Settings').build(() => {
|
|
343
|
+
add.label('Name:');
|
|
344
|
+
add.edit().value(model.name$);
|
|
235
345
|
|
|
236
|
-
|
|
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.
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
365
|
+
---
|
|
280
366
|
|
|
281
|
-
|
|
367
|
+
### Observables
|
|
282
368
|
|
|
283
|
-
|
|
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>('
|
|
372
|
+
const name$ = new Observable<string>('initial');
|
|
287
373
|
|
|
288
374
|
// Subscribe to changes
|
|
289
|
-
name
|
|
375
|
+
name$.connect((value) => console.log(value));
|
|
290
376
|
|
|
291
|
-
// Set value
|
|
292
|
-
name
|
|
377
|
+
// Set value — fires all subscribers
|
|
378
|
+
name$.value = 'updated';
|
|
293
379
|
|
|
294
|
-
//
|
|
295
|
-
name
|
|
296
|
-
(prev, current) => current.toUpperCase() // Transform
|
|
297
|
-
);
|
|
380
|
+
// Transform values before they are applied
|
|
381
|
+
name$.intercept((prev, next) => next.trim());
|
|
298
382
|
|
|
299
|
-
//
|
|
300
|
-
name
|
|
301
|
-
name
|
|
302
|
-
name
|
|
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
|
-
|
|
390
|
+
---
|
|
307
391
|
|
|
308
|
-
|
|
392
|
+
### Dialog Builder Reference
|
|
309
393
|
|
|
310
|
-
|
|
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
|
-
|
|
396
|
+
**Widgets**
|
|
320
397
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
327
|
-
const figures = allNodes.filter(n => NodeHelper.isFigure(n));
|
|
328
|
-
const unique = ArrayHelper.distinct(figures);
|
|
329
|
-
```
|
|
411
|
+
**Layout**
|
|
330
412
|
|
|
331
|
-
|
|
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
|
-
|
|
420
|
+
Most widget builders expose a fluent chain:
|
|
334
421
|
|
|
335
422
|
```typescript
|
|
336
|
-
add.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
add.edit().value(nameObservable);
|
|
346
|
-
});
|
|
428
|
+
add.button('Apply')
|
|
429
|
+
.clicked(() => applyChanges());
|
|
347
430
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.value(
|
|
351
|
-
|
|
431
|
+
add.tab('Options').build(() => {
|
|
432
|
+
add.group('Colors').build(() => {
|
|
433
|
+
add.colorPicker().value(model.color$);
|
|
434
|
+
});
|
|
352
435
|
});
|
|
353
436
|
```
|
|
354
437
|
|
|
355
|
-
|
|
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
|
-
###
|
|
440
|
+
### Available Helpers
|
|
362
441
|
|
|
363
|
-
The
|
|
442
|
+
The framework ships helpers for common Daz Studio tasks, all importable from `@dsf/helpers/*`.
|
|
364
443
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
457
|
+
```typescript
|
|
458
|
+
import * as SceneHelper from '@dsf/helpers/scene-helper';
|
|
459
|
+
import * as NodeHelper from '@dsf/helpers/node-helper';
|
|
373
460
|
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
│ ├──
|
|
388
|
-
│
|
|
389
|
-
│
|
|
390
|
-
│
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
└──
|
|
478
|
+
└── package.json
|
|
397
479
|
```
|
|
398
480
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
492
|
+
---
|
|
410
493
|
|
|
411
|
-
###
|
|
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
|
-
|
|
516
|
+
#### Publishing
|
|
414
517
|
|
|
415
|
-
|
|
416
|
-
- Bug fixes, patches, or minor improvements
|
|
417
|
-
- Example: `fix: resolve dialog builder layout issue`
|
|
518
|
+
Every push to `master` automatically:
|
|
418
519
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
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
|
-
|
|
428
|
-
- Documentation, style, or non-publishing changes
|
|
429
|
-
- Example: `update README examples`
|
|
528
|
+
---
|
|
430
529
|
|
|
431
|
-
|
|
530
|
+
## Resources
|
|
432
531
|
|
|
433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
537
|
+
The `src/examples/` folder contains ready-to-run scripts demonstrating common patterns and fuller reference implementations for common Daz Studio workflows.
|