@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
383 lines (347 loc) • 12.5 kB
text/typescript
import {PersistedConfigWithShaders} from './../../../nodes/utils/BasePersistedConfig';
import {Number2, PolyDictionary} from '../../../../types/GlobalTypes';
import {BaseNodeType, TypedNode} from '../../../nodes/_Base';
import {NodeContext} from '../../../poly/NodeContext';
import type {JsonExportDispatcher} from './Dispatcher';
import {ParamJsonExporterData} from '../../../nodes/utils/io/IOController';
import {ParamType} from '../../../poly/ParamType';
import {BaseConnectionPointData} from '../../../nodes/utils/io/connections/_Base';
import {sanitizeExportedString} from './sanitize';
import {isString} from '../../../../core/Type';
import {VelocityColliderFunctionBody} from '../../../nodes/js/code/assemblers/_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;
inputName?: string;
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>>;
maxInputsCount?: number;
inputs?: InputData[];
connection_points?: IoConnectionPointsData;
flags?: FlagsData;
cloned_state_overriden?: boolean;
persisted_config?: PersistedConfigWithShaders;
polyNode?: {
locked: boolean;
};
}
export interface NodeJsonExporterUIData {
pos?: Number2;
comment?: string;
selection?: string[];
nodes?: PolyDictionary<NodeJsonExporterUIData>;
}
export type NodeJSONShadersData = PolyDictionary<PolyDictionary<string>>;
export type NodeJSONFunctionBodiesData = PolyDictionary<string | PolyDictionary<string>>;
type BaseNodeTypeWithIO = TypedNode<NodeContext, any>;
export interface JSONExporterDataRequestOption {
showPolyNodesData?: boolean;
withPersistedConfig?: boolean;
}
export class NodeJsonExporter<T extends BaseNodeTypeWithIO> {
private _data: NodeJsonExporterData | undefined; // = {} as NodeJsonExporterData;
constructor(protected _node: T, protected dispatcher: JsonExportDispatcher) {}
async data(options: JSONExporterDataRequestOption): Promise<NodeJsonExporterData> {
if (!this._isRoot()) {
this._node.scene().nodesController.registerNodeContextSignature(this._node);
}
this._data = {
type: this._node.type(),
} as NodeJsonExporterData;
if (this._node.polyNodeController) {
this._data['polyNode'] = {
locked: this._node.polyNodeController.locked(),
};
}
// const required_imports = this._node.required_imports()
// if(required_imports){
// this._data['required_imports'] = required_imports
// }
const nodes_data = await 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;
// }
}
const params_data = this.params_data();
if (Object.keys(params_data).length > 0) {
this._data['params'] = params_data;
}
if (!this._isRoot()) {
//data['custom'] = []
if (this._node.io.inputs.maxInputsCountOverriden()) {
this._data['maxInputsCount'] = this._node.io.inputs.maxInputsCount();
}
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.hasBypass() || this._node.flags.hasDisplay() || this._node.flags.hasOptimize()) {
if (this._node.flags.hasBypass()) {
if (this._node.flags.bypass?.active()) {
flags_data['bypass'] = this._node.flags.bypass.active();
}
}
if (this._node.flags.hasDisplay()) {
// only save the display flag if it is true, or if the parent does not have a displayNodeController
// This will then always save it for OBJ
// And only if true for SOP
if (this._node.flags.display?.active() || !this._node.parent()?.displayNodeController) {
flags_data['display'] = this._node.flags.display?.active();
}
}
if (this._node.flags.hasOptimize()) {
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;
}
}
// inputs clone
if (this._node.io.inputs.overrideClonedStateAllowed()) {
const overriden = this._node.io.inputs.clonedStateOverriden();
if (overriden) {
this._data['cloned_state_overriden'] = overriden;
}
}
// persisted config
const withPersistedConfig = options.withPersistedConfig == null ? true : options.withPersistedConfig;
if (withPersistedConfig == true) {
const persisted_config = this._node.persisted_config;
if (persisted_config) {
const persisted_config_data = options.showPolyNodesData
? await persisted_config.toData()
: await persisted_config.toDataWithoutShaders();
if (persisted_config_data) {
this._data.persisted_config = persisted_config_data;
}
}
}
// custom
this.add_custom();
return this._data;
}
uiData(options: JSONExporterDataRequestOption): NodeJsonExporterUIData {
const data: NodeJsonExporterUIData = this.ui_data_without_children();
const children = this._node.children();
if (children.length > 0) {
const nodesData: PolyDictionary<NodeJsonExporterUIData> = {};
for (let child of children) {
const node_exporter = this.dispatcher.dispatchNode(child); //.visit(JsonExporterVisitor); //.json_exporter()
nodesData[child.name()] = node_exporter.uiData(options);
}
data['nodes'] = nodesData;
}
return data;
}
protected ui_data_without_children(): NodeJsonExporterUIData {
const data: NodeJsonExporterUIData = {} as NodeJsonExporterUIData;
if (!this._isRoot()) {
const ui_data = this._node.uiData;
data['pos'] = ui_data.position().toArray() as Number2;
const comment = ui_data.comment();
if (comment) {
data['comment'] = sanitizeExportedString(comment);
}
// selection
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> = {};
const selectedNodes: BaseNodeType[] = [];
selection.nodes(selectedNodes);
for (const selected_node of selectedNodes) {
selected_ids[selected_node.graphNodeId()] = true;
}
for (const 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) {
data['selection'] = selection_data;
}
}
}
}
return data;
}
async persistedConfigData(
shadersData: NodeJSONShadersData,
jsFunctionBodiesData: NodeJSONFunctionBodiesData,
options: JSONExporterDataRequestOption
): Promise<void> {
const children = this._node.children();
if (children.length > 0) {
for (let child of children) {
const node_exporter = this.dispatcher.dispatchNode(child);
await node_exporter.persistedConfigData(shadersData, jsFunctionBodiesData, options);
}
}
if (this._node.persisted_config) {
const persisted_config_data = await this._node.persisted_config.toData();
if (persisted_config_data) {
if (persisted_config_data.shaders) {
shadersData[this._node.path()] = persisted_config_data.shaders;
return;
}
if (persisted_config_data.functionBody != null) {
if (isString(persisted_config_data.functionBody)) {
jsFunctionBodiesData[this._node.path()] = persisted_config_data.functionBody;
return;
} else {
const dict: Record<string, string> = {};
const keys = Object.keys(persisted_config_data.functionBody) as Array<
keyof VelocityColliderFunctionBody
>;
for (let key of keys) {
dict[key] = persisted_config_data.functionBody[key];
}
jsFunctionBodiesData[this._node.path()] = dict;
return;
}
}
console.warn(`persisted config data not handled`, persisted_config_data);
}
}
}
// async jsFunctionBodies(data: NodeJSONFunctionBodiesData, options: JSONExporterDataRequestOption = {}): Promise<void> {
// const children = this._node.children();
// if (children.length > 0) {
// for (let child of children) {
// const node_exporter = this.dispatcher.dispatchNode(child);
// await node_exporter.jsFunctionBodies(data);
// }
// }
// if (this._node.persisted_config) {
// const persisted_config_data = await this._node.persisted_config.toData();
// if (persisted_config_data && persisted_config_data.functionBody) {
// data[this._node.path()] = persisted_config_data.functionBody
// }
// }
// }
private _isRoot() {
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.inputConnection(input_index)!;
if (this._node.io.inputs.hasNamedInputs()) {
const inputConnectionPoints = this._node.io.inputs.namedInputConnectionPoints();
const outputConnectionPoints = input.io.outputs.namedOutputConnectionPoints();
if (inputConnectionPoints && outputConnectionPoints) {
const inputName = inputConnectionPoints[input_index]?.name();
const output_index = connection.outputIndex();
const output_name = outputConnectionPoints[output_index]?.name();
if (output_name) {
data[input_index] = {
index: input_index,
inputName: inputName,
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.hasNamedInputs() || this._node.io.outputs.hasNamedOutputs()) {
const data: IoConnectionPointsData = {};
if (this._node.io.inputs.hasNamedInputs()) {
data['in'] = [];
const connectionPoints = this._node.io.inputs.namedInputConnectionPoints();
if (connectionPoints) {
for (let cp of connectionPoints) {
if (cp) {
data['in'].push(cp.toJSON());
}
}
}
}
if (this._node.io.outputs.hasNamedOutputs()) {
data['out'] = [];
const connectionPoints = this._node.io.outputs.namedOutputConnectionPoints();
if (connectionPoints) {
for (let cp of connectionPoints) {
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.parentParam()) {
const param_exporter = this.dispatcher.dispatchParam(param);
if (param_exporter.required()) {
const params_data = param_exporter.data();
data[param.name()] = params_data;
}
}
}
return data;
}
protected async nodes_data(options: JSONExporterDataRequestOption) {
const data: PolyDictionary<NodeJsonExporterData> = {};
for (let child of this._node.children()) {
const node_exporter = this.dispatcher.dispatchNode(child); //.json_exporter()
data[child.name()] = await node_exporter.data(options);
}
return data;
}
protected add_custom() {}
}