propertyTabComponent.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import * as React from "react";
  2. import { GlobalState } from '../../globalState';
  3. import { Nullable } from 'babylonjs/types';
  4. import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
  5. import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
  6. import { StringTools } from '../../stringTools';
  7. import { FileButtonLineComponent } from '../../sharedComponents/fileButtonLineComponent';
  8. import { Tools } from 'babylonjs/Misc/tools';
  9. import { SerializationTools } from '../../serializationTools';
  10. import { CheckBoxLineComponent } from '../../sharedComponents/checkBoxLineComponent';
  11. import { DataStorage } from '../../dataStorage';
  12. import { GraphNode } from '../../diagram/graphNode';
  13. import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
  14. import { GraphFrame } from '../../diagram/graphFrame';
  15. import { TextLineComponent } from '../../sharedComponents/textLineComponent';
  16. import { Engine } from 'babylonjs/Engines/engine';
  17. import { FramePropertyTabComponent } from '../../diagram/properties/framePropertyComponent';
  18. import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
  19. import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
  20. import { Color3LineComponent } from '../../sharedComponents/color3LineComponent';
  21. import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
  22. import { Color4LineComponent } from '../../sharedComponents/color4LineComponent';
  23. import { Vector2LineComponent } from '../../sharedComponents/vector2LineComponent';
  24. import { Vector3LineComponent } from '../../sharedComponents/vector3LineComponent';
  25. import { Vector4LineComponent } from '../../sharedComponents/vector4LineComponent';
  26. import { Observer } from 'babylonjs/Misc/observable';
  27. require("./propertyTab.scss");
  28. interface IPropertyTabComponentProps {
  29. globalState: GlobalState;
  30. }
  31. export class PropertyTabComponent extends React.Component<IPropertyTabComponentProps, { currentNode: Nullable<GraphNode>, currentFrame: Nullable<GraphFrame> }> {
  32. private _onBuiltObserver: Nullable<Observer<void>>;
  33. constructor(props: IPropertyTabComponentProps) {
  34. super(props)
  35. this.state = { currentNode: null, currentFrame: null };
  36. }
  37. componentDidMount() {
  38. this.props.globalState.onSelectionChangedObservable.add(selection => {
  39. if (selection instanceof GraphNode) {
  40. this.setState({ currentNode: selection, currentFrame: null });
  41. } else if (selection instanceof GraphFrame) {
  42. this.setState({ currentNode: null, currentFrame: selection });
  43. } else {
  44. this.setState({ currentNode: null, currentFrame: null });
  45. }
  46. });
  47. this._onBuiltObserver = this.props.globalState.onBuiltObservable.add(() => {
  48. this.forceUpdate();
  49. });
  50. }
  51. componentWillReceiveProps() {
  52. this.props.globalState.onBuiltObservable.remove(this._onBuiltObserver);
  53. }
  54. processInputBlockUpdate(ib: InputBlock) {
  55. this.props.globalState.onUpdateRequiredObservable.notifyObservers();
  56. if (ib.isConstant) {
  57. this.props.globalState.onRebuildRequiredObservable.notifyObservers();
  58. }
  59. }
  60. renderInputBlock(block: InputBlock) {
  61. switch (block.type) {
  62. case NodeMaterialBlockConnectionPointTypes.Float:
  63. let cantDisplaySlider = (isNaN(block.min) || isNaN(block.max) || block.min === block.max);
  64. return (
  65. <div key={block.uniqueId} >
  66. {
  67. block.isBoolean &&
  68. <CheckBoxLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"
  69. onValueChanged={() => {
  70. this.processInputBlockUpdate(block);
  71. }}/>
  72. }
  73. {
  74. !block.isBoolean && cantDisplaySlider &&
  75. <FloatLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"
  76. onChange={() => this.processInputBlockUpdate(block)}/>
  77. }
  78. {
  79. !block.isBoolean && !cantDisplaySlider &&
  80. <SliderLineComponent key={block.uniqueId} label={block.name} target={block} propertyName="value"
  81. step={(block.max - block.min) / 100.0} minimum={block.min} maximum={block.max}
  82. onChange={() => this.processInputBlockUpdate(block)}/>
  83. }
  84. </div>
  85. );
  86. case NodeMaterialBlockConnectionPointTypes.Color3:
  87. return (
  88. <Color3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
  89. propertyName="value"
  90. onChange={() => this.processInputBlockUpdate(block)}
  91. />
  92. )
  93. case NodeMaterialBlockConnectionPointTypes.Color4:
  94. return (
  95. <Color4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block} propertyName="value"
  96. onChange={() => this.processInputBlockUpdate(block)}/>
  97. )
  98. case NodeMaterialBlockConnectionPointTypes.Vector2:
  99. return (
  100. <Vector2LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
  101. propertyName="value"
  102. onChange={() => this.processInputBlockUpdate(block)}/>
  103. )
  104. case NodeMaterialBlockConnectionPointTypes.Vector3:
  105. return (
  106. <Vector3LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
  107. propertyName="value"
  108. onChange={() => this.processInputBlockUpdate(block)}/>
  109. )
  110. case NodeMaterialBlockConnectionPointTypes.Vector4:
  111. return (
  112. <Vector4LineComponent globalState={this.props.globalState} key={block.uniqueId} label={block.name} target={block}
  113. propertyName="value"
  114. onChange={() => this.processInputBlockUpdate(block)}/>
  115. )
  116. }
  117. return null;
  118. }
  119. load(file: File) {
  120. Tools.ReadFile(file, (data) => {
  121. let decoder = new TextDecoder("utf-8");
  122. SerializationTools.Deserialize(JSON.parse(decoder.decode(data)), this.props.globalState);
  123. }, undefined, true);
  124. }
  125. save() {
  126. let json = SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState);
  127. StringTools.DownloadAsFile(this.props.globalState.hostDocument, json, "nodeMaterial.json");
  128. }
  129. customSave() {
  130. this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Saving your material to Babylon.js snippet server...", isError: false});
  131. this.props.globalState.customSave!.action(SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState)).then(() => {
  132. this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Material saved successfully", isError: false});
  133. }).catch(err => {
  134. this.props.globalState.onLogRequiredObservable.notifyObservers({message: err, isError: true});
  135. });
  136. }
  137. render() {
  138. if (this.state.currentNode) {
  139. return (
  140. <div id="propertyTab">
  141. <div id="header">
  142. <img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
  143. <div id="title">
  144. NODE MATERIAL EDITOR
  145. </div>
  146. </div>
  147. {this.state.currentNode.renderProperties()}
  148. </div>
  149. );
  150. }
  151. if (this.state.currentFrame) {
  152. return (
  153. <FramePropertyTabComponent globalState={this.props.globalState} frame={this.state.currentFrame}/>
  154. );
  155. }
  156. let gridSize = DataStorage.ReadNumber("GridSize", 20);
  157. return (
  158. <div id="propertyTab">
  159. <div id="header">
  160. <img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
  161. <div id="title">
  162. NODE MATERIAL EDITOR
  163. </div>
  164. </div>
  165. <div>
  166. <LineContainerComponent title="GENERAL">
  167. <TextLineComponent label="Version" value={Engine.Version}/>
  168. <TextLineComponent label="Help" value="doc.babylonjs.com" underline={true} onLink={() => window.open('https://doc.babylonjs.com/how_to/node_material', '_blank')}/>
  169. <ButtonLineComponent label="Reset to default" onClick={() => {
  170. this.props.globalState.nodeMaterial!.setToDefault();
  171. this.props.globalState.onResetRequiredObservable.notifyObservers();
  172. }} />
  173. </LineContainerComponent>
  174. <LineContainerComponent title="UI">
  175. <ButtonLineComponent label="Zoom to fit" onClick={() => {
  176. this.props.globalState.onZoomToFitRequiredObservable.notifyObservers();
  177. }} />
  178. <ButtonLineComponent label="Reorganize" onClick={() => {
  179. this.props.globalState.onReOrganizedRequiredObservable.notifyObservers();
  180. }} />
  181. </LineContainerComponent>
  182. <LineContainerComponent title="OPTIONS">
  183. <CheckBoxLineComponent label="Embed textures when saving"
  184. isSelected={() => DataStorage.ReadBoolean("EmbedTextures", true)}
  185. onSelect={(value: boolean) => {
  186. DataStorage.StoreBoolean("EmbedTextures", value);
  187. }}
  188. />
  189. <SliderLineComponent label="Grid size" minimum={0} maximum={100} step={5}
  190. decimalCount={0}
  191. directValue={gridSize}
  192. onChange={value => {
  193. DataStorage.StoreNumber("GridSize", value);
  194. this.props.globalState.onGridSizeChanged.notifyObservers();
  195. this.forceUpdate();
  196. }}
  197. />
  198. <CheckBoxLineComponent label="Show grid"
  199. isSelected={() => DataStorage.ReadBoolean("ShowGrid", true)}
  200. onSelect={(value: boolean) => {
  201. DataStorage.StoreBoolean("ShowGrid", value);
  202. this.props.globalState.onGridSizeChanged.notifyObservers();
  203. }}
  204. />
  205. </LineContainerComponent>
  206. <LineContainerComponent title="FILE">
  207. <FileButtonLineComponent label="Load" onClick={(file) => this.load(file)} accept=".json" />
  208. <ButtonLineComponent label="Save" onClick={() => {
  209. this.save();
  210. }} />
  211. {
  212. this.props.globalState.customSave &&
  213. <ButtonLineComponent label={this.props.globalState.customSave!.label} onClick={() => {
  214. this.customSave();
  215. }} />
  216. }
  217. <ButtonLineComponent label="Generate code" onClick={() => {
  218. StringTools.DownloadAsFile(this.props.globalState.hostDocument, this.props.globalState.nodeMaterial!.generateCode(), "code.txt");
  219. }} />
  220. <ButtonLineComponent label="Export shaders" onClick={() => {
  221. StringTools.DownloadAsFile(this.props.globalState.hostDocument, this.props.globalState.nodeMaterial!.compiledShaders, "shaders.txt");
  222. }} />
  223. </LineContainerComponent>
  224. <LineContainerComponent title="INPUTS">
  225. {
  226. this.props.globalState.nodeMaterial.getInputBlocks().map(ib => {
  227. if (!ib.isUniform || ib.isSystemValue || !ib.name) {
  228. return null;
  229. }
  230. return this.renderInputBlock(ib);
  231. })
  232. }
  233. </LineContainerComponent>
  234. </div>
  235. </div>
  236. );
  237. }
  238. }