mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-05-05 19:09:37 +00:00
stuff
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"exclude": ["transform-regenerator"]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
module.exports = function (config)
|
||||
{
|
||||
config.set({
|
||||
basePath: '../', // base is the javascript folder
|
||||
files: [
|
||||
{ pattern: '_build/JsMaterialXGenShader.js', watched: false, included: true, served: true },
|
||||
{ pattern: '_build/JsMaterialXGenShader.wasm', watched: false, included: false, served: true },
|
||||
{ pattern: '_build/JsMaterialXGenShader.data', watched: false, included: false, served: true, nocache: true },
|
||||
{ pattern: 'browser/*.spec.js', watched: true, included: true, served: true },
|
||||
],
|
||||
mime: {
|
||||
'application/wasm': ['wasm'],
|
||||
'application/octet-stream; charset=UTF-8': ['data'],
|
||||
},
|
||||
proxies: {
|
||||
'/JsMaterialXGenShader.data': '/base/_build/JsMaterialXGenShader.data',
|
||||
},
|
||||
reporters: ['mocha'],
|
||||
client: {
|
||||
mocha: {
|
||||
reporter: 'html'
|
||||
}
|
||||
},
|
||||
browsers: ['Chrome'],
|
||||
port: 8080,
|
||||
autoWatch: true,
|
||||
concurrency: Infinity,
|
||||
// logLevel: config.LOG_DEBUG,
|
||||
frameworks: ['mocha', 'chai'],
|
||||
plugins: [
|
||||
'karma-chai',
|
||||
'karma-chrome-launcher',
|
||||
'karma-mocha',
|
||||
'karma-mocha-reporter',
|
||||
],
|
||||
customLaunchers: {
|
||||
ChromeHeadlessGL: {
|
||||
base: 'Chrome',
|
||||
flags: [
|
||||
'--headless=new',
|
||||
'--enable-gpu',
|
||||
'--enable-webgl',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
// MaterialX is served through a script tag in the test setup.
|
||||
|
||||
function createStandardSurfaceMaterial(mx)
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
const ssName = 'SR_default';
|
||||
const ssNode = doc.addChildOfCategory('standard_surface', ssName);
|
||||
ssNode.setType('surfaceshader');
|
||||
const smNode = doc.addChildOfCategory('surfacematerial', 'Default');
|
||||
smNode.setType('material');
|
||||
const shaderElement = smNode.addInput('surfaceshader');
|
||||
shaderElement.setType('surfaceshader');
|
||||
shaderElement.setNodeName(ssName);
|
||||
expect(doc.validate()).to.be.true;
|
||||
// Release local wrappers
|
||||
shaderElement.delete();
|
||||
smNode.delete();
|
||||
ssNode.delete();
|
||||
return doc;
|
||||
}
|
||||
|
||||
describe('Generate Shaders', function ()
|
||||
{
|
||||
let mx;
|
||||
const canvas = document.createElement('canvas');
|
||||
const gl = canvas.getContext('webgl2');
|
||||
|
||||
this.timeout(60000);
|
||||
|
||||
before(async function ()
|
||||
{
|
||||
mx = await MaterialX();
|
||||
});
|
||||
|
||||
it('Compile Shaders', () =>
|
||||
{
|
||||
const doc = createStandardSurfaceMaterial(mx);
|
||||
|
||||
const generators = []
|
||||
if (typeof mx.EsslShaderGenerator != 'undefined')
|
||||
generators.push(mx.EsslShaderGenerator.create());
|
||||
if (typeof mx.GlslShaderGenerator != 'undefined')
|
||||
generators.push(mx.GlslShaderGenerator.create());
|
||||
if (typeof mx.MslShaderGenerator != 'undefined')
|
||||
generators.push(mx.MslShaderGenerator.create());
|
||||
if (typeof mx.OslShaderGenerator != 'undefined')
|
||||
generators.push(mx.OslShaderGenerator.create());
|
||||
if (typeof mx.VkShaderGenerator != 'undefined')
|
||||
generators.push(mx.VkShaderGenerator.create());
|
||||
if (typeof mx.WgslShaderGenerator != 'undefined')
|
||||
generators.push(mx.WgslShaderGenerator.create());
|
||||
if (typeof mx.MdlShaderGenerator != 'undefined')
|
||||
generators.push(mx.MdlShaderGenerator.create());
|
||||
if (typeof mx.SlangShaderGenerator != 'undefined')
|
||||
generators.push(mx.SlangShaderGenerator.create());
|
||||
|
||||
const elem = mx.findRenderableElement(doc);
|
||||
for (let gen of generators)
|
||||
{
|
||||
console.log("Generating shader for " + gen.getTarget() + "...");
|
||||
|
||||
const genContext = new mx.GenContext(gen);
|
||||
const stdlib = mx.loadStandardLibraries(genContext);
|
||||
doc.importLibrary(stdlib);
|
||||
|
||||
try
|
||||
{
|
||||
const mxShader = gen.generate(elem.getNamePath(), elem, genContext);
|
||||
|
||||
const fShader = mxShader.getSourceCode("pixel");
|
||||
|
||||
if (gen.getTarget() == 'essl')
|
||||
{
|
||||
const vShader = mxShader.getSourceCode("vertex");
|
||||
|
||||
const glVertexShader = gl.createShader(gl.VERTEX_SHADER);
|
||||
gl.shaderSource(glVertexShader, vShader);
|
||||
gl.compileShader(glVertexShader);
|
||||
if (!gl.getShaderParameter(glVertexShader, gl.COMPILE_STATUS))
|
||||
{
|
||||
console.error("-------- VERTEX SHADER FAILED TO COMPILE: ----------------");
|
||||
console.error("--- VERTEX SHADER LOG ---");
|
||||
console.error(gl.getShaderInfoLog(glVertexShader));
|
||||
console.error("--- VERTEX SHADER START ---");
|
||||
console.error(fShader);
|
||||
console.error("--- VERTEX SHADER END ---");
|
||||
}
|
||||
expect(gl.getShaderParameter(glVertexShader, gl.COMPILE_STATUS)).to.equal(true);
|
||||
|
||||
const glPixelShader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
gl.shaderSource(glPixelShader, fShader);
|
||||
gl.compileShader(glPixelShader);
|
||||
if (!gl.getShaderParameter(glPixelShader, gl.COMPILE_STATUS))
|
||||
{
|
||||
console.error("-------- PIXEL SHADER FAILED TO COMPILE: ----------------");
|
||||
console.error("--- PIXEL SHADER LOG ---");
|
||||
console.error(gl.getShaderInfoLog(glPixelShader));
|
||||
console.error("--- PIXEL SHADER START ---");
|
||||
console.error(fShader);
|
||||
console.error("--- PIXEL SHADER END ---");
|
||||
}
|
||||
expect(gl.getShaderParameter(glPixelShader, gl.COMPILE_STATUS)).to.equal(true);
|
||||
// Cleanup GL shaders
|
||||
gl.deleteShader(glVertexShader);
|
||||
gl.deleteShader(glPixelShader);
|
||||
}
|
||||
// Cleanup shader wrapper
|
||||
mxShader.delete();
|
||||
}
|
||||
catch (errPtr)
|
||||
{
|
||||
console.error("-------- Failed code generation: ----------------");
|
||||
if (typeof mx.getExceptionMessage === 'function')
|
||||
{
|
||||
console.error(mx.getExceptionMessage(errPtr));
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error(errPtr);
|
||||
}
|
||||
}
|
||||
// Cleanup per-generator wrappers
|
||||
stdlib.delete();
|
||||
genContext.delete();
|
||||
gen.delete();
|
||||
}
|
||||
// Cleanup element and document
|
||||
elem.delete();
|
||||
doc.delete();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
import { expect } from 'chai';
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
import { getMtlxStrings } from './testHelpers';
|
||||
|
||||
describe('Code Examples', () =>
|
||||
{
|
||||
it('Building a MaterialX Document', async () =>
|
||||
{
|
||||
const mx = await Module();
|
||||
// Create a document.
|
||||
const doc = mx.createDocument();
|
||||
|
||||
// Create a node graph with a single image node and output.
|
||||
const nodeGraph = doc.addNodeGraph();
|
||||
expect(doc.getNodeGraphs().length).to.equal(1);
|
||||
const image = nodeGraph.addNode('image');
|
||||
const nodes = nodeGraph.getNodes();
|
||||
expect(nodes.length).to.equal(1);
|
||||
expect(nodes[0]).to.eql(image);
|
||||
|
||||
image.setInputValueString('file', 'image1.tif', 'filename');
|
||||
const input = image.getInput('file');
|
||||
expect(input).to.not.be.null;
|
||||
expect(input.getValue().getData()).to.equal('image1.tif');
|
||||
|
||||
const output = nodeGraph.addOutput();
|
||||
const outputs = nodeGraph.getOutputs();
|
||||
expect(outputs.length).to.equal(1);
|
||||
expect(outputs[0]).to.eql(output);
|
||||
|
||||
output.setConnectedNode(image);
|
||||
const connectedNode = output.getConnectedNode();
|
||||
expect(connectedNode).to.not.be.null;
|
||||
expect(connectedNode instanceof mx.Node).to.be.true;
|
||||
|
||||
// Create a simple shader interface.
|
||||
const simpleSrf = doc.addNodeDef('ND_simpleSrf', 'surfaceshader', 'simpleSrf');
|
||||
const nodeDefs = doc.getNodeDefs();
|
||||
expect(nodeDefs.length).to.equal(1);
|
||||
expect(nodeDefs[0]).to.eql(simpleSrf);
|
||||
|
||||
simpleSrf.setInputValueColor3('diffColor', new mx.Color3(1.0, 1.0, 1.0));
|
||||
let inputValue = simpleSrf.getInputValue('diffColor');
|
||||
expect(inputValue).to.not.be.null;
|
||||
expect(inputValue.getData()).to.eql(new mx.Color3(1.0, 1.0, 1.0));
|
||||
|
||||
simpleSrf.setInputValueColor3('specColor', new mx.Color3(0.0, 0.0, 0.0));
|
||||
inputValue = simpleSrf.getInputValue('specColor');
|
||||
expect(inputValue).to.not.be.null;
|
||||
expect(inputValue.getData()).to.eql(new mx.Color3(0.0, 0.0, 0.0));
|
||||
|
||||
const roughness = simpleSrf.setInputValueFloat('roughness', 0.25);
|
||||
inputValue = simpleSrf.getInputValue('roughness');
|
||||
expect(inputValue).to.not.be.null;
|
||||
expect(inputValue.getData()).to.equal(0.25);
|
||||
|
||||
// // Create a material that instantiates the shader.
|
||||
// const material = doc.addMaterial();
|
||||
// const materials = doc.getMaterials();
|
||||
// expect(materials.length).to.equal(1);
|
||||
// expect(materials[0]).to.eql(material);
|
||||
// const refSimpleSrf = material.addShaderRef('SR_simpleSrf', 'simpleSrf');
|
||||
// const shaderRefs = material.getShaderRefs();
|
||||
// expect(shaderRefs.length).to.equal(1);
|
||||
// expect(shaderRefs[0]).to.eql(refSimpleSrf);
|
||||
// expect(shaderRefs[0].getName()).to.equal('SR_simpleSrf');
|
||||
|
||||
// // Bind roughness to a new value within this material.
|
||||
// const bindInput = refSimpleSrf.addBindInput('roughness');
|
||||
// const bindInputs = refSimpleSrf.getBindInputs();
|
||||
// expect(bindInputs.length).to.equal(1);
|
||||
// expect(bindInputs[0]).to.eql(bindInput);
|
||||
// bindInput.setValuefloat(0.5);
|
||||
// expect(bindInput.getValue()).to.not.be.null;
|
||||
// expect(bindInput.getValue().getData()).to.equal(0.5);
|
||||
|
||||
// // Validate the value of roughness in the context of this material.
|
||||
// expect(roughness.getBoundValue(material).getValueString()).to.equal('0.5');
|
||||
// Cleanup wrappers
|
||||
nodeDefs.forEach(nd => nd.delete());
|
||||
output.delete();
|
||||
image.delete();
|
||||
nodeGraph.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Traversing a Document Tree', async () =>
|
||||
{
|
||||
const xmlStr = getMtlxStrings(
|
||||
['standard_surface_greysphere_calibration.mtlx'],
|
||||
'../../resources/Materials/Examples/StandardSurface'
|
||||
)[0];
|
||||
const mx = await Module();
|
||||
|
||||
// Read a document from disk.
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlString(doc, xmlStr);
|
||||
|
||||
// Traverse the document tree in depth-first order.
|
||||
const elements = doc.traverseTree();
|
||||
let imageCount = 0;
|
||||
for (let elem of elements)
|
||||
{
|
||||
if (elem.isANode('image'))
|
||||
{
|
||||
imageCount++;
|
||||
}
|
||||
}
|
||||
expect(imageCount).to.greaterThan(0);
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Building a MaterialX Document', async () =>
|
||||
{
|
||||
const xmlStr = getMtlxStrings(['standard_surface_marble_solid.mtlx'], '../../resources/Materials/Examples/StandardSurface')[0];
|
||||
const mx = await Module();
|
||||
|
||||
// Read a document from disk.
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlString(doc, xmlStr);
|
||||
|
||||
// let materialCount = 0;
|
||||
// let shaderInputCount = 0;
|
||||
// // Iterate through 1.37 materials for which there should be none
|
||||
// const materials = doc.getMaterials();
|
||||
// materials.forEach((material) => {
|
||||
// materialCount++;
|
||||
|
||||
// // For each shader input, find all upstream images in the dataflow graph.
|
||||
// const primaryShaderInputs = material.getPrimaryShaderInputs();
|
||||
// primaryShaderInputs.forEach((input) => {
|
||||
// const graphIter = input.traverseGraph(material);
|
||||
// let edge = graphIter.next();
|
||||
// while (edge) {
|
||||
// shaderInputCount++;
|
||||
// edge = graphIter.next();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// expect(materialCount).to.equal(0);
|
||||
// expect(shaderInputCount).to.equal(0);
|
||||
doc.delete();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,250 @@
|
||||
import { expect } from 'chai';
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
import { getMtlxStrings } from './testHelpers';
|
||||
|
||||
describe('Custom Bindings', () =>
|
||||
{
|
||||
const examplesPath = '../../resources/Materials/Examples/StandardSurface';
|
||||
|
||||
let mx;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Optional parameters work as expected', () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
// Call a method without optional argument
|
||||
const nodeGraph = doc.addNodeGraph();
|
||||
expect(nodeGraph).to.be.instanceof(mx.NodeGraph);
|
||||
expect(nodeGraph.getName()).to.equal('nodegraph1'); // Auto-constructed default value
|
||||
// Call a method with optional argument
|
||||
const nodeGraph2 = doc.addNodeGraph('myGraph');
|
||||
expect(nodeGraph2).to.be.instanceof(mx.NodeGraph);
|
||||
expect(nodeGraph2.getName()).to.equal('myGraph');
|
||||
|
||||
// Call a method that requires at least one parameter
|
||||
const node = nodeGraph.addNode('node');
|
||||
expect(node).to.be.instanceof(mx.Node);
|
||||
|
||||
// Omitting non-optional parameter should throw
|
||||
expect(() => { nodeGraph.addNode(); }).to.throw;
|
||||
|
||||
// Cleanup
|
||||
node.delete();
|
||||
nodeGraph2.delete();
|
||||
nodeGraph.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Vector <-> Array conversion', () =>
|
||||
{
|
||||
// Functions that return vectors in C++ should return an array in JS
|
||||
const doc = mx.createDocument();
|
||||
const nodeGraph = doc.addNodeGraph();
|
||||
const nodeGraphB = doc.addNodeGraph();
|
||||
const nodeGraphs = doc.getNodeGraphs();
|
||||
expect(nodeGraphs).to.be.an.instanceof(Array);
|
||||
expect(nodeGraphs.length).to.equal(2);
|
||||
|
||||
// Elements fetched through the vector -> array conversion should be editable and changes should be reflected
|
||||
// in the original objects.
|
||||
// Note: We cannot simply compare these objects for equality, since they're separately constructed pointers
|
||||
// to the same object.
|
||||
const backdrop = nodeGraph.addBackdrop();
|
||||
const backDrops = nodeGraphs[0].getBackdrops();
|
||||
expect(backDrops.length).to.equal(1);
|
||||
nodeGraphs[0].addBackdrop();
|
||||
expect(nodeGraph.getBackdrops().length).to.equal(2);
|
||||
|
||||
// Functions that expect vectors as parameters in C++ should accept arrays in JS
|
||||
// Built-in types (strings)
|
||||
const pathSegments = ['path', 'to', 'something'];
|
||||
const namePath = mx.createNamePath(pathSegments);
|
||||
expect(namePath).to.equal(pathSegments.join(mx.NAME_PATH_SEPARATOR));
|
||||
|
||||
// Complex (smart pointer) types
|
||||
const node1 = nodeGraph.addNode('node');
|
||||
const node2 = nodeGraph.addNode('node');
|
||||
const node3 = nodeGraph.addNode('node', 'anotherNode');
|
||||
backdrop.setContainsElements([node1, node2, node3]);
|
||||
const nodes = backdrop.getContainsElements();
|
||||
expect(nodes.length).to.equal(3);
|
||||
expect(nodes[0].getName()).to.equal('node1'); // Name auto-constructed from category
|
||||
expect(nodes[1].getName()).to.equal('node2'); // Name auto-constructed from category
|
||||
expect(nodes[2].getName()).to.equal('anotherNode'); // Name set explicitly at creation time
|
||||
|
||||
// Cleanup created wrappers
|
||||
nodes.forEach(n => n.delete());
|
||||
backdrop.delete();
|
||||
node3.delete();
|
||||
node2.delete();
|
||||
node1.delete();
|
||||
nodeGraphB.delete();
|
||||
nodeGraph.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('C++ exception handling', () =>
|
||||
{
|
||||
// Exceptions that are thrown and caught in C++ shouldn't bubble up to JS
|
||||
const doc = mx.createDocument();
|
||||
const nodeGraph1 = doc.addNodeGraph();
|
||||
const nodeGraph2 = doc.addNodeGraph();
|
||||
nodeGraph1.setInheritsFrom(nodeGraph2);
|
||||
nodeGraph2.setInheritsFrom(nodeGraph1);
|
||||
expect(nodeGraph1.hasInheritanceCycle()).to.not.throw;
|
||||
expect(nodeGraph1.hasInheritanceCycle()).to.be.true;
|
||||
|
||||
// Exceptions that are not caught in C++ should throw
|
||||
nodeGraph1.addNode('node', 'node1');
|
||||
expect(() => { nodeGraph1.addNode('node', 'node1'); }).to.throw;
|
||||
try
|
||||
{
|
||||
nodeGraph1.addNode('node', 'node1');
|
||||
} catch (err)
|
||||
{
|
||||
expect(mx.getExceptionMessage(err)).to.be.a('string');
|
||||
}
|
||||
// Cleanup
|
||||
nodeGraph2.delete();
|
||||
nodeGraph1.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('getReferencedSourceUris', async () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
const filename = 'standard_surface_look_brass_tiled.mtlx';
|
||||
await mx.readFromXmlFile(doc, filename, examplesPath);
|
||||
const sourceUris = doc.getReferencedSourceUris();
|
||||
expect(sourceUris).to.be.instanceof(Array);
|
||||
expect(sourceUris.length).to.equal(3);
|
||||
expect(sourceUris[0]).to.be.a('string');
|
||||
expect(sourceUris.includes('standard_surface_brass_tiled.mtlx')).to.be.true;
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Should invoke correct instance of \'validate\'', () =>
|
||||
{
|
||||
// We check whether the correct function is called by provoking an error message that is specific to the
|
||||
// function that we expect to be called.
|
||||
const message = {};
|
||||
|
||||
// Should invoke Document::validate.
|
||||
const doc = mx.createDocument();
|
||||
expect(doc.validate()).to.be.true;
|
||||
doc.removeAttribute(mx.InterfaceElement.VERSION_ATTRIBUTE);
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Should invoke Node::validate
|
||||
const node = doc.addNode('node');
|
||||
expect(node.validate()).to.be.true;
|
||||
node.setCategory('');
|
||||
expect(node.validate()).to.be.false;
|
||||
expect(node.validate(message)).to.be.false;
|
||||
expect(message.message).to.include('Node element is missing a category');
|
||||
|
||||
// Should invoke inherited ValueElement::validate
|
||||
const token = new mx.Token(node, 'token');
|
||||
expect(token.validate()).to.be.true;
|
||||
token.setUnitType('bogus');
|
||||
expect(token.validate()).to.be.false;
|
||||
expect(token.validate(message)).to.be.false;
|
||||
expect(message.message).to.include('Unit type definition does not exist in document')
|
||||
|
||||
// Cleanup
|
||||
token.delete();
|
||||
node.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('StringResolver name substitution getters', () =>
|
||||
{
|
||||
const fnTestData = {
|
||||
fnKey: 'fnValue',
|
||||
fnKey1: 'fnValue1'
|
||||
};
|
||||
const fnTestKeys = Object.keys(fnTestData);
|
||||
|
||||
const gnTestData = {
|
||||
gnKey: 'gnValue',
|
||||
gnKey1: 'gnValue1'
|
||||
};
|
||||
const gnTestKeys = Object.keys(gnTestData);
|
||||
|
||||
const resolver = mx.StringResolver.create();
|
||||
|
||||
resolver.setFilenameSubstitution(fnTestKeys[0], fnTestData[fnTestKeys[0]]);
|
||||
resolver.setFilenameSubstitution(fnTestKeys[1], fnTestData[fnTestKeys[1]]);
|
||||
const fnSubs = resolver.getFilenameSubstitutions();
|
||||
expect(fnSubs).to.be.instanceof(Object);
|
||||
expect(Object.keys(fnSubs).length).to.equal(2);
|
||||
expect(fnSubs).to.deep.equal(fnTestData);
|
||||
|
||||
resolver.setGeomNameSubstitution(gnTestKeys[0], gnTestData[gnTestKeys[0]]);
|
||||
resolver.setGeomNameSubstitution(gnTestKeys[1], gnTestData[gnTestKeys[1]]);
|
||||
const gnSubs = resolver.getGeomNameSubstitutions();
|
||||
expect(gnSubs).to.be.instanceof(Object);
|
||||
expect(Object.keys(gnSubs).length).to.equal(2);
|
||||
expect(gnSubs).to.deep.equal(gnTestData);
|
||||
resolver.delete();
|
||||
});
|
||||
|
||||
it('getShaderNodes', async () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
const fileNames = ['standard_surface_marble_solid.mtlx'];
|
||||
const mtlxStrs = getMtlxStrings(fileNames, examplesPath);
|
||||
await mx.readFromXmlString(doc, mtlxStrs[0]);
|
||||
let matNodes = doc.getMaterialNodes();
|
||||
expect(matNodes.length).to.equal(1);
|
||||
const matNode = matNodes[0];
|
||||
|
||||
// Should return a surface shader node but no displacement shader node
|
||||
let shaderNodes = mx.getShaderNodes(matNode);
|
||||
expect(shaderNodes).to.be.instanceof(Array);
|
||||
expect(shaderNodes.length).to.equal(1);
|
||||
expect(shaderNodes[0].getType()).to.equal(mx.SURFACE_SHADER_TYPE_STRING);
|
||||
shaderNodes = mx.getShaderNodes(matNode, mx.DISPLACEMENT_SHADER_TYPE_STRING);
|
||||
expect(shaderNodes).to.be.instanceof(Array);
|
||||
expect(shaderNodes.length).to.equal(0);
|
||||
|
||||
// Cleanup wrappers
|
||||
shaderNodes.forEach(s => s.delete());
|
||||
matNodes.forEach(n => n.delete());
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('createValidName', () =>
|
||||
{
|
||||
const testString = '_Note_:Please,turn.this+-into*1#valid\nname for_me';
|
||||
const replaceRegex = /[^a-zA-Z0-9_:]/g
|
||||
expect(mx.createValidName(testString)).to.equal(testString.replace(replaceRegex, '_'));
|
||||
expect(mx.createValidName(testString, '-')).to.equal(testString.replace(replaceRegex, '-'));
|
||||
});
|
||||
|
||||
it('getVersionIntegers', () =>
|
||||
{
|
||||
const versionStringArr = mx.getVersionString().split('.').map((value) => parseInt(value, 10));
|
||||
|
||||
// Global getVersionIntegers
|
||||
const globalVersion = mx.getVersionIntegers();
|
||||
expect(globalVersion).to.be.instanceof(Array);
|
||||
expect(globalVersion.length).to.equal(3);
|
||||
expect(globalVersion).to.deep.equal(versionStringArr);
|
||||
|
||||
// Document.getVersionIntegers
|
||||
versionStringArr.pop();
|
||||
const doc = mx.createDocument();
|
||||
const docVersion = doc.getVersionIntegers();
|
||||
expect(docVersion).to.be.instanceof(Array);
|
||||
expect(docVersion.length).to.equal(2);
|
||||
expect(docVersion).to.deep.equal(versionStringArr);
|
||||
doc.delete();
|
||||
|
||||
// InterfaceElement.getVersionIntegers (via NodeDef)
|
||||
// TODO: This function can currently not be called, since we have a linker issue that messes up this function.
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:include href="cycle.mtlx" />
|
||||
</materialx>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:include href="../folder2/include3.mtlx" />
|
||||
</materialx>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<simple_srf name="sr_ps" type="surfaceshader">
|
||||
<input name="specColor" type="color3" value="0.05, 0.05, 0.05" />
|
||||
<input name="specRoughness" type="float" value="0.14" />
|
||||
</simple_srf>
|
||||
<surfacematerial name="paint_semigloss" type="material">
|
||||
<input name="surfaceshader" type="surfaceshader" nodename="sr_ps" />
|
||||
</surfacematerial>
|
||||
</materialx>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:include href="folder2/include3.mtlx" />
|
||||
</materialx>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:include href="include2.mtlx" />
|
||||
</materialx>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<materialx version="1.38" colorspace="lin_rec709" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<xi:include href="include1.mtlx" />
|
||||
<xi:include href="folder/include2.mtlx" />
|
||||
</materialx>
|
||||
@@ -0,0 +1,188 @@
|
||||
import { expect } from 'chai';
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Document', () =>
|
||||
{
|
||||
let mx, doc;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
// Create a document.
|
||||
doc = mx.createDocument();
|
||||
});
|
||||
|
||||
function expectError(type, cb)
|
||||
{
|
||||
try
|
||||
{
|
||||
cb();
|
||||
throw new Error('Expected function to throw!');
|
||||
} catch (exceptionPtr)
|
||||
{
|
||||
const message = mx.getExceptionMessage(exceptionPtr);
|
||||
expect(message.indexOf(type) !== -1).to.be.true;
|
||||
}
|
||||
}
|
||||
|
||||
let nodeGraph;
|
||||
it('Build document', () =>
|
||||
{
|
||||
// Create a node graph with constant and image sources.
|
||||
nodeGraph = doc.addNodeGraph();
|
||||
expect(nodeGraph).to.exist;
|
||||
expectError('Child name is not unique: nodegraph1', () =>
|
||||
{
|
||||
doc.addNodeGraph(nodeGraph.getName());
|
||||
});
|
||||
const constant = nodeGraph.addNode('constant');
|
||||
const image = nodeGraph.addNode('image');
|
||||
|
||||
// Connect sources to outputs
|
||||
const output1 = nodeGraph.addOutput();
|
||||
const output2 = nodeGraph.addOutput();
|
||||
output1.setConnectedNode(constant);
|
||||
output2.setConnectedNode(image);
|
||||
expect(output1.getConnectedNode()).to.eql(constant);
|
||||
expect(output2.getConnectedNode()).to.eql(image);
|
||||
expect(output1.getUpstreamElement()).to.eql(constant);
|
||||
expect(output2.getUpstreamElement()).to.eql(image);
|
||||
|
||||
// Set constant node color
|
||||
const color = new mx.Color3(0.1, 0.2, 0.3);
|
||||
constant.setInputValueColor3('value', color);
|
||||
expect(constant.getInputValue('value').getData()).to.eql(color);
|
||||
|
||||
// Set image node file
|
||||
const file = 'image1.tif';
|
||||
image.setInputValueString('file', file, 'filename');
|
||||
expect(image.getInputValue('file').getData()).to.eql(file);
|
||||
|
||||
// Create a custom nodedef
|
||||
const nodeDef = doc.addNodeDef('nodeDef1', 'float', 'turbulence3d');
|
||||
nodeDef.setInputValueInteger('octaves', 3);
|
||||
nodeDef.setInputValueFloat('lacunarity', 2.0);
|
||||
nodeDef.setInputValueFloat('gain', 0.5);
|
||||
|
||||
// Reference the custom nodedef
|
||||
const custom = nodeGraph.addNode('turbulence3d', 'turbulence1', 'float');
|
||||
expect(custom.getInputValue('octaves').getData()).to.equal(3);
|
||||
custom.setInputValueInteger('octaves', 5);
|
||||
expect(custom.getInputValue('octaves').getData()).to.equal(5);
|
||||
|
||||
// Test scoped attributes
|
||||
nodeGraph.setFilePrefix('folder/');
|
||||
nodeGraph.setColorSpace('lin_rec709');
|
||||
expect(image.getInput('file').getResolvedValueString()).to.equal('folder/image1.tif');
|
||||
expect(constant.getActiveColorSpace()).to.equal('lin_rec709');
|
||||
|
||||
// Create a simple shader interface
|
||||
const simpleSrf = doc.addNodeDef('', 'surfaceshader', 'simpleSrf');
|
||||
simpleSrf.setInputValueColor3('diffColor', new mx.Color3(1.0, 1.0, 1.0));
|
||||
simpleSrf.setInputValueColor3('specColor', new mx.Color3(0.0, 0.0, 0.0));
|
||||
const roughness = simpleSrf.setInputValueFloat('roughness', 0.25);
|
||||
expect(roughness.getIsUniform()).to.be.false;
|
||||
roughness.setIsUniform(true);
|
||||
expect(roughness.getIsUniform()).to.be.true;
|
||||
|
||||
// Instantiate shader and material nodes
|
||||
const shaderNode = doc.addNodeInstance(simpleSrf);
|
||||
const materialNode = doc.addMaterialNode('', shaderNode);
|
||||
expect(materialNode.getUpstreamElement().equals(shaderNode)).to.be.true;
|
||||
|
||||
// Bind the diffuse color input to the constant color output
|
||||
shaderNode.setConnectedOutput('diffColor', output1);
|
||||
expect(shaderNode.getUpstreamElement().equals(constant)).to.be.true;
|
||||
|
||||
// Bind the roughness input to a value
|
||||
const instanceRoughness = shaderNode.setInputValueFloat('roughness', 0.5);
|
||||
expect(instanceRoughness.getValue().getData()).to.equal(0.5);
|
||||
expect(instanceRoughness.getDefaultValue().getData()).to.equal(0.25);
|
||||
|
||||
// Create a look for the material
|
||||
const look = doc.addLook();
|
||||
expect(doc.getLooks().length).to.equal(1);
|
||||
|
||||
// Bind the material to a geometry string
|
||||
let matAssign1 = look.addMaterialAssign('matAssign1', materialNode.getName());
|
||||
matAssign1 = look.getMaterialAssign('matAssign1');
|
||||
expect(matAssign1);
|
||||
matAssign1.setGeom('/robot1');
|
||||
expect(matAssign1.getReferencedMaterial().equals(materialNode)).to.be.true;
|
||||
expect(mx.getGeometryBindings(materialNode, '/robot1').length).to.equal(1);
|
||||
expect(mx.getGeometryBindings(materialNode, '/robot2').length).to.equal(0);
|
||||
|
||||
// Bind the material to a collection
|
||||
let matAssign2 = look.addMaterialAssign('matAssign2', materialNode.getName());
|
||||
matAssign2 = look.getMaterialAssign('matAssign1');
|
||||
expect(matAssign2);
|
||||
const collection = doc.addCollection();
|
||||
collection.setIncludeGeom('/robot2');
|
||||
collection.setExcludeGeom('/robot2/left_arm');
|
||||
matAssign2.setCollection(collection);
|
||||
expect(matAssign2.getReferencedMaterial().equals(materialNode)).to.be.true;
|
||||
expect(mx.getGeometryBindings(materialNode, '/robot2').length).to.equal(1);
|
||||
expect(mx.getGeometryBindings(materialNode, '/robot2/right_arm').length).to.equal(1);
|
||||
expect(mx.getGeometryBindings(materialNode, '/robot2/left_arm').length).to.equal(0);
|
||||
|
||||
const materialAssigns = look.getMaterialAssigns();
|
||||
expect(materialAssigns.length).to.equal(2);
|
||||
|
||||
// Create a property assignment
|
||||
const propertyAssign = look.addPropertyAssign();
|
||||
propertyAssign.setProperty('twosided');
|
||||
propertyAssign.setGeom('/robot1');
|
||||
propertyAssign.setValueBoolean(true);
|
||||
expect(propertyAssign.getProperty()).to.equal('twosided');
|
||||
expect(propertyAssign.getGeom()).to.equal('/robot1');
|
||||
expect(propertyAssign.getValue().getData()).to.equal(true);
|
||||
let propertyAssigns = look.getPropertyAssigns();
|
||||
expect(propertyAssigns.length).to.equal(1);
|
||||
|
||||
// Create a property set assignment
|
||||
const propertySet = doc.addPropertySet();
|
||||
propertySet.setPropertyValueBoolean('matte', false);
|
||||
expect(propertySet.getPropertyValue('matte').getData()).to.equal(false);
|
||||
const propertySetAssign = look.addPropertySetAssign();
|
||||
propertySetAssign.setPropertySet(propertySet);
|
||||
propertySetAssign.setGeom('/robot1');
|
||||
expect(propertySetAssign.getPropertySet().equals(propertySet)).to.be.true;
|
||||
expect(propertySetAssign.getGeom()).to.equal('/robot1');
|
||||
|
||||
// Create a variant set
|
||||
const variantSet = doc.addVariantSet();
|
||||
variantSet.addVariant('original');
|
||||
variantSet.addVariant('damaged');
|
||||
expect(variantSet.getVariants().length).to.equal(2);
|
||||
|
||||
// Validate the document
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Disconnect output from sources
|
||||
output1.setConnectedNode(null);
|
||||
output2.setConnectedNode(null);
|
||||
expect(output1.getConnectedNode()).to.equal(null);
|
||||
expect(output2.getConnectedNode()).to.equal(null);
|
||||
// Cleanup created wrappers
|
||||
propertySetAssign.delete();
|
||||
propertySet.delete();
|
||||
propertyAssign.delete();
|
||||
variantSet.delete();
|
||||
collection.delete();
|
||||
matAssign2.delete();
|
||||
matAssign1.delete();
|
||||
look.delete();
|
||||
instanceRoughness.delete();
|
||||
shaderNode.delete();
|
||||
materialNode.delete();
|
||||
simpleSrf.delete();
|
||||
output2.delete();
|
||||
output1.delete();
|
||||
custom.delete();
|
||||
color.delete();
|
||||
image.delete();
|
||||
constant.delete();
|
||||
nodeDef.delete();
|
||||
nodeGraph.delete();
|
||||
doc.delete();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
import { expect } from 'chai';
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Element', () =>
|
||||
{
|
||||
let mx, doc, valueTypes;
|
||||
|
||||
const primitiveValueTypes = {
|
||||
Integer: 10,
|
||||
Boolean: true,
|
||||
String: 'test',
|
||||
Float: 15,
|
||||
IntegerArray: [1, 2, 3, 4, 5],
|
||||
FloatArray: [12, 14], // Not using actual floats to avoid precision problems
|
||||
StringArray: ['first', 'second'],
|
||||
BooleanArray: [true, true, false],
|
||||
}
|
||||
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
doc = mx.createDocument();
|
||||
valueTypes = {
|
||||
Color3: new mx.Color3(1, 0, 0.5),
|
||||
Color4: new mx.Color4(0, 1, 0.5, 1),
|
||||
Vector2: new mx.Vector2(0, 1),
|
||||
Vector3: new mx.Vector3(0, 1, 2),
|
||||
Vector4: new mx.Vector4(0, 1, 2, 1),
|
||||
Matrix33: new mx.Matrix33(0, 1, 2, 3, 4, 5, 6, 7, 8),
|
||||
Matrix44: new mx.Matrix44(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
|
||||
};
|
||||
});
|
||||
|
||||
after(() =>
|
||||
{
|
||||
// Cleanup typed helper objects and document
|
||||
Object.values(valueTypes).forEach(v => v.delete());
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
describe('value setters', () =>
|
||||
{
|
||||
const checkValue = (types, assertionCallback) =>
|
||||
{
|
||||
const elem = doc.addChildOfCategory('geomprop');
|
||||
Object.keys(types).forEach((typeName) =>
|
||||
{
|
||||
const setFn = `setValue${typeName}`;
|
||||
elem[setFn](types[typeName]);
|
||||
assertionCallback(elem.getValue().getData(), typeName);
|
||||
});
|
||||
elem.delete();
|
||||
};
|
||||
|
||||
it('should work with expected type', () =>
|
||||
{
|
||||
checkValue(valueTypes, (returnedValue, typeName) =>
|
||||
{
|
||||
expect(returnedValue).to.be.an.instanceof(mx[`${typeName}`]);
|
||||
expect(returnedValue.equals(valueTypes[typeName])).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with expected primitive type', () =>
|
||||
{
|
||||
checkValue(primitiveValueTypes, (returnedValue, typeName) =>
|
||||
{
|
||||
expect(returnedValue).to.eql(primitiveValueTypes[typeName]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail for incorrect type', () =>
|
||||
{
|
||||
const elem = doc.addChildOfCategory('geomprop');
|
||||
expect(() => elem.Matrix33(true)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('typed value setters', () =>
|
||||
{
|
||||
const checkTypes = (types, assertionCallback) =>
|
||||
{
|
||||
const elem = doc.addChildOfCategory('geomprop');
|
||||
Object.keys(types).forEach((typeName) =>
|
||||
{
|
||||
const setFn = `setTypedAttribute${typeName}`;
|
||||
const getFn = `getTypedAttribute${typeName}`;
|
||||
elem[setFn](typeName, types[typeName]);
|
||||
assertionCallback(elem[getFn](typeName), types[typeName]);
|
||||
});
|
||||
elem.delete();
|
||||
};
|
||||
|
||||
it('should work with expected custom type', () =>
|
||||
{
|
||||
checkTypes(valueTypes, (returnedValue, originalValue) =>
|
||||
{
|
||||
expect(returnedValue.equals(originalValue)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with expected primitive type', () =>
|
||||
{
|
||||
checkTypes(primitiveValueTypes, (returnedValue, originalValue) =>
|
||||
{
|
||||
expect(returnedValue).to.eql(originalValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail for incorrect type', () =>
|
||||
{
|
||||
const elem = doc.addChildOfCategory('geomprop');
|
||||
expect(() => elem.setTypedAttributeColor3('wrongType', true)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
it('factory invocation should match specialized functions', () =>
|
||||
{
|
||||
// List based in source/MaterialXCore/Element.cpp
|
||||
const elemtypeArr = [
|
||||
'Backdrop',
|
||||
'Collection',
|
||||
'GeomInfo',
|
||||
'MaterialAssign',
|
||||
'PropertySetAssign',
|
||||
'Visibility',
|
||||
'GeomPropDef',
|
||||
'Look',
|
||||
'LookGroup',
|
||||
'PropertySet',
|
||||
'TypeDef',
|
||||
'AttributeDef',
|
||||
'NodeGraph',
|
||||
'Implementation',
|
||||
'Node',
|
||||
'NodeDef',
|
||||
'Variant',
|
||||
'Member',
|
||||
'TargetDef',
|
||||
'GeomProp',
|
||||
'Input',
|
||||
'Output',
|
||||
'Property',
|
||||
'PropertyAssign',
|
||||
'Unit',
|
||||
'UnitDef',
|
||||
'UnitTypeDef',
|
||||
'VariantAssign',
|
||||
'VariantSet',
|
||||
];
|
||||
|
||||
elemtypeArr.forEach((typeName) =>
|
||||
{
|
||||
const specializedFn = `addChild${typeName}`;
|
||||
const factoryName = typeName.toLowerCase();
|
||||
const type = mx[typeName];
|
||||
expect(doc[specializedFn]()).to.be.an.instanceof(type);
|
||||
expect(doc.addChildOfCategory(factoryName)).to.be.an.instanceof(type);
|
||||
});
|
||||
|
||||
const specialElemType = {
|
||||
'MaterialX': mx.Document,
|
||||
'Comment': mx.CommentElement,
|
||||
'Generic': mx.GenericElement,
|
||||
};
|
||||
|
||||
Object.keys(specialElemType).forEach((typeName) =>
|
||||
{
|
||||
const specializedFn = `addChild${typeName}`;
|
||||
const factoryName = typeName.toLowerCase();
|
||||
expect(doc[specializedFn]()).to.be.an.instanceof(specialElemType[typeName]);
|
||||
expect(doc.addChildOfCategory(factoryName)).to.be.an.instanceof(specialElemType[typeName]);
|
||||
});
|
||||
// No doc.delete() here; cleaned up in after()
|
||||
});
|
||||
});
|
||||
|
||||
describe('Equivalence', () =>
|
||||
{
|
||||
let mx, doc, doc2
|
||||
|
||||
before(async () => {
|
||||
mx = await Module();
|
||||
doc = mx.createDocument();
|
||||
doc.addNodeGraph("graph");
|
||||
doc2 = mx.createDocument();
|
||||
doc2.addNodeGraph("graph1");
|
||||
});
|
||||
|
||||
it('Compare document equivalency', () =>
|
||||
{
|
||||
let options = new mx.ElementEquivalenceOptions();
|
||||
let differences = {};
|
||||
options.performValueComparisons = false;
|
||||
let result = doc.isEquivalent(doc2, options, differences);
|
||||
expect(result).to.be.false;
|
||||
expect(differences.message).to.not.be.empty;
|
||||
result = doc.isEquivalent(doc2, options, undefined);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { expect } from 'chai';;
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Environ', () =>
|
||||
{
|
||||
let mx;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Environment variables', () =>
|
||||
{
|
||||
expect(mx.getEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR)).to.equal('');
|
||||
mx.setEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR, 'test');
|
||||
expect(mx.getEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR)).to.equal('test');
|
||||
mx.removeEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR);
|
||||
expect(mx.getEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR)).to.equal('');
|
||||
});
|
||||
});
|
||||
+5670
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "MaterialXTest",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./_build/",
|
||||
"copyBuild": "copyfiles -f ../build/bin/JsMaterialX* _build",
|
||||
"pretest": "npm run clean && npm run copyBuild",
|
||||
"test": "npm run mocha",
|
||||
"test:browser": "npm run karma -- --browsers ChromeHeadlessGL --singleRun true",
|
||||
"mocha": "mocha *.spec.js --require @babel/register --timeout 5000",
|
||||
"karma": "karma start browser/karma.conf.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.5",
|
||||
"@babel/preset-env": "^7.28.5",
|
||||
"@babel/register": "^7.28.3",
|
||||
"chai": "^4.5.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"karma": "^6.4.4",
|
||||
"karma-chai": "^0.1.0",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"mocha": "^11.7.5",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
export function getMtlxStrings(fileNames, subPath)
|
||||
{
|
||||
const mtlxStrs = [];
|
||||
for (let i = 0; i < fileNames.length; i++)
|
||||
{
|
||||
const p = path.resolve(subPath, fileNames[parseInt(i, 10)]);
|
||||
const t = fs.readFileSync(p, 'utf8');
|
||||
mtlxStrs.push(t);
|
||||
}
|
||||
return mtlxStrs;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
import { expect } from 'chai';
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Traversal', () =>
|
||||
{
|
||||
let mx;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Traverse Graph', () =>
|
||||
{
|
||||
// Create a document.
|
||||
const doc = mx.createDocument();
|
||||
// Create a node graph with the following structure:
|
||||
//
|
||||
// [image1] [constant] [image2]
|
||||
// \ / |
|
||||
// [multiply] [contrast] [noise3d]
|
||||
// \____________ | ____________/
|
||||
// [mix]
|
||||
// |
|
||||
// [output]
|
||||
//
|
||||
const nodeGraph = doc.addNodeGraph();
|
||||
const image1 = nodeGraph.addNode('image');
|
||||
const image2 = nodeGraph.addNode('image');
|
||||
const constant = nodeGraph.addNode('constant');
|
||||
const multiply = nodeGraph.addNode('multiply');
|
||||
const contrast = nodeGraph.addNode('contrast');
|
||||
const noise3d = nodeGraph.addNode('noise3d');
|
||||
const mix = nodeGraph.addNode('mix');
|
||||
const output = nodeGraph.addOutput();
|
||||
multiply.setConnectedNode('in1', image1);
|
||||
multiply.setConnectedNode('in2', constant);
|
||||
contrast.setConnectedNode('in', image2);
|
||||
mix.setConnectedNode('fg', multiply);
|
||||
mix.setConnectedNode('bg', contrast);
|
||||
mix.setConnectedNode('mask', noise3d);
|
||||
output.setConnectedNode(mix);
|
||||
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Traverse the document tree (implicit iterator).
|
||||
let nodeCount = 0;
|
||||
for (let elem of doc.traverseTree())
|
||||
{
|
||||
if (elem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
}
|
||||
elem.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(7);
|
||||
|
||||
// Traverse the document tree (explicit iterator)
|
||||
let treeIter = doc.traverseTree();
|
||||
nodeCount = 0;
|
||||
let maxElementDepth = 0;
|
||||
for (let elem of treeIter)
|
||||
{
|
||||
if (elem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
}
|
||||
maxElementDepth = Math.max(maxElementDepth, treeIter.getElementDepth());
|
||||
elem.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(7);
|
||||
expect(maxElementDepth).to.equal(3);
|
||||
|
||||
// Traverse the document tree (prune subtree).
|
||||
nodeCount = 0;
|
||||
treeIter = doc.traverseTree();
|
||||
for (let elem of treeIter)
|
||||
{
|
||||
if (elem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
}
|
||||
if (elem instanceof mx.NodeGraph)
|
||||
{
|
||||
treeIter.setPruneSubtree(true);
|
||||
}
|
||||
elem.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(0);
|
||||
|
||||
// Traverse upstream from the graph output (implicit iterator)
|
||||
nodeCount = 0;
|
||||
for (let edge of output.traverseGraph())
|
||||
{
|
||||
const upstreamElem = edge.getUpstreamElement();
|
||||
const connectingElem = edge.getConnectingElement();
|
||||
const downstreamElem = edge.getDownstreamElement();
|
||||
if (upstreamElem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
if (downstreamElem instanceof mx.Node)
|
||||
{
|
||||
expect(connectingElem instanceof mx.Input).to.be.true;
|
||||
}
|
||||
}
|
||||
if (upstreamElem) upstreamElem.delete();
|
||||
if (connectingElem) connectingElem.delete();
|
||||
if (downstreamElem) downstreamElem.delete();
|
||||
if (edge) edge.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(7);
|
||||
|
||||
// Traverse upstream from the graph output (explicit iterator)
|
||||
nodeCount = 0;
|
||||
maxElementDepth = 0;
|
||||
let maxNodeDepth = 0;
|
||||
let graphIter = output.traverseGraph();
|
||||
for (let edge of graphIter)
|
||||
{
|
||||
const upstreamElem = edge.getUpstreamElement();
|
||||
if (upstreamElem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
}
|
||||
maxElementDepth = Math.max(maxElementDepth, graphIter.getElementDepth());
|
||||
maxNodeDepth = Math.max(maxNodeDepth, graphIter.getNodeDepth());
|
||||
if (upstreamElem) upstreamElem.delete();
|
||||
if (edge) edge.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(7);
|
||||
expect(maxElementDepth).to.equal(3);
|
||||
expect(maxNodeDepth).to.equal(3);
|
||||
|
||||
// Traverse upstream from the graph output (prune subgraph)
|
||||
nodeCount = 0;
|
||||
graphIter = output.traverseGraph();
|
||||
for (let edge of graphIter)
|
||||
{
|
||||
const upstreamElem = edge.getUpstreamElement();
|
||||
expect(upstreamElem.getSelf()).to.be.an.instanceof(mx.Element);
|
||||
if (upstreamElem instanceof mx.Node)
|
||||
{
|
||||
nodeCount++;
|
||||
}
|
||||
if (upstreamElem.getCategory() === 'multiply')
|
||||
{
|
||||
graphIter.setPruneSubgraph(true);
|
||||
}
|
||||
if (upstreamElem) upstreamElem.delete();
|
||||
if (edge) edge.delete();
|
||||
}
|
||||
expect(nodeCount).to.equal(5);
|
||||
|
||||
// Create and detect a cycle
|
||||
multiply.setConnectedNode('in2', mix);
|
||||
expect(output.hasUpstreamCycle()).to.be.true;
|
||||
expect(doc.validate()).to.be.false;
|
||||
multiply.setConnectedNode('in2', constant);
|
||||
expect(output.hasUpstreamCycle()).to.be.false;
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Create and detect a loop
|
||||
contrast.setConnectedNode('in', contrast);
|
||||
expect(output.hasUpstreamCycle()).to.be.true;
|
||||
expect(doc.validate()).to.be.false;
|
||||
contrast.setConnectedNode('in', image2);
|
||||
expect(output.hasUpstreamCycle()).to.be.false;
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Cleanup wrappers
|
||||
output.delete();
|
||||
mix.delete();
|
||||
noise3d.delete();
|
||||
contrast.delete();
|
||||
multiply.delete();
|
||||
constant.delete();
|
||||
image2.delete();
|
||||
image1.delete();
|
||||
nodeGraph.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
describe("Traverse inheritance", () =>
|
||||
{
|
||||
let nodeDefInheritanceLevel2, nodeDefInheritanceLevel1, nodeDefParent;
|
||||
let doc;
|
||||
beforeEach(() =>
|
||||
{
|
||||
doc = mx.createDocument();
|
||||
nodeDefParent = doc.addNodeDef();
|
||||
nodeDefParent.setName('BaseClass');
|
||||
nodeDefInheritanceLevel1 = doc.addNodeDef();
|
||||
nodeDefInheritanceLevel1.setName('InheritanceLevel1');
|
||||
nodeDefInheritanceLevel2 = doc.addNodeDef();
|
||||
nodeDefInheritanceLevel2.setName('InheritanceLevel2');
|
||||
nodeDefInheritanceLevel2.setInheritsFrom(nodeDefInheritanceLevel1);
|
||||
nodeDefInheritanceLevel1.setInheritsFrom(nodeDefParent);
|
||||
});
|
||||
afterEach(() =>
|
||||
{
|
||||
nodeDefInheritanceLevel2.delete();
|
||||
nodeDefInheritanceLevel1.delete();
|
||||
nodeDefParent.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('for of loop', () =>
|
||||
{
|
||||
const inheritanceIterator = nodeDefInheritanceLevel2.traverseInheritance();
|
||||
let inheritanceChainLength = 0;
|
||||
for (const elem of inheritanceIterator)
|
||||
{
|
||||
if (elem instanceof mx.NodeDef)
|
||||
{
|
||||
inheritanceChainLength++;
|
||||
}
|
||||
}
|
||||
expect(inheritanceChainLength).to.equal(2);;
|
||||
});
|
||||
|
||||
it('while loop', () =>
|
||||
{
|
||||
const inheritanceIterator = nodeDefInheritanceLevel2.traverseInheritance();
|
||||
let inheritanceChainLength = 0;
|
||||
let elem = inheritanceIterator.next();
|
||||
while (!elem.done)
|
||||
{
|
||||
if (elem.value instanceof mx.NodeDef)
|
||||
{
|
||||
inheritanceChainLength++;
|
||||
}
|
||||
elem = inheritanceIterator.next();
|
||||
}
|
||||
expect(inheritanceChainLength).to.equal(2);;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
import { expect } from 'chai';;
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Types', () =>
|
||||
{
|
||||
let mx;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Vectors', () =>
|
||||
{
|
||||
const v1 = new mx.Vector3(1, 2, 3);
|
||||
let v2 = new mx.Vector3(2, 4, 6);
|
||||
|
||||
// Indexing operators
|
||||
expect(v1.getItem(2)).to.equal(3);
|
||||
|
||||
v1.setItem(2, 4);
|
||||
expect(v1.getItem(2)).to.equal(4);
|
||||
v1.setItem(2, 3);
|
||||
// Component-wise operators
|
||||
let res = v2.add(v1);
|
||||
expect(res.equals(new mx.Vector3(3, 6, 9))).to.be.true;
|
||||
|
||||
res = v2.sub(v1);
|
||||
expect(res.equals(new mx.Vector3(1, 2, 3))).to.be.true;
|
||||
|
||||
res = v2.multiply(v1);
|
||||
expect(res.equals(new mx.Vector3(2, 8, 18))).to.be.true;
|
||||
|
||||
res = v2.divide(v1);
|
||||
expect(res.equals(new mx.Vector3(2, 2, 2))).to.be.true;
|
||||
|
||||
v2 = v2.add(v1);
|
||||
expect(v2.equals(new mx.Vector3(3, 6, 9))).to.be.true;
|
||||
|
||||
v2 = v2.sub(v1);
|
||||
expect(v2.equals(new mx.Vector3(2, 4, 6))).to.be.true;
|
||||
|
||||
v2 = v2.multiply(v1);
|
||||
expect(v2.equals(new mx.Vector3(2, 8, 18))).to.be.true;
|
||||
|
||||
v2 = v2.divide(v1);
|
||||
expect(v2.equals(new mx.Vector3(2, 4, 6))).to.be.true;
|
||||
|
||||
expect(v1.multiply(new mx.Vector3(2, 2, 2)).equals(v2)).to.be.true;
|
||||
expect(v2.divide(new mx.Vector3(2, 2, 2)).equals(v1)).to.be.true;
|
||||
|
||||
// Geometric methods
|
||||
let v3 = new mx.Vector4(4, 4, 4, 4);
|
||||
expect(v3.getMagnitude()).to.equal(8);
|
||||
expect(v3.getNormalized().getMagnitude()).to.equal(1);
|
||||
expect(v1.dot(v2)).to.equal(28);
|
||||
expect(v1.cross(v2).equals(new mx.Vector3())).to.be.true;
|
||||
|
||||
// Vector copy
|
||||
const v4 = v2.copy();
|
||||
expect(v4.equals(v2)).to.be.true;
|
||||
v4.setItem(0, v4.getItem(0) + 1);
|
||||
expect(v4.notEquals(v2)).to.be.true;
|
||||
});
|
||||
|
||||
function multiplyMatrix(matrix, val)
|
||||
{
|
||||
const clonedMatrix = matrix.copy();
|
||||
for (let i = 0; i < clonedMatrix.numRows(); ++i)
|
||||
{
|
||||
for (let k = 0; k < clonedMatrix.numColumns(); ++k)
|
||||
{
|
||||
const v = clonedMatrix.getItem(i, k);
|
||||
clonedMatrix.setItem(i, k, v * val);
|
||||
}
|
||||
}
|
||||
return clonedMatrix;
|
||||
}
|
||||
|
||||
function divideMatrix(matrix, val)
|
||||
{
|
||||
const clonedMatrix = matrix.copy();
|
||||
for (let i = 0; i < clonedMatrix.numRows(); ++i)
|
||||
{
|
||||
for (let k = 0; k < clonedMatrix.numColumns(); ++k)
|
||||
{
|
||||
const v = clonedMatrix.getItem(i, k);
|
||||
clonedMatrix.setItem(i, k, v / val);
|
||||
}
|
||||
}
|
||||
return clonedMatrix;
|
||||
}
|
||||
|
||||
it('Matrices', () =>
|
||||
{
|
||||
// Translation and scale
|
||||
const trans = mx.Matrix44.createTranslation(new mx.Vector3(1, 2, 3));
|
||||
const scale = mx.Matrix44.createScale(new mx.Vector3(2, 2, 2));
|
||||
expect(trans.equals(new mx.Matrix44(1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
1, 2, 3, 1)));
|
||||
expect(scale.equals(new mx.Matrix44(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, 2, 0,
|
||||
0, 0, 0, 1)));
|
||||
|
||||
// Indexing operators
|
||||
expect(trans.getItem(3, 2)).to.equal(3);
|
||||
trans.setItem(3, 2, 4);
|
||||
expect(trans.getItem(3, 2)).to.equal(4);
|
||||
trans.setItem(3, 2, 3);
|
||||
|
||||
// Matrix methods
|
||||
expect(trans.getTranspose().equals(
|
||||
new mx.Matrix44(1, 0, 0, 1,
|
||||
0, 1, 0, 2,
|
||||
0, 0, 1, 3,
|
||||
0, 0, 0, 1)
|
||||
)).to.be.true;
|
||||
expect(scale.getTranspose().equals(scale)).to.be.true;
|
||||
expect(trans.getDeterminant()).to.equal(1);
|
||||
expect(scale.getDeterminant()).to.equal(8);
|
||||
expect(trans.getInverse().equals(
|
||||
mx.Matrix44.createTranslation(new mx.Vector3(-1, -2, -3)))).to.be.true;
|
||||
|
||||
// Matrix product
|
||||
const prod1 = trans.multiply(scale);
|
||||
const prod2 = scale.multiply(trans);
|
||||
const prod3 = multiplyMatrix(trans, 2);
|
||||
let prod4 = trans;
|
||||
prod4 = prod4.multiply(scale);
|
||||
expect(prod1.equals(new mx.Matrix44(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, 2, 0,
|
||||
2, 4, 6, 1)));
|
||||
expect(prod2.equals(new mx.Matrix44(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, 2, 0,
|
||||
1, 2, 3, 1)));
|
||||
expect(prod3.equals(new mx.Matrix44(2, 0, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, 2, 0,
|
||||
2, 4, 6, 2)));
|
||||
expect(prod4.equals(prod1));
|
||||
|
||||
// Matrix division
|
||||
const quot1 = prod1.divide(scale);
|
||||
const quot2 = prod2.divide(trans);
|
||||
const quot3 = divideMatrix(prod3, 2);
|
||||
let quot4 = quot1;
|
||||
quot4 = quot4.divide(trans);
|
||||
expect(quot1.equals(trans)).to.be.true;
|
||||
expect(quot2.equals(scale)).to.be.true;
|
||||
expect(quot3.equals(trans)).to.be.true;
|
||||
|
||||
// 2D rotation
|
||||
const _epsilon = 1e-4;
|
||||
const rot1 = mx.Matrix33.createRotation(Math.PI / 2);
|
||||
const rot2 = mx.Matrix33.createRotation(Math.PI);
|
||||
expect(rot1.multiply(rot1).isEquivalent(rot2, _epsilon));
|
||||
expect(rot2.isEquivalent(mx.Matrix33.createScale(new mx.Vector2(-1, -1)), _epsilon));
|
||||
expect(rot2.multiply(rot2).isEquivalent(mx.Matrix33.IDENTITY, _epsilon));
|
||||
|
||||
// 3D rotation
|
||||
const rotX = mx.Matrix44.createRotationX(Math.PI);
|
||||
const rotY = mx.Matrix44.createRotationY(Math.PI);
|
||||
const rotZ = mx.Matrix44.createRotationZ(Math.PI);
|
||||
expect(rotX.multiply(rotY).isEquivalent(mx.Matrix44.createScale(new mx.Vector3(-1, -1, 1)), _epsilon));
|
||||
expect(rotX.multiply(rotZ).isEquivalent(mx.Matrix44.createScale(new mx.Vector3(-1, 1, -1)), _epsilon));
|
||||
expect(rotY.multiply(rotZ).isEquivalent(mx.Matrix44.createScale(new mx.Vector3(1, -1, -1)), _epsilon));
|
||||
|
||||
// Matrix copy
|
||||
const trans2 = trans.copy();
|
||||
expect(trans2.equals(trans)).to.be.true;
|
||||
trans2.setItem(0, 0, trans2.getItem(0, 0) + 1);
|
||||
expect(trans2.notEquals(trans)).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { expect } from 'chai';;
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
|
||||
describe('Value', () =>
|
||||
{
|
||||
let mx;
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Create values of different types', () =>
|
||||
{
|
||||
const testValues = {
|
||||
integer: '1',
|
||||
boolean: 'true',
|
||||
float: '1.1',
|
||||
color3: '0.1, 0.2, 0.3',
|
||||
color4: '0.1, 0.2, 0.3, 0.4',
|
||||
vector2: '1.1, 2.1',
|
||||
vector3: '1.1, 2.1, 3.1',
|
||||
vector4: '1.1, 2.1, 3.1, 4.1',
|
||||
matrix33: '0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1',
|
||||
matrix44: '1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1',
|
||||
string: 'value',
|
||||
integerarray: '1, 2, 3',
|
||||
booleanarray: 'false, true, false',
|
||||
floatarray: '1.1, 2.1, 3.1',
|
||||
stringarray: "'one', 'two', 'three'",
|
||||
};
|
||||
|
||||
for (let type in testValues)
|
||||
{
|
||||
const value = testValues[String(type)];
|
||||
const newValue = mx.Value.createValueFromStrings(value, type);
|
||||
const typeString = newValue.getTypeString();
|
||||
const valueString = newValue.getValueString();
|
||||
expect(typeString).to.equal(type);
|
||||
expect(valueString).to.equal(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
import Module from './_build/JsMaterialXCore.js';
|
||||
import { expect } from 'chai';
|
||||
import { getMtlxStrings } from './testHelpers';
|
||||
|
||||
const TIMEOUT = 60000;
|
||||
|
||||
describe('XmlIo', () =>
|
||||
{
|
||||
let mx;
|
||||
|
||||
// These should be relative to cwd
|
||||
const includeTestPath = 'data/includes';
|
||||
const libraryPath = '../../libraries/stdlib';
|
||||
const examplesPath = '../../resources/Materials/Examples';
|
||||
// TODO: Is there a better way to get these filenames than hardcoding them here?
|
||||
// The C++ tests load all files in the given directories. This would work in Node, but not in the browser.
|
||||
// Should we use a pre-test script that fetches the files and makes them available somehow?
|
||||
const libraryFilenames = ['stdlib_defs.mtlx', 'stdlib_ng.mtlx'];
|
||||
const exampleFilenames = [
|
||||
'StandardSurface/standard_surface_brass_tiled.mtlx',
|
||||
'StandardSurface/standard_surface_brick_procedural.mtlx',
|
||||
'StandardSurface/standard_surface_carpaint.mtlx',
|
||||
'StandardSurface/standard_surface_marble_solid.mtlx',
|
||||
'UsdPreviewSurface/usd_preview_surface_gold.mtlx',
|
||||
'UsdPreviewSurface/usd_preview_surface_plastic.mtlx',
|
||||
];
|
||||
|
||||
async function readStdLibrary(asString = false)
|
||||
{
|
||||
const libs = [];
|
||||
let iterable = libraryFilenames;
|
||||
if (asString)
|
||||
{
|
||||
const libraryMtlxStrings = getMtlxStrings(libraryFilenames, libraryPath);
|
||||
iterable = libraryMtlxStrings;
|
||||
}
|
||||
for (let file of iterable)
|
||||
{
|
||||
const lib = mx.createDocument();
|
||||
if (asString)
|
||||
{
|
||||
await mx.readFromXmlString(lib, file, libraryPath);
|
||||
} else
|
||||
{
|
||||
await mx.readFromXmlFile(lib, file, libraryPath);
|
||||
}
|
||||
libs.push(lib);
|
||||
};
|
||||
return libs;
|
||||
}
|
||||
|
||||
async function readAndValidateExamples(examples, libraries, readFunc, searchPath = undefined)
|
||||
{
|
||||
for (let file of examples)
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
await readFunc(doc, file, searchPath);
|
||||
// Import stdlib into the current document and validate it.
|
||||
for (let lib of libraries)
|
||||
{
|
||||
doc.importLibrary(lib);
|
||||
}
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
// Make sure the document does actually contain something.
|
||||
let valueElementCount = 0;
|
||||
const treeIter = doc.traverseTree();
|
||||
for (const elem of treeIter)
|
||||
{
|
||||
if (elem instanceof mx.ValueElement)
|
||||
{
|
||||
valueElementCount++;
|
||||
}
|
||||
// Release wrapper created by iterator
|
||||
elem.delete();
|
||||
}
|
||||
expect(valueElementCount).to.be.greaterThan(0);
|
||||
doc.delete();
|
||||
};
|
||||
}
|
||||
|
||||
before(async () =>
|
||||
{
|
||||
mx = await Module();
|
||||
});
|
||||
|
||||
it('Read XML from file', async () =>
|
||||
{
|
||||
// Read the standard library
|
||||
const libs = await readStdLibrary(false);
|
||||
|
||||
// Read and validate the example documents.
|
||||
await readAndValidateExamples(exampleFilenames, libs,
|
||||
async (document, file, sp) =>
|
||||
{
|
||||
await mx.readFromXmlFile(document, file, sp);
|
||||
}, examplesPath);
|
||||
|
||||
// Read the same document twice, and verify that duplicate elements
|
||||
// are skipped.
|
||||
const doc = mx.createDocument();
|
||||
const filename = 'StandardSurface/standard_surface_carpaint.mtlx';
|
||||
await mx.readFromXmlFile(doc, filename, examplesPath);
|
||||
const copy = doc.copy();
|
||||
await mx.readFromXmlFile(doc, filename, examplesPath);
|
||||
expect(doc.validate()).to.be.true;
|
||||
expect(copy.equals(doc)).to.be.true;
|
||||
copy.delete();
|
||||
doc.delete();
|
||||
libs.forEach(l => l.delete());
|
||||
}).timeout(TIMEOUT);
|
||||
|
||||
it('Read XML from string', async () =>
|
||||
{
|
||||
// Read the standard library
|
||||
const libs = await readStdLibrary(true);
|
||||
|
||||
// Read and validate each example document.
|
||||
const examplesStrings = getMtlxStrings(exampleFilenames, examplesPath);
|
||||
await readAndValidateExamples(examplesStrings, libs,
|
||||
async (document, file) =>
|
||||
{
|
||||
await mx.readFromXmlString(document, file);
|
||||
});
|
||||
|
||||
// Read the same document twice, and verify that duplicate elements
|
||||
// are skipped.
|
||||
const doc = mx.createDocument();
|
||||
const file = examplesStrings[exampleFilenames.indexOf('StandardSurface/standard_surface_carpaint.mtlx')];
|
||||
await mx.readFromXmlString(doc, file);
|
||||
const copy = doc.copy();
|
||||
await mx.readFromXmlString(doc, file);
|
||||
expect(doc.validate()).to.be.true;
|
||||
expect(copy.equals(doc)).to.be.true;
|
||||
copy.delete();
|
||||
doc.delete();
|
||||
libs.forEach(l => l.delete());
|
||||
}).timeout(TIMEOUT);
|
||||
|
||||
it('Read XML with recursive includes', async () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc, includeTestPath + '/root.mtlx');
|
||||
expect(doc.getChild('paint_semigloss')).to.exist;
|
||||
expect(doc.validate()).to.be.true;
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Locate XML includes via search path', async () =>
|
||||
{
|
||||
const searchPath = includeTestPath + ';' + includeTestPath + '/folder';
|
||||
const filename = 'non_relative_includes.mtlx';
|
||||
const doc = mx.createDocument();
|
||||
expect(async () => await mx.readFromXmlFile(doc, filename, includeTestPath)).to.throw;
|
||||
await mx.readFromXmlFile(doc, filename, searchPath);
|
||||
expect(doc.getChild('paint_semigloss')).to.exist;
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
const doc2 = mx.createDocument();
|
||||
const mtlxString = getMtlxStrings([filename], includeTestPath);
|
||||
expect(async () => await mx.readFromXmlString(doc2, mtlxString[0])).to.throw;
|
||||
await mx.readFromXmlString(doc2, mtlxString[0], searchPath);
|
||||
expect(doc2.getChild('paint_semigloss')).to.exist;
|
||||
expect(doc2.validate()).to.be.true;
|
||||
expect(doc2.equals(doc)).to.be.true;
|
||||
doc2.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Locate XML includes via environment variable', async () =>
|
||||
{
|
||||
const searchPath = includeTestPath + ';' + includeTestPath + '/folder';
|
||||
const filename = 'non_relative_includes.mtlx';
|
||||
|
||||
const doc = mx.createDocument();
|
||||
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/' + filename)).to.throw;
|
||||
mx.setEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR, searchPath);
|
||||
await mx.readFromXmlFile(doc, filename);
|
||||
mx.removeEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR);
|
||||
expect(doc.getChild('paint_semigloss')).to.exist;
|
||||
expect(doc.validate()).to.be.true;
|
||||
|
||||
const doc2 = mx.createDocument();
|
||||
const mtlxString = getMtlxStrings([filename], includeTestPath);
|
||||
expect(async () => await mx.readFromXmlString(doc2, mtlxString[0])).to.throw;
|
||||
mx.setEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR, searchPath);
|
||||
await mx.readFromXmlString(doc2, mtlxString[0]);
|
||||
mx.removeEnviron(mx.MATERIALX_SEARCH_PATH_ENV_VAR);
|
||||
expect(doc2.getChild('paint_semigloss')).to.exist;
|
||||
expect(doc2.validate()).to.be.true;
|
||||
expect(doc2.equals(doc)).to.be.true;
|
||||
doc2.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Locate XML includes via absolute search paths', async () =>
|
||||
{
|
||||
let absolutePath;
|
||||
if (typeof window === 'object')
|
||||
{
|
||||
// We're in the browser
|
||||
const cwd = window.location.origin + window.location.pathname;
|
||||
absolutePath = cwd + '/' + includeTestPath;
|
||||
} else if (typeof process === 'object')
|
||||
{
|
||||
// We're in Node
|
||||
const nodePath = require('path');
|
||||
absolutePath = nodePath.resolve(includeTestPath);
|
||||
}
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc, 'root.mtlx', absolutePath);
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Detect XML include cycles', async () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/cycle.mtlx')).to.throw;
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Disabling XML includes', async () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
const readOptions = new mx.XmlReadOptions();
|
||||
readOptions.readXIncludes = false;
|
||||
expect(async () => await mx.readFromXmlFile(doc, includeTestPath + '/cycle.mtlx', readOptions)).to.not.throw;
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
it('Write to XML string', async () =>
|
||||
{
|
||||
// Read all example documents and write them to an XML string
|
||||
const searchPath = libraryPath + ';' + examplesPath;
|
||||
for (let filename of exampleFilenames)
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc, filename, searchPath);
|
||||
|
||||
// Serialize to XML.
|
||||
const writeOptions = new mx.XmlWriteOptions();
|
||||
writeOptions.writeXIncludeEnable = false;
|
||||
const xmlString = mx.writeToXmlString(doc, writeOptions);
|
||||
writeOptions.delete();
|
||||
|
||||
// Verify that the serialized document is identical.
|
||||
const writtenDoc = mx.createDocument();
|
||||
await mx.readFromXmlString(writtenDoc, xmlString);
|
||||
expect(writtenDoc).to.eql(doc);
|
||||
writtenDoc.delete();
|
||||
doc.delete();
|
||||
};
|
||||
});
|
||||
|
||||
it('Prepend include tag', () =>
|
||||
{
|
||||
const doc = mx.createDocument();
|
||||
const includePath = "SomePath";
|
||||
const writeOptions = new mx.XmlWriteOptions();
|
||||
mx.prependXInclude(doc, includePath);
|
||||
const xmlString = mx.writeToXmlString(doc, writeOptions);
|
||||
expect(xmlString).to.include(includePath);
|
||||
writeOptions.delete();
|
||||
doc.delete();
|
||||
});
|
||||
|
||||
// Node only, because we cannot read from a downloaded file in the browser
|
||||
it('Write XML to file', async () =>
|
||||
{
|
||||
const filename = '_build/testFile.mtlx';
|
||||
const includeRegex = /<xi:include href="(.*)"\s*\/>/g;
|
||||
const doc = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc, 'root.mtlx', includeTestPath);
|
||||
|
||||
// Write using includes
|
||||
mx.writeToXmlFile(doc, filename);
|
||||
// Read written document and compare with the original
|
||||
const doc2 = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc2, filename, includeTestPath);
|
||||
expect(doc2.equals(doc));
|
||||
// Read written file content and verify that includes are preserved
|
||||
let fileString = getMtlxStrings([filename], '')[0];
|
||||
let matches = Array.from(fileString.matchAll(includeRegex));
|
||||
expect(matches.length).to.be.greaterThan(0);
|
||||
|
||||
// Write inlining included content
|
||||
const writeOptions = new mx.XmlWriteOptions();
|
||||
writeOptions.writeXIncludeEnable = false;
|
||||
mx.writeToXmlFile(doc, filename, writeOptions);
|
||||
// Read written document and compare with the original
|
||||
const doc3 = mx.createDocument();
|
||||
await mx.readFromXmlFile(doc3, filename);
|
||||
expect(doc3.equals(doc));
|
||||
expect(doc.getChild('paint_semigloss')).to.exist;
|
||||
// Read written file content and verify that includes are inlined
|
||||
fileString = getMtlxStrings([filename], '')[0];
|
||||
matches = Array.from(fileString.matchAll(includeRegex));
|
||||
expect(matches.length).to.equal(0);
|
||||
doc3.delete();
|
||||
doc2.delete();
|
||||
doc.delete();
|
||||
writeOptions.delete();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user