diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index 9720cebcc9..39cc5de182 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -423,6 +423,7 @@ endif ()
include(OCCT/OCCT.cmake)
include(OpenCV/OpenCV.cmake)
include(python3/python3.cmake)
+include(wxInspector/wxInspector.cmake)
set(_dep_list
dep_Boost
@@ -446,6 +447,7 @@ set(_dep_list
${EXPAT_PKG}
dep_libnoise
dep_python3
+ dep_wxInspector
)
if (MSVC)
diff --git a/deps/wxInspector/wxInspector.cmake b/deps/wxInspector/wxInspector.cmake
new file mode 100644
index 0000000000..97c3810809
--- /dev/null
+++ b/deps/wxInspector/wxInspector.cmake
@@ -0,0 +1,13 @@
+orcaslicer_add_cmake_project(
+ wxInspector
+ URL https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip
+ URL_HASH SHA256=0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7
+ DEPENDS ${WXWIDGETS_PKG}
+ CMAKE_ARGS
+ -DCMAKE_CXX_FLAGS="-DwxDEBUG_LEVEL=0"
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON
+)
+
+if (MSVC)
+ add_debug_dep(dep_wxInspector)
+endif ()
diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake
index 682b28bac4..1e2cc85f78 100644
--- a/deps/wxWidgets/wxWidgets.cmake
+++ b/deps/wxWidgets/wxWidgets.cmake
@@ -26,6 +26,7 @@ orcaslicer_add_cmake_project(
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
GIT_TAG v3.3.2
GIT_SHALLOW ON
+ GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON
diff --git a/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md b/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md
new file mode 100644
index 0000000000..5b3669ace4
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md
@@ -0,0 +1,111 @@
+# Move `wxInspectable` into `DPIAware` — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Move `wxInspector::wxInspectable` from individual leaf classes into the common `DPIAware
` template so every DPIAware widget is automatically inspectable and gets the inspector keyboard shortcut.
+
+**Architecture:** `DPIAware
` gains `wxInspector::wxInspectable` as a second base class and calls `SetupInspectorAccelerator(this)` in its constructor. `DPIDialog` and `MainFrame` drop their now-redundant `wxInspectable` inheritance and `SetupInspectorAccelerator` calls.
+
+**Tech Stack:** C++17, wxWidgets, wxInspector
+
+## Global Constraints
+
+- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe`
+- Use `--config RelWithDebInfo` for all builds
+- Cross-platform: must compile on Windows, macOS, and Linux
+- Match existing code style: PascalCase classes, `#pragma once`
+- Do NOT commit files under `.superpowers/`
+- Do NOT commit `task.md`
+
+---
+
+### Task 1: Move `wxInspectable` and `SetupInspectorAccelerator` into `DPIAware
`
+
+**Files:**
+- Modify: `src/slic3r/GUI/GUI_Utils.hpp:92` (DPIAware template — add wxInspectable base + SetupInspectorAccelerator call)
+- Modify: `src/slic3r/GUI/GUI_Utils.hpp:276` (DPIDialog — drop wxInspectable + SetupInspectorAccelerator)
+- Modify: `src/slic3r/GUI/MainFrame.hpp:96` (MainFrame — drop wxInspectable)
+- Modify: `src/slic3r/GUI/MainFrame.cpp:304` (MainFrame constructor — drop SetupInspectorAccelerator)
+
+**Interfaces:**
+- Consumes: Nothing (standalone refactor)
+- Produces: All DPIAware widgets automatically inherit `wxInspector::wxInspectable` and get Ctrl+Shift+I accelerator
+
+- [ ] **Step 1: Add `wxInspectable` to `DPIAware
` and call `SetupInspectorAccelerator`**
+
+In `src/slic3r/GUI/GUI_Utils.hpp`, line 92, change the base class:
+
+```cpp
+// Before:
+template class DPIAware : public P
+// After:
+template class DPIAware : public P, public wxInspector::wxInspectable
+```
+
+In the constructor body of `DPIAware`, after `this->CenterOnParent();` (currently line 110), add:
+
+```cpp
+SetupInspectorAccelerator(this);
+```
+
+(`` is already included at line 23.)
+
+- [ ] **Step 2: Remove redundant `wxInspectable` and `SetupInspectorAccelerator` from `DPIDialog`**
+
+In `src/slic3r/GUI/GUI_Utils.hpp`, line 276, change:
+
+```cpp
+// Before:
+class DPIDialog : public DPIAware, public wxInspector::wxInspectable
+// After:
+class DPIDialog : public DPIAware
+```
+
+In the `DPIDialog` constructor body, remove the `SetupInspectorAccelerator(this);` line (currently line 286). The rest of the constructor stays.
+
+- [ ] **Step 3: Remove redundant `wxInspectable` from `MainFrame`**
+
+In `src/slic3r/GUI/MainFrame.hpp`, line 96, change:
+
+```cpp
+// Before:
+class MainFrame : public DPIFrame, public wxInspector::wxInspectable
+// After:
+class MainFrame : public DPIFrame
+```
+
+`MainFrame` now gets `wxInspectable` through `DPIFrame` → `DPIAware`.
+
+- [ ] **Step 4: Remove redundant `SetupInspectorAccelerator` from `MainFrame` constructor**
+
+In `src/slic3r/GUI/MainFrame.cpp`, line 304, remove the line:
+
+```cpp
+SetupInspectorAccelerator(this);
+```
+
+It is now called automatically by the `DPIAware` constructor.
+
+- [ ] **Step 5: Build to verify compilation**
+
+```powershell
+$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
+& $cmakePath --build . --config RelWithDebInfo --target ALL_BUILD -- -m
+```
+
+Expected: Build succeeds with zero new errors or warnings.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/MainFrame.hpp src/slic3r/GUI/MainFrame.cpp
+git commit -m "refactor: move wxInspectable and SetupInspectorAccelerator into DPIAware
+
+DPIAware now inherits wxInspector::wxInspectable and calls
+SetupInspectorAccelerator in its constructor, making all DPIAware
+widgets automatically appear in the inspector tree with the
+Ctrl+Shift+I shortcut. Remove redundant wxInspectable inheritance
+and SetupInspectorAccelerator calls from DPIDialog and MainFrame.
+
+Co-Authored-By: Claude "
+```
diff --git a/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md b/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md
new file mode 100644
index 0000000000..c3061b223d
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md
@@ -0,0 +1,753 @@
+# wxInspector Plugins for OrcaSlicer — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build two wxInspector plugins (DPIAware + CustomWidgets) that expose OrcaSlicer custom control properties in the inspector's property grid.
+
+**Architecture:** Two plugins in a shared folder under `src/slic3r/Utils/wxInspectorPlugins/`. DPIAwarePlugin uses `dynamic_cast/` for detection; CustomWidgetsPlugin uses per-type `dynamic_cast`. Both registered as static singletons via a single inline function in `Registration.hpp`, called from `MainFrame` constructor.
+
+**Tech Stack:** C++17, wxWidgets, wxInspector plugin API (`wx/inspector/plugin.h`, `wx/inspector/inspector.h`), OrcaSlicer custom widget headers
+
+## Global Constraints
+
+- Plugins placed under `src/slic3r/Utils/wxInspectorPlugins/`
+- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe`
+- Minimal source changes: only trivial (one-line) getters/setters added to existing classes
+- Cross-platform: must compile on Windows, macOS, and Linux
+- Match existing code style: PascalCase classes, snake_case functions, `#pragma once`
+
+---
+
+### Task 1: Add getters/setters to existing Orca widget headers
+
+**Files:**
+- Modify: `src/slic3r/GUI/GUI_Utils.hpp` (DPIAware template — add 4 methods)
+- Modify: `src/slic3r/GUI/Widgets/Button.hpp` (add 3 getters)
+- Modify: `src/slic3r/GUI/Widgets/CheckBox.hpp` (add 1 getter)
+- Modify: `src/slic3r/GUI/Widgets/TextInput.hpp` (add 1 getter)
+- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.hpp` (add 4 getter declarations)
+- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.cpp` (add 4 getter implementations)
+
+**Interfaces:**
+- Consumes: Nothing (prerequisite for all other tasks)
+- Produces:
+ - `DPIAware::set_scale_factor(float)`, `DPIAware
::set_prev_scale_factor(float)`, `DPIAware
::set_em_unit(int)`, `DPIAware
::force_rescale() const`
+ - `Button::GetStyle()`, `Button::GetType()`, `Button::IsSelected()`
+ - `CheckBox::IsHalfChecked()`
+ - `TextInput::GetCornerRadius()`
+ - `LabeledStaticBox::GetCornerRadius()`, `LabeledStaticBox::GetBorderWidth()`, `LabeledStaticBox::GetBorderColor()`, `LabeledStaticBox::GetScale()`
+
+- [ ] **Step 1: Add DPIAware setters/getter in GUI_Utils.hpp**
+
+After line 184 (`float prev_scale_factor() const { return m_prev_scale_factor; }`), add:
+
+```cpp
+void set_scale_factor(float v) { m_scale_factor = v; }
+void set_prev_scale_factor(float v) { m_prev_scale_factor = v; }
+void set_em_unit(int v) { m_em_unit = v; }
+bool force_rescale() const { return m_force_rescale; }
+```
+
+- [ ] **Step 2: Add Button getters in Button.hpp**
+
+After line 79 (`void SetSelected(bool selected = true) { m_selected = selected; }`), add:
+
+```cpp
+ButtonStyle GetStyle() const { return m_style; }
+ButtonType GetType() const { return m_type; }
+bool IsSelected() const { return m_selected; }
+```
+
+- [ ] **Step 3: Add CheckBox getter in CheckBox.hpp**
+
+After line 16 (`void SetHalfChecked(bool value = true);`), add:
+
+```cpp
+bool IsHalfChecked() const { return m_half_checked; }
+```
+
+- [ ] **Step 4: Add TextInput getter in TextInput.hpp**
+
+After line 44 (`void SetCornerRadius(double radius);`), add:
+
+```cpp
+int GetCornerRadius() const { return static_cast(radius); }
+```
+
+(Note: `radius` is inherited from `StaticBox` which has it as a protected `double` member.)
+
+- [ ] **Step 5: Add LabeledStaticBox getter declarations in LabeledStaticBox.hpp**
+
+After line 46 (`bool Enable(bool enable) override;`), add:
+
+```cpp
+int GetCornerRadius() const { return m_radius; }
+int GetBorderWidth() const { return m_border_width; }
+StateColor GetBorderColor() const { return border_color; }
+float GetScale() const { return m_scale; }
+```
+
+(Note: all of `m_radius`, `m_border_width`, `border_color`, `m_scale` are protected members, accessible to inline methods.)
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/Widgets/Button.hpp src/slic3r/GUI/Widgets/CheckBox.hpp src/slic3r/GUI/Widgets/TextInput.hpp src/slic3r/GUI/Widgets/LabeledStaticBox.hpp
+git commit -m "feat: add getters/setters for wxInspector plugin access
+
+Add minimal public accessors to DPIAware (set_scale_factor,
+set_prev_scale_factor, set_em_unit, force_rescale), Button
+(GetStyle, GetType, IsSelected), CheckBox (IsHalfChecked),
+TextInput (GetCornerRadius), and LabeledStaticBox
+(GetCornerRadius, GetBorderWidth, GetBorderColor, GetScale)."
+```
+
+---
+
+### Task 2: Create Registration helper header
+
+**Files:**
+- Create: `src/slic3r/Utils/wxInspectorPlugins/Registration.hpp`
+
+**Interfaces:**
+- Consumes: Nothing (forward-declares plugin classes)
+- Produces: `RegisterOrcaInspectorPlugins()`
+
+- [ ] **Step 1: Create directory**
+
+```bash
+mkdir -p src/slic3r/Utils/wxInspectorPlugins
+```
+
+- [ ] **Step 2: Write Registration.hpp**
+
+```cpp
+#pragma once
+
+namespace wxInspector {
+class wxInspectorPlugin;
+void RegisterPlugin(wxInspectorPlugin* plugin);
+}
+
+// Forward declare our plugins
+class DPIAwarePlugin;
+class CustomWidgetsPlugin;
+
+inline void RegisterOrcaInspectorPlugins()
+{
+ static DPIAwarePlugin dpiaware;
+ static CustomWidgetsPlugin customWidgets;
+ wxInspector::RegisterPlugin(&dpiaware);
+ wxInspector::RegisterPlugin(&customWidgets);
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/slic3r/Utils/wxInspectorPlugins/Registration.hpp
+git commit -m "feat: add wxInspector plugin registration helper
+
+Add RegisterOrcaInspectorPlugins() inline function that creates
+and registers the DPIAwarePlugin and CustomWidgetsPlugin as
+static instances (matching wxInspector's built-in pattern)."
+```
+
+---
+
+### Task 3: Create DPIAwarePlugin
+
+**Files:**
+- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp`
+- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp`
+
+**Interfaces:**
+- Consumes: Task 1 (DPIAware getters/setters), Task 2 (registration pattern)
+- Produces: `class DPIAwarePlugin : public wxInspector::wxInspectorPlugin`
+
+- [ ] **Step 1: Write DPIAwarePlugin.hpp**
+
+```cpp
+#pragma once
+
+#include
+
+class DPIAwarePlugin : public wxInspector::wxInspectorPlugin
+{
+public:
+ wxString GetName() const override;
+
+ bool CanProvideProperties(wxClassInfo* info) override;
+
+ wxVector GetProperties(
+ wxInspector::InspectableObject& obj) override;
+};
+```
+
+- [ ] **Step 2: Write DPIAwarePlugin.cpp**
+
+```cpp
+#include "DPIAwarePlugin.hpp"
+
+#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware
+
+#include
+
+namespace {
+
+template
+void addDPIProps(T* dpi, wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String,
+ wxString::Format("%.2f", dpi->scale_factor()), false, {},
+ [dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); },
+ [dpi](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ dpi->set_scale_factor((float) val);
+ return true;
+ }});
+
+ props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String,
+ wxString::Format("%.2f", dpi->prev_scale_factor()), false, {},
+ [dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); },
+ [dpi](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ dpi->set_prev_scale_factor((float) val);
+ return true;
+ }});
+
+ props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer,
+ wxString::Format("%d", dpi->em_unit()), false, {},
+ [dpi]() { return wxString::Format("%d", dpi->em_unit()); },
+ [dpi](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ dpi->set_em_unit((int) val);
+ return true;
+ }});
+
+ props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly,
+ dpi->normal_font().GetNativeFontInfoDesc(), true, {},
+ [dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); },
+ nullptr});
+
+ props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean,
+ dpi->force_rescale() ? "true" : "false", true, {},
+ [dpi]() { return dpi->force_rescale() ? "true" : "false"; },
+ nullptr});
+}
+
+} // anonymous namespace
+
+wxString DPIAwarePlugin::GetName() const
+{
+ return "OrcaDPIAware";
+}
+
+bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info)
+{
+ return info->IsKindOf(CLASSINFO(wxWindow));
+}
+
+wxVector DPIAwarePlugin::GetProperties(
+ wxInspector::InspectableObject& obj)
+{
+ wxVector props;
+ wxWindow* win = obj.AsWindow();
+ if (!win) return props;
+
+ if (auto* frame = dynamic_cast(win)) {
+ addDPIProps(frame, props);
+ } else if (auto* dlg = dynamic_cast(win)) {
+ addDPIProps(dlg, props);
+ }
+
+ return props;
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
+git commit -m "feat: add DPIAware wxInspector plugin
+
+Exposes DPI scaling properties (scale_factor, prev_scale_factor,
+em_unit, normal_font, force_rescale) on DPIFrame and DPIDialog
+widgets. Uses dynamic_cast for detection and a template helper
+to capture the correct static type for lambda accessors."
+```
+
+---
+
+### Task 4: Create CustomWidgetsPlugin
+
+**Files:**
+- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp`
+- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp`
+
+**Interfaces:**
+- Consumes: Task 1 (all widget getters), Task 2 (registration pattern)
+- Produces: `class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin`
+
+- [ ] **Step 1: Write CustomWidgetsPlugin.hpp**
+
+```cpp
+#pragma once
+
+#include
+
+class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin
+{
+public:
+ wxString GetName() const override;
+
+ bool CanProvideProperties(wxClassInfo* info) override;
+
+ wxVector GetProperties(
+ wxInspector::InspectableObject& obj) override;
+
+private:
+ void addButtonProps(class Button* btn,
+ wxVector& props);
+ void addCheckBoxProps(class CheckBox* cb,
+ wxVector& props);
+ void addTextInputProps(class TextInput* ti,
+ wxVector& props);
+ void addSwitchButtonProps(class SwitchButton* sb,
+ wxVector& props);
+ void addProgressBarProps(class ProgressBar* pb,
+ wxVector& props);
+ void addLabelProps(class Label* lbl,
+ wxVector& props);
+ void addLabeledStaticBoxProps(class LabeledStaticBox* lsb,
+ wxVector& props);
+};
+```
+
+- [ ] **Step 2: Write CustomWidgetsPlugin.cpp — includes and GetName/CanProvideProperties**
+
+```cpp
+#include "CustomWidgetsPlugin.hpp"
+
+#include "slic3r/GUI/Widgets/Button.hpp"
+#include "slic3r/GUI/Widgets/CheckBox.hpp"
+#include "slic3r/GUI/Widgets/TextInput.hpp"
+#include "slic3r/GUI/Widgets/SwitchButton.hpp"
+#include "slic3r/GUI/Widgets/ProgressBar.hpp"
+#include "slic3r/GUI/Widgets/Label.hpp"
+#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp"
+
+#include
+#include
+
+wxString CustomWidgetsPlugin::GetName() const
+{
+ return "OrcaCustomWidgets";
+}
+
+bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info)
+{
+ return info->IsKindOf(CLASSINFO(wxWindow));
+}
+
+wxVector CustomWidgetsPlugin::GetProperties(
+ wxInspector::InspectableObject& obj)
+{
+ wxVector props;
+ wxWindow* win = obj.AsWindow();
+ if (!win) return props;
+
+ if (auto* btn = dynamic_cast(win))
+ addButtonProps(btn, props);
+ if (auto* cb = dynamic_cast(win))
+ addCheckBoxProps(cb, props);
+ if (auto* ti = dynamic_cast(win))
+ addTextInputProps(ti, props);
+ if (auto* sb = dynamic_cast(win))
+ addSwitchButtonProps(sb, props);
+ if (auto* pb = dynamic_cast(win))
+ addProgressBarProps(pb, props);
+ if (auto* lbl = dynamic_cast(win))
+ addLabelProps(lbl, props);
+ if (auto* lsb = dynamic_cast(win))
+ addLabeledStaticBoxProps(lsb, props);
+
+ return props;
+}
+```
+
+- [ ] **Step 3: Write CustomWidgetsPlugin.cpp — addButtonProps**
+
+```cpp
+void CustomWidgetsPlugin::addButtonProps(Button* btn,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ wxVector styleChoices;
+ styleChoices.push_back("Regular");
+ styleChoices.push_back("Confirm");
+ styleChoices.push_back("Alert");
+ styleChoices.push_back("Disabled");
+
+ auto styleToStr = [](ButtonStyle s) -> wxString {
+ switch (s) {
+ case ButtonStyle::Regular: return "Regular";
+ case ButtonStyle::Confirm: return "Confirm";
+ case ButtonStyle::Alert: return "Alert";
+ case ButtonStyle::Disabled: return "Disabled";
+ }
+ return "Regular";
+ };
+
+ props.push_back({"Button Style", "Orca Button", PropertyType::Choice,
+ styleToStr(btn->GetStyle()), false, styleChoices,
+ [btn, styleToStr]() { return styleToStr(btn->GetStyle()); },
+ [btn](const wxString& v) {
+ ButtonStyle s = ButtonStyle::Regular;
+ if (v == "Confirm") s = ButtonStyle::Confirm;
+ else if (v == "Alert") s = ButtonStyle::Alert;
+ else if (v == "Disabled") s = ButtonStyle::Disabled;
+ btn->SetStyle(s, btn->GetType());
+ return true;
+ }});
+
+ wxVector typeChoices;
+ typeChoices.push_back("Compact");
+ typeChoices.push_back("Window");
+ typeChoices.push_back("Choice");
+ typeChoices.push_back("Parameter");
+ typeChoices.push_back("Icon");
+ typeChoices.push_back("Expanded");
+
+ auto typeToStr = [](ButtonType t) -> wxString {
+ switch (t) {
+ case ButtonType::Compact: return "Compact";
+ case ButtonType::Window: return "Window";
+ case ButtonType::Choice: return "Choice";
+ case ButtonType::Parameter: return "Parameter";
+ case ButtonType::Icon: return "Icon";
+ case ButtonType::Expanded: return "Expanded";
+ }
+ return "Compact";
+ };
+
+ props.push_back({"Button Type", "Orca Button", PropertyType::Choice,
+ typeToStr(btn->GetType()), false, typeChoices,
+ [btn, typeToStr]() { return typeToStr(btn->GetType()); },
+ [btn](const wxString& v) {
+ ButtonType t = ButtonType::Compact;
+ if (v == "Window") t = ButtonType::Window;
+ else if (v == "Choice") t = ButtonType::Choice;
+ else if (v == "Parameter") t = ButtonType::Parameter;
+ else if (v == "Icon") t = ButtonType::Icon;
+ else if (v == "Expanded") t = ButtonType::Expanded;
+ btn->SetStyle(btn->GetStyle(), t);
+ return true;
+ }});
+
+ props.push_back({"Selected", "Orca Button", PropertyType::Boolean,
+ btn->IsSelected() ? "true" : "false", false, {},
+ [btn]() { return btn->IsSelected() ? "true" : "false"; },
+ [btn](const wxString& v) {
+ btn->SetSelected(v == "true");
+ btn->Refresh();
+ return true;
+ }});
+}
+```
+
+- [ ] **Step 4: Write CustomWidgetsPlugin.cpp — addCheckBoxProps**
+
+```cpp
+void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean,
+ cb->IsHalfChecked() ? "true" : "false", false, {},
+ [cb]() { return cb->IsHalfChecked() ? "true" : "false"; },
+ [cb](const wxString& v) {
+ cb->SetHalfChecked(v == "true");
+ return true;
+ }});
+}
+```
+
+- [ ] **Step 5: Write CustomWidgetsPlugin.cpp — addTextInputProps**
+
+```cpp
+void CustomWidgetsPlugin::addTextInputProps(TextInput* ti,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Label", "Orca TextInput", PropertyType::String,
+ ti->GetLabel(), false, {},
+ [ti]() { return ti->GetLabel(); },
+ [ti](const wxString& v) { ti->SetLabel(v); return true; }});
+
+ props.push_back({"Text Value", "Orca TextInput", PropertyType::String,
+ ti->GetTextCtrl()->GetValue(), false, {},
+ [ti]() { return ti->GetTextCtrl()->GetValue(); },
+ [ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }});
+
+ props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer,
+ wxString::Format("%d", ti->GetCornerRadius()), false, {},
+ [ti]() { return wxString::Format("%d", ti->GetCornerRadius()); },
+ [ti](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ ti->SetCornerRadius((double) val);
+ ti->Refresh();
+ return true;
+ }});
+}
+```
+
+- [ ] **Step 6: Write CustomWidgetsPlugin.cpp — addSwitchButtonProps**
+
+```cpp
+void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean,
+ sb->GetValue() ? "true" : "false", false, {},
+ [sb]() { return sb->GetValue() ? "true" : "false"; },
+ [sb](const wxString& v) {
+ sb->SetValue(v == "true");
+ return true;
+ }});
+}
+```
+
+(Note: `GetValue()` and `SetValue()` are inherited from `wxBitmapToggleButton` → `wxToggleButton`.)
+
+- [ ] **Step 7: Write CustomWidgetsPlugin.cpp — addProgressBarProps**
+
+```cpp
+void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String,
+ wxString::Format("%.2f", pb->m_proportion), false, {},
+ [pb]() { return wxString::Format("%.2f", pb->m_proportion); },
+ [pb](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ pb->m_proportion = val;
+ pb->Refresh();
+ return true;
+ }});
+
+ props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean,
+ pb->m_shownumber ? "true" : "false", false, {},
+ [pb]() { return pb->m_shownumber ? "true" : "false"; },
+ [pb](const wxString& v) {
+ pb->m_shownumber = (v == "true");
+ pb->Refresh();
+ return true;
+ }});
+}
+```
+
+(Note: `m_proportion` and `m_shownumber` are public members on `ProgressBar`.)
+
+- [ ] **Step 8: Write CustomWidgetsPlugin.cpp — addLabelProps**
+
+```cpp
+void CustomWidgetsPlugin::addLabelProps(Label* lbl,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ bool isHyperlink = (lbl->GetWindowStyleFlag() & 0x0020) != 0; // LB_HYPERLINK
+
+ props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean,
+ isHyperlink ? "true" : "false", true, {},
+ [lbl]() {
+ return (lbl->GetWindowStyleFlag() & 0x0020) ? "true" : "false";
+ },
+ nullptr});
+
+ props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly,
+ wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {},
+ [lbl]() {
+ return wxString::Format("%d", lbl->GetFont().GetPointSize());
+ },
+ nullptr});
+}
+```
+
+- [ ] **Step 9: Write CustomWidgetsPlugin.cpp — addLabeledStaticBoxProps**
+
+```cpp
+void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer,
+ wxString::Format("%d", lsb->GetCornerRadius()), false, {},
+ [lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); },
+ [lsb](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ lsb->SetCornerRadius((int) val);
+ return true;
+ }});
+
+ props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer,
+ wxString::Format("%d", lsb->GetBorderWidth()), false, {},
+ [lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); },
+ [lsb](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ lsb->SetBorderWidth((int) val);
+ return true;
+ }});
+
+ // Border Color: display as hex string
+ wxColour bc = lsb->GetBorderColor().colorForStates(0);
+ props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String,
+ bc.GetAsString(wxC2S_HTML_SYNTAX), false, {},
+ [lsb]() {
+ return lsb->GetBorderColor()
+ .colorForStates(0)
+ .GetAsString(wxC2S_HTML_SYNTAX);
+ },
+ [lsb](const wxString& v) {
+ wxColour c(v);
+ if (!c.IsOk()) return false;
+ lsb->SetBorderColor(StateColor(c));
+ return true;
+ }});
+
+ props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly,
+ wxString::Format("%.2f", lsb->GetScale()), true, {},
+ [lsb]() { return wxString::Format("%.2f", lsb->GetScale()); },
+ nullptr});
+}
+```
+
+- [ ] **Step 10: Commit**
+
+```bash
+git add src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
+git commit -m "feat: add OrcaCustomWidgets wxInspector plugin
+
+Exposes Orca-specific properties on 7 widget types:
+- Button: Style, Type, Selected
+- CheckBox: Half Checked
+- TextInput: Label, Text Value, Corner Radius
+- SwitchButton: Value
+- ProgressBar: Proportion, Show Number
+- Label: Is Hyperlink, Font Point Size
+- LabeledStaticBox: Corner Radius, Border Width, Border Color, Scale
+
+Each widget type uses dynamic_cast for safe detection."
+```
+
+---
+
+### Task 5: Wire plugins into MainFrame and CMakeLists
+
+**Files:**
+- Modify: `src/slic3r/GUI/MainFrame.cpp` (add include + registration call)
+- Modify: `src/slic3r/CMakeLists.txt` (add 4 source files)
+
+**Interfaces:**
+- Consumes: Tasks 1-4 (all plugins and registration helper)
+- Produces: Registered plugins available at runtime, buildable project
+
+- [ ] **Step 1: Add include in MainFrame.cpp**
+
+After the existing includes (around line 30, near the other Utils includes), add:
+
+```cpp
+#include "slic3r/Utils/wxInspectorPlugins/Registration.hpp"
+```
+
+- [ ] **Step 2: Add registration call in MainFrame constructor**
+
+After `SetupInspectorAccelerator(this);` (currently line ~303), add:
+
+```cpp
+RegisterOrcaInspectorPlugins();
+```
+
+- [ ] **Step 3: Add source files to CMakeLists.txt**
+
+Find the `SLIC3R_GUI_SOURCES` list in `src/slic3r/CMakeLists.txt`. After the existing `Utils/*.cpp` entries (around line 650-754), add:
+
+```cmake
+Utils/wxInspectorPlugins/DPIAwarePlugin.hpp
+Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
+Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp
+Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
+Utils/wxInspectorPlugins/Registration.hpp
+```
+
+(Note: Add all 5 files — 2 .hpp + 2 .cpp + 1 Registration.hpp. wxWidgets cmake needs headers listed too for the resource system.)
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/slic3r/GUI/MainFrame.cpp src/slic3r/CMakeLists.txt
+git commit -m "feat: wire wxInspector plugins into MainFrame and build
+
+- Call RegisterOrcaInspectorPlugins() after SetupInspectorAccelerator
+- Add all plugin source files to SLIC3R_GUI_SOURCES"
+```
+
+---
+
+### Task 6: Build and verify
+
+**Files:**
+- None modified (verification only)
+
+- [ ] **Step 1: Configure the build**
+
+```powershell
+$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
+& $cmakePath --build . --config Debug --target ALL_BUILD -- -m
+```
+
+Expected: Build succeeds with zero errors and zero warnings from our new files.
+
+- [ ] **Step 2: Fix any compilation errors**
+
+If the build fails:
+- Check that `#include` paths resolve (the `slic3r/GUI/…` relative paths use `src/` as the include root — verify this is set up in CMake via `include_directories`)
+- Check that `ButtonStyle` and `ButtonType` enums are visible (they're defined in `Button.hpp`)
+- Check that `StateColor` constructor from `wxColour` is valid (it has `StateColor(wxColour const&)`)
+- Check that `LabeledStaticBox::GetBorderColor()` returns by value (StateColor copy is fine)
+- On macOS: static box margin removal call needs `#ifdef __WXOSX__` guard
+
+- [ ] **Step 3: Launch OrcaSlicer and verify inspector**
+
+Launch the built OrcaSlicer, press Ctrl+Shift+I to open the inspector:
+1. Select the MainFrame in the tree — verify "DPI Scaling" category appears with Scale Factor, Prev Scale Factor, EM Unit, Normal Font, Force Rescale
+2. Select an Orca Button — verify "Orca Button" category appears
+3. Select an Orca CheckBox — verify "Orca CheckBox" category appears
+4. Edit a property value (e.g., Scale Factor) — verify the setter applies correctly
+5. Select a LabeledStaticBox — verify corner radius, border width, border color, scale appear
+
+- [ ] **Step 5: Commit (if fixes were needed) or mark complete**
+
+```bash
+git status
+```
+
+If clean: verification complete. If changes were made: `git add` and commit with fix message.
diff --git a/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md b/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md
new file mode 100644
index 0000000000..3a4e873af6
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md
@@ -0,0 +1,102 @@
+# Move `wxInspectable` into `DPIAware` — Design Spec
+
+Date: 2026-07-23
+Branch: `dev/layout-inspector`
+
+## Overview
+
+Move the `wxInspector::wxInspectable` base class from individual leaf classes (`DPIDialog`, `MainFrame`) into the common `DPIAware` template. This makes every DPIAware widget automatically visible in the inspector tree without requiring each subclass to opt in.
+
+## Motivation
+
+Currently, only `DPIDialog` and `MainFrame` explicitly inherit `wxInspectable`. `DPIFrame` (which `MainFrame` inherits from) does not — `MainFrame` adds it manually. This means:
+
+- Any `DPIAware` widget that isn't `DPIDialog` or `MainFrame` is invisible in the inspector tree
+- `DPIFrame` subclasses (`BaseTransparentDPIFrame`, `ImageDPIFrame`, `ModelMallDialog`, `MediaFileFrame`, `SecondaryCheckDialog`, `PrintErrorDialog`, etc.) don't appear
+- Adding a new DPIAware widget type requires remembering to also inherit `wxInspectable`
+
+Moving `wxInspectable` to `DPIAware` fixes this for all current and future DPIAware widgets at once.
+
+## Design
+
+### Change 1: `GUI_Utils.hpp` — `DPIAware`
+
+Add `wxInspector::wxInspectable` as a second base class, and call `SetupInspectorAccelerator(this)` in the constructor (after `this->CenterOnParent()`):
+
+```cpp
+// Before:
+template class DPIAware : public P
+
+// After:
+template class DPIAware : public P, public wxInspector::wxInspectable
+```
+
+Add in the constructor body (after `this->CenterOnParent()` at line 110):
+```cpp
+SetupInspectorAccelerator(this);
+```
+
+This gives every `DPIAware` widget both inspectability and the Ctrl+Shift+I keyboard shortcut automatically. `#include ` is already present in the file.
+
+### Change 2: `GUI_Utils.hpp` — `DPIDialog`
+
+Remove the now-redundant `wxInspector::wxInspectable` and the `SetupInspectorAccelerator(this)` call:
+
+```cpp
+// Before:
+class DPIDialog : public DPIAware, public wxInspector::wxInspectable
+// ...
+ SetupInspectorAccelerator(this);
+
+// After:
+class DPIDialog : public DPIAware
+// (SetupInspectorAccelerator call removed — now done in DPIAware constructor)
+```
+
+`DPIDialog` gets `wxInspectable` and the accelerator through `DPIAware` now.
+
+### Change 3: `MainFrame.hpp` — `MainFrame`
+
+Remove the now-redundant `wxInspector::wxInspectable`:
+
+```cpp
+// Before:
+class MainFrame : public DPIFrame, public wxInspector::wxInspectable
+
+// After:
+class MainFrame : public DPIFrame
+```
+
+`MainFrame` gets `wxInspectable` through `DPIFrame` → `DPIAware`.
+
+### Change 4: `MainFrame.cpp` — `MainFrame` constructor
+
+Remove the now-redundant `SetupInspectorAccelerator(this)` call (line 304). It will be called automatically by the `DPIAware` constructor.
+
+## Impact
+
+| Widget | Before | After |
+|--------|--------|-------|
+| `DPIDialog` subclasses (~80) | ✓ inspectable | ✓ inspectable (transitive) |
+| `MainFrame` | ✓ inspectable | ✓ inspectable (transitive) |
+| `DPIFrame` subclasses (8 others) | ✗ invisible | ✓ inspectable |
+| Future `DPIAware` | ✗ invisible | ✓ inspectable |
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `src/slic3r/GUI/GUI_Utils.hpp` | `DPIAware` gains `wxInspector::wxInspectable` + `SetupInspectorAccelerator(this)` call; `DPIDialog` drops redundant `wxInspector::wxInspectable` and `SetupInspectorAccelerator(this)` |
+| `src/slic3r/GUI/MainFrame.hpp` | `MainFrame` drops redundant `wxInspector::wxInspectable` |
+| `src/slic3r/GUI/MainFrame.cpp` | Remove redundant `SetupInspectorAccelerator(this)` from MainFrame constructor |
+
+## Non-Goals
+
+- The `DPIAwarePlugin` detection logic (`dynamic_cast` / `dynamic_cast`) is unchanged
+- No new DPI properties — this is purely about tree visibility and accelerator setup
+
+## Risk Assessment
+
+- **Multiple inheritance**: `DPIAware` already has a vtable (virtual destructor). Adding `wxInspectable` adds a second base but no additional data members. The `wxInspector::wxInspectable` class is expected to be a lightweight marker interface.
+- **Build**: No new includes needed; `` is already included in `GUI_Utils.hpp`.
+- **Cross-platform**: The change is standard C++ multiple inheritance — no platform-specific concerns.
diff --git a/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md b/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md
new file mode 100644
index 0000000000..5715635969
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md
@@ -0,0 +1,244 @@
+# wxInspector Plugins for OrcaSlicer Custom Controls — Design Spec
+
+Date: 2026-07-23
+Branch: `dev/layout-inspector`
+
+## Overview
+
+Create wxInspector plugins that expose OrcaSlicer's custom widget properties in the inspector's property grid. Without these plugins, the inspector shows only generic wxWidgets properties — missing all DPI-awareness data, custom styling, and Orca-specific control state.
+
+## Goals
+
+1. **DPIAware properties** — Inspect and update `scale_factor`, `prev_scale_factor`, `em_unit`, and `normal_font` on any DPIAware-derived widget
+2. **Custom widget properties** — Surface Orca-specific properties on `Button`, `CheckBox`, `TextInput`, `SwitchButton`, `ProgressBar`, `Label`, and `LabeledStaticBox`
+3. **Minimal source changes** — Only add trivial (one-line) getters/setters to existing classes; no architectural refactoring of Orca's widget hierarchy
+
+## Non-Goals
+
+- Custom inspector panels or AUI tabs (use the existing property grid and method invoker)
+- Python-plugin integration (this is C++ wxInspector, not Orca's Python plugin system)
+- Event logging customization (the built-in event logger already works)
+
+## Architecture
+
+### Two Plugins
+
+| Plugin | Class | Files |
+|--------|-------|-------|
+| DPIAware plugin | `DPIAwarePlugin` | `DPIAwarePlugin.hpp`, `DPIAwarePlugin.cpp` |
+| Custom widgets plugin | `CustomWidgetsPlugin` | `CustomWidgetsPlugin.hpp`, `CustomWidgetsPlugin.cpp` |
+| Registration helper | inline function | `Registration.hpp` |
+
+All files live under `src/slic3r/Utils/wxInspectorPlugins/`.
+
+### Plugin Detection Strategy
+
+**DPIAware plugin**: Uses `dynamic_cast` and `dynamic_cast` as detection gates. `DPIFrame` = `DPIAware`, `DPIDialog` = `DPIAware`. Since these are concrete typedefs, `dynamic_cast` works at runtime. This covers `MainFrame`, `SettingsDialog`, and all 8 calibration dialogs (which inherit `DPIDialog`).
+
+**Custom widgets plugin**: Gates broadly on `CLASSINFO(wxWindow)`, then uses per-type `dynamic_cast` inside `GetProperties` to check each Orca-specific type. Only matching types append properties.
+
+### Registration
+
+A single `RegisterOrcaInspectorPlugins()` inline function in `Registration.hpp` creates both plugins as function-local statics (matching the wxInspector built-in provider pattern) and registers them via `wxInspector::RegisterPlugin()`.
+
+Called once from `MainFrame::MainFrame()` after `SetupInspectorAccelerator(this)`.
+
+### Why Separate Plugins?
+
+- DPIAware is a C++ template concept (not a wxClassInfo-isKindOf check), so it needs its own detection logic
+- Custom widgets use standard wxClassInfo-based detection, matching the built-in provider pattern
+- Two focused files are easier to review and maintain than one monolithic plugin
+- Compile-time failure isolation: if a widget header changes, only one plugin breaks
+
+## DPIAware Plugin — Property Specification
+
+### Source Changes (GUI_Utils.hpp)
+
+Four one-liner methods added to the `DPIAware` template class (public section):
+
+```cpp
+float scale_factor() const { return m_scale_factor; } // already exists
+float prev_scale_factor() const { return m_prev_scale_factor; } // already exists
+int em_unit() const { return m_em_unit; } // already exists
+void set_scale_factor(float v) { m_scale_factor = v; } // NEW
+void set_prev_scale_factor(float v) { m_prev_scale_factor = v; } // NEW
+void set_em_unit(int v) { m_em_unit = v; } // NEW
+bool force_rescale() const { return m_force_rescale; } // NEW
+// m_normal_font getter already exists: normal_font()
+```
+
+### Detection
+
+```cpp
+bool CanProvideProperties(wxClassInfo* info) override {
+ // Gated in GetProperties via dynamic_cast on the window itself
+ return info->IsKindOf(CLASSINFO(wxWindow));
+}
+```
+
+In `GetProperties`:
+```cpp
+auto* win = obj.AsWindow();
+bool isDPI = dynamic_cast(win) || dynamic_cast(win);
+if (!isDPI) return props;
+```
+
+### Property Table (category: "DPI Scaling")
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Scale Factor | String (float) | Yes | `dpi->scale_factor()` | `dpi->set_scale_factor(v)` |
+| Prev Scale Factor | String (float) | Yes | `dpi->prev_scale_factor()` | `dpi->set_prev_scale_factor(v)` |
+| EM Unit | Integer | Yes | `dpi->em_unit()` | `dpi->set_em_unit(v)` |
+| Normal Font | ReadOnly | No | `dpi->normal_font().GetNativeFontInfoDesc()` | — |
+| Force Rescale | Boolean (ReadOnly) | No | `dpi->force_rescale()` | — |
+
+**Note on setters**: The setters simply store values. They do NOT trigger a widget rescale/layout. To see the effect of a changed scale factor, use the inspector's Methods panel to call `Layout()` or resize the window — which triggers the DPI_CHANGED event path naturally.
+
+## Custom Widgets Plugin — Property Specification
+
+All properties are appended to the built-in wxWindow properties. Each widget type is independently detected via `dynamic_cast`.
+
+### Detection gates (in `GetProperties`)
+
+```cpp
+auto* win = obj.AsWindow();
+if (auto* btn = dynamic_cast(win)) { addButtonProperties(btn, props); }
+if (auto* cb = dynamic_cast(win)) { addCheckBoxProperties(cb, props); }
+if (auto* ti = dynamic_cast(win)) { addTextInputProperties(ti, props); }
+if (auto* sb = dynamic_cast(win)) { addSwitchButtonProperties(sb, props); }
+if (auto* pb = dynamic_cast(win)) { addProgressBarProperties(pb, props); }
+if (auto* lbl = dynamic_cast(win)) { addLabelProperties(lbl, props); }
+if (auto* lsb = dynamic_cast(win)) { addLabeledStaticBoxProperties(lsb, props); }
+```
+
+### Orca Button (`Button`) — category: "Orca Button"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Button Style | Choice | Yes | enum→string | string→enum |
+| Button Type | Choice | Yes | enum→string | string→enum |
+| Selected | Boolean | Yes | `m_selected` (needs getter) | `SetSelected(v)` |
+| Active Icon | ReadOnly | No | icon name string | — |
+| Inactive Icon | ReadOnly | No | icon name string | — |
+
+Choices for Button Style: `Regular`, `Confirm`, `Alert`, `Disabled`
+Choices for Button Type: `Compact`, `Window`, `Choice`, `Parameter`, `Icon`, `Expanded`
+
+**Source changes needed**: Button's `m_selected` is private. Add one-liner getter:
+```cpp
+bool IsSelected() const { return m_selected; }
+```
+
+### Orca CheckBox (`CheckBox`) — category: "Orca CheckBox"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Half Checked | Boolean | Yes | `m_half_checked` (needs getter) | `SetHalfChecked(v)` |
+
+**Source changes needed**: `m_half_checked` is private. Add one-liner getter:
+```cpp
+bool IsHalfChecked() const { return m_half_checked; }
+```
+
+### Orca TextInput (`TextInput`) — category: "Orca TextInput"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Label | String | Yes | `GetLabel()` (inherited from wxWindow) | `SetLabel(v)` (exists) |
+| Text Value | String | Yes | `GetTextCtrl()->GetValue()` (GetTextCtrl is public) | `GetTextCtrl()->SetValue(v)` |
+| Corner Radius | Integer | Yes | `GetCornerRadius()` (NEW) | `SetCornerRadius(v)` (exists) |
+
+**Source changes needed**: Add one getter to `TextInput`:
+```cpp
+int GetCornerRadius() const { return static_cast(radius); }
+```
+(`radius` is inherited from StaticBox. `SetCornerRadius(double)` already exists. `GetTextCtrl()` is already public.)
+
+### Orca SwitchButton (`SwitchButton`) — category: "Orca SwitchButton"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Value | Boolean | Yes | existing getter | existing setter |
+
+### Orca ProgressBar (`ProgressBar`) — category: "Orca ProgressBar"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Proportion | Float (0-1) | Yes | `pb->m_proportion` (public member) | `pb->m_proportion = v` |
+| Show Number | Boolean | Yes | `pb->m_shownumber` (public member) | `pb->m_shownumber = v` |
+
+**No source changes needed**: `m_proportion` and `m_shownumber` are already public members. `SetValue(int)` and `SetProgress(int)` already exist as public methods.
+
+### Orca Label (`Label`) — category: "Orca Label"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Is Hyperlink | Boolean | No | existing flag check | — |
+| Font Size | ReadOnly | No | `GetFont().GetPointSize()` | — |
+
+### LabeledStaticBox — category: "LabeledStaticBox"
+
+| Name | Type | Editable | Getter | Setter |
+|------|------|----------|--------|--------|
+| Corner Radius | Integer | Yes | `GetCornerRadius()` (NEW) | `SetCornerRadius(v)` (exists) |
+| Border Width | Integer | Yes | `GetBorderWidth()` (NEW) | `SetBorderWidth(v)` (exists) |
+| Border Color | String (hex) | Yes | `GetBorderColor()` (NEW) | `SetBorderColor(v)` (exists) |
+| Scale | Float (ReadOnly) | No | `m_scale` (protected, needs getter) | — |
+
+**Source changes needed**: Four one-liner getters added to `LabeledStaticBox`:
+```cpp
+int GetCornerRadius() const { return m_radius; }
+int GetBorderWidth() const { return m_border_width; }
+StateColor GetBorderColor() const { return border_color; }
+float GetScale() const { return m_scale; }
+```
+
+## Files Modified (Existing Code)
+
+| File | Changes |
+|------|---------|
+| `src/slic3r/GUI/GUI_Utils.hpp` | +4 methods in `DPIAware`: `set_scale_factor()`, `set_prev_scale_factor()`, `set_em_unit()`, `force_rescale()` |
+| `src/slic3r/GUI/Widgets/LabeledStaticBox.hpp` | +4 getter declarations: `GetCornerRadius()`, `GetBorderWidth()`, `GetBorderColor()`, `GetScale()` |
+| `src/slic3r/GUI/Widgets/LabeledStaticBox.cpp` | +4 getter implementations |
+| `src/slic3r/GUI/Widgets/Button.hpp` | +1 getter: `IsSelected()` |
+| `src/slic3r/GUI/Widgets/CheckBox.hpp` | +1 getter: `IsHalfChecked()` |
+| `src/slic3r/GUI/Widgets/TextInput.hpp` | +1 getter: `GetCornerRadius()` |
+| `src/slic3r/GUI/Widgets/ProgressBar.hpp` | None (public members are used directly) |
+| `src/slic3r/GUI/MainFrame.cpp` | +1 `#include`, +1 call to `RegisterOrcaInspectorPlugins()` |
+| `src/slic3r/CMakeLists.txt` | +4 entries in `SLIC3R_GUI_SOURCES` (the .cpp plugin files) |
+
+## Files Created
+
+```
+src/slic3r/Utils/wxInspectorPlugins/
+├── DPIAwarePlugin.hpp
+├── DPIAwarePlugin.cpp
+├── CustomWidgetsPlugin.hpp
+├── CustomWidgetsPlugin.cpp
+└── Registration.hpp
+```
+
+## Build & Linking
+
+The `wxInspector` dependency is already wired:
+- `deps/wxInspector/wxInspector.cmake` fetches and builds wxInspector
+- `src/CMakeLists.txt` lines 92-93 link `wxInspector::wxInspector` into `wxWidgets_LIBRARIES`
+- The plugin files only need `#include ` and `#include ` — both available from the installed dependency
+
+No new CMake dependencies needed. Only the new source files need listing in `SLIC3R_GUI_SOURCES`.
+
+## Error Handling & Edge Cases
+
+- **Stale pointers**: Plugin lambdas capture raw pointers, regenerated on every `GetProperties` call (matching wxInspector's built-in provider pattern). Pointers live only until the next tree selection.
+- **Widget destruction**: If a widget is destroyed while the inspector is showing its properties, `InspectableObject::IsValid()` returns false and properties are not displayed. The inspector won't show stale data.
+- **Invalid property values**: Setters use `sscanf` / `ToLong` with validation (matching built-in patterns). Bogus input is rejected — setter returns `false`, property grid shows error state.
+- **DPI drift**: Setting `scale_factor` without triggering rescale means displayed sizes don't match the new factor. This is acceptable — the inspector is a developer tool; operators know to call `Layout()` after making changes.
+- **Missing widget type**: If a `dynamic_cast` fails for all types, only built-in wxWindow properties are shown. No crash, no error — just reduced info.
+
+## Future Work (Out of Scope)
+
+- **StateColor visualization**: `StateColor` is a multi-value type (maps bitmask states to colors). A full solution would need a custom property editor (e.g., a table showing each state→color pair). Keep it simple for now.
+- **ScalableBitmap display**: Could show the bitmap as an inline thumbnail. Complex property editor work — deferred.
+- **More widget types**: `SwitchBoard`, `MultiSwitchButton`, `StepCtrl`, `FanControl`, `DropDown`, `ComboBox`, `AMS*` widgets could all benefit. Add as needed.
+- **Property refresh on tree selection**: Currently properties are static snapshots. A "refresh" button or auto-poll could keep values current for rapidly-changing widgets (progress bars, etc.). The built-in wxInspector already provides a tree-refresh button.
diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
index a1699f2b93..c33425f23f 100644
--- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
+++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
@@ -306,6 +306,12 @@ modules:
sha256: c08bc65a81971c1dd5783182826503369466c7e67374d1646519adf05207b684
dest: external-packages/python3
+ # wxInspector 1.0.0
+ - type: file
+ url: https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip
+ sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7
+ dest: external-packages/wxInspector
+
# ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 07c492e617..79b49cfd16 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -88,6 +88,10 @@ if (SLIC3R_GUI)
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL)
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
+
+ find_package(wxInspector REQUIRED)
+ list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector")
+
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
add_subdirectory(slic3r)
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 06ebbbf41d..2dee4bc780 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -51,4 +51,9 @@
// Enable extension of tool position imgui dialog to show actual speed profile
#define ENABLE_ACTUAL_SPEED_DEBUG 1
+// Disable layout inspector for public release
+#if BBL_RELEASE_TO_PUBLIC
+#define WXINSPECTOR_DISABLE
+#endif
+
#endif // _prusaslicer_technologies_h_
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 35cf96d171..b98396f943 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -752,6 +752,11 @@ set(SLIC3R_GUI_SOURCES
Utils/WxFontUtils.hpp
Utils/FileTransferUtils.cpp
Utils/FileTransferUtils.hpp
+ Utils/wxInspectorPlugins/DPIAwarePlugin.hpp
+ Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
+ Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp
+ Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
+ Utils/wxInspectorPlugins/Registration.hpp
)
add_subdirectory(GUI/DeviceCore)
diff --git a/src/slic3r/GUI/DeviceCore/DevUtil.h b/src/slic3r/GUI/DeviceCore/DevUtil.h
index 0b02dd25a9..6644b8b5b9 100644
--- a/src/slic3r/GUI/DeviceCore/DevUtil.h
+++ b/src/slic3r/GUI/DeviceCore/DevUtil.h
@@ -13,6 +13,7 @@
#include
#include
+#include
#include "nlohmann/json.hpp"
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 29aad072f0..aa57f76598 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -96,6 +96,7 @@
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp"
+#include "../Utils/wxInspectorPlugins/Registration.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/Http.hpp"
#include "../Utils/InstanceID.hpp"
@@ -2835,6 +2836,9 @@ bool GUI_App::on_init_inner()
::Label::initSysFont();
+ // Register wxInspector plugins for Orca custom controls
+ RegisterOrcaInspectorPlugins();
+
// Set initialization of image handlers before any UI actions - See GH issue #7469
wxInitAllImageHandlers();
#ifdef NDEBUG
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index d6767d3310..427ba9f0ad 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include "Event.hpp"
@@ -88,7 +89,7 @@ void update_dark_ui(wxWindow* window);
extern std::deque dialogStack;
-template class DPIAware : public P
+template class DPIAware : public P, public wxInspector::wxInspectable
{
public:
DPIAware(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition,
@@ -107,6 +108,7 @@ public:
this->SetFont(m_normal_font);
#endif
this->CenterOnParent();
+ SetupInspectorAccelerator(this);
#ifdef _WIN32
update_dark_ui(this);
#endif
@@ -182,6 +184,11 @@ public:
float scale_factor() const { return m_scale_factor; }
float prev_scale_factor() const { return m_prev_scale_factor; }
+ // Only meant to be used by inspector, not public API
+ void set_scale_factor(float v) { m_scale_factor = v; }
+ void set_prev_scale_factor(float v) { m_prev_scale_factor = v; }
+ void set_em_unit(int v) { m_em_unit = v; }
+ bool force_rescale() const { return m_force_rescale; }
int em_unit() const { return m_em_unit; }
// int font_size() const { return m_font_size; }
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 85923c87fc..5d7f629075 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -739,7 +739,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
return;
}
- if (evt.CmdDown() && evt.GetKeyCode() == 'I') {
+ if (evt.CmdDown() && evt.GetKeyCode() == 'I' && !evt.ShiftDown()) {
if (!can_add_models()) return;
if (m_plater) { m_plater->add_file(); }
return;
diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp
index bc093b512a..94b245a75b 100644
--- a/src/slic3r/GUI/Widgets/Button.hpp
+++ b/src/slic3r/GUI/Widgets/Button.hpp
@@ -77,6 +77,11 @@ public:
void SetSelected(bool selected = true) { m_selected = selected; }
+ // Only meant to be used by inspector, not public API
+ ButtonStyle GetStyle() const { return m_style; }
+ ButtonType GetType() const { return m_type; }
+ bool IsSelected() const { return m_selected; }
+
bool Enable(bool enable = true) override;
void EnableTooltipEvenDisabled();// The tip will be shown even if the button is disabled
diff --git a/src/slic3r/GUI/Widgets/CheckBox.hpp b/src/slic3r/GUI/Widgets/CheckBox.hpp
index 01b801a215..abff45ecf1 100644
--- a/src/slic3r/GUI/Widgets/CheckBox.hpp
+++ b/src/slic3r/GUI/Widgets/CheckBox.hpp
@@ -15,6 +15,9 @@ public:
void SetHalfChecked(bool value = true);
+ // Only meant to be used by inspector, not public API
+ bool IsHalfChecked() const { return m_half_checked; }
+
void Rescale();
#ifdef __WXOSX__
diff --git a/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp b/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp
index 3418269e57..f42175ae05 100644
--- a/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp
+++ b/src/slic3r/GUI/Widgets/LabeledStaticBox.hpp
@@ -46,6 +46,12 @@ public:
bool Enable(bool enable) override;
+ // Only meant to be used by inspector, not public API
+ int GetCornerRadius() const { return m_radius; }
+ int GetBorderWidth() const { return m_border_width; }
+ StateColor GetBorderColor() const { return border_color; }
+ float GetScale() const { return m_scale; }
+
private:
void PickDC(wxDC& dc);
diff --git a/src/slic3r/GUI/Widgets/TextInput.hpp b/src/slic3r/GUI/Widgets/TextInput.hpp
index 20407b5de2..9aca7037c4 100644
--- a/src/slic3r/GUI/Widgets/TextInput.hpp
+++ b/src/slic3r/GUI/Widgets/TextInput.hpp
@@ -43,6 +43,9 @@ public:
void SetCornerRadius(double radius);
+ // Only meant to be used by inspector, not public API
+ int GetCornerRadius() const { return static_cast(radius); }
+
void SetLabel(const wxString& label);
void SetStaticTips(const wxString& tips, const wxBitmap& bitmap);
diff --git a/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
new file mode 100644
index 0000000000..df1b495bff
--- /dev/null
+++ b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
@@ -0,0 +1,274 @@
+#include "CustomWidgetsPlugin.hpp"
+
+#include "slic3r/GUI/Widgets/Button.hpp"
+#include "slic3r/GUI/Widgets/CheckBox.hpp"
+#include "slic3r/GUI/Widgets/TextInput.hpp"
+#include "slic3r/GUI/Widgets/SwitchButton.hpp"
+#include "slic3r/GUI/Widgets/ProgressBar.hpp"
+#include "slic3r/GUI/Widgets/Label.hpp"
+#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp"
+
+#include
+#include
+
+wxString CustomWidgetsPlugin::GetName() const
+{
+ return "OrcaCustomWidgets";
+}
+
+bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info)
+{
+ return info->IsKindOf(CLASSINFO(wxWindow));
+}
+
+wxVector CustomWidgetsPlugin::GetProperties(
+ wxInspector::InspectableObject& obj)
+{
+ wxVector props;
+ wxWindow* win = obj.AsWindow();
+ if (!win) return props;
+
+ if (auto* btn = dynamic_cast(win))
+ addButtonProps(btn, props);
+ if (auto* cb = dynamic_cast(win))
+ addCheckBoxProps(cb, props);
+ if (auto* ti = dynamic_cast(win))
+ addTextInputProps(ti, props);
+ if (auto* sb = dynamic_cast(win))
+ addSwitchButtonProps(sb, props);
+ if (auto* pb = dynamic_cast(win))
+ addProgressBarProps(pb, props);
+ if (auto* lbl = dynamic_cast(win))
+ addLabelProps(lbl, props);
+ if (auto* lsb = dynamic_cast(win))
+ addLabeledStaticBoxProps(lsb, props);
+
+ return props;
+}
+
+void CustomWidgetsPlugin::addButtonProps(Button* btn,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ wxVector styleChoices;
+ styleChoices.push_back("Regular");
+ styleChoices.push_back("Confirm");
+ styleChoices.push_back("Alert");
+ styleChoices.push_back("Disabled");
+
+ auto styleToStr = [](ButtonStyle s) -> wxString {
+ switch (s) {
+ case ButtonStyle::Regular: return "Regular";
+ case ButtonStyle::Confirm: return "Confirm";
+ case ButtonStyle::Alert: return "Alert";
+ case ButtonStyle::Disabled: return "Disabled";
+ }
+ return "Regular";
+ };
+
+ props.push_back({"Button Style", "Orca Button", PropertyType::Choice,
+ styleToStr(btn->GetStyle()), false, styleChoices,
+ [btn, styleToStr]() { return styleToStr(btn->GetStyle()); },
+ [btn](const wxString& v) {
+ ButtonStyle s = ButtonStyle::Regular;
+ if (v == "Confirm") s = ButtonStyle::Confirm;
+ else if (v == "Alert") s = ButtonStyle::Alert;
+ else if (v == "Disabled") s = ButtonStyle::Disabled;
+ btn->SetStyle(s, btn->GetType());
+ return true;
+ }});
+
+ wxVector typeChoices;
+ typeChoices.push_back("Compact");
+ typeChoices.push_back("Window");
+ typeChoices.push_back("Choice");
+ typeChoices.push_back("Parameter");
+ typeChoices.push_back("Icon");
+ typeChoices.push_back("Expanded");
+
+ auto typeToStr = [](ButtonType t) -> wxString {
+ switch (t) {
+ case ButtonType::Compact: return "Compact";
+ case ButtonType::Window: return "Window";
+ case ButtonType::Choice: return "Choice";
+ case ButtonType::Parameter: return "Parameter";
+ case ButtonType::Icon: return "Icon";
+ case ButtonType::Expanded: return "Expanded";
+ }
+ return "Compact";
+ };
+
+ props.push_back({"Button Type", "Orca Button", PropertyType::Choice,
+ typeToStr(btn->GetType()), false, typeChoices,
+ [btn, typeToStr]() { return typeToStr(btn->GetType()); },
+ [btn](const wxString& v) {
+ ButtonType t = ButtonType::Compact;
+ if (v == "Window") t = ButtonType::Window;
+ else if (v == "Choice") t = ButtonType::Choice;
+ else if (v == "Parameter") t = ButtonType::Parameter;
+ else if (v == "Icon") t = ButtonType::Icon;
+ else if (v == "Expanded") t = ButtonType::Expanded;
+ btn->SetStyle(btn->GetStyle(), t);
+ return true;
+ }});
+
+ props.push_back({"Selected", "Orca Button", PropertyType::Boolean,
+ btn->IsSelected() ? "true" : "false", false, {},
+ [btn]() { return btn->IsSelected() ? "true" : "false"; },
+ [btn](const wxString& v) {
+ btn->SetSelected(v == "true");
+ btn->Refresh();
+ return true;
+ }});
+}
+
+void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean,
+ cb->IsHalfChecked() ? "true" : "false", false, {},
+ [cb]() { return cb->IsHalfChecked() ? "true" : "false"; },
+ [cb](const wxString& v) {
+ cb->SetHalfChecked(v == "true");
+ return true;
+ }});
+}
+
+void CustomWidgetsPlugin::addTextInputProps(TextInput* ti,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Label", "Orca TextInput", PropertyType::String,
+ ti->GetLabel(), false, {},
+ [ti]() { return ti->GetLabel(); },
+ [ti](const wxString& v) { ti->SetLabel(v); return true; }});
+
+ props.push_back({"Text Value", "Orca TextInput", PropertyType::String,
+ ti->GetTextCtrl()->GetValue(), false, {},
+ [ti]() { return ti->GetTextCtrl()->GetValue(); },
+ [ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }});
+
+ props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer,
+ wxString::Format("%d", ti->GetCornerRadius()), false, {},
+ [ti]() { return wxString::Format("%d", ti->GetCornerRadius()); },
+ [ti](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ ti->SetCornerRadius((double) val);
+ ti->Refresh();
+ return true;
+ }});
+}
+
+void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean,
+ sb->GetValue() ? "true" : "false", false, {},
+ [sb]() { return sb->GetValue() ? "true" : "false"; },
+ [sb](const wxString& v) {
+ sb->SetValue(v == "true");
+ return true;
+ }});
+}
+
+void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String,
+ wxString::Format("%.2f", pb->m_proportion), false, {},
+ [pb]() { return wxString::Format("%.2f", pb->m_proportion); },
+ [pb](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ pb->m_proportion = val;
+ pb->Refresh();
+ return true;
+ }});
+
+ props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean,
+ pb->m_shownumber ? "true" : "false", false, {},
+ [pb]() { return pb->m_shownumber ? "true" : "false"; },
+ [pb](const wxString& v) {
+ pb->m_shownumber = (v == "true");
+ pb->Refresh();
+ return true;
+ }});
+}
+
+void CustomWidgetsPlugin::addLabelProps(Label* lbl,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ bool isHyperlink = (lbl->GetWindowStyleFlag() & LB_HYPERLINK) != 0;
+
+ props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean,
+ isHyperlink ? "true" : "false", true, {},
+ [lbl]() {
+ return (lbl->GetWindowStyleFlag() & LB_HYPERLINK) ? "true" : "false";
+ },
+ nullptr});
+
+ props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly,
+ wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {},
+ [lbl]() {
+ return wxString::Format("%d", lbl->GetFont().GetPointSize());
+ },
+ nullptr});
+}
+
+void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb,
+ wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer,
+ wxString::Format("%d", lsb->GetCornerRadius()), false, {},
+ [lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); },
+ [lsb](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ lsb->SetCornerRadius((int) val);
+ return true;
+ }});
+
+ props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer,
+ wxString::Format("%d", lsb->GetBorderWidth()), false, {},
+ [lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); },
+ [lsb](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ lsb->SetBorderWidth((int) val);
+ return true;
+ }});
+
+ // Border Color: display as hex string
+ wxColour bc = lsb->GetBorderColor().colorForStates(0);
+ props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String,
+ bc.GetAsString(wxC2S_HTML_SYNTAX), false, {},
+ [lsb]() {
+ return lsb->GetBorderColor()
+ .colorForStates(0)
+ .GetAsString(wxC2S_HTML_SYNTAX);
+ },
+ [lsb](const wxString& v) {
+ wxColour c(v);
+ if (!c.IsOk()) return false;
+ lsb->SetBorderColor(StateColor(c));
+ return true;
+ }});
+
+ props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly,
+ wxString::Format("%.2f", lsb->GetScale()), true, {},
+ [lsb]() { return wxString::Format("%.2f", lsb->GetScale()); },
+ nullptr});
+}
diff --git a/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp
new file mode 100644
index 0000000000..083ed8a74c
--- /dev/null
+++ b/src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include
+
+class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin
+{
+public:
+ wxString GetName() const override;
+
+ bool CanProvideProperties(wxClassInfo* info) override;
+
+ wxVector GetProperties(
+ wxInspector::InspectableObject& obj) override;
+
+private:
+ void addButtonProps(class Button* btn,
+ wxVector& props);
+ void addCheckBoxProps(class CheckBox* cb,
+ wxVector& props);
+ void addTextInputProps(class TextInput* ti,
+ wxVector& props);
+ void addSwitchButtonProps(class SwitchButton* sb,
+ wxVector& props);
+ void addProgressBarProps(class ProgressBar* pb,
+ wxVector& props);
+ void addLabelProps(class Label* lbl,
+ wxVector& props);
+ void addLabeledStaticBoxProps(class LabeledStaticBox* lsb,
+ wxVector& props);
+};
diff --git a/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
new file mode 100644
index 0000000000..f52a5d4313
--- /dev/null
+++ b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
@@ -0,0 +1,82 @@
+#include "DPIAwarePlugin.hpp"
+
+#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware
+
+#include
+#include
+
+namespace {
+
+template
+void addDPIProps(T* dpi, wxVector& props)
+{
+ using namespace wxInspector;
+
+ props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String,
+ wxString::Format("%.2f", dpi->scale_factor()), false, {},
+ [dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); },
+ [dpi](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ dpi->set_scale_factor((float) val);
+ return true;
+ }});
+
+ props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String,
+ wxString::Format("%.2f", dpi->prev_scale_factor()), false, {},
+ [dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); },
+ [dpi](const wxString& v) {
+ double val;
+ if (wxSscanf(v, "%lf", &val) != 1) return false;
+ dpi->set_prev_scale_factor((float) val);
+ return true;
+ }});
+
+ props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer,
+ wxString::Format("%d", dpi->em_unit()), false, {},
+ [dpi]() { return wxString::Format("%d", dpi->em_unit()); },
+ [dpi](const wxString& v) {
+ long val;
+ if (!v.ToLong(&val)) return false;
+ dpi->set_em_unit((int) val);
+ return true;
+ }});
+
+ props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly,
+ dpi->normal_font().GetNativeFontInfoDesc(), true, {},
+ [dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); },
+ nullptr});
+
+ props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean,
+ dpi->force_rescale() ? "true" : "false", true, {},
+ [dpi]() { return dpi->force_rescale() ? "true" : "false"; },
+ nullptr});
+}
+
+} // anonymous namespace
+
+wxString DPIAwarePlugin::GetName() const
+{
+ return "OrcaDPIAware";
+}
+
+bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info)
+{
+ return info->IsKindOf(CLASSINFO(wxWindow));
+}
+
+wxVector DPIAwarePlugin::GetProperties(
+ wxInspector::InspectableObject& obj)
+{
+ wxVector props;
+ wxWindow* win = obj.AsWindow();
+ if (!win) return props;
+
+ if (auto* frame = dynamic_cast(win)) {
+ addDPIProps(frame, props);
+ } else if (auto* dlg = dynamic_cast(win)) {
+ addDPIProps(dlg, props);
+ }
+
+ return props;
+}
diff --git a/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp
new file mode 100644
index 0000000000..5d78342967
--- /dev/null
+++ b/src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+class DPIAwarePlugin : public wxInspector::wxInspectorPlugin
+{
+public:
+ wxString GetName() const override;
+
+ bool CanProvideProperties(wxClassInfo* info) override;
+
+ wxVector GetProperties(
+ wxInspector::InspectableObject& obj) override;
+};
diff --git a/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp b/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp
new file mode 100644
index 0000000000..54bbc0dbe4
--- /dev/null
+++ b/src/slic3r/Utils/wxInspectorPlugins/Registration.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "DPIAwarePlugin.hpp"
+#include "CustomWidgetsPlugin.hpp"
+
+#include
+
+inline void RegisterOrcaInspectorPlugins()
+{
+ static DPIAwarePlugin dpiaware;
+ static CustomWidgetsPlugin customWidgets;
+ wxInspector::RegisterPlugin(&dpiaware);
+ wxInspector::RegisterPlugin(&customWidgets);
+}