Files
UsdLayerManager/src/main.cpp
T
indigo 038450a1ca Vendor node editor in-tree; add material presets; debug drag regression
Move the imgui-node-editor subset the build actually compiles from
third_party/imgui-node-editor to src/ui/NodeEditor (version-controlled,
MIT LICENSE included). FindImguiNodeEditor.cmake points at the new
location; CMakeLists excludes src/ui/NodeEditor from UI_SOURCES so it
isn't compiled twice.

Material editor presets: a Presets menu (disabled with no open material)
builds a UsdPreviewSurface + UsdUVTexture graph fed by an st primvar
reader, or a MaterialX standard_surface + image graph fed by a texcoord
node. Each is one idempotent, undoable command that re-normalizes layout.
Create Material becomes an icon button. New nodes route through
FindFreeCanvasSpot so a creation never lands on top of an existing node
(overlapping nodes fight over the editor hit test and become undraggable).

Shader-ball preview: pick a previewable output (terminal or 3/4-component
color-like) instead of always the first output, so scalar-only nodes keep
the whole-material preview rather than failing Storm codegen. Per-shape
camera frame-fit margins and auto-clip framing.

Fixes: DeletePrimCommand::Undo recreates missing destination ancestors
before SdfCopySpec (parent material may have been deleted after the
command ran). ConfigWindowsMoveFromTitleBarOnly stops a content-area drag
in the node canvas from moving the whole Material Editor window.

Temporary (marked for removal once the node-editor drag regression is
diagnosed): main.cpp mirrors LOG_INFO to %APPDATA%\UsdLayerManager\
debug.log; a g_AxNodeEditorDebugLog hook in the vendored editor plus a
[NodeGraph] event-trace block in RenderNodeGraphCanvas dump click/drag/
selection/position-save state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:35:23 +08:00

136 lines
6.1 KiB
C++

#include "ui/Application.h"
#include "utils/Logger.h"
#include <pxr/base/plug/registry.h>
#include <cstdio>
#include <exception>
#include <Windows.h>
#include <string>
#include <filesystem>
static void SetUsdPluginPath() {
char exePath[MAX_PATH];
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
std::string exeDir(exePath);
size_t lastSlash = exeDir.find_last_of("\\/");
if (lastSlash != std::string::npos) {
exeDir = exeDir.substr(0, lastSlash);
}
std::string pluginPath = exeDir + "\\usd";
SetEnvironmentVariableA("PXR_PLUGINPATH_NAME", pluginPath.c_str());
std::vector<std::string> pluginPaths;
WIN32_FIND_DATAA findData;
std::string searchPattern = pluginPath + "\\*";
HANDLE hFind = FindFirstFileA(searchPattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
std::string dirName(findData.cFileName);
if (dirName != "." && dirName != "..") {
std::string plugInfoPath = pluginPath + "\\" + dirName + "\\resources\\plugInfo.json";
DWORD attrs = GetFileAttributesA(plugInfoPath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES && !(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
pluginPaths.push_back(plugInfoPath);
}
}
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
// Pre-flight: attempt to load each plugin DLL before registering it.
// This catches missing dependencies (e.g. sycl8.dll → Intel GPU drivers)
// gracefully and shows our own warning rather than a cryptic TF_ERROR.
std::vector<std::string> checkedPaths;
for (const auto& infoPath : pluginPaths) {
// Derive the DLL path from the plugInfo.json location:
// <plugin>/resources/plugInfo.json → ../<plugin>.dll
// e.g. usd/hdCycles/resources/plugInfo.json → usd/hdCycles.dll
size_t resPos = infoPath.rfind("\\resources\\plugInfo.json");
if (resPos != std::string::npos) {
std::string pluginDir = infoPath.substr(0, resPos);
size_t dirSlash = pluginDir.rfind('\\');
std::string pluginName = (dirSlash != std::string::npos)
? pluginDir.substr(dirSlash + 1) : pluginDir;
std::string dllPath = pluginPath + "\\" + pluginName + ".dll";
DWORD dllAttrs = GetFileAttributesA(dllPath.c_str());
if (dllAttrs != INVALID_FILE_ATTRIBUTES) {
// DLL file exists — verify it can actually be loaded.
// No LOAD_WITH_ALTERED_SEARCH_PATH: keep the application
// directory (exe dir, where embree4.dll etc. live) in the
// DLL search path so transitive deps resolve correctly.
HMODULE h = LoadLibraryA(dllPath.c_str());
if (!h) {
DWORD err = GetLastError();
char msg[256] = {};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, err, 0, msg, static_cast<DWORD>(sizeof(msg) - 1), nullptr);
std::string errMsg(msg);
while (!errMsg.empty() && (errMsg.back() == '\r' || errMsg.back() == '\n'))
errMsg.pop_back();
char hexErr[12] = {};
sprintf_s(hexErr, "0x%08X", static_cast<unsigned>(err));
LOG_WARNING("Skipping plugin " + pluginName
+ " (DLL load failed " + hexErr + "): " + errMsg);
continue; // Skip registering this plugin
}
// Intentionally do NOT FreeLibrary here. Releasing the handle
// drops the refcount to 0 and unloads the DLL; when USD's plug
// system later loads it again DllMain re-fires, re-registering
// any TfEnvSettings and causing "duplicate definition" warnings.
// Keeping the handle loaded means USD's LoadLibrary is a no-op
// (refcount bump only, no DllMain call), preventing duplicates.
// The process holds these handles for its entire lifetime anyway.
}
}
checkedPaths.push_back(infoPath);
}
if (!checkedPaths.empty()) {
auto& registry = pxr::PlugRegistry::GetInstance();
auto registered = registry.RegisterPlugins(checkedPaths);
LOG_INFO("Registered " + std::to_string(registered.size()) + " USD plugins from " + pluginPath);
} else {
LOG_WARNING("No USD plugins found at " + pluginPath);
}
}
int main(int /*argc*/, char* /*argv*/[]) {
try {
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
// Temporary: capture LOG_INFO to a file so the [NodeGraph]/[ed] debug
// traces are readable after the fact (this is a WIN32-subsystem app,
// so stdout isn't visible when launched normally). Remove once the
// node-editor drag regression is diagnosed.
if (const char* appData = getenv("APPDATA")) {
std::filesystem::path logDir = std::filesystem::path(appData) / "UsdLayerManager";
std::filesystem::create_directories(logDir);
UsdLayerManager::Logger::Instance().SetLogFile((logDir / "debug.log").string());
}
LOG_INFO("=== USD Layer Manager Starting ===");
SetUsdPluginPath();
UsdLayerManager::Application app;
if (!app.Initialize("USD Layer Manager", 1280, 720)) {
LOG_ERROR("Failed to initialize application");
return 1;
}
app.Run();
app.Shutdown();
LOG_INFO("=== USD Layer Manager Exiting ===");
return 0;
} catch (const std::exception& e) {
LOG_ERROR(std::string("Unhandled exception: ") + e.what());
return 1;
} catch (...) {
LOG_ERROR("Unknown exception occurred");
return 1;
}
}