diff options
Diffstat (limited to 'GUI')
| -rw-r--r-- | GUI/GUI/GUI/Frame.cpp | 72 | ||||
| -rw-r--r-- | GUI/GUI/GUI/Frame.h | 2 | ||||
| -rw-r--r-- | GUI/GUI/GUI/GUI.vcxproj | 2 | ||||
| -rw-r--r-- | GUI/GUI/GUI/GUI.vcxproj.filters | 6 | ||||
| -rw-r--r-- | GUI/GUI/GUI/Logging.cpp | 17 | ||||
| -rw-r--r-- | GUI/GUI/GUI/Logging.h | 30 | ||||
| -rw-r--r-- | GUI/GUI/GUI/PythonWrapper.cpp | 147 | ||||
| -rw-r--r-- | GUI/README.md | 10 |
8 files changed, 164 insertions, 122 deletions
diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp index 5c1fbeb..2ecc255 100644 --- a/GUI/GUI/GUI/Frame.cpp +++ b/GUI/GUI/GUI/Frame.cpp @@ -1,4 +1,5 @@ #include "Frame.h"
+#include "Logging.h"
#include "PythonWrapper.h"
#include <filesystem>
@@ -181,6 +182,8 @@ namespace { constexpr int kModelDefault = 2; // base.en
} // namespace
+using ::Logging::Log;
+
Frame::Frame()
: wxFrame(nullptr, wxID_ANY, "TaSTT"),
py_app_(nullptr),
@@ -212,7 +215,7 @@ Frame::Frame() transcribe_out->SetMinSize(transcribe_out_sz);
transcribe_out_ = transcribe_out;
- transcribe_out_->AppendText(PythonWrapper::GetVersion() + "\n");
+ Log(transcribe_out_, "{}\n", PythonWrapper::GetVersion());
auto* py_config_panel = new wxPanel(transcribe_panel, ID_PY_CONFIG_PANEL);
{
@@ -438,19 +441,15 @@ void Frame::OnNavbarUnity(wxCommandEvent& event) void Frame::OnSetupPython(wxCommandEvent& event)
{
- transcribe_out_->AppendText("Setting up Python virtual environment\n");
- transcribe_out_->AppendText("This could take several minutes, please be patient!\n");
- transcribe_out_->AppendText("This will download ~5GB of dependencies.\n");
+ Log(transcribe_out_, "Setting up Python virtual environment\n");
+ Log(transcribe_out_, "This could take several minutes, please be patient!\n");
+ Log(transcribe_out_, "This will download ~5GB of dependencies.\n");
{
std::string transcribe_out;
- std::ostringstream transcribe_out_oss;
- transcribe_out_oss << " Installing pip" << std::endl;
- transcribe_out_->AppendText(transcribe_out_oss.str());
+ Log(transcribe_out_, " Installing pip\n");
if (!PythonWrapper::InstallPip(&transcribe_out)) {
- std::ostringstream transcribe_out_oss;
- transcribe_out_oss << "Failed to install pip: " << transcribe_out;
- transcribe_out_->AppendText(transcribe_out_oss.str());
+ Log(transcribe_out_, "Failed to install pip: {}\n", transcribe_out);
}
}
@@ -466,28 +465,21 @@ void Frame::OnSetupPython(wxCommandEvent& event) };
for (const auto& pip_dep : pip_deps) {
- {
- std::ostringstream transcribe_out_oss;
- transcribe_out_oss << " Installing " << pip_dep << std::endl;
- transcribe_out_->AppendText(transcribe_out_oss.str());
- }
+ Log(transcribe_out_, " Installing {}\n", pip_dep);
std::string py_stdout, py_stderr;
bool res = PythonWrapper::InvokeWithArgs({ "-m", "pip", "install", pip_dep }, &py_stdout, &py_stderr);
if (!res) {
- std::ostringstream transcribe_out_oss;
- transcribe_out_oss << "Failed to install " << pip_dep << ": " << py_stderr << std::endl;
- transcribe_out_->AppendText(transcribe_out_oss.str());
+ Log(transcribe_out_, "Failed to install {}: {}\n", pip_dep, py_stderr);
return;
}
}
- transcribe_out_->AppendText("Python virtual environment successfully set up!\n");
+ Log(transcribe_out_, "Python virtual environment successfully set up!\n");
}
void Frame::OnDumpMics(wxCommandEvent& event)
{
- transcribe_out_->AppendText(PythonWrapper::DumpMics());
- transcribe_out_->AppendText("\n");
+ Log(transcribe_out_, "{}\n", PythonWrapper::DumpMics());
}
#define DEBUG
@@ -553,22 +545,18 @@ void Frame::OnGenerateFX(wxCommandEvent& event) void Frame::OnAppStart(wxCommandEvent& event) {
if (py_app_) {
if (wxProcess::Exists(py_app_->GetPid())) {
- transcribe_out_->AppendText("Transcription engine already running\n");
+ Log(transcribe_out_, "Transcription engine already running\n");
return;
}
delete py_app_;
py_app_ = nullptr;
}
- transcribe_out_->AppendText("Launching transcription engine\n");
+ Log(transcribe_out_, "Launching transcription engine\n");
auto cb = [&](wxProcess* proc, int ret) -> void {
- std::ostringstream transcribe_out_oss;
- transcribe_out_oss << "Transcription engine exited with code " << ret << std::endl;
-
- DrainApp(proc, transcribe_out_oss);
-
- transcribe_out_->AppendText(transcribe_out_oss.str());
+ Log(transcribe_out_, "Transcription engine exited with code {}\n", ret);
+ DrainApp(proc, transcribe_out_);
return;
};
@@ -590,7 +578,7 @@ void Frame::OnAppStart(wxCommandEvent& event) { kLangChoices[which_lang].ToStdString(),
kModelChoices[which_model].ToStdString());
if (!p) {
- transcribe_out_->AppendText("Failed to launch transcription engine\n");
+ Log(transcribe_out_, "Failed to launch transcription engine\n");
return;
}
@@ -601,7 +589,7 @@ void Frame::OnAppStop(wxCommandEvent& event) { if (py_app_) {
const long pid = py_app_->GetPid();
- transcribe_out_->AppendText("Stopping transcription engine...\n");
+ Log(transcribe_out_, "Stopping transcription engine...\n");
// Closing stdout causes the app to exit. It takes it quite a while
// to exit gracefully; be patient.
@@ -615,11 +603,7 @@ void Frame::OnAppStop(wxCommandEvent& event) { wxMilliSleep(10);
}
- {
- std::ostringstream oss;
- DrainApp(py_app_, oss);
- transcribe_out_->AppendText(oss.str());
- }
+ DrainApp(py_app_, transcribe_out_);
// Now shut it down.
bool first = true;
@@ -627,39 +611,37 @@ void Frame::OnAppStop(wxCommandEvent& event) { while (wxProcess::Exists(pid)) {
wxProcess::Kill(pid, wxSIGKILL);
if (++loop_cnt % 100 == 0) {
- transcribe_out_->AppendText("Waiting for transcription engine to exit");
+ Log(transcribe_out_, "Waiting for transcription engine to exit\n");
}
wxMilliSleep(10);
}
// Since we don't process the termination event, py_app_ deletes itself!
py_app_ = nullptr;
- transcribe_out_->AppendText("Stopped transcription engine\n");
+ Log(transcribe_out_, "Stopped transcription engine\n");
}
else {
- transcribe_out_->AppendText("Transcription engine already stopped\n");
+ Log(transcribe_out_, "Transcription engine already stopped\n");
}
}
void Frame::OnAppDrain(wxTimerEvent& event) {
- std::ostringstream oss;
- DrainApp(py_app_, oss);
- transcribe_out_->AppendText(oss.str());
+ DrainApp(py_app_, transcribe_out_);
}
-void Frame::DrainApp(wxProcess* proc, std::ostringstream& oss) {
+void Frame::DrainApp(wxProcess* proc, wxTextCtrl* frame) {
if (!proc) {
return;
}
while (proc->IsInputAvailable()) {
wxTextInputStream iss(*(proc->GetInputStream()));
- oss << " " << iss.ReadLine() << std::endl;
+ Log(frame, " {}\n", iss.ReadLine());
}
while (proc->IsErrorAvailable()) {
wxTextInputStream iss(*(proc->GetErrorStream()));
- oss << " " << iss.ReadLine() << std::endl;
+ Log(frame, " {}\n", iss.ReadLine());
}
}
diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h index 1d847ca..9b94036 100644 --- a/GUI/GUI/GUI/Frame.h +++ b/GUI/GUI/GUI/Frame.h @@ -49,7 +49,7 @@ private: void OnAppStart(wxCommandEvent& event);
void OnAppStop(wxCommandEvent& event);
void OnAppDrain(wxTimerEvent& event);
- void DrainApp(wxProcess* proc, std::ostringstream& oss);
+ void DrainApp(wxProcess* proc, wxTextCtrl *frame);
void OnGenerateFX(wxCommandEvent& event);
void LoadAndSetIcons();
diff --git a/GUI/GUI/GUI/GUI.vcxproj b/GUI/GUI/GUI/GUI.vcxproj index cd0e5f0..8327365 100644 --- a/GUI/GUI/GUI/GUI.vcxproj +++ b/GUI/GUI/GUI/GUI.vcxproj @@ -137,12 +137,14 @@ <ItemGroup>
<ClCompile Include="App.cpp" />
<ClCompile Include="Frame.cpp" />
+ <ClCompile Include="Logging.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="PythonWrapper.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="App.h" />
<ClInclude Include="Frame.h" />
+ <ClInclude Include="Logging.h" />
<ClInclude Include="PythonWrapper.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="ScopeGuard.h" />
diff --git a/GUI/GUI/GUI/GUI.vcxproj.filters b/GUI/GUI/GUI/GUI.vcxproj.filters index 5118c26..348026a 100644 --- a/GUI/GUI/GUI/GUI.vcxproj.filters +++ b/GUI/GUI/GUI/GUI.vcxproj.filters @@ -27,6 +27,9 @@ <ClCompile Include="PythonWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="Logging.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="PythonWrapper.h">
@@ -44,6 +47,9 @@ <ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="Logging.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="GUI.rc">
diff --git a/GUI/GUI/GUI/Logging.cpp b/GUI/GUI/GUI/Logging.cpp new file mode 100644 index 0000000..6727ba1 --- /dev/null +++ b/GUI/GUI/GUI/Logging.cpp @@ -0,0 +1,17 @@ +#include "Logging.h"
+
+#include <regex>
+#include <string>
+
+std::string Logging::HidePII(const std::string&& str,
+ const std::string& replacement) {
+ try {
+ std::regex c_users("(C:\\\\Users\\\\)[a-zA-Z0-9_]+");
+ std::string real_replacement = "$1" + replacement;
+ return std::regex_replace(str, c_users, real_replacement);
+ }
+ catch (const std::regex_error& e) {
+ wxLogFatalError(e.what());
+ }
+ wxLogFatalError("Unhandled regex error (HidePII)");
+}
diff --git a/GUI/GUI/GUI/Logging.h b/GUI/GUI/GUI/Logging.h new file mode 100644 index 0000000..95e7802 --- /dev/null +++ b/GUI/GUI/GUI/Logging.h @@ -0,0 +1,30 @@ +#pragma once
+
+#pragma once
+
+#include <wx/wxprec.h>
+
+#ifndef WX_PRECOMP
+#include <wx/wx.h>
+#endif
+
+#include <format>
+#include <string>
+#include <string_view>
+
+namespace Logging {
+ // Remove personally identifying information (PII) from str.
+ //
+ // For example, this translates "C:/Users/foo/Desktop" to "C:/Users/*****/Desktop".
+ std::string HidePII(const std::string&& str, const std::string& replacement = "*****");
+
+ // Provides a simple Python format()-like interface to wxTextCtrl.
+ // Ex: Log(my_textctrl_, "{}\n", "Hello, world!");
+ template<typename... Args>
+ void Log(wxTextCtrl* frame, std::string_view format, Args&&... args) {
+ const std::string raw = std::vformat(format, std::make_format_args(args...));
+ const std::string masked = HidePII(std::move(raw));
+ frame->AppendText(masked);
+ }
+}
+
diff --git a/GUI/GUI/GUI/PythonWrapper.cpp b/GUI/GUI/GUI/PythonWrapper.cpp index 972982f..2e37994 100644 --- a/GUI/GUI/GUI/PythonWrapper.cpp +++ b/GUI/GUI/GUI/PythonWrapper.cpp @@ -1,3 +1,4 @@ +#include "Logging.h" #include "PythonWrapper.h" #include <stdio.h> @@ -5,6 +6,8 @@ #include <filesystem> #include <sstream> +using ::Logging::Log; + class PythonProcess : public wxProcess { public: PythonProcess(std::function<void(wxProcess* proc, int ret)>&& exit_callback) : exit_cb_(exit_callback) { @@ -178,18 +181,14 @@ bool PythonWrapper::GenerateAnimator( { if (std::filesystem::exists(tastt_generated_dir_path)) { - std::ostringstream oss; - oss << "Erasing " << tastt_generated_dir_path << std::endl; - out->AppendText(oss.str()); + Log(out, "Erasing {}\n", tastt_generated_dir_path.string()); std::filesystem::remove_all(tastt_generated_dir_path); } - std::ostringstream oss; - oss << "Creating " << tastt_generated_dir_path << std::endl; - out->AppendText(oss.str()); + Log(out, "Creating {}\n", tastt_generated_dir_path.string()); std::filesystem::create_directories(tastt_generated_dir_path); } { - out->AppendText("Copying canned animations... "); + Log(out, "Copying canned animations... "); auto opts = std::filesystem::copy_options(); opts |= std::filesystem::copy_options::overwrite_existing; opts |= std::filesystem::copy_options::recursive; @@ -197,13 +196,13 @@ bool PythonWrapper::GenerateAnimator( std::filesystem::copy("Resources/Animations", tastt_animations_path, opts, error); if (error.value()) { wxLogError("Failed to copy animations: %s (%d)", error.message(), error.value()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } - out->AppendText("success!\n"); + Log(out, "success!\n"); } { - out->AppendText("Copying canned assets... "); + Log(out, "Copying canned assets... "); auto opts = std::filesystem::copy_options(); opts |= std::filesystem::copy_options::overwrite_existing; opts |= std::filesystem::copy_options::recursive; @@ -211,13 +210,13 @@ bool PythonWrapper::GenerateAnimator( std::filesystem::copy("Resources/UnityAssets", tastt_assets_path, opts, error); if (error.value()) { wxLogError("Failed to copy animations: %s (%d)", error.message(), error.value()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } - out->AppendText("success!\n"); + Log(out, "success!\n"); } { - out->AppendText("Copying canned shaders... "); + Log(out, "Copying canned shaders... "); auto opts = std::filesystem::copy_options(); opts |= std::filesystem::copy_options::overwrite_existing; opts |= std::filesystem::copy_options::recursive; @@ -225,13 +224,13 @@ bool PythonWrapper::GenerateAnimator( std::filesystem::copy("Resources/Shaders", tastt_shaders_path, opts, error); if (error.value()) { wxLogError("Failed to copy animations: %s (%d)", error.message(), error.value()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } - out->AppendText("success!\n"); + Log(out, "success!\n"); } { - out->AppendText("Copying canned fonts... "); + Log(out, "Copying canned fonts... "); auto opts = std::filesystem::copy_options(); opts |= std::filesystem::copy_options::overwrite_existing; opts |= std::filesystem::copy_options::recursive; @@ -239,83 +238,83 @@ bool PythonWrapper::GenerateAnimator( std::filesystem::copy("Resources/Fonts", tastt_fonts_path, opts, error); if (error.value()) { wxLogError("Failed to copy animations: %s (%d)", error.message(), error.value()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } - out->AppendText("success!\n"); + Log(out, "success!\n"); } { - out->AppendText("Generating guid.map... "); + Log(out, "Generating guid.map... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libunity_path, "guid_map", "--project_root", unity_assets_path, "--save_to", guid_map_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to generate guid.map: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Generating animations... "); + Log(out, "Generating animations... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libtastt_path, "gen_anims", "--gen_anim_dir", tastt_animations_path.string(), "--guid_map", guid_map_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to generate animations: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Generating FX layer... "); + Log(out, "Generating FX layer... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libtastt_path, "gen_fx", "--fx_dest", tastt_fx0_path.string(), "--gen_anim_dir", tastt_animations_path.string(), "--guid_map", guid_map_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to generate FX layer: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Adding enable/disable toggle... "); + Log(out, "Adding enable/disable toggle... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libunity_path, "add_toggle", "--fx0", tastt_fx0_path.string(), @@ -323,48 +322,48 @@ bool PythonWrapper::GenerateAnimator( "--gen_anim_dir", tastt_animations_path.string(), "--guid_map", guid_map_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to add enable/disable toggle: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Merging with user animator... "); + Log(out, "Merging with user animator... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libunity_path, "merge", "--fx0", unity_animator_path, "--fx1", tastt_fx1_path.string(), "--fx_dest", tastt_fx2_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to merge animators: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Setting noop animations... "); + Log(out, "Setting noop animations... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ libunity_path, "set_noop_anim", "--fx0", tastt_fx2_path.string(), @@ -372,65 +371,65 @@ bool PythonWrapper::GenerateAnimator( "--gen_anim_dir", tastt_animations_path.string(), "--guid_map", guid_map_path.string() }, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to set noop animations: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Generating avatar parameters... "); + Log(out, "Generating avatar parameters... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ generate_params_path, "--old_params", unity_parameters_path, "--new_params", tastt_params_path.string()}, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to generate avatar parameters: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } { - out->AppendText("Generating avatar menu... "); + Log(out, "Generating avatar menu... "); std::string py_stdout, py_stderr; if (InvokeWithArgs({ generate_menu_path, "--old_menu", unity_menu_path, "--new_menu", tastt_menu_path.string()}, &py_stdout, &py_stderr)) { - out->AppendText("success!\n"); - out->AppendText(py_stdout.c_str()); + Log(out, "success!\n"); + Log(out, py_stdout.c_str()); if (!py_stdout.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } - out->AppendText(py_stderr.c_str()); + Log(out, py_stderr.c_str()); if (!py_stderr.empty()) { - out->AppendText("\n"); + Log(out, "\n"); } } else { wxLogError("Failed to generate avatar menu: %s", py_stderr.c_str()); - out->AppendText("failed!\n"); + Log(out, "failed!\n"); return false; } } diff --git a/GUI/README.md b/GUI/README.md index 079474d..f8cc3a1 100644 --- a/GUI/README.md +++ b/GUI/README.md @@ -1,10 +1,16 @@ ## Build instructions 0. Open Powershell. -1. Execute Libraries/fetch.ps1. -2. Install Visual Studio 2022. +1. Make sure you've downloaded submodules: +``` +$ git submodule init +$ git submodule update +``` +2. Execute Libraries/fetch.ps1. 3. Open Libraries/wx/build/msw/wx\_vc17.sln with Visual Studio 2022. 4. Build x64/Release. + 1. The build configuration is in the top. By default it's probably Debug/x64. + 2. To build: ctrl+shift+B 5. Open GUI/GUI.sln with Visual Studio 2022. 6. Build x64/Release. 7. Run package.ps1 from powershell. |
