import * as React from "react"; import { GlobalState } from '../../globalState'; import { Nullable } from 'babylonjs/types'; import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent'; import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent'; import { StringTools } from '../../stringTools'; import { FileButtonLineComponent } from '../../sharedComponents/fileButtonLineComponent'; import { Tools } from 'babylonjs/Misc/tools'; import { SerializationTools } from '../../serializationTools'; import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent'; import { DataStorage } from 'babylonjs/Misc/dataStorage'; import { GraphNode } from '../../diagram/graphNode'; import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent'; import { GraphFrame } from '../../diagram/graphFrame'; import { TextLineComponent } from '../../sharedComponents/textLineComponent'; import { Engine } from 'babylonjs/Engines/engine'; import { FramePropertyTabComponent } from '../../diagram/properties/framePropertyComponent'; import { FrameNodePortPropertyTabComponent } from '../../diagram/properties/frameNodePortPropertyComponent'; import { NodePortPropertyTabComponent } from '../../diagram/properties/nodePortPropertyComponent'; import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock'; import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes'; import { Color3LineComponent } from '../../sharedComponents/color3LineComponent'; import { FloatLineComponent } from '../../sharedComponents/floatLineComponent'; import { Color4LineComponent } from '../../sharedComponents/color4LineComponent'; import { Vector2LineComponent } from '../../sharedComponents/vector2LineComponent'; import { Vector3LineComponent } from '../../sharedComponents/vector3LineComponent'; import { Vector4LineComponent } from '../../sharedComponents/vector4LineComponent'; import { Observer } from 'babylonjs/Misc/observable'; import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial'; import { FrameNodePort } from '../../diagram/frameNodePort'; import { NodePort } from '../../diagram/nodePort'; import { isFramePortData } from '../../diagram/graphCanvas'; require("./propertyTab.scss"); interface IPropertyTabComponentProps { globalState: GlobalState; } interface IPropertyTabComponentState { currentNode: Nullable, currentFrame: Nullable, currentFrameNodePort: Nullable, currentNodePort: Nullable } export class PropertyTabComponent extends React.Component { private _onBuiltObserver: Nullable>; constructor(props: IPropertyTabComponentProps) { super(props) this.state = { currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: null }; } componentDidMount() { this.props.globalState.onSelectionChangedObservable.add(selection => { if (selection instanceof GraphNode) { this.setState({ currentNode: selection, currentFrame: null, currentFrameNodePort: null, currentNodePort: null }); } else if (selection instanceof GraphFrame) { this.setState({ currentNode: null, currentFrame: selection, currentFrameNodePort: null, currentNodePort: null }); } else if(isFramePortData(selection)) { this.setState({ currentNode: null, currentFrame: selection.frame, currentFrameNodePort: selection.port, currentNodePort: null }); } else if (selection instanceof NodePort && selection.hasLabel()) { this.setState({ currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: selection}) } else { this.setState({ currentNode: null, currentFrame: null, currentFrameNodePort: null, currentNodePort: null }); } }); this._onBuiltObserver = this.props.globalState.onBuiltObservable.add(() => { this.forceUpdate(); }); } componentWillUnmount() { this.props.globalState.onBuiltObservable.remove(this._onBuiltObserver); } processInputBlockUpdate(ib: InputBlock) { this.props.globalState.onUpdateRequiredObservable.notifyObservers(); if (ib.isConstant) { this.props.globalState.onRebuildRequiredObservable.notifyObservers(); } } renderInputBlock(block: InputBlock) { switch (block.type) { case NodeMaterialBlockConnectionPointTypes.Float: let cantDisplaySlider = (isNaN(block.min) || isNaN(block.max) || block.min === block.max); return (
{ block.isBoolean && { this.processInputBlockUpdate(block); }}/> } { !block.isBoolean && cantDisplaySlider && this.processInputBlockUpdate(block)}/> } { !block.isBoolean && !cantDisplaySlider && this.processInputBlockUpdate(block)}/> }
); case NodeMaterialBlockConnectionPointTypes.Color3: return ( this.processInputBlockUpdate(block)} /> ) case NodeMaterialBlockConnectionPointTypes.Color4: return ( this.processInputBlockUpdate(block)}/> ) case NodeMaterialBlockConnectionPointTypes.Vector2: return ( this.processInputBlockUpdate(block)}/> ) case NodeMaterialBlockConnectionPointTypes.Vector3: return ( this.processInputBlockUpdate(block)}/> ) case NodeMaterialBlockConnectionPointTypes.Vector4: return ( this.processInputBlockUpdate(block)}/> ) } return null; } load(file: File) { Tools.ReadFile(file, (data) => { let decoder = new TextDecoder("utf-8"); SerializationTools.Deserialize(JSON.parse(decoder.decode(data)), this.props.globalState); }, undefined, true); } save() { let json = SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState); StringTools.DownloadAsFile(this.props.globalState.hostDocument, json, "nodeMaterial.json"); } customSave() { this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Saving your material to Babylon.js snippet server...", isError: false}); this.props.globalState.customSave!.action(SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState)).then(() => { this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Material saved successfully", isError: false}); }).catch(err => { this.props.globalState.onLogRequiredObservable.notifyObservers({message: err, isError: true}); }); } saveToSnippetServer() { const material = this.props.globalState.nodeMaterial; const xmlHttp = new XMLHttpRequest(); let json = SerializationTools.Serialize(material, this.props.globalState); xmlHttp.onreadystatechange = () => { if (xmlHttp.readyState == 4) { if (xmlHttp.status == 200) { var snippet = JSON.parse(xmlHttp.responseText); const oldId = material.snippetId; material.snippetId = snippet.id; if (snippet.version && snippet.version != "0") { material.snippetId += "#" + snippet.version; } this.forceUpdate(); if (navigator.clipboard) { navigator.clipboard.writeText(material.snippetId); } let windowAsAny = window as any; if (windowAsAny.Playground && oldId) { windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers({ regex: new RegExp(oldId, "g"), replace: material.snippetId }); } alert("NodeMaterial saved with ID: " + material.snippetId + " (please note that the id was also saved to your clipboard)"); } else { alert(`Unable to save your node material. It may be too large (${(dataToSend.payload.length / 1024).toFixed(2)} KB) because of embedded textures. Please reduce texture sizes or point to a specific url instead of embedding them and try again.`); } } } xmlHttp.open("POST", NodeMaterial.SnippetUrl + (material.snippetId ? "/" + material.snippetId : ""), true); xmlHttp.setRequestHeader("Content-Type", "application/json"); var dataToSend = { payload : JSON.stringify({ nodeMaterial: json }), name: "", description: "", tags: "" }; xmlHttp.send(JSON.stringify(dataToSend)); } loadFromSnippet() { const material = this.props.globalState.nodeMaterial; const scene = material.getScene(); let snippedID = window.prompt("Please enter the snippet ID to use"); if (!snippedID) { return; } this.props.globalState.onSelectionChangedObservable.notifyObservers(null); NodeMaterial.ParseFromSnippetAsync(snippedID, scene, "", material).then(() => { material.build(); this.props.globalState.onResetRequiredObservable.notifyObservers(); }).catch(err => { alert("Unable to load your node material: " + err); }); } render() { if (this.state.currentNode) { return (
{this.state.currentNode?.renderProperties() || this.state.currentNodePort?.node.renderProperties()}
); } if (this.state.currentFrameNodePort && this.state.currentFrame) { return ( ); } if (this.state.currentNodePort) { return ( ); } if (this.state.currentFrame) { return ( ); } let gridSize = DataStorage.ReadNumber("GridSize", 20); return (
window.open('https://doc.babylonjs.com/how_to/node_material', '_blank')}/> { this.props.globalState.nodeMaterial!.setToDefault(); this.props.globalState.onResetRequiredObservable.notifyObservers(); }} /> { this.props.globalState.onZoomToFitRequiredObservable.notifyObservers(); }} /> { this.props.globalState.onReOrganizedRequiredObservable.notifyObservers(); }} /> DataStorage.ReadBoolean("EmbedTextures", true)} onSelect={(value: boolean) => { DataStorage.WriteBoolean("EmbedTextures", value); }} /> { DataStorage.WriteNumber("GridSize", value); this.props.globalState.onGridSizeChanged.notifyObservers(); this.forceUpdate(); }} /> DataStorage.ReadBoolean("ShowGrid", true)} onSelect={(value: boolean) => { DataStorage.WriteBoolean("ShowGrid", value); this.props.globalState.onGridSizeChanged.notifyObservers(); }} /> this.load(file)} accept=".json" /> { this.save(); }} /> { StringTools.DownloadAsFile(this.props.globalState.hostDocument, this.props.globalState.nodeMaterial!.generateCode(), "code.txt"); }} /> { StringTools.DownloadAsFile(this.props.globalState.hostDocument, this.props.globalState.nodeMaterial!.compiledShaders, "shaders.txt"); }} /> { this.props.globalState.customSave && { this.customSave(); }} /> } { !this.props.globalState.customSave && { this.props.globalState.nodeMaterial!.snippetId && } this.loadFromSnippet()} /> { this.saveToSnippetServer(); }} /> } { this.props.globalState.nodeMaterial.getInputBlocks().map(ib => { if (!ib.isUniform || ib.isSystemValue || !ib.name) { return null; } return this.renderInputBlock(ib); }) }
); } }