dazscript-framework 1.0.15 → 1.0.16
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
|
@@ -501,7 +501,7 @@ The framework ships helpers for common Daz Studio tasks, all importable from `@d
|
|
|
501
501
|
|---|---|
|
|
502
502
|
| `scene-helper` | `getRoot()`, `getSelectedNode()`, `getNodes()` |
|
|
503
503
|
| `node-helper` | Type checks (`isFigure`, `isBone`), transforms, visibility |
|
|
504
|
-
| `property-helper` |
|
|
504
|
+
| `property-helper` | Set paths, unlock, cast numeric types, and inspect property inputs/outputs |
|
|
505
505
|
| `array-helper` | `distinct()`, `flatten()`, `groupBy()` |
|
|
506
506
|
| `string-helper` | Case, trimming, splitting |
|
|
507
507
|
| `directory-helper` | File and path operations |
|
package/dist/scripts/cli.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { confirm, prompt } from './message-box-helper'
|
|
3
|
+
|
|
4
|
+
vi.mock('@dsf/common/log', () => ({ debug: vi.fn() }))
|
|
5
|
+
|
|
6
|
+
describe('message-box-helper', () => {
|
|
7
|
+
const question = vi.fn()
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
question.mockReset()
|
|
11
|
+
vi.stubGlobal('MessageBox', { question })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('treats the first confirm button as ok', () => {
|
|
15
|
+
question.mockReturnValue(0)
|
|
16
|
+
|
|
17
|
+
expect(confirm('Continue?')).toEqual({ ok: true, cancel: false })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('treats the second confirm button as cancel', () => {
|
|
21
|
+
question.mockReturnValue(1)
|
|
22
|
+
|
|
23
|
+
expect(confirm('Continue?')).toEqual({ ok: false, cancel: true })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('treats the first prompt button as cancel so Esc is also cancel', () => {
|
|
27
|
+
question.mockReturnValue(0)
|
|
28
|
+
|
|
29
|
+
expect(prompt('Reset?', 'Reset', 'Zero', 'Default')).toEqual({ cancel: true, selection: -1 })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('maps the second prompt button to the first selection', () => {
|
|
33
|
+
question.mockReturnValue(1)
|
|
34
|
+
|
|
35
|
+
expect(prompt('Reset?', 'Reset', 'Zero', 'Default')).toEqual({ cancel: false, selection: 0 })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('maps the third prompt button to the second selection', () => {
|
|
39
|
+
question.mockReturnValue(2)
|
|
40
|
+
|
|
41
|
+
expect(prompt('Reset?', 'Reset', 'Zero', 'Default')).toEqual({ cancel: false, selection: 1 })
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -20,7 +20,7 @@ export const confirm = (message?: string): { ok: boolean, cancel: boolean } => {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const prompt = (text: string, title: string, button0: string, button1?: string): { cancel: boolean, selection: number } => {
|
|
23
|
-
const response = MessageBox.question(text, title, button0, button1 ?? ""
|
|
23
|
+
const response = MessageBox.question(text, title, "Cancel", button0, button1 ?? "")
|
|
24
24
|
|
|
25
|
-
return { cancel: response === 0, selection: response };
|
|
26
|
-
}
|
|
25
|
+
return { cancel: response === 0, selection: response - 1 };
|
|
26
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { getPropertyInputs, getPropertyOutputs } from './property-helper'
|
|
3
|
+
|
|
4
|
+
vi.mock('@dsf/core/global', () => ({
|
|
5
|
+
sceneHelper: {
|
|
6
|
+
getInternalName: vi.fn(),
|
|
7
|
+
getNode: vi.fn(),
|
|
8
|
+
getUniqueMorphName: vi.fn(),
|
|
9
|
+
setInternalName: vi.fn(),
|
|
10
|
+
setPropertyPath: vi.fn()
|
|
11
|
+
}
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
describe('property-helper relationships', () => {
|
|
15
|
+
const property = (name: string, inheritsTypes: string[] = []) => ({
|
|
16
|
+
name,
|
|
17
|
+
inherits: (type: string) => inheritsTypes.indexOf(type) >= 0,
|
|
18
|
+
getNumControllers: () => 0,
|
|
19
|
+
getController: () => null,
|
|
20
|
+
getNumSlaveControllers: () => 0,
|
|
21
|
+
getSlaveController: () => null,
|
|
22
|
+
getLinkProperty: () => null,
|
|
23
|
+
getFollowProperty: () => null
|
|
24
|
+
}) as any
|
|
25
|
+
|
|
26
|
+
it('gets input properties from normal and redirected controller properties', () => {
|
|
27
|
+
const driver = property('driver')
|
|
28
|
+
const redirectedDriver = property('redirectedDriver')
|
|
29
|
+
const controller = {
|
|
30
|
+
getProperty: () => driver,
|
|
31
|
+
getCurrentProperty: () => redirectedDriver
|
|
32
|
+
}
|
|
33
|
+
const driven = {
|
|
34
|
+
...property('driven'),
|
|
35
|
+
getNumControllers: () => 1,
|
|
36
|
+
getController: () => controller
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(getPropertyInputs(driven as any)).toEqual([
|
|
40
|
+
{ property: driver, controller, source: 'controller' },
|
|
41
|
+
{ property: redirectedDriver, controller, source: 'currentController' }
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('gets input properties from direct numeric link and follow properties', () => {
|
|
46
|
+
const link = property('link')
|
|
47
|
+
const follow = property('follow')
|
|
48
|
+
const driven = {
|
|
49
|
+
...property('driven', ['DzNumericProperty', 'DzFloatProperty']),
|
|
50
|
+
getLinkProperty: () => link,
|
|
51
|
+
getFollowProperty: () => follow
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expect(getPropertyInputs(driven as any)).toEqual([
|
|
55
|
+
{ property: link, controller: null, source: 'link' },
|
|
56
|
+
{ property: follow, controller: null, source: 'follow' }
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('gets output properties from slave controller owners', () => {
|
|
61
|
+
const output = property('output')
|
|
62
|
+
const controller = {
|
|
63
|
+
getOwner: () => output
|
|
64
|
+
}
|
|
65
|
+
const driver = {
|
|
66
|
+
...property('driver'),
|
|
67
|
+
getNumSlaveControllers: () => 1,
|
|
68
|
+
getSlaveController: () => controller
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
expect(getPropertyOutputs(driver as any)).toEqual([
|
|
72
|
+
{ property: output, controller, source: 'slaveController' }
|
|
73
|
+
])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('does not duplicate the same relation', () => {
|
|
77
|
+
const driver = property('driver')
|
|
78
|
+
const controller = {
|
|
79
|
+
getProperty: () => driver,
|
|
80
|
+
getCurrentProperty: () => driver
|
|
81
|
+
}
|
|
82
|
+
const driven = {
|
|
83
|
+
...property('driven'),
|
|
84
|
+
getNumControllers: () => 2,
|
|
85
|
+
getController: () => controller
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
expect(getPropertyInputs(driven as any)).toEqual([
|
|
89
|
+
{ property: driver, controller, source: 'controller' },
|
|
90
|
+
{ property: driver, controller, source: 'currentController' }
|
|
91
|
+
])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
})
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { sceneHelper } from '@dsf/core/global'
|
|
2
2
|
|
|
3
|
+
export type PropertyRelationSource = 'controller' | 'currentController' | 'link' | 'follow' | 'slaveController'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A property relationship discovered from Daz controller, link, follow, or slave-controller APIs.
|
|
7
|
+
*/
|
|
8
|
+
export type PropertyRelation = {
|
|
9
|
+
/** The related input or output property. */
|
|
10
|
+
property: DzProperty
|
|
11
|
+
/** The controller that exposed the relation, or null for direct numeric property links. */
|
|
12
|
+
controller: DzController | null
|
|
13
|
+
/** The Daz API surface that exposed the relation. */
|
|
14
|
+
source: PropertyRelationSource
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
/**
|
|
4
18
|
* Sets the DzPropertyGroup path (and appropriate geometryregion_dz) for the given property.
|
|
5
19
|
* @param property The property to change the path of.
|
|
@@ -18,6 +32,11 @@ export const getName = (property: DzProperty): string => {
|
|
|
18
32
|
return sceneHelper.getInternalName(property).valueOf()
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Ensures the property's internal name is unique on its owning node.
|
|
37
|
+
* @param property The property whose internal name should be checked.
|
|
38
|
+
* @returns The unique internal name assigned to the property.
|
|
39
|
+
*/
|
|
21
40
|
export const ensureNameIsUnique = (property: DzProperty): string => {
|
|
22
41
|
const name = getName(property)
|
|
23
42
|
const uniqueName = sceneHelper.getUniqueMorphName(sceneHelper.getNode(property), name)
|
|
@@ -39,24 +58,122 @@ export const unlock = (property: DzProperty, then: (property: DzProperty) => voi
|
|
|
39
58
|
if (locked) property.lock(false)
|
|
40
59
|
}
|
|
41
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Adds a property relation unless the same property, controller, and source were already collected.
|
|
63
|
+
* @param relations The relation collection to update.
|
|
64
|
+
* @param property The related property to add, or null to skip.
|
|
65
|
+
* @param controller The controller that exposed the relation, or null for direct property links.
|
|
66
|
+
* @param source The Daz API surface that exposed the relation.
|
|
67
|
+
*/
|
|
68
|
+
const addRelation = (relations: PropertyRelation[], property: DzProperty | null, controller: DzController | null, source: PropertyRelationSource): void => {
|
|
69
|
+
if (!property) return
|
|
70
|
+
|
|
71
|
+
for (let relation of relations) {
|
|
72
|
+
if (relation.property === property && relation.controller === controller && relation.source === source) return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
relations.push({ property, controller, source })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets the source property referenced by a controller.
|
|
80
|
+
* @param controller The controller to inspect.
|
|
81
|
+
* @param current If true, uses getCurrentProperty; otherwise, uses getProperty.
|
|
82
|
+
* @returns The controller's property, or null when the controller does not expose that method.
|
|
83
|
+
*/
|
|
84
|
+
const getControllerProperty = (controller: DzController, current: boolean): DzProperty | null => {
|
|
85
|
+
const methodName = current ? 'getCurrentProperty' : 'getProperty'
|
|
86
|
+
const method = (controller as any)[methodName]
|
|
87
|
+
if (typeof method !== 'function') return null
|
|
88
|
+
|
|
89
|
+
return method.call(controller) as DzProperty | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Gets properties that drive this property through controllers or direct numeric links.
|
|
94
|
+
* @param property The property whose input/driving properties should be collected.
|
|
95
|
+
* @returns Related input properties with the controller/source that exposed each relation.
|
|
96
|
+
*/
|
|
97
|
+
export const getPropertyInputs = (property: DzProperty): PropertyRelation[] => {
|
|
98
|
+
const relations: PropertyRelation[] = []
|
|
99
|
+
const controllerCount = property.getNumControllers()
|
|
100
|
+
|
|
101
|
+
for (let index = 0; index < controllerCount; index++) {
|
|
102
|
+
const controller = property.getController(index)
|
|
103
|
+
addRelation(relations, getControllerProperty(controller, false), controller, 'controller')
|
|
104
|
+
addRelation(relations, getControllerProperty(controller, true), controller, 'currentController')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const numericProperty = cast<DzNumericProperty>(property, 'DzNumericProperty')
|
|
108
|
+
if (numericProperty) {
|
|
109
|
+
addRelation(relations, numericProperty.getLinkProperty(), null, 'link')
|
|
110
|
+
addRelation(relations, numericProperty.getFollowProperty(), null, 'follow')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return relations
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Gets properties driven by this property through slave controllers.
|
|
118
|
+
* @param property The property whose output/driven properties should be collected.
|
|
119
|
+
* @returns Related output properties with the controller/source that exposed each relation.
|
|
120
|
+
*/
|
|
121
|
+
export const getPropertyOutputs = (property: DzProperty): PropertyRelation[] => {
|
|
122
|
+
const relations: PropertyRelation[] = []
|
|
123
|
+
const controllerCount = property.getNumSlaveControllers()
|
|
124
|
+
|
|
125
|
+
for (let index = 0; index < controllerCount; index++) {
|
|
126
|
+
const controller = property.getSlaveController(index)
|
|
127
|
+
addRelation(relations, controller.getOwner(), controller, 'slaveController')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return relations
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks whether a property is a Daz numeric property.
|
|
135
|
+
* @param property The property to check.
|
|
136
|
+
* @returns True if the property inherits DzNumericProperty.
|
|
137
|
+
*/
|
|
42
138
|
export const isNumeric = (property: DzProperty): boolean => {
|
|
43
139
|
return property.inherits('DzNumericProperty')
|
|
44
140
|
}
|
|
45
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Casts a property to a specific Daz property type when it inherits that type.
|
|
144
|
+
* @param property The property to cast.
|
|
145
|
+
* @param type The Daz property class name to test.
|
|
146
|
+
* @returns The property typed as T, or null when it does not inherit the requested type.
|
|
147
|
+
*/
|
|
46
148
|
export const cast = <T extends DzProperty>(property: DzProperty, type: 'DzNumericProperty' | 'DzFloatProperty' | 'DzIntProperty'): T | null => {
|
|
47
149
|
return property.inherits(type)
|
|
48
150
|
? property as T
|
|
49
151
|
: null
|
|
50
152
|
}
|
|
51
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Casts a property to a numeric float or int property.
|
|
156
|
+
* @param property The property to cast.
|
|
157
|
+
* @returns The property as DzFloatProperty or DzIntProperty, or null if it is neither.
|
|
158
|
+
*/
|
|
52
159
|
export const toNumeric = (property: DzProperty): DzFloatProperty | DzIntProperty | null => {
|
|
53
160
|
return toFloat(property) ?? toInt(property)
|
|
54
161
|
}
|
|
55
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Casts a property to a float property.
|
|
165
|
+
* @param property The property to cast.
|
|
166
|
+
* @returns The property as DzFloatProperty, or null if it is not one.
|
|
167
|
+
*/
|
|
56
168
|
export const toFloat = (property: DzProperty): DzFloatProperty | null => {
|
|
57
169
|
return cast<DzFloatProperty>(property, 'DzFloatProperty')
|
|
58
170
|
}
|
|
59
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Casts a property to an int property.
|
|
174
|
+
* @param property The property to cast.
|
|
175
|
+
* @returns The property as DzIntProperty, or null if it is not one.
|
|
176
|
+
*/
|
|
60
177
|
export const toInt = (property: DzProperty): DzIntProperty | null => {
|
|
61
178
|
return cast<DzIntProperty>(property, 'DzIntProperty')
|
|
62
|
-
}
|
|
179
|
+
}
|