Skip to content

Commit

Permalink
Merge branch 'main' into triplanar_axes
Browse files Browse the repository at this point in the history
  • Loading branch information
jstone-lucasfilm authored Aug 25, 2023
2 parents e138658 + 77dd15b commit 82fdb5f
Show file tree
Hide file tree
Showing 15 changed files with 479 additions and 112 deletions.
281 changes: 281 additions & 0 deletions javascript/MaterialXView/source/dropHandling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import * as THREE from 'three';
import * as fflate from 'three/examples/jsm/libs/fflate.module.js';

const debugFileHandling = false;
let loadingCallback;

export function setLoadingCallback(cb) {
loadingCallback = cb;
}

export function dropHandler(ev) {
if (debugFileHandling) console.log('File(s) dropped', ev.dataTransfer.items, ev.dataTransfer.files);

// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();

if (ev.dataTransfer.items)
{
const allEntries = [];

let haveGetAsEntry = false;
if (ev.dataTransfer.items.length > 0)
haveGetAsEntry =
("getAsEntry" in ev.dataTransfer.items[0]) ||
("webkitGetAsEntry" in ev.dataTransfer.items[0]);

// Useful for debugging file handling on platforms that don't support newer file system APIs
// haveGetAsEntry = false;

if (haveGetAsEntry) {
for (var i = 0; i < ev.dataTransfer.items.length; i++)
{
let item = ev.dataTransfer.items[i];
let entry = ("getAsEntry" in item) ? item.getAsEntry() : item.webkitGetAsEntry();
allEntries.push(entry);
}
handleFilesystemEntries(allEntries);
return;
}

for (var i = 0; i < ev.dataTransfer.items.length; i++)
{
let item = ev.dataTransfer.items[i];

// API when there's no "getAsEntry" support
console.log(item.kind, item);
if (item.kind === 'file')
{
var file = item.getAsFile();
testAndLoadFile(file);
}
// could also be a directory
else if (item.kind === 'directory')
{
var dirReader = item.createReader();
dirReader.readEntries(function(entries) {
for (var i = 0; i < entries.length; i++) {
console.log(entries[i].name);
var entry = entries[i];
if (entry.isFile) {
entry.file(function(file) {
testAndLoadFile(file);
});
}
}
});
}
}
} else {
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
let file = ev.dataTransfer.files[i];
testAndLoadFile(file);
}
}
}

export function dragOverHandler(ev) {
ev.preventDefault();
}

async function getBufferFromFile(fileEntry) {

if (fileEntry instanceof ArrayBuffer) return fileEntry;
if (fileEntry instanceof String) return fileEntry;

const name = fileEntry.fullPath || fileEntry.name;
const ext = name.split('.').pop();
const readAsText = ext === 'mtlx';

if (debugFileHandling) console.log("reading ", fileEntry, "as text?", readAsText);

if (debugFileHandling) console.log("getBufferFromFile", fileEntry);
const buffer = await new Promise((resolve, reject) => {
function readFile(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
if (debugFileHandling) console.log("loaded", "should be text?", readAsText, this.result);
resolve(this.result);
};

if (readAsText)
reader.readAsText(file);
else
reader.readAsArrayBuffer(file);
}

if ("file" in fileEntry) {
fileEntry.file(function(file) {
readFile(file);
}, (e) => {
console.error("Error reading file ", e);
});
}
else {
readFile(fileEntry);
}
});
return buffer;
}

async function handleFilesystemEntries(entries) {
const allFiles = [];
const fileIgnoreList = [
'.gitignore',
'README.md',
'.DS_Store',
]
const dirIgnoreList = [
'.git',
'node_modules',
]

for (let entry of entries) {
if (debugFileHandling) console.log("file entry", entry)
if (entry.isFile) {
if (debugFileHandling) console.log("single file", entry);
if (fileIgnoreList.includes(entry.name)) {
continue;
}
allFiles.push(entry);
}
else if (entry.isDirectory) {
if (dirIgnoreList.includes(entry.name)) {
continue;
}
const files = await readDirectory(entry);
if (debugFileHandling) console.log("all files", files);
for (const file of files) {
if (fileIgnoreList.includes(file.name)) {
continue;
}
allFiles.push(file);
}
}
}

const imageLoader = new THREE.ImageLoader();

// unpack zip files first
for (const fileEntry of allFiles) {
// special case: zip archives
if (fileEntry.fullPath.toLowerCase().endsWith('.zip')) {
await new Promise(async (resolve, reject) => {
const arrayBuffer = await getBufferFromFile(fileEntry);

// use fflate to unpack them and add the files to the cache
fflate.unzip(new Uint8Array(arrayBuffer), (error, unzipped) => {
// push these files into allFiles
for (const [filePath, buffer] of Object.entries(unzipped)) {

// mock FileEntry for easier usage downstream
const blob = new Blob([buffer]);
const newFileEntry = {
fullPath: "/" + filePath,
name: filePath.split('/').pop(),
file: (callback) => {
callback(blob);
},
isFile: true,
};
allFiles.push(newFileEntry);
}

resolve();
});
});
}
}

// sort so mtlx files come first
allFiles.sort((a, b) => {
if (a.name.endsWith('.mtlx') && !b.name.endsWith('.mtlx')) {
return -1;
}
if (!a.name.endsWith('.mtlx') && b.name.endsWith('.mtlx')) {
return 1;
}
return 0;
});

if (debugFileHandling) console.log("all files", allFiles);

// put all files in three' Cache
for (const fileEntry of allFiles) {

const allowedFileTypes = [
'png', 'jpg', 'jpeg'
];

const ext = fileEntry.fullPath.split('.').pop();
if (!allowedFileTypes.includes(ext)) {
// console.log("skipping file", fileEntry.fullPath);
continue;
}

const buffer = await getBufferFromFile(fileEntry);
const img = await imageLoader.loadAsync(URL.createObjectURL(new Blob([buffer])));
if (debugFileHandling) console.log("caching file", fileEntry.fullPath, img);
THREE.Cache.add(fileEntry.fullPath, img);
}

// TODO we could also allow dropping of multiple MaterialX files (or folders with them inside)
// and seed the dropdown from that.
// At that point, actually reading files and textures into memory should be deferred until they are actually used.
const rootFile = allFiles[0];
THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile));

