Init Repo
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
#include "Application.h"
|
||||
#include "../utils/Logger.h"
|
||||
#include "../utils/FileDialog.h"
|
||||
#include "../utils/PathUtils.h"
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/references.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <imgui.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helper: convert a raw string into a valid USD identifier.
|
||||
// ---------------------------------------------------------------------------
|
||||
static std::string SanitizeUsdNameApp(const std::string& raw) {
|
||||
std::string result;
|
||||
result.reserve(raw.size());
|
||||
for (char c : raw) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '_')
|
||||
result += c;
|
||||
else
|
||||
result += '_';
|
||||
}
|
||||
if (result.empty() || std::isdigit(static_cast<unsigned char>(result[0])))
|
||||
result = "_" + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
Application::Application()
|
||||
: m_showDemoWindow(false)
|
||||
, m_showStageInfo(true)
|
||||
, m_running(false) {
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Application::Initialize(const std::string& windowTitle, int width, int height) {
|
||||
LOG_INFO("Initializing USD Layer Manager Application...");
|
||||
|
||||
// Create and initialize ImGui context
|
||||
m_imguiContext = std::make_unique<ImGuiContext>();
|
||||
if (!m_imguiContext->Initialize(windowTitle, width, height)) {
|
||||
LOG_ERROR("Failed to initialize ImGui context");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create managers
|
||||
m_stageManager = std::make_unique<UsdStageManager>();
|
||||
m_layerManager = std::make_unique<LayerManager>();
|
||||
m_propertyManager = std::make_unique<PropertyManager>();
|
||||
m_layerPanel = std::make_unique<LayerPanel>();
|
||||
m_layerPanel->SetLayerManager(m_layerManager.get());
|
||||
m_layerPanel->SetCommandHistory(&m_commandHistory);
|
||||
m_sceneHierarchyPanel = std::make_unique<SceneHierarchyPanel>();
|
||||
m_sceneHierarchyPanel->SetPropertyManager(m_propertyManager.get());
|
||||
m_sceneHierarchyPanel->SetCommandHistory(&m_commandHistory);
|
||||
m_viewportPanel = std::make_unique<ViewportPanel>();
|
||||
m_viewportPanel->SetCommandHistory(&m_commandHistory);
|
||||
m_propertyPanel = std::make_unique<PropertyPanel>();
|
||||
m_propertyPanel->SetPropertyManager(m_propertyManager.get());
|
||||
m_propertyPanel->SetCommandHistory(&m_commandHistory);
|
||||
|
||||
// Initialize IconManager — must happen after OpenGL context is ready (ImGui init above).
|
||||
m_iconManager = std::make_unique<IconManager>();
|
||||
m_iconManager->Initialize(ResourcePath("resources/icons"), 24);
|
||||
m_sceneHierarchyPanel->SetIconManager(m_iconManager.get());
|
||||
m_viewportPanel->SetIconManager(m_iconManager.get());
|
||||
|
||||
m_sceneHierarchyPanel->SetOnPrimSelected(
|
||||
[this](const std::string& path) {
|
||||
m_viewportPanel->SetSelectedPrimPath(path);
|
||||
m_propertyPanel->SetSelectedPrimPath(path);
|
||||
});
|
||||
|
||||
m_sceneHierarchyPanel->SetOnStageMetadataChanged(
|
||||
[this]() {
|
||||
RefreshManagers();
|
||||
});
|
||||
|
||||
// Single click in viewport → sync hierarchy + property panel
|
||||
m_viewportPanel->OnPrimPicked = [this](const std::string& path) {
|
||||
m_sceneHierarchyPanel->SetSelectedPath(path);
|
||||
m_propertyPanel->SetSelectedPrimPath(path);
|
||||
};
|
||||
|
||||
// Rect drag in viewport → sync hierarchy + property panel (primary path)
|
||||
m_viewportPanel->OnPrimsPickedRect = [this](const std::vector<std::string>& paths) {
|
||||
m_sceneHierarchyPanel->SetSelectedPaths(paths);
|
||||
m_propertyPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
|
||||
};
|
||||
|
||||
if (!m_stageManager->CreateInMemoryStage()) {
|
||||
LOG_ERROR("Failed to create default in-memory stage");
|
||||
} else {
|
||||
RefreshManagers();
|
||||
}
|
||||
|
||||
LOG_INFO("Application initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::Run() {
|
||||
LOG_INFO("Starting application main loop...");
|
||||
m_running = true;
|
||||
|
||||
while (m_running && m_imguiContext->ProcessEvents()) {
|
||||
Update();
|
||||
RenderUI();
|
||||
}
|
||||
|
||||
LOG_INFO("Application main loop ended");
|
||||
}
|
||||
|
||||
void Application::Shutdown() {
|
||||
m_viewportPanel.reset();
|
||||
m_sceneHierarchyPanel.reset();
|
||||
m_propertyPanel.reset();
|
||||
m_layerPanel.reset();
|
||||
m_propertyManager.reset();
|
||||
m_layerManager.reset();
|
||||
|
||||
if (m_iconManager) {
|
||||
m_iconManager->Shutdown();
|
||||
m_iconManager.reset();
|
||||
}
|
||||
|
||||
if (m_stageManager) {
|
||||
m_stageManager->CloseStage();
|
||||
m_stageManager.reset();
|
||||
}
|
||||
|
||||
if (m_imguiContext) {
|
||||
LOG_INFO("Shutting down application...");
|
||||
m_imguiContext->Shutdown();
|
||||
m_imguiContext.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::RefreshManagers() {
|
||||
m_commandHistory.Clear();
|
||||
if (m_stageManager->HasStage()) {
|
||||
auto stage = m_stageManager->GetStage();
|
||||
m_layerManager->SetStage(stage);
|
||||
m_propertyManager->SetStage(stage);
|
||||
m_sceneHierarchyPanel->SetStage(stage);
|
||||
m_viewportPanel->SetStage(stage);
|
||||
m_viewportPanel->FrameScene();
|
||||
m_propertyPanel->SetStage(stage);
|
||||
} else {
|
||||
m_layerManager->SetStage(nullptr);
|
||||
m_propertyManager->SetStage(nullptr);
|
||||
m_sceneHierarchyPanel->SetStage(nullptr);
|
||||
m_viewportPanel->SetStage(nullptr);
|
||||
m_propertyPanel->SetStage(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::Update() {
|
||||
// Process undo/redo hotkeys (Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z).
|
||||
// Only fire when no ImGui text-input widget has keyboard focus.
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (!io.WantTextInput) {
|
||||
if (io.KeyCtrl && !io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Z, false)) {
|
||||
m_commandHistory.Undo();
|
||||
}
|
||||
if (io.KeyCtrl && (ImGui::IsKeyPressed(ImGuiKey_Y, false) ||
|
||||
(io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Z, false)))) {
|
||||
m_commandHistory.Redo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::RenderUI() {
|
||||
m_imguiContext->NewFrame();
|
||||
|
||||
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
|
||||
|
||||
if (m_showDemoWindow) {
|
||||
ImGui::ShowDemoWindow(&m_showDemoWindow);
|
||||
}
|
||||
|
||||
RenderMenuBar();
|
||||
|
||||
if (m_showStageInfo && m_stageManager->HasStage()) {
|
||||
RenderStageInfo();
|
||||
}
|
||||
|
||||
ImGui::Begin("Layer Panel", nullptr, ImGuiWindowFlags_NoCollapse);
|
||||
m_layerPanel->Render();
|
||||
ImGui::End();
|
||||
|
||||
m_viewportPanel->Render();
|
||||
|
||||
// Scene Hierarchy is rendered AFTER the viewport so that viewport picks
|
||||
// (OnPrimPicked / OnPrimsPickedRect) are visible to the hierarchy in the
|
||||
// same frame — eliminating the one-frame-late scroll/highlight lag.
|
||||
ImGui::Begin("Scene Hierarchy", nullptr, ImGuiWindowFlags_NoCollapse);
|
||||
m_sceneHierarchyPanel->Render();
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("Property Panel", nullptr, ImGuiWindowFlags_NoCollapse);
|
||||
m_propertyPanel->Render();
|
||||
ImGui::End();
|
||||
|
||||
m_imguiContext->Render();
|
||||
}
|
||||
|
||||
void Application::RenderMenuBar() {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open...", "Ctrl+O")) {
|
||||
OpenUsdFile();
|
||||
}
|
||||
if (ImGui::MenuItem("New", "Ctrl+N")) {
|
||||
CreateNewUsdFile();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
bool hasStage = m_stageManager->HasStage();
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, hasStage)) {
|
||||
SaveUsdFile();
|
||||
}
|
||||
if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S", false, hasStage)) {
|
||||
SaveUsdFileAs();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Close", nullptr, false, hasStage)) {
|
||||
CloseUsdFile();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Exit", "Alt+F4")) {
|
||||
m_running = false;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
// Edit menu — Undo / Redo
|
||||
{
|
||||
bool canUndo = m_commandHistory.CanUndo();
|
||||
bool canRedo = m_commandHistory.CanRedo();
|
||||
|
||||
std::string undoLabel = canUndo
|
||||
? ("Undo: " + m_commandHistory.GetUndoDescription())
|
||||
: "Undo";
|
||||
std::string redoLabel = canRedo
|
||||
? ("Redo: " + m_commandHistory.GetRedoDescription())
|
||||
: "Redo";
|
||||
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
ImGui::BeginDisabled(!canUndo);
|
||||
if (ImGui::MenuItem(undoLabel.c_str(), "Ctrl+Z"))
|
||||
m_commandHistory.Undo();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!canRedo);
|
||||
if (ImGui::MenuItem(redoLabel.c_str(), "Ctrl+Y"))
|
||||
m_commandHistory.Redo();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// Stage editing menu — always available (default stage is always present).
|
||||
bool hasStage = m_stageManager->HasStage();
|
||||
if (ImGui::BeginMenu("Stage", hasStage)) {
|
||||
if (ImGui::MenuItem("Add Reference...")) {
|
||||
AddReferenceToStage();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Create Prim")) {
|
||||
static const char* primTypes[] = {
|
||||
"Xform", "Scope",
|
||||
"Mesh", "Sphere", "Cube", "Cylinder", "Cone", "Capsule",
|
||||
"Camera",
|
||||
"SphereLight", "DomeLight", "RectLight", "DiskLight", "CylinderLight", "DistantLight"
|
||||
};
|
||||
for (const char* t : primTypes) {
|
||||
if (ImGui::MenuItem(t)) {
|
||||
CreatePrimOnStage(t);
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("Stage Info", nullptr, &m_showStageInfo);
|
||||
ImGui::MenuItem("Show Demo Window", nullptr, &m_showDemoWindow);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) {
|
||||
// Future: Show about dialog
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::RenderStageInfo() {
|
||||
ImGui::Begin("Stage Info", &m_showStageInfo);
|
||||
|
||||
if (m_stageManager->HasStage()) {
|
||||
ImGui::Text("Root Layer:");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "%s",
|
||||
m_stageManager->GetRootLayerIdentifier().c_str());
|
||||
|
||||
std::string realPath = m_stageManager->GetRootLayerPath();
|
||||
if (!realPath.empty()) {
|
||||
ImGui::Text("Real Path:");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextWrapped("%s", realPath.c_str());
|
||||
}
|
||||
|
||||
auto stage = m_stageManager->GetStage();
|
||||
if (stage) {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Pseudo Root: %s", stage->GetPseudoRoot().GetPath().GetText());
|
||||
ImGui::Text("Default Prim: %s",
|
||||
stage->HasDefaultPrim() ? stage->GetDefaultPrim().GetPath().GetText() : "(none)");
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No stage loaded");
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Application::OpenUsdFile() {
|
||||
std::string filePath = FileDialog::OpenFile(
|
||||
"USD Files (*.usd;*.usda;*.usdc)\0*.usd;*.usda;*.usdc\0All Files (*.*)\0*.*\0",
|
||||
"Open USD File",
|
||||
m_imguiContext->GetWindowHandle()
|
||||
);
|
||||
|
||||
if (!filePath.empty()) {
|
||||
if (m_stageManager->OpenStage(filePath)) {
|
||||
RefreshManagers();
|
||||
} else {
|
||||
LOG_ERROR("Failed to open USD file: " + m_stageManager->GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::CreateNewUsdFile() {
|
||||
// Create a fresh anonymous in-memory stage — no file path required.
|
||||
// The user can save via File > Save As... when they are ready.
|
||||
if (m_stageManager->CreateInMemoryStage()) {
|
||||
RefreshManagers();
|
||||
LOG_INFO("Created new default in-memory stage");
|
||||
} else {
|
||||
LOG_ERROR("Failed to create new in-memory stage");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SaveUsdFile() {
|
||||
if (!m_stageManager->HasStage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_stageManager->SaveStage()) {
|
||||
LOG_ERROR("Failed to save USD file: " + m_stageManager->GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SaveUsdFileAs() {
|
||||
if (!m_stageManager->HasStage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filePath = FileDialog::SaveFile(
|
||||
"USD Files (*.usd;*.usda;*.usdc)\0*.usd;*.usda;*.usdc\0All Files (*.*)\0*.*\0",
|
||||
"Save USD File As",
|
||||
"usd",
|
||||
m_imguiContext->GetWindowHandle()
|
||||
);
|
||||
|
||||
if (!filePath.empty()) {
|
||||
if (!m_stageManager->SaveStageAs(filePath)) {
|
||||
LOG_ERROR("Failed to save USD file: " + m_stageManager->GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::CloseUsdFile() {
|
||||
m_stageManager->CloseStage();
|
||||
// Re-create a fresh default stage so the app is always in an editable state.
|
||||
if (m_stageManager->CreateInMemoryStage()) {
|
||||
RefreshManagers();
|
||||
LOG_INFO("Closed stage — reset to new default in-memory stage");
|
||||
} else {
|
||||
RefreshManagers();
|
||||
LOG_ERROR("Failed to re-create default stage after close");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::AddReferenceToStage() {
|
||||
if (!m_stageManager->HasStage()) return;
|
||||
|
||||
std::string filePath = FileDialog::OpenFile(
|
||||
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0All Files (*.*)\0*.*\0",
|
||||
"Add Reference File",
|
||||
m_imguiContext->GetWindowHandle()
|
||||
);
|
||||
if (filePath.empty()) return;
|
||||
|
||||
auto stage = m_stageManager->GetStage();
|
||||
|
||||
// Derive a valid USD prim name from the file stem.
|
||||
std::string stem = std::filesystem::path(filePath).stem().string();
|
||||
std::string xformName = SanitizeUsdNameApp(stem);
|
||||
if (xformName.empty()) xformName = "Reference";
|
||||
|
||||
// Avoid name collision — append _N if the path already exists.
|
||||
std::string finalName = xformName;
|
||||
int suffix = 1;
|
||||
while (stage->GetPrimAtPath(pxr::SdfPath("/" + finalName)).IsValid()) {
|
||||
finalName = xformName + "_" + std::to_string(suffix++);
|
||||
}
|
||||
|
||||
try {
|
||||
pxr::SdfPath xformPath("/" + finalName);
|
||||
pxr::UsdPrim xformPrim = stage->DefinePrim(xformPath, pxr::TfToken("Xform"));
|
||||
if (xformPrim.IsValid()) {
|
||||
bool ok = xformPrim.GetReferences().AddReference(filePath);
|
||||
if (ok) {
|
||||
LOG_INFO("Added reference '" + filePath + "' under prim: " + xformPath.GetString());
|
||||
} else {
|
||||
LOG_ERROR("Failed to add reference '" + filePath + "' to: " + xformPath.GetString());
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to define Xform prim: " + xformPath.GetString());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("Add reference error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::CreatePrimOnStage(const std::string& typeName) {
|
||||
if (!m_stageManager->HasStage()) return;
|
||||
|
||||
auto stage = m_stageManager->GetStage();
|
||||
|
||||
// Auto-generate a unique prim name from the type (e.g. Sphere → /Sphere, /Sphere_1, …).
|
||||
std::string baseName = typeName;
|
||||
std::string finalName = baseName;
|
||||
int suffix = 1;
|
||||
while (stage->GetPrimAtPath(pxr::SdfPath("/" + finalName)).IsValid()) {
|
||||
finalName = baseName + "_" + std::to_string(suffix++);
|
||||
}
|
||||
|
||||
try {
|
||||
pxr::SdfPath primPath("/" + finalName);
|
||||
pxr::UsdPrim prim = stage->DefinePrim(primPath, pxr::TfToken(typeName));
|
||||
if (prim.IsValid()) {
|
||||
LOG_INFO("Created prim '" + primPath.GetString() + "' of type " + typeName);
|
||||
// Sync selection to the new prim.
|
||||
m_sceneHierarchyPanel->SetSelectedPath(primPath.GetString());
|
||||
m_propertyPanel->SetSelectedPrimPath(primPath.GetString());
|
||||
} else {
|
||||
LOG_ERROR("Failed to create prim of type: " + typeName);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("Create prim error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
Reference in New Issue
Block a user