From e6f9a05fa6bef5b18560568be9649826689244c0 Mon Sep 17 00:00:00 2001 From: sunag Date: Wed, 22 Feb 2023 15:49:50 -0300 Subject: [PATCH 1/8] NodeEditor: Adds support for exporting Materials and Objects3D --- examples/jsm/node-editor/NodeEditor.js | 10 ++------ examples/jsm/node-editor/NodeEditorUtils.js | 12 +++++++++ examples/jsm/node-editor/core/BaseNode.js | 23 +++++++++++------ .../materials/BasicMaterialEditor.js | 8 +++--- .../node-editor/materials/MaterialEditor.js | 25 +++++++++++++++++++ .../materials/PointsMaterialEditor.js | 8 +++--- .../materials/StandardMaterialEditor.js | 8 +++--- .../jsm/node-editor/scene/Object3DEditor.js | 9 ++++++- 8 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 examples/jsm/node-editor/NodeEditorUtils.js create mode 100644 examples/jsm/node-editor/materials/MaterialEditor.js diff --git a/examples/jsm/node-editor/NodeEditor.js b/examples/jsm/node-editor/NodeEditor.js index 7476504f9384c2..934a13d0cbb636 100644 --- a/examples/jsm/node-editor/NodeEditor.js +++ b/examples/jsm/node-editor/NodeEditor.js @@ -33,6 +33,7 @@ import { PointsEditor } from './scene/PointsEditor.js'; import { MeshEditor } from './scene/MeshEditor.js'; import { FileEditor } from './core/FileEditor.js'; import { FileURLEditor } from './core/FileURLEditor.js'; +import { exportJSON } from './NodeEditorUtils.js'; import { EventDispatcher } from 'three'; Styles.icons.unlink = 'ti ti-unlink'; @@ -540,14 +541,7 @@ export class NodeEditor extends EventDispatcher { saveButton.onClick( () => { - const json = JSON.stringify( this.canvas.toJSON() ); - - const a = document.createElement( 'a' ); - const file = new Blob( [ json ], { type: 'text/plain' } ); - - a.href = URL.createObjectURL( file ); - a.download = 'node_editor.json'; - a.click(); + exportJSON( this.canvas.toJSON(), 'node_editor' ); } ); diff --git a/examples/jsm/node-editor/NodeEditorUtils.js b/examples/jsm/node-editor/NodeEditorUtils.js new file mode 100644 index 00000000000000..796527af4fbdbf --- /dev/null +++ b/examples/jsm/node-editor/NodeEditorUtils.js @@ -0,0 +1,12 @@ +export const exportJSON = ( object, name ) => { + + const json = JSON.stringify( object ); + + const a = document.createElement( 'a' ); + const file = new Blob( [ json ], { type: 'text/plain' } ); + + a.href = URL.createObjectURL( file ); + a.download = name + '.json'; + a.click(); + +}; diff --git a/examples/jsm/node-editor/core/BaseNode.js b/examples/jsm/node-editor/core/BaseNode.js index 79d9beb7c25917..8997959c6a26fc 100644 --- a/examples/jsm/node-editor/core/BaseNode.js +++ b/examples/jsm/node-editor/core/BaseNode.js @@ -31,24 +31,33 @@ export class BaseNode extends Node { .setSerializable( false ) .setOutput( outputLength ); - const closeButton = new ButtonInput().onClick( () => { + const contextButton = new ButtonInput().onClick( () => { context.open(); } ).setIcon( 'ti ti-dots' ); - const context = new ContextMenu( this.dom ); - context.add( new ButtonInput( 'Remove' ).setIcon( 'ti ti-trash' ).onClick( () => { + const onAddButtons = () => { + + context.removeEventListener( 'show', onAddButtons ); + + context.add( new ButtonInput( 'Remove' ).setIcon( 'ti ti-trash' ).onClick( () => { + + this.dispose(); - this.dispose(); + } ) ); - } ) ); + }; + + const context = new ContextMenu( this.dom ); + context.addEventListener( 'show', onAddButtons ); this.title = title; - this.closeButton = closeButton; + + this.contextButton = contextButton; this.context = context; - title.addButton( closeButton ); + title.addButton( contextButton ); this.add( title ); diff --git a/examples/jsm/node-editor/materials/BasicMaterialEditor.js b/examples/jsm/node-editor/materials/BasicMaterialEditor.js index 1bd6ef8f8b5b7e..e2d77110fce402 100644 --- a/examples/jsm/node-editor/materials/BasicMaterialEditor.js +++ b/examples/jsm/node-editor/materials/BasicMaterialEditor.js @@ -1,14 +1,14 @@ import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js'; -import { BaseNode } from '../core/BaseNode.js'; +import { MaterialEditor } from './MaterialEditor.js'; import { MeshBasicNodeMaterial } from 'three/nodes'; -export class BasicMaterialEditor extends BaseNode { +export class BasicMaterialEditor extends MaterialEditor { constructor() { const material = new MeshBasicNodeMaterial(); - super( 'Basic Material', 1, material ); + super( 'Basic Material', material ); this.setWidth( 300 ); @@ -42,8 +42,6 @@ export class BasicMaterialEditor extends BaseNode { this.opacity = opacity; this.position = position; - this.material = material; - this.update(); } diff --git a/examples/jsm/node-editor/materials/MaterialEditor.js b/examples/jsm/node-editor/materials/MaterialEditor.js new file mode 100644 index 00000000000000..c93b023aa0d55c --- /dev/null +++ b/examples/jsm/node-editor/materials/MaterialEditor.js @@ -0,0 +1,25 @@ +import { BaseNode } from '../core/BaseNode.js'; +import { ButtonInput } from '../../libs/flow.module.js'; +import { exportJSON } from '../NodeEditorUtils.js'; + +export class MaterialEditor extends BaseNode { + + constructor( name, material, width = 300 ) { + + super( name, 1, material, width ); + + this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { + + exportJSON( this.material.toJSON(), 'node_material' ); + + } ) ); + + } + + get material() { + + return this.value; + + } + +} diff --git a/examples/jsm/node-editor/materials/PointsMaterialEditor.js b/examples/jsm/node-editor/materials/PointsMaterialEditor.js index eab50af4f3e4ee..f06917c6e8fd66 100644 --- a/examples/jsm/node-editor/materials/PointsMaterialEditor.js +++ b/examples/jsm/node-editor/materials/PointsMaterialEditor.js @@ -1,15 +1,15 @@ import { ColorInput, ToggleInput, SliderInput, LabelElement } from '../../libs/flow.module.js'; -import { BaseNode } from '../core/BaseNode.js'; +import { MaterialEditor } from './MaterialEditor.js'; import { PointsNodeMaterial } from 'three/nodes'; import * as THREE from 'three'; -export class PointsMaterialEditor extends BaseNode { +export class PointsMaterialEditor extends MaterialEditor { constructor() { const material = new PointsNodeMaterial(); - super( 'Points Material', 1, material ); + super( 'Points Material', material ); this.setWidth( 300 ); @@ -57,8 +57,6 @@ export class PointsMaterialEditor extends BaseNode { this.position = position; this.sizeAttenuation = sizeAttenuation; - this.material = material; - this.update(); } diff --git a/examples/jsm/node-editor/materials/StandardMaterialEditor.js b/examples/jsm/node-editor/materials/StandardMaterialEditor.js index 2f05c1ab4ff68a..28a5f8cbfe0a3f 100644 --- a/examples/jsm/node-editor/materials/StandardMaterialEditor.js +++ b/examples/jsm/node-editor/materials/StandardMaterialEditor.js @@ -1,14 +1,14 @@ import { ColorInput, SliderInput, LabelElement } from '../../libs/flow.module.js'; -import { BaseNode } from '../core/BaseNode.js'; +import { MaterialEditor } from './MaterialEditor.js'; import { MeshStandardNodeMaterial } from 'three/nodes'; -export class StandardMaterialEditor extends BaseNode { +export class StandardMaterialEditor extends MaterialEditor { constructor() { const material = new MeshStandardNodeMaterial(); - super( 'Standard Material', 1, material ); + super( 'Standard Material', material ); this.setWidth( 300 ); @@ -70,8 +70,6 @@ export class StandardMaterialEditor extends BaseNode { this.normal = normal; this.position = position; - this.material = material; - this.update(); } diff --git a/examples/jsm/node-editor/scene/Object3DEditor.js b/examples/jsm/node-editor/scene/Object3DEditor.js index 9754d37f687909..bf189c63ed1b5f 100644 --- a/examples/jsm/node-editor/scene/Object3DEditor.js +++ b/examples/jsm/node-editor/scene/Object3DEditor.js @@ -1,6 +1,7 @@ -import { NumberInput, StringInput, LabelElement } from '../../libs/flow.module.js'; +import { NumberInput, StringInput, LabelElement, ButtonInput } from '../../libs/flow.module.js'; import { BaseNode } from '../core/BaseNode.js'; import { Group, MathUtils, Vector3 } from 'three'; +import { exportJSON } from '../NodeEditorUtils.js'; export class Object3DEditor extends BaseNode { @@ -23,6 +24,12 @@ export class Object3DEditor extends BaseNode { this.onValidElement = () => {}; + this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { + + exportJSON( this.object3d.toJSON(), 'object3d' ); + + } ) ); + } setEditor( editor ) { From 52ca7c0743b638475e2973fcf7d51902ba69ee08 Mon Sep 17 00:00:00 2001 From: sunag Date: Thu, 23 Feb 2023 02:18:23 -0300 Subject: [PATCH 2/8] cleanup --- examples/jsm/node-editor/materials/BasicMaterialEditor.js | 2 -- examples/jsm/node-editor/materials/PointsMaterialEditor.js | 2 -- examples/jsm/node-editor/materials/StandardMaterialEditor.js | 2 -- 3 files changed, 6 deletions(-) diff --git a/examples/jsm/node-editor/materials/BasicMaterialEditor.js b/examples/jsm/node-editor/materials/BasicMaterialEditor.js index e2d77110fce402..38e9a4fdffe909 100644 --- a/examples/jsm/node-editor/materials/BasicMaterialEditor.js +++ b/examples/jsm/node-editor/materials/BasicMaterialEditor.js @@ -10,8 +10,6 @@ export class BasicMaterialEditor extends MaterialEditor { super( 'Basic Material', material ); - this.setWidth( 300 ); - const color = new LabelElement( 'color' ).setInput( 3 ); const opacity = new LabelElement( 'opacity' ).setInput( 1 ); const position = new LabelElement( 'position' ).setInput( 3 ); diff --git a/examples/jsm/node-editor/materials/PointsMaterialEditor.js b/examples/jsm/node-editor/materials/PointsMaterialEditor.js index f06917c6e8fd66..9b47747e1e4c9b 100644 --- a/examples/jsm/node-editor/materials/PointsMaterialEditor.js +++ b/examples/jsm/node-editor/materials/PointsMaterialEditor.js @@ -11,8 +11,6 @@ export class PointsMaterialEditor extends MaterialEditor { super( 'Points Material', material ); - this.setWidth( 300 ); - const color = new LabelElement( 'color' ).setInput( 3 ); const opacity = new LabelElement( 'opacity' ).setInput( 1 ); const size = new LabelElement( 'size' ).setInput( 1 ); diff --git a/examples/jsm/node-editor/materials/StandardMaterialEditor.js b/examples/jsm/node-editor/materials/StandardMaterialEditor.js index 28a5f8cbfe0a3f..b80c91d54c8bbc 100644 --- a/examples/jsm/node-editor/materials/StandardMaterialEditor.js +++ b/examples/jsm/node-editor/materials/StandardMaterialEditor.js @@ -10,8 +10,6 @@ export class StandardMaterialEditor extends MaterialEditor { super( 'Standard Material', material ); - this.setWidth( 300 ); - const color = new LabelElement( 'color' ).setInput( 3 ); const opacity = new LabelElement( 'opacity' ).setInput( 1 ); const metalness = new LabelElement( 'metalness' ).setInput( 1 ); From c05c5abc21ffebd602e82595466ddb71792c1570 Mon Sep 17 00:00:00 2001 From: sunag Date: Thu, 23 Feb 2023 02:22:13 -0300 Subject: [PATCH 3/8] fix empty auto-complete --- examples/jsm/node-editor/NodeEditor.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/jsm/node-editor/NodeEditor.js b/examples/jsm/node-editor/NodeEditor.js index 934a13d0cbb636..1fd37ede97e0d8 100644 --- a/examples/jsm/node-editor/NodeEditor.js +++ b/examples/jsm/node-editor/NodeEditor.js @@ -857,10 +857,14 @@ export class NodeEditor extends EventDispatcher { } else if ( key === 'Enter' ) { - nodeButtonsVisible[ nodeButtonsIndex ].dom.click(); + if ( nodeButtonsVisible[ nodeButtonsIndex ] !== undefined ) { - e.preventDefault(); - e.stopImmediatePropagation(); + nodeButtonsVisible[ nodeButtonsIndex ].dom.click(); + + e.preventDefault(); + e.stopImmediatePropagation(); + + } } @@ -905,7 +909,11 @@ export class NodeEditor extends EventDispatcher { } - nodeButtonsVisible[ nodeButtonsIndex ].setSelected( true ); + if ( nodeButtonsVisible[ nodeButtonsIndex ] !== undefined ) { + + nodeButtonsVisible[ nodeButtonsIndex ].setSelected( true ); + + } } ); From 0ec1826199f4b549074ec4a2adb797f0fd420807 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Feb 2023 16:29:46 -0300 Subject: [PATCH 4/8] NodeLoader: Fix deserialization single node. --- examples/jsm/nodes/loaders/NodeLoader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/nodes/loaders/NodeLoader.js b/examples/jsm/nodes/loaders/NodeLoader.js index 54a9d7cb55a092..946fc55ae82f49 100644 --- a/examples/jsm/nodes/loaders/NodeLoader.js +++ b/examples/jsm/nodes/loaders/NodeLoader.js @@ -83,7 +83,7 @@ class NodeLoader extends Loader { const node = nodeObject( createNodeFromType( type ) ); node.uuid = json.uuid; - const nodes = this.parseNodes( json.inputNodes ); + const nodes = this.parseNodes( json.nodes ); const meta = { nodes, textures: this.textures }; json.meta = meta; From 39cd0c9eb15a32b673cbc748a12f2ed93512a375 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Feb 2023 16:49:38 -0300 Subject: [PATCH 5/8] Node: Added NodeArray[] auto serialization. --- examples/jsm/nodes/core/Node.js | 38 +++++++++++++++++++++++++--- examples/jsm/nodes/core/NodeUtils.js | 10 +++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/examples/jsm/nodes/core/Node.js b/examples/jsm/nodes/core/Node.js index cacf598290318c..a32d67f0da8634 100644 --- a/examples/jsm/nodes/core/Node.js +++ b/examples/jsm/nodes/core/Node.js @@ -273,7 +273,21 @@ class Node { for ( const property of nodeKeys ) { - inputNodes[ property ] = this[ property ].toJSON( json.meta ).uuid; + if ( Array.isArray( this[ property ] ) ) { + + inputNodes[ property ] = []; + + for ( const node of this[ property ] ) { + + inputNodes[ property ].push( node.toJSON( json.meta ).uuid ); + + } + + } else { + + inputNodes[ property ] = this[ property ].toJSON( json.meta ).uuid; + + } } @@ -291,9 +305,25 @@ class Node { for ( const property in json.inputNodes ) { - const uuid = json.inputNodes[ property ]; + if ( Array.isArray( json.inputNodes[ property ] ) ) { + + const inputArray = []; - this[ property ] = nodes[ uuid ]; + for ( const uuid of json.inputNodes[ property ] ) { + + inputArray.push( nodes[ uuid ] ); + + } + + this[ property ] = inputArray; + + } else { + + const uuid = json.inputNodes[ property ]; + + this[ property ] = nodes[ uuid ]; + + } } @@ -333,7 +363,7 @@ class Node { } }; - meta.nodes[ data.uuid ] = data; + if ( isRoot !== true ) meta.nodes[ data.uuid ] = data; this.serialize( data ); diff --git a/examples/jsm/nodes/core/NodeUtils.js b/examples/jsm/nodes/core/NodeUtils.js index 45766f3e6caf3f..c7453a5a47c404 100644 --- a/examples/jsm/nodes/core/NodeUtils.js +++ b/examples/jsm/nodes/core/NodeUtils.js @@ -30,7 +30,15 @@ export const getNodesKeys = ( object ) => { const value = object[ name ]; - if ( value && value.isNode === true ) { + if ( Array.isArray( value ) ) { + + if ( value[ 0 ] && value[ 0 ].isNode === true ) { + + props.push( name ); + + } + + } else if ( value && value.isNode === true ) { props.push( name ); From ecc1a22af41cf393a52107b069456fe92e444e04 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Feb 2023 16:51:48 -0300 Subject: [PATCH 6/8] NodeEditor: Show Export option whenever it is available. --- examples/jsm/node-editor/core/BaseNode.js | 11 +++++++++++ examples/jsm/node-editor/materials/MaterialEditor.js | 8 -------- examples/jsm/node-editor/scene/Object3DEditor.js | 9 +-------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/examples/jsm/node-editor/core/BaseNode.js b/examples/jsm/node-editor/core/BaseNode.js index 8997959c6a26fc..8c029c770ffaa3 100644 --- a/examples/jsm/node-editor/core/BaseNode.js +++ b/examples/jsm/node-editor/core/BaseNode.js @@ -1,4 +1,5 @@ import { Node, ButtonInput, TitleElement, ContextMenu } from '../../libs/flow.module.js'; +import { exportJSON } from '../NodeEditorUtils.js'; export const onNodeValidElement = ( inputElement, outputElement ) => { @@ -41,6 +42,16 @@ export class BaseNode extends Node { context.removeEventListener( 'show', onAddButtons ); + if ( this.value && this.value.toJSON !== undefined ) { + + this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { + + exportJSON( this.value.toJSON(), this.constructor.name ); + + } ) ); + + } + context.add( new ButtonInput( 'Remove' ).setIcon( 'ti ti-trash' ).onClick( () => { this.dispose(); diff --git a/examples/jsm/node-editor/materials/MaterialEditor.js b/examples/jsm/node-editor/materials/MaterialEditor.js index c93b023aa0d55c..87debd7c2637c3 100644 --- a/examples/jsm/node-editor/materials/MaterialEditor.js +++ b/examples/jsm/node-editor/materials/MaterialEditor.js @@ -1,6 +1,4 @@ import { BaseNode } from '../core/BaseNode.js'; -import { ButtonInput } from '../../libs/flow.module.js'; -import { exportJSON } from '../NodeEditorUtils.js'; export class MaterialEditor extends BaseNode { @@ -8,12 +6,6 @@ export class MaterialEditor extends BaseNode { super( name, 1, material, width ); - this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { - - exportJSON( this.material.toJSON(), 'node_material' ); - - } ) ); - } get material() { diff --git a/examples/jsm/node-editor/scene/Object3DEditor.js b/examples/jsm/node-editor/scene/Object3DEditor.js index bf189c63ed1b5f..9754d37f687909 100644 --- a/examples/jsm/node-editor/scene/Object3DEditor.js +++ b/examples/jsm/node-editor/scene/Object3DEditor.js @@ -1,7 +1,6 @@ -import { NumberInput, StringInput, LabelElement, ButtonInput } from '../../libs/flow.module.js'; +import { NumberInput, StringInput, LabelElement } from '../../libs/flow.module.js'; import { BaseNode } from '../core/BaseNode.js'; import { Group, MathUtils, Vector3 } from 'three'; -import { exportJSON } from '../NodeEditorUtils.js'; export class Object3DEditor extends BaseNode { @@ -24,12 +23,6 @@ export class Object3DEditor extends BaseNode { this.onValidElement = () => {}; - this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { - - exportJSON( this.object3d.toJSON(), 'object3d' ); - - } ) ); - } setEditor( editor ) { From c40725e48b55022ff360212768822f109dd206cf Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Feb 2023 17:01:00 -0300 Subject: [PATCH 7/8] BaseNode: Slightly safer .toJSON() check. --- examples/jsm/node-editor/core/BaseNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/node-editor/core/BaseNode.js b/examples/jsm/node-editor/core/BaseNode.js index 8c029c770ffaa3..08472d5b5445eb 100644 --- a/examples/jsm/node-editor/core/BaseNode.js +++ b/examples/jsm/node-editor/core/BaseNode.js @@ -42,7 +42,7 @@ export class BaseNode extends Node { context.removeEventListener( 'show', onAddButtons ); - if ( this.value && this.value.toJSON !== undefined ) { + if ( this.value && typeof this.value.toJSON === 'function' ) { this.context.add( new ButtonInput( 'Export' ).setIcon( 'ti ti-download' ).onClick( () => { From ddabea4d0011934cb071741e3e9ea703b312f54b Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Feb 2023 17:20:01 -0300 Subject: [PATCH 8/8] NodeUtils: Fix getCacheKey() after getNodeKeys() update. --- examples/jsm/nodes/core/NodeUtils.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/jsm/nodes/core/NodeUtils.js b/examples/jsm/nodes/core/NodeUtils.js index c7453a5a47c404..528844190b55ef 100644 --- a/examples/jsm/nodes/core/NodeUtils.js +++ b/examples/jsm/nodes/core/NodeUtils.js @@ -12,7 +12,23 @@ export const getCacheKey = ( object ) => { for ( const property of getNodesKeys( object ) ) { - cacheKey += `${ property }:${ object[ property ].getCacheKey() },`; + const node = object[ property ]; + + // @TODO: Think about implement NodeArray and NodeObject. + + if ( Array.isArray( node ) ) { + + for ( const subNode of node ) { + + cacheKey += `${ property }:${ subNode.getCacheKey() },`; + + } + + } else { + + cacheKey += `${ property }:${ node.getCacheKey() },`; + + } }