dazscript-framework
Version:
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 inclu
237 lines (198 loc) • 8.95 kB
text/typescript
import { app, sceneHelper } from '@dsf/core/global';
import { TreeNode } from '@dsf/lib/tree-node';
import { group } from './array-helper';
import { entries } from './record-helper';
export const isBone = (node: DzNode): boolean => {
return node.iskindof('DzBone')
}
export const isFigure = (node: DzNode): boolean => {
return node.iskindof('DzSkeleton')
}
/**
* Returns the root of a node
* @param node
* @returns The root of the node, if the node is part of a figure, return the figure (skeleton) otherwise return the node itself
*/
export const getRoot = (node: DzNode): DzNode => {
if (node && node.className() === "DzBone" && node.getSkeleton)
return node.getSkeleton();
else
return node;
}
export const getFigure = (node: DzNode): DzSkeleton | null => {
return node.getSkeleton?.() ?? null
}
/**
* Find a property by the given name or internal name
* @param node the node to search on
* @param name the name or internal name of the property
* @returns The first property with the given name or internal name, or NULL.
*/
export const findProperty = <T extends DzProperty = DzProperty>(node: DzNode, name: string): T | null => {
return sceneHelper.findPropertyOnNode(name, node) as T
?? sceneHelper.findPropertyOnNodeByInternalName(name, node) as T
}
/**
* Get all properties associated with the node.
* @param node The node to get the properties from.
* @param includeModifiers Whether or not to include the properties of DzModifiers.
* @returns All properties associated with the node.
*/
export const getProperties = (node: DzNode, includeModifiers: boolean = false): DzProperty[] => {
return sceneHelper.getPropertiesOnNode(node, includeModifiers)
}
export const getChildren = (node: DzNode, recursive: boolean, includeParented?: boolean, includeFittedTo?: boolean) => {
return node.getNodeChildren(recursive ?? false).filter(n =>
includeParented || isBodyPartOf(n, node.getSkeleton())
&& (includeFittedTo || !isFitting(getFigure(n), node)))
}
/**
* Returns true if the specified node is a body part of a figure
* @param node
* @param figure
* @returns
*/
export const isBodyPartOf = (node: DzNode, figure: DzSkeleton): boolean => {
return isBone(node) && isChildOf(node, figure) && node.getSkeleton()?.getLabel() == figure.getSkeleton()?.getLabel()
}
/**
* Returns true if the specified node is parented or a body part of a figure
* @param node
* @param figure
* @returns
*/
export const isChildOf = (node: DzNode, figure: DzNode): boolean => {
return node.isNodeDescendantOf(figure, true)
}
/**
* Get the fitting target of the node
* @param node
* @returns the fitting target (skeleton) of the node or null
*/
export const getFittingTarget = (node: DzNode): DzSkeleton | null => {
return getRoot(node).getSkeleton()?.getFollowTarget()
}
/**
* Returns true if the node is fitted to another node (eg: it's a clothing item)
* @param figure
* @param target if specified, check if the node is fitted to the target node, otherwise check if the node is fitted to any other node
* @returns true if the node is fitted to another node
*/
export const isFitting = (figure: DzSkeleton, target?: DzNode): boolean => {
return target
? figure?.getFollowTarget() == target
: figure?.getFollowTarget() != null
}
export const isClothingType = (node: DzNode): boolean => {
if (!isFigure(node)) return false
const figure = getFigure(node)!
const assetMgr = app.getAssetMgr()
const type = assetMgr.getTypeForNode(figure)
return assetMgr.isClothingType(type) || type === 'Follower'
}
export const isHairType = (node: DzNode): boolean => {
if (!isFigure(node)) return false
const figure = getFigure(node)!
const assetMgr = app.getAssetMgr()
const type = assetMgr.getTypeForNode(figure)
return assetMgr.isHairType(type)
}
export const isGeoShell = (node: DzNode): boolean => {
return node.inherits('DzGeometryShellNode')
}
export const getTransforms = (node: DzNode, include: { rotations?: boolean, translations?: boolean, scale?: boolean }) => {
let transforms: DzFloatProperty[] = []
if (include.rotations === true) transforms = getRotations(node)
if (include.translations === true) transforms = transforms.concat(getTranslations(node))
if (include.scale === true) transforms = transforms.concat(getScales(node))
return transforms
}
export const getRotations = (node: DzNode): DzFloatProperty[] => {
if (!node) return [];
const xRotate = sceneHelper.findPropertyOnNode('XRotate', node) as DzFloatProperty
const yRotate = sceneHelper.findPropertyOnNode('YRotate', node) as DzFloatProperty
const zRotate = sceneHelper.findPropertyOnNode('ZRotate', node) as DzFloatProperty
return [xRotate, yRotate, zRotate];
}
export const getTranslations = (node: DzNode): DzFloatProperty[] => {
return !node ? [] : [node.getXPosControl(), node.getYPosControl(), node.getZPosControl()];
}
export const getScales = (node: DzNode): DzFloatProperty[] => {
return [node.getXScaleControl(), node.getYScaleControl(), node.getZScaleControl(), node.getScaleControl()];
}
export const getPropertiesTree = <T = DzProperty>(node: DzNode, map?: (property: DzProperty) => T/*, filter?: (property: DzProperty) => boolean*/, showProgress: boolean = false): TreeNode<T>[] => {
if (showProgress) startProgress(`Collecting Properties`, 3)
let root: TreeNode<T>;
// Fetch properties and group them by their path
const properties = sceneHelper.getPropertiesOnNode(node);
if (showProgress) stepProgress()
const grouped = group(properties, (p) => p.getPath());
if (showProgress) stepProgress()
// Root TreeNode
root = new TreeNode<T>('root', '');
const pathMap: Record<string, TreeNode<T>> = { '': root };
// Process grouped entries
const groupedEntries = entries(grouped);
if (showProgress) stepProgress()
if (showProgress) startProgress(`Processing ${groupedEntries.length} properties`, groupedEntries.length)
for (const [path, props] of groupedEntries) {
const paths = path.split('/');
let currentPath = '';
// Create nodes for the path hierarchy
for (let i = 0; i < paths.length; i++) {
currentPath = paths.slice(0, i + 1).join('/');
if (!pathMap[currentPath]) {
const newNode = new TreeNode<T>(paths[i], currentPath);
pathMap[currentPath] = newNode;
if (i > 0) {
const parentPath = paths.slice(0, i).join('/');
const parentNode = pathMap[parentPath];
parentNode?.addChild(newNode);
newNode.parent = parentNode;
}
}
}
// Add property nodes to the current path
const currentNode = pathMap[path];
if (currentNode) {
for (const property of props) {
const newNode = new TreeNode<T>(
property.getLabel(),
property.getPath(),
map?.(property) ?? (property as T)
);
currentNode.addChild(newNode);
newNode.parent = currentNode;
}
}
if (showProgress) stepProgress()
}
if (showProgress) finishProgress()
if (showProgress) finishProgress()
// Return root children or an empty array if none exist
return root.children.length > 0 ? root.children : [];
};
export const getPropertiesPathsTree = (node: DzNode): TreeNode<string>[] => {
let items = entries(group(sceneHelper.getPropertiesOnNode(node), (p) => p.getPath())).map(x => ({ path: x[0], properties: x[1] }))
const root = new TreeNode<string>('root', '')
const pathMap: { [key: string]: TreeNode<string> } = { '': root }
items.forEach(element => {
const paths = element.path.split('/')
let currentPath = ''
paths.forEach((path, index) => {
currentPath += `${index === 0 ? '' : '/'}${path}`
if (!pathMap[currentPath]) {
const newNode = new TreeNode<string>(path, currentPath)
pathMap[currentPath] = newNode
if (index !== 0) {
const parentPath = currentPath.substring(0, currentPath.lastIndexOf('/'))
const parentNode = pathMap[parentPath]
parentNode?.addChild(newNode)
newNode.parent = parentNode
}
}
})
})
const rootChildren = pathMap[''].children
return rootChildren && rootChildren.length > 0 ? rootChildren : root.children
}