| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- import { MtlXLibrary } from './MaterialXNodeLibrary.js';
- import { MaterialXLogCodes } from './MaterialXLog.js';
- import registryData from './MaterialXNodeInterfaceRegistry.js';
- const SKIP_ELEMENTS = new Set( [ 'materialx', 'input', 'output' ] );
- const CONTAINER_ELEMENTS = new Set( [ 'nodegraph' ] );
- const LIBRARY_FALLBACK_OUTPUT = 'out';
- function formatInputElement( inputNodeX ) {
- const attrs = [];
- for ( const name of [ 'name', 'type', 'nodename', 'nodegraph', 'interfacename', 'output', 'value' ] ) {
- const value = inputNodeX.getAttribute( name );
- if ( value !== null && value !== '' ) {
- attrs.push( `${name}="${value}"` );
- }
- }
- return `<input ${attrs.join( ' ' )}>`;
- }
- function formatNodeElement( nodeX ) {
- const attrs = [];
- for ( const name of [ 'name', 'type', 'nodedef' ] ) {
- const value = nodeX.getAttribute( name );
- if ( value !== null && value !== '' ) {
- attrs.push( `${name}="${value}"` );
- }
- }
- return `<${nodeX.element} ${attrs.join( ' ' )}>`;
- }
- function scoreNodedefCandidate( candidateName, nodeX ) {
- const candidate = registryData.resolved[ candidateName ];
- if ( ! candidate ) return - 1;
- let score = 0;
- const nodeType = nodeX.type;
- const outputType = candidate.outputs.out || Object.values( candidate.outputs )[ 0 ];
- if ( nodeType && outputType === nodeType ) {
- score += 4;
- }
- for ( const child of nodeX.children ) {
- if ( child.element !== 'input' ) continue;
- const expectedType = candidate.inputs[ child.name ];
- if ( ! expectedType || ! child.type ) continue;
- if ( expectedType === child.type ) {
- score += 3;
- } else if ( typesCompatible( expectedType, child.type ) ) {
- score += 1;
- } else {
- score -= 2;
- }
- }
- return score;
- }
- function resolveInterface( nodeX ) {
- const nodedefName = nodeX.getAttribute( 'nodedef' );
- if ( nodedefName && registryData.resolved[ nodedefName ] ) {
- return { source: 'nodedef', name: nodedefName, ...registryData.resolved[ nodedefName ] };
- }
- const candidates = registryData.byNode[ nodeX.element ] || [];
- if ( candidates.length > 0 ) {
- let selected = candidates[ 0 ];
- let bestScore = - Infinity;
- for ( const candidateName of candidates ) {
- const score = scoreNodedefCandidate( candidateName, nodeX );
- if ( score > bestScore ) {
- bestScore = score;
- selected = candidateName;
- }
- }
- const resolved = registryData.resolved[ selected ];
- if ( resolved ) {
- return { source: 'nodedef', name: selected, ...resolved };
- }
- }
- const libraryEntry = MtlXLibrary[ nodeX.element ];
- if ( libraryEntry ) {
- const inputs = Object.fromEntries( libraryEntry.params.map( ( param ) => [ param, null ] ) );
- const outputs = nodeX.type ? { [ LIBRARY_FALLBACK_OUTPUT ]: nodeX.type } : {};
- return { source: 'library', name: nodeX.element, node: nodeX.element, inputs, outputs };
- }
- return null;
- }
- function resolveReferencedNode( inputNodeX ) {
- const materialX = inputNodeX.materialX;
- const referencePath = inputNodeX.referencePath;
- if ( ! referencePath ) return null;
- return materialX.getMaterialXNode( referencePath ) || null;
- }
- function resolveSourceOutputType( sourceNodeX, outputName ) {
- if ( sourceNodeX.element === 'output' ) {
- return {
- interface: null,
- outputType: sourceNodeX.type,
- outputName: sourceNodeX.name || 'out',
- };
- }
- const sourceInterface = resolveInterface( sourceNodeX );
- if ( ! sourceInterface ) return { interface: null, outputType: null, outputName };
- if ( outputName ) {
- if ( sourceInterface.outputs[ outputName ] ) {
- return {
- interface: sourceInterface,
- outputType: sourceInterface.outputs[ outputName ],
- outputName,
- };
- }
- return {
- interface: sourceInterface,
- outputType: null,
- outputName,
- };
- }
- if ( sourceNodeX.type && sourceNodeX.type !== 'multioutput' ) {
- return {
- interface: sourceInterface,
- outputType: sourceNodeX.type,
- outputName: 'out',
- };
- }
- if ( sourceInterface.outputs.out ) {
- return {
- interface: sourceInterface,
- outputType: sourceInterface.outputs.out,
- outputName: 'out',
- };
- }
- const declaredOutputs = Object.entries( sourceInterface.outputs );
- if ( declaredOutputs.length === 1 ) {
- const [ singleName, singleType ] = declaredOutputs[ 0 ];
- return {
- interface: sourceInterface,
- outputType: singleType,
- outputName: singleName,
- };
- }
- if ( sourceNodeX.type ) {
- return {
- interface: sourceInterface,
- outputType: sourceNodeX.type,
- outputName: null,
- };
- }
- return { interface: sourceInterface, outputType: null, outputName: null };
- }
- function typesCompatible( expectedType, actualType ) {
- if ( ! expectedType || ! actualType ) return true;
- if ( expectedType === actualType ) return true;
- if ( ( expectedType === 'float' && actualType === 'integer' ) || ( expectedType === 'integer' && actualType === 'float' ) ) {
- return true;
- }
- return false;
- }
- function validatePortConnection( inputNodeX, parentNodeX, log ) {
- const sourceNodeX = resolveReferencedNode( inputNodeX );
- if ( ! sourceNodeX ) return;
- const { outputType } = resolveSourceOutputType( sourceNodeX, inputNodeX.output );
- const inputSnippet = formatInputElement( inputNodeX );
- if ( inputNodeX.output && outputType === null ) {
- log.add(
- MaterialXLogCodes.INVALID_OUTPUT_CONNECTION,
- `No output found for port connection: ${inputSnippet}`,
- parentNodeX.name,
- );
- return;
- }
- const inputType = inputNodeX.type;
- if ( inputType && outputType && typesCompatible( inputType, outputType ) === false ) {
- log.add(
- MaterialXLogCodes.TYPE_MISMATCH,
- `Mismatched types in port connection: ${inputSnippet}`,
- parentNodeX.name,
- );
- }
- }
- function validateNodeInputs( nodeX, log ) {
- if ( nodeX.element === 'materialx' ) {
- for ( const child of nodeX.children ) {
- validateNodeInputs( child, log );
- }
- return;
- }
- if ( SKIP_ELEMENTS.has( nodeX.element ) ) return;
- if ( CONTAINER_ELEMENTS.has( nodeX.element ) ) {
- for ( const child of nodeX.children ) {
- validateNodeInputs( child, log );
- }
- return;
- }
- const nodeInterface = resolveInterface( nodeX );
- if ( nodeInterface ) {
- for ( const child of nodeX.children ) {
- if ( child.element !== 'input' ) continue;
- const inputName = child.name;
- if ( ! inputName ) continue;
- if ( nodeInterface.inputs[ inputName ] === undefined ) {
- log.add(
- MaterialXLogCodes.UNKNOWN_INPUT,
- `Node interface error: Input '${inputName}' doesn't match declaration: ${formatNodeElement( nodeX )}`,
- nodeX.name,
- );
- } else if (
- child.type &&
- nodeInterface.inputs[ inputName ] &&
- child.hasReference === false &&
- typesCompatible( nodeInterface.inputs[ inputName ], child.type ) === false
- ) {
- log.add(
- MaterialXLogCodes.TYPE_MISMATCH,
- `Input '${inputName}' type '${child.type}' doesn't match nodedef type '${nodeInterface.inputs[ inputName ]}' on ${formatNodeElement( nodeX )}`,
- nodeX.name,
- );
- }
- if ( child.hasReference ) {
- validatePortConnection( child, nodeX, log );
- }
- }
- } else if ( MtlXLibrary[ nodeX.element ] === undefined && nodeX.element !== 'surfacematerial' ) {
- // Defer unsupported-node warnings to compile-time behavior.
- for ( const child of nodeX.children ) {
- if ( child.element === 'input' && child.hasReference ) {
- validatePortConnection( child, nodeX, log );
- }
- }
- }
- for ( const child of nodeX.children ) {
- if ( child.element !== 'input' && child.element !== 'output' ) {
- validateNodeInputs( child, log );
- }
- }
- }
- function createStrictInterfaceValidator() {
- return function validateMaterialXInterfaces( rootNode, log ) {
- validateNodeInputs( rootNode, log );
- };
- }
- export { createStrictInterfaceValidator };
|