UNPKG

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
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 }