Search
Search
Search
Search
Information
Information
Light
Dark
Open actions menu
Basic upload method
Bypass upload method
Tips!
If you encounter an error (by firewall) while uploading using both methods,
try changing extension of the file before uploading it and rename it right after.
This uploader supports multiple file upload.
Submit
~
var
www
uibuilder.cmshelp.dk
httpdocs
node_modules
three
src
nodes
code
File Content:
ScriptableNode.js
import Node from '../core/Node.js'; import { scriptableValue } from './ScriptableValueNode.js'; import { nodeProxy, float } from '../tsl/TSLBase.js'; import { hashArray, hashString } from '../core/NodeUtils.js'; /** * A Map-like data structure for managing resources of scriptable nodes. * * @augments Map */ class Resources extends Map { get( key, callback = null, ...params ) { if ( this.has( key ) ) return super.get( key ); if ( callback !== null ) { const value = callback( ...params ); this.set( key, value ); return value; } } } class Parameters { constructor( scriptableNode ) { this.scriptableNode = scriptableNode; } get parameters() { return this.scriptableNode.parameters; } get layout() { return this.scriptableNode.getLayout(); } getInputLayout( id ) { return this.scriptableNode.getInputLayout( id ); } get( name ) { const param = this.parameters[ name ]; const value = param ? param.getValue() : null; return value; } } /** * Defines the resources (e.g. namespaces) of scriptable nodes. * * @type {Resources} */ export const ScriptableNodeResources = new Resources(); /** * This type of node allows to implement nodes with custom scripts. The script * section is represented as an instance of `CodeNode` written with JavaScript. * The script itself must adhere to a specific structure. * * - main(): Executed once by default and every time `node.needsUpdate` is set. * - layout: The layout object defines the script's interface (inputs and outputs). * * ```js * ScriptableNodeResources.set( 'TSL', TSL ); * * const scriptableNode = scriptable( js( ` * layout = { * outputType: 'node', * elements: [ * { name: 'source', inputType: 'node' }, * ] * }; * * const { mul, oscSine } = TSL; * * function main() { * const source = parameters.get( 'source' ) || float(); * return mul( source, oscSine() ) ); * } * * ` ) ); * * scriptableNode.setParameter( 'source', color( 1, 0, 0 ) ); * * const material = new THREE.MeshBasicNodeMaterial(); * material.colorNode = scriptableNode; * ``` * * @augments Node */ class ScriptableNode extends Node { static get type() { return 'ScriptableNode'; } /** * Constructs a new scriptable node. * * @param {?CodeNode} [codeNode=null] - The code node. * @param {Object} [parameters={}] - The parameters definition. */ constructor( codeNode = null, parameters = {} ) { super(); /** * The code node. * * @type {?CodeNode} * @default null */ this.codeNode = codeNode; /** * The parameters definition. * * @type {Object} * @default {} */ this.parameters = parameters; this._local = new Resources(); this._output = scriptableValue(); this._outputs = {}; this._source = this.source; this._method = null; this._object = null; this._value = null; this._needsOutputUpdate = true; this.onRefresh = this.onRefresh.bind( this ); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isScriptableNode = true; } /** * The source code of the scriptable node. * * @type {string} */ get source() { return this.codeNode ? this.codeNode.code : ''; } /** * Sets the reference of a local script variable. * * @param {string} name - The variable name. * @param {Object} value - The reference to set. * @return {Resources} The resource map */ setLocal( name, value ) { return this._local.set( name, value ); } /** * Gets the value of a local script variable. * * @param {string} name - The variable name. * @return {Object} The value. */ getLocal( name ) { return this._local.get( name ); } /** * Event listener for the `refresh` event. */ onRefresh() { this._refresh(); } /** * Returns an input from the layout with the given id/name. * * @param {string} id - The id/name of the input. * @return {Object} The element entry. */ getInputLayout( id ) { for ( const element of this.getLayout() ) { if ( element.inputType && ( element.id === id || element.name === id ) ) { return element; } } } /** * Returns an output from the layout with the given id/name. * * @param {string} id - The id/name of the output. * @return {Object} The element entry. */ getOutputLayout( id ) { for ( const element of this.getLayout() ) { if ( element.outputType && ( element.id === id || element.name === id ) ) { return element; } } } /** * Defines a script output for the given name and value. * * @param {string} name - The name of the output. * @param {Node} value - The node value. * @return {ScriptableNode} A reference to this node. */ setOutput( name, value ) { const outputs = this._outputs; if ( outputs[ name ] === undefined ) { outputs[ name ] = scriptableValue( value ); } else { outputs[ name ].value = value; } return this; } /** * Returns a script output for the given name. * * @param {string} name - The name of the output. * @return {ScriptableValueNode} The node value. */ getOutput( name ) { return this._outputs[ name ]; } /** * Returns a parameter for the given name * * @param {string} name - The name of the parameter. * @return {ScriptableValueNode} The node value. */ getParameter( name ) { return this.parameters[ name ]; } /** * Sets a value for the given parameter name. * * @param {string} name - The parameter name. * @param {any} value - The parameter value. * @return {ScriptableNode} A reference to this node. */ setParameter( name, value ) { const parameters = this.parameters; if ( value && value.isScriptableNode ) { this.deleteParameter( name ); parameters[ name ] = value; parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh ); } else if ( value && value.isScriptableValueNode ) { this.deleteParameter( name ); parameters[ name ] = value; parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); } else if ( parameters[ name ] === undefined ) { parameters[ name ] = scriptableValue( value ); parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); } else { parameters[ name ].value = value; } return this; } /** * Returns the value of this node which is the value of * the default output. * * @return {Node} The value. */ getValue() { return this.getDefaultOutput().getValue(); } /** * Deletes a parameter from the script. * * @param {string} name - The parameter to remove. * @return {ScriptableNode} A reference to this node. */ deleteParameter( name ) { let valueNode = this.parameters[ name ]; if ( valueNode ) { if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); valueNode.events.removeEventListener( 'refresh', this.onRefresh ); } return this; } /** * Deletes all parameters from the script. * * @return {ScriptableNode} A reference to this node. */ clearParameters() { for ( const name of Object.keys( this.parameters ) ) { this.deleteParameter( name ); } this.needsUpdate = true; return this; } /** * Calls a function from the script. * * @param {string} name - The function name. * @param {...any} params - A list of parameters. * @return {any} The result of the function call. */ call( name, ...params ) { const object = this.getObject(); const method = object[ name ]; if ( typeof method === 'function' ) { return method( ...params ); } } /** * Asynchronously calls a function from the script. * * @param {string} name - The function name. * @param {...any} params - A list of parameters. * @return {Promise<any>} The result of the function call. */ async callAsync( name, ...params ) { const object = this.getObject(); const method = object[ name ]; if ( typeof method === 'function' ) { return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params ); } } /** * Overwritten since the node types is inferred from the script's output. * * @param {NodeBuilder} builder - The current node builder * @return {string} The node type. */ getNodeType( builder ) { return this.getDefaultOutputNode().getNodeType( builder ); } /** * Refreshes the script node. * * @param {?string} [output=null] - An optional output. */ refresh( output = null ) { if ( output !== null ) { this.getOutput( output ).refresh(); } else { this._refresh(); } } /** * Returns an object representation of the script. * * @return {Object} The result object. */ getObject() { if ( this.needsUpdate ) this.dispose(); if ( this._object !== null ) return this._object; // const refresh = () => this.refresh(); const setOutput = ( id, value ) => this.setOutput( id, value ); const parameters = new Parameters( this ); const THREE = ScriptableNodeResources.get( 'THREE' ); const TSL = ScriptableNodeResources.get( 'TSL' ); const method = this.getMethod(); const params = [ parameters, this._local, ScriptableNodeResources, refresh, setOutput, THREE, TSL ]; this._object = method( ...params ); const layout = this._object.layout; if ( layout ) { if ( layout.cache === false ) { this._local.clear(); } // default output this._output.outputType = layout.outputType || null; if ( Array.isArray( layout.elements ) ) { for ( const element of layout.elements ) { const id = element.id || element.name; if ( element.inputType ) { if ( this.getParameter( id ) === undefined ) this.setParameter( id, null ); this.getParameter( id ).inputType = element.inputType; } if ( element.outputType ) { if ( this.getOutput( id ) === undefined ) this.setOutput( id, null ); this.getOutput( id ).outputType = element.outputType; } } } } return this._object; } deserialize( data ) { super.deserialize( data ); for ( const name in this.parameters ) { let valueNode = this.parameters[ name ]; if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); valueNode.events.addEventListener( 'refresh', this.onRefresh ); } } /** * Returns the layout of the script. * * @return {Object} The script's layout. */ getLayout() { return this.getObject().layout; } /** * Returns default node output of the script. * * @return {Node} The default node output. */ getDefaultOutputNode() { const output = this.getDefaultOutput().value; if ( output && output.isNode ) { return output; } return float(); } /** * Returns default output of the script. * * @return {ScriptableValueNode} The default output. */ getDefaultOutput() { return this._exec()._output; } /** * Returns a function created from the node's script. * * @return {Function} The function representing the node's code. */ getMethod() { if ( this.needsUpdate ) this.dispose(); if ( this._method !== null ) return this._method; // const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ]; const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ]; const properties = interfaceProps.join( ', ' ); const declarations = 'var ' + properties + '; var output = {};\n'; const returns = '\nreturn { ...output, ' + properties + ' };'; const code = declarations + this.codeNode.code + returns; // this._method = new Function( ...parametersProps, code ); return this._method; } /** * Frees all internal resources. */ dispose() { if ( this._method === null ) return; if ( this._object && typeof this._object.dispose === 'function' ) { this._object.dispose(); } this._method = null; this._object = null; this._source = null; this._value = null; this._needsOutputUpdate = true; this._output.value = null; this._outputs = {}; } setup() { return this.getDefaultOutputNode(); } getCacheKey( force ) { const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ]; for ( const param in this.parameters ) { values.push( this.parameters[ param ].getCacheKey( force ) ); } return hashArray( values ); } set needsUpdate( value ) { if ( value === true ) this.dispose(); } get needsUpdate() { return this.source !== this._source; } /** * Executes the `main` function of the script. * * @private * @return {ScriptableNode} A reference to this node. */ _exec() { if ( this.codeNode === null ) return this; if ( this._needsOutputUpdate === true ) { this._value = this.call( 'main' ); this._needsOutputUpdate = false; } this._output.value = this._value; return this; } /** * Executes the refresh. * * @private */ _refresh() { this.needsUpdate = true; this._exec(); this._output.refresh(); } } export default ScriptableNode; /** * TSL function for creating a scriptable node. * * @tsl * @function * @param {?CodeNode} [codeNode=null] - The code node. * @param {Object} [parameters={}] - The parameters definition. * @returns {ScriptableNode} */ export const scriptable = /*@__PURE__*/ nodeProxy( ScriptableNode );
Edit
Download
Unzip
Chmod
Delete