polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
284 lines (253 loc) • 8.71 kB
text/typescript
import {Number2, PolyDictionary} from '../../../../types/GlobalTypes';
import {TypedNode} from '../../../nodes/_Base';
import {SceneJsonExporter} from './Scene';
import {NodeContext} from '../../../poly/NodeContext';
import {JsonExportDispatcher} from './Dispatcher';
import {ParamJsonExporterData} from '../../../nodes/utils/io/IOController';
import {ParamType} from '../../../poly/ParamType';
import {BaseConnectionPointData} from '../../../nodes/utils/io/connections/_Base';
// revert to using index instead of name
// for gl nodes such as the if node, whose input names
// changes depending on the input
interface NamedInputData {
index: number;
node: string;
output: string;
}
type IndexedInputData = string | null;
export type InputData = NamedInputData | IndexedInputData;
interface FlagsData {
bypass?: boolean;
display?: boolean;
optimize?: boolean;
}
export interface IoConnectionPointsData {
in?: BaseConnectionPointData[];
out?: BaseConnectionPointData[];
}
export interface NodeJsonExporterData {
type: string;
nodes?: PolyDictionary<NodeJsonExporterData>;
children_context?: NodeContext;
params?: PolyDictionary<ParamJsonExporterData<ParamType>>;
inputs?: InputData[];
connection_points?: IoConnectionPointsData;
selection?: string[];
flags?: FlagsData;
cloned_state_overriden?: boolean;
persisted_config?: object;
}
export interface NodeJsonExporterUIData {
pos?: Number2;
comment?: string;
nodes: PolyDictionary<NodeJsonExporterUIData>;
}
type BaseNodeTypeWithIO = TypedNode<NodeContext, any>;
export interface DataRequestOption {
showPolyNodesData?: boolean;
}
export class NodeJsonExporter<T extends BaseNodeTypeWithIO> {
private _data: NodeJsonExporterData | undefined; // = {} as NodeJsonExporterData;
constructor(protected _node: T) {}
data(options: DataRequestOption = {}): NodeJsonExporterData {
if (!this.is_root()) {
this._node.scene().nodesController.register_node_context_signature(this._node);
}
this._data = {
type: this._node.type(),
} as NodeJsonExporterData;
// const required_imports = this._node.required_imports()
// if(required_imports){
// this._data['required_imports'] = required_imports
// }
const nodes_data = this.nodes_data(options);
if (Object.keys(nodes_data).length > 0) {
this._data['nodes'] = nodes_data;
// required by the Store::Scene::Exporter.rb
// Update: removed as there should be a better way
// const context = this._node.childrenController?.context;
// if (context) {
// this._data['children_context'] = context;
// }
}
if (!this.is_root()) {
const params_data = this.params_data();
if (Object.keys(params_data).length > 0) {
this._data['params'] = params_data;
}
//data['custom'] = []
const inputs_data = this.inputs_data();
if (inputs_data.length > 0) {
this._data['inputs'] = inputs_data;
}
const connection_points_data = this.connection_points_data();
if (connection_points_data) {
this._data['connection_points'] = connection_points_data;
}
}
if (this._node.flags) {
const flags_data: FlagsData = {};
if (this._node.flags.has_bypass() || this._node.flags.has_display() || this._node.flags.has_optimize()) {
if (this._node.flags.has_bypass()) {
if (this._node.flags.bypass?.active()) {
flags_data['bypass'] = this._node.flags.bypass.active();
}
}
if (this._node.flags.has_display()) {
// only save the display flag if it is true, or if the parent does not have a display_node_controller
// This will then always save it for OBJ
// And only if true for SOP
if (this._node.flags.display?.active() || !this._node.parent()?.display_node_controller) {
flags_data['display'] = this._node.flags.display?.active();
}
}
if (this._node.flags.has_optimize()) {
if (this._node.flags.optimize?.active()) {
flags_data['optimize'] = this._node.flags.optimize?.active();
}
}
}
if (Object.keys(flags_data).length > 0) {
this._data['flags'] = flags_data;
}
}
if (this._node.childrenAllowed()) {
const selection = this._node.childrenController?.selection;
if (selection && this._node.children().length > 0) {
// only save the nodes that are still present, in case the selection just got deleted
const selected_children: BaseNodeTypeWithIO[] = [];
const selected_ids: PolyDictionary<boolean> = {};
for (let selected_node of selection.nodes()) {
selected_ids[selected_node.graphNodeId()] = true;
}
for (let child of this._node.children()) {
if (child.graphNodeId() in selected_ids) {
selected_children.push(child);
}
}
const selection_data = selected_children.map((n) => n.name());
if (selection_data.length > 0) {
this._data['selection'] = selection_data;
}
}
}
// inputs clone
if (this._node.io.inputs.override_cloned_state_allowed()) {
const overriden = this._node.io.inputs.cloned_state_overriden();
if (overriden) {
this._data['cloned_state_overriden'] = overriden;
}
}
// persisted config
if (this._node.persisted_config) {
const persisted_config_data = this._node.persisted_config.toJSON();
if (persisted_config_data) {
this._data.persisted_config = persisted_config_data;
}
}
// custom
this.add_custom();
return this._data;
}
ui_data(options: DataRequestOption = {}): NodeJsonExporterUIData {
const data: NodeJsonExporterUIData = this.ui_data_without_children();
const children = this._node.children();
if (children.length > 0) {
data['nodes'] = {};
children.forEach((child) => {
const node_exporter = JsonExportDispatcher.dispatch_node(child); //.visit(JsonExporterVisitor); //.json_exporter()
data['nodes'][child.name()] = node_exporter.ui_data(options);
});
}
return data;
}
protected ui_data_without_children(): NodeJsonExporterUIData {
const data: NodeJsonExporterUIData = {} as NodeJsonExporterUIData;
if (!this.is_root()) {
const ui_data = this._node.uiData;
data['pos'] = ui_data.position().toArray() as Number2;
const comment = ui_data.comment();
if (comment) {
data['comment'] = SceneJsonExporter.sanitize_string(comment);
}
}
return data;
}
private is_root() {
return this._node.parent() === null && this._node.graphNodeId() == this._node.root().graphNodeId();
}
protected inputs_data() {
const data: InputData[] = [];
this._node.io.inputs.inputs().forEach((input, input_index) => {
if (input) {
const connection = this._node.io.connections.input_connection(input_index)!;
if (this._node.io.inputs.has_named_inputs) {
// const input_name = this._node.io.inputs.named_input_connection_points[input_index].name;
const output_index = connection.output_index;
const output_name = input.io.outputs.named_output_connection_points[output_index]?.name();
if (output_name) {
data[input_index] = {
index: input_index,
node: input.name(),
output: output_name,
};
}
} else {
data[input_index] = input.name();
}
}
});
return data;
}
protected connection_points_data() {
if (!this._node.io.has_connection_points_controller) {
return;
}
if (!this._node.io.connection_points.initialized()) {
return;
}
if (this._node.io.inputs.has_named_inputs || this._node.io.outputs.has_named_outputs) {
const data: IoConnectionPointsData = {};
if (this._node.io.inputs.has_named_inputs) {
data['in'] = [];
for (let cp of this._node.io.inputs.named_input_connection_points) {
if (cp) {
data['in'].push(cp.toJSON());
}
}
}
if (this._node.io.outputs.has_named_outputs) {
data['out'] = [];
for (let cp of this._node.io.outputs.named_output_connection_points) {
if (cp) {
data['out'].push(cp.toJSON());
}
}
}
return data;
}
}
protected params_data() {
const data: PolyDictionary<ParamJsonExporterData<ParamType>> = {};
for (let param_name of this._node.params.names) {
const param = this._node.params.get(param_name);
if (param && !param.parent_param) {
const param_exporter = JsonExportDispatcher.dispatch_param(param);
if (param_exporter.required()) {
const params_data = param_exporter.data();
data[param.name()] = params_data;
}
}
}
return data;
}
protected nodes_data(options: DataRequestOption = {}) {
const data: PolyDictionary<NodeJsonExporterData> = {};
for (let child of this._node.children()) {
const node_exporter = JsonExportDispatcher.dispatch_node(child); //.json_exporter()
data[child.name()] = node_exporter.data(options);
}
return data;
}
protected add_custom() {}
}