WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
ExportPanel.cpp
Go to the documentation of this file.
1// ExportPanel.cpp – Export panel (format, path, animation selection, export)
2#include "ExportPanel.h"
3#include <cassert>
4#include "imgui.h"
5#include "imgui_stdlib.h"
6#include "ExporterPlugin.h"
7#include "WoWModel.h"
8#include "Logger.h"
9#include "string_utils.h"
10
11#include <algorithm>
12#include <format>
13
14namespace
15{
16
17std::string wstringToString(const std::wstring& ws)
18{
19 std::string s;
20 s.reserve(ws.size());
21 for (wchar_t c : ws)
22 s.push_back(static_cast<char>(c & 0x7F));
23 return s;
24}
25
26std::wstring stringToWstring(const std::string& s)
27{
28 std::wstring ws;
29 ws.reserve(s.size());
30 for (char c : s)
31 ws.push_back(static_cast<wchar_t>(static_cast<unsigned char>(c)));
32 return ws;
33}
34
35void doExport(ExportPanel::DrawContext& ctx)
36{
37 WoWModel* model = ctx.getLoadedModel ? ctx.getLoadedModel() : nullptr;
38 if (!model)
39 {
40 *ctx.exportStatus = "No model loaded.";
41 return;
42 }
43
44 if (*ctx.selectedExporter < 0 ||
45 *ctx.selectedExporter >= static_cast<int>(ctx.exporters->size()))
46 {
47 *ctx.exportStatus = "Invalid exporter selection.";
48 return;
49 }
50
51 ExporterPlugin* exporter = (*ctx.exporters)[*ctx.selectedExporter].get();
52
53 // Build file path with appropriate extension
54 std::string pathStr = *ctx.exportPath;
55 std::wstring filter = exporter->fileSaveFilter();
56 std::string ext;
57 {
58 std::string f = wstringToString(filter);
59 auto pos = f.find("*.");
60 if (pos != std::string::npos)
61 {
62 auto end = f.find_first_of(";|)", pos);
63 ext = f.substr(pos + 1,
64 (end == std::string::npos) ? std::string::npos : end - pos - 1);
65 }
66 }
67
68 if (!ext.empty() && !core::endsWithIgnoreCase(pathStr, ext))
69 pathStr += ext;
70
71 // Gather selected animation indices
72 if (exporter->canExportAnimation())
73 {
74 std::vector<int> animsToExport;
75 for (size_t i = 0; i < ctx.exportAnimChecked->size() && i < model->anims.size(); ++i)
76 {
77 if ((*ctx.exportAnimChecked)[i])
78 animsToExport.push_back(model->anims[i].Index);
79 }
80 exporter->setAnimationsToExport(animsToExport);
81 }
82
83 std::wstring wpath = stringToWstring(pathStr);
84 LOG_INFO << "Exporting model to: " << pathStr;
85
86 if (exporter->exportModel(model, wpath))
87 {
88 *ctx.exportStatus = "Export successful: " + pathStr;
89 LOG_INFO << "Export complete: " << pathStr;
90 }
91 else
92 {
93 *ctx.exportStatus = "Export failed: " + pathStr;
94 LOG_ERROR << "Export failed: " << pathStr;
95 }
96}
97
98} // anonymous namespace
99
100namespace ExportPanel
101{
102
104{
105 assert(ctx.exporters && "DrawContext::exporters must not be null");
106 assert(ctx.selectedExporter && "DrawContext::selectedExporter must not be null");
107 assert(ctx.exportPath && "DrawContext::exportPath must not be null");
108 assert(ctx.exportStatus && "DrawContext::exportStatus must not be null");
109
110 WoWModel* eModel = ctx.getLoadedModel ? ctx.getLoadedModel() : nullptr;
111 if (eModel)
112 {
113 // ---- Format selector ----
114 ImGui::SeparatorText("Format");
115 if (!ctx.exporters->empty())
116 {
117 for (int i = 0; i < static_cast<int>(ctx.exporters->size()); ++i)
118 {
119 std::string label = wstringToString((*ctx.exporters)[i]->menuLabel());
120 ImGui::RadioButton(label.c_str(), ctx.selectedExporter, i);
121 if (i < static_cast<int>(ctx.exporters->size()) - 1)
122 ImGui::SameLine();
123 }
124 }
125
126 // ---- Output path ----
127 ImGui::SeparatorText("Output");
128 ImGui::Text("File Path:");
129 ImGui::SetNextItemWidth(-1);
130 ImGui::InputText("##exportPath", ctx.exportPath);
131
132 // ---- Animation selection (only for exporters that support it) ----
133 bool canAnim = (*ctx.selectedExporter >= 0 &&
134 *ctx.selectedExporter < static_cast<int>(ctx.exporters->size()) &&
135 (*ctx.exporters)[*ctx.selectedExporter]->canExportAnimation());
136
137 if (canAnim && !ctx.animEntries->empty())
138 {
139 ImGui::SeparatorText("Animations");
140
141 if (ctx.exportAnimChecked->size() != ctx.animEntries->size())
142 ctx.exportAnimChecked->assign(ctx.animEntries->size(), 1);
143
144 if (ImGui::Button("Select All"))
145 std::fill(ctx.exportAnimChecked->begin(),
146 ctx.exportAnimChecked->end(), static_cast<char>(1));
147 ImGui::SameLine();
148 if (ImGui::Button("Select None"))
149 std::fill(ctx.exportAnimChecked->begin(),
150 ctx.exportAnimChecked->end(), static_cast<char>(0));
151
152 int checkedCount = 0;
153 for (char b : *ctx.exportAnimChecked) if (b) ++checkedCount;
154 ImGui::Text("%d / %d selected", checkedCount,
155 static_cast<int>(ctx.animEntries->size()));
156
157 ImGui::BeginChild("##AnimExportList", ImVec2(0, 200), ImGuiChildFlags_Borders);
158 ImGuiListClipper clipper;
159 clipper.Begin(static_cast<int>(ctx.animEntries->size()));
160 while (clipper.Step())
161 {
162 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
163 {
164 ImGui::PushID(i);
165 bool checked = (*ctx.exportAnimChecked)[i] != 0;
166 if (ImGui::Checkbox((*ctx.animEntries)[i].label.c_str(), &checked))
167 (*ctx.exportAnimChecked)[i] = checked ? 1 : 0;
168 ImGui::PopID();
169 }
170 }
171 ImGui::EndChild();
172 }
173 else if (canAnim)
174 {
175 ImGui::SeparatorText("Animations");
176 ImGui::TextDisabled("No animations on current model.");
177 }
178
179 // ---- Export buttons ----
180 ImGui::Separator();
181 if (ImGui::Button("Export", ImVec2(-1, 0)))
182 doExport(ctx);
183
184 if (canAnim && !ctx.animEntries->empty() && *ctx.selectedAnimCombo >= 0)
185 {
186 if (ImGui::Button("Export Current Anim Only", ImVec2(-1, 0)))
187 {
188 std::vector<char> saved = *ctx.exportAnimChecked;
189 ctx.exportAnimChecked->assign(ctx.animEntries->size(), 0);
190 if (*ctx.selectedAnimCombo < static_cast<int>(ctx.exportAnimChecked->size()))
191 (*ctx.exportAnimChecked)[*ctx.selectedAnimCombo] = 1;
192 doExport(ctx);
193 *ctx.exportAnimChecked = std::move(saved);
194 }
195 }
196
197 // ---- Status ----
198 if (!ctx.exportStatus->empty())
199 {
200 bool isError = ctx.exportStatus->find("failed") != std::string::npos ||
201 ctx.exportStatus->find("No ") != std::string::npos ||
202 ctx.exportStatus->find("Invalid") != std::string::npos;
203 if (isError)
204 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", ctx.exportStatus->c_str());
205 else
206 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s", ctx.exportStatus->c_str());
207 }
208 }
209 else
210 {
211 ImGui::TextDisabled("No model loaded.");
212 }
213}
214
215} // namespace ExportPanel
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
Abstract base class for model export plugins (OBJ, FBX, etc.).
virtual bool exportModel(Model *, std::wstring file)=0
void setAnimationsToExport(std::vector< int > values)
virtual std::wstring fileSaveFilter() const =0
bool canExportAnimation() const
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
std::vector< ModelAnimation > anims
Definition WoWModel.h:178
void end(GLFWwindow *window, const char *statusText)
ImGui panel for exporting models to OBJ / FBX formats.
void draw(DrawContext &ctx)
std::string wstringToString(const std::wstring &ws)
Convert a wide string to narrow ASCII (lossy, for display only).
bool endsWithIgnoreCase(const std::string &s, const std::string &suffix)
Per-frame context for the export panel.
Definition ExportPanel.h:20
std::string * exportPath
Definition ExportPanel.h:27
std::vector< std::unique_ptr< ExporterPlugin > > * exporters
Definition ExportPanel.h:22
std::vector< AnimEntry > * animEntries
Definition ExportPanel.h:24
std::vector< char > * exportAnimChecked
Definition ExportPanel.h:25
std::function< WoWModel *()> getLoadedModel
Definition ExportPanel.h:21
std::string * exportStatus
Definition ExportPanel.h:28