if (debugFileHandling) console.log("CACHE", THREE.Cache.files);

loadingCallback(rootFile);
}

async function readDirectory(directory) {
let entries = [];
let getEntries = async (directory) => {
let dirReader = directory.createReader();
await new Promise((resolve, reject) => {
dirReader.readEntries(
async (results) => {
if (results.length) {
// entries = entries.concat(results);
for (let entry of results) {
if (entry.isDirectory) {
await getEntries(entry);
}
else {
entries.push(entry);
}
}
}
resolve();
},
(error) => {
/* handle error — error is a FileError object */
},
)}
)};

await getEntries(directory);
return entries;
}

async function testAndLoadFile(file) {
let ext = file.name.split('.').pop();
if (debugFileHandling) console.log(file.name + ", " + file.size + ", " + ext);

const arrayBuffer = await getBufferFromFile(file);
console.log(arrayBuffer)

// mock a fileEntry and pass through the same loading logic
const newFileEntry = {
fullPath: "/" + file.name,
name: file.name.split('/').pop(),
isFile: true,
file: (callback) => {
callback(file);
}
};

handleFilesystemEntries([newFileEntry]);
}
15 changes: 15 additions & 0 deletions javascript/MaterialXView/source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';

import { Viewer } from './viewer.js'
import { dropHandler, dragOverHandler, setLoadingCallback } from './dropHandling.js';

let renderer, composer, orbitControls;

Expand Down Expand Up @@ -115,6 +116,20 @@ function init()
console.error(Number.isInteger(err) ? this.getMx().getExceptionMessage(err) : err);
})

// allow dropping files and directories
document.addEventListener('drop', dropHandler, false);
document.addEventListener('dragover', dragOverHandler, false);

setLoadingCallback(file => {
materialFilename = file.fullPath || file.name;
viewer.getEditor().clearFolders();
viewer.getMaterial().loadMaterials(viewer, materialFilename);
viewer.getEditor().updateProperties(0.9);
viewer.getScene().setUpdateTransforms();
});

// enable three.js Cache so that dropped files can reference each other
THREE.Cache.enabled = true;
}

function onWindowResize()
Expand Down
1 change: 1 addition & 0 deletions javascript/MaterialXView/source/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ export class Material

// Set search path. Assumes images are relative to current file
// location.
if (!materialFilename) materialFilename = "/";
const paths = materialFilename.split('/');
paths.pop();
const searchPath = paths.join('/');
Expand Down
12 changes: 12 additions & 0 deletions source/MaterialXCore/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class Document::Cache
for (ElementPtr elem : doc.lock()->traverseTree())
{
const string& nodeName = elem->getAttribute(PortElement::NODE_NAME_ATTRIBUTE);
const string& nodeGraphName = elem->getAttribute(PortElement::NODE_GRAPH_ATTRIBUTE);
const string& nodeString = elem->getAttribute(NodeDef::NODE_ATTRIBUTE);
const string& nodeDefString = elem->getAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE);

Expand All @@ -97,6 +98,17 @@ class Document::Cache
portElementMap.emplace(portElem->getQualifiedName(nodeName), portElem);
}
}
else
{
if (!nodeGraphName.empty())
{
PortElementPtr portElem = elem->asA<PortElement>();
if (portElem)
{
portElementMap.emplace(portElem->getQualifiedName(nodeGraphName), portElem);
}
}
}
if (!nodeString.empty())
{
NodeDefPtr nodeDef = elem->asA<NodeDef>();
Expand Down
19 changes: 19 additions & 0 deletions source/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,25 @@ InterfaceElementPtr NodeGraph::getImplementation() const
return nodedef ? nodedef->getImplementation() : InterfaceElementPtr();
}

vector<PortElementPtr> NodeGraph::getDownstreamPorts() const
{
vector<PortElementPtr> downstreamPorts;
for (PortElementPtr port : getDocument()->getMatchingPorts(getQualifiedName(getName())))
{
ElementPtr node = port->getParent();
ElementPtr graph = node ? node->getParent() : nullptr;
if (graph && graph->isA<GraphElement>() && graph == getParent())
{
downstreamPorts.push_back(port);
}
}
std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b)
{
return a->getName() > b->getName();
});
return downstreamPorts;
}

bool NodeGraph::validate(string* message) const
{
bool res = true;
Expand Down
8 changes: 8 additions & 0 deletions source/MaterialXCore/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ class MX_CORE_API NodeGraph : public GraphElement
/// none was found.
InterfaceElementPtr getImplementation() const;

/// @}
/// @name Traversal
/// @{

/// Return a vector of all downstream ports that connect to this graph, ordered by
/// the names of the port elements.
vector<PortElementPtr> getDownstreamPorts() const;

/// @}
/// @name Utility
/// @{
Expand Down
Loading

0 comments on commit 82fdb5f

Please sign in to comment.