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 deleted file mode 100644 index 5b3669ace4..0000000000 --- a/docs/superpowers/plans/2026-07-23-wx-inspectable-on-dpiaware-plan.md +++ /dev/null @@ -1,111 +0,0 @@ -# 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 deleted file mode 100644 index c3061b223d..0000000000 --- a/docs/superpowers/plans/2026-07-23-wx-inspector-plugins-plan.md +++ /dev/null @@ -1,753 +0,0 @@ -# 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/plans/2026-08-14-ffmpeg-player-macos.md b/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md deleted file mode 100644 index 811626fc8b..0000000000 --- a/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md +++ /dev/null @@ -1,376 +0,0 @@ -# macOS FFmpeg Media Player 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:** Make macOS use the same FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) as Windows/Linux, linking the static FFmpeg libraries from the deps build, and remove the old `wxMediaCtrl2.mm` BambuPlayer-based player. - -**Architecture:** The new player is platform-neutral C++ already used on Linux/Windows. Enabling it on macOS is pure build wiring: compile `wxMediaCtrl3.cpp` + `AVVideoDecoder.cpp` on macOS, drop the `__WXMAC__` alias that redirects `wxMediaCtrl3` to the old `wxMediaCtrl2`, and link static FFmpeg (`libavcodec.a`/`libswscale.a`/`libavutil.a`) from the deps install. The Bambu stream API is dlsym'd at runtime from the network plugin (`libBambuSource.dylib`), which already exports it — no plugin changes needed. Rendering reuses the existing `wxImage` → `DrawBitmap` paint path (same as Linux). - -**Tech Stack:** C++17, wxWidgets, CMake, FFmpeg 7.0.3 (libavcodec/libswscale/libavutil), macOS (Xcode generator), `deps/` ExternalProject build system. - -## Global Constraints - -- Branch: `dev/ffmpeg-player-macos`. Commit after every task. -- **Linux and Windows builds must not change** — the FFmpeg deps flag change is guarded by `APPLE`; Linux keeps `--enable-shared`, Windows keeps its prebuilt DLL zips. -- Static FFmpeg only on macOS: deps produce `libavcodec.a`/`libswscale.a`/`libavutil.a`; the app links those explicitly — the app binary must have **no** `libav*` dylib references (`otool -L` check). -- Follow existing code style: PascalCase classes, snake_case functions, C++17. -- No changes to `StatusPanel.cpp`, `MediaPlayCtrl.*`, or the BambuTunnel interface — the app already creates `wxMediaCtrl3` and uses only its public interface. -- The player cannot be unit-tested (hardware/plugin-dependent GUI code); verification is build-level, link-level, and manual runtime on a Mac. -- `localization/i18n/list.txt` references only `wxMediaCtrl2.cpp` (Win/Linux, stays) — no translation-list changes needed. -- Build dirs on the dev machine: main app = `build_arm64/` (Xcode generator, multi-config), deps = `deps/build/arm64/` (Unix Makefiles). App target name: `OrcaSlicer`. Substitute your own configured build dirs where noted. - ---- - -### Task 1: Enable wxMediaCtrl3 on macOS and link static FFmpeg - -**Files:** -- Modify: `src/slic3r/GUI/wxMediaCtrl3.h` (lines 18–22: the `#ifdef __WXMAC__` alias branch) -- Modify: `src/slic3r/GUI/wxMediaCtrl3.cpp:13` (uncomment the event define) -- Modify: `src/slic3r/GUI/wxMediaCtrl2.cpp:101` (remove the event define) -- Modify: `src/slic3r/CMakeLists.txt` (APPLE source list ~lines 779–792; FFmpeg link block ~lines 905–910) - -**Interfaces:** -- Consumes: nothing new (all classes already exist). -- Produces: `wxMediaCtrl3` class compiled on macOS with the same interface as Linux/Windows — `Load(wxURI)`, `Play()`, `Stop()`, `SetIdleImage(wxString)`, `GetState()`, `GetLastError()`, `GetVideoSize()`, event `EVT_MEDIA_CTRL_STAT` defined once in the lib (from `wxMediaCtrl3.cpp`). - -- [ ] **Step 1: Remove the macOS alias in wxMediaCtrl3.h** - -Current (lines 16–23 of `src/slic3r/GUI/wxMediaCtrl3.h`): - -```cpp -void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); - -#ifdef __WXMAC__ - -#include "wxMediaCtrl2.h" -#define wxMediaCtrl3 wxMediaCtrl2 - -#else - -#define BAMBU_DYNAMIC -``` - -New: - -```cpp -void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); - -#define BAMBU_DYNAMIC -``` - -Also remove the matching `#endif` that closed the `#else` branch (the one before the final `#endif /* wxMediaCtrl3_h */`), so the file's `#ifndef`/`#endif` guard pair stays balanced. - -- [ ] **Step 2: Move the EVT_MEDIA_CTRL_STAT definition into wxMediaCtrl3.cpp** - -In `src/slic3r/GUI/wxMediaCtrl3.cpp:13`, uncomment: - -```cpp -//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); -``` - -becomes: - -```cpp -wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); -``` - -In `src/slic3r/GUI/wxMediaCtrl2.cpp:101`, delete: - -```cpp -wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); -``` - -(One definition total in the lib — `MediaPlayCtrl.cpp:59` binds this event on the media ctrl.) - -- [ ] **Step 3: Update the APPLE source list in CMakeLists.txt** - -In `src/slic3r/CMakeLists.txt`, the APPLE branch (currently compiles `wxMediaCtrl2.mm`, which becomes dead on macOS): - -```cmake - GUI/wxMediaCtrl2.mm - GUI/wxMediaCtrl2.h - GUI/wxMediaCtrl3.h - ) -``` - -becomes: - -```cmake - GUI/AVVideoDecoder.cpp - GUI/AVVideoDecoder.hpp - GUI/wxMediaCtrl3.cpp - GUI/wxMediaCtrl3.h - ) -``` - -(The `else ()` branch — Win/Linux — stays exactly as it is.) - -- [ ] **Step 4: Link static FFmpeg on macOS** - -In `src/slic3r/CMakeLists.txt`, the FFmpeg block (currently `if (NOT APPLE)`): - -```cmake -if (NOT APPLE) - pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET - libavcodec - libswscale - libavutil - ) - target_link_libraries(libslic3r_gui PkgConfig::LIBAV) -endif() -``` - -becomes: - -```cmake -if (APPLE) - # Static FFmpeg from the deps install: nothing to bundle into the .app, - # no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil. - find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) - find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) - find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) - target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) - target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) -else () - pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET - libavcodec - libswscale - libavutil - ) - target_link_libraries(libslic3r_gui PkgConfig::LIBAV) -endif() -``` - -The deps install (`${CMAKE_PREFIX_PATH}/lib`) already contains the three `.a` files from the existing arm64 deps build — no deps rebuild needed for this task. - -- [ ] **Step 5: Reconfigure and build the app** - -Run (Xcode generator; `cmake` re-runs automatically on build): - -```bash -cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer -``` - -Expected: configure succeeds (no `pkg_check_modules` errors on macOS, `find_library` finds all three `.a` files), compile succeeds (`wxMediaCtrl3.cpp` and `AVVideoDecoder.cpp` compile on macOS without changes), link succeeds. - -If CMake complains that `wxMediaCtrl3.h` is included but not in the source list or similar IDE-only warnings — ignore; headers in the list are cosmetic. - -- [ ] **Step 6: Verify no dynamic FFmpeg dependency** - -```bash -otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg" -``` - -Expected: prints `OK: no dynamic FFmpeg` (empty grep output). This is the whole point of static linking — nothing to bundle into the `.app`. - -- [ ] **Step 7: Quick sanity — macOS unit tests still pass** - -```bash -ctest --test-dir build_arm64/tests/libslic3r --output-on-failure -``` - -Expected: passes (add `-C RelWithDebInfo` if the multi-config generator requires it). If no tests were built in this build dir, build target `tests` first (`cmake --build build_arm64 --config RelWithDebInfo --target tests`). - -- [ ] **Step 8: Commit** - -```bash -git add src/slic3r/CMakeLists.txt src/slic3r/GUI/wxMediaCtrl3.h src/slic3r/GUI/wxMediaCtrl3.cpp src/slic3r/GUI/wxMediaCtrl2.cpp -git commit -m "feat: use FFmpeg media player on macOS with static FFmpeg" -``` - ---- - -### Task 2: Static-only FFmpeg in the macOS deps build - -**Files:** -- Modify: `deps/FFMPEG/FFMPEG.cmake` (non-MSVC branch, APPLE section and CONFIGURE_COMMAND) - -**Interfaces:** -- Consumes: nothing. -- Produces: a deps install on macOS containing only `libavcodec.a`, `libswscale.a`, `libavutil.a` (+ headers) — no `libav*` dylibs, so no bundling/rpath machinery is ever needed on macOS. Linux and Windows output are unchanged. - -- [ ] **Step 1: Add the static flag variable** - -In `deps/FFMPEG/FFMPEG.cmake`, inside the non-MSVC `else ()` branch, in the existing `if (APPLE)` block: - -```cmake - if (APPLE) - set(_minos_cmd - "CFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}" - "LDFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}" - ) -``` - -add after the `_minos_cmd` set: - -```cmake - # Static FFmpeg: nothing to bundle into the .app, no rpath handling. - # Shared flags must come AFTER --enable-shared below so they win. - set(_link_cmd --enable-static --disable-shared) -``` - -and add a matching `else ()` after the `if (IS_CROSS_COMPILE) ... endif()` block inside that `if (APPLE)`, so non-Apple Unix keeps shared: - -```cmake - else () - set(_link_cmd --enable-shared) - endif () -``` - -(If the existing `if (IS_CROSS_COMPILE)` block is the last thing inside `if (APPLE)`, the new `else ()` closes the `if (APPLE)` itself.) - -- [ ] **Step 2: Use the variable in CONFIGURE_COMMAND** - -In the `ExternalProject_Add(dep_FFMPEG ...)` configure command: - -```cmake - "--prefix=${DESTDIR}" - --enable-shared -``` - -becomes: - -```cmake - "--prefix=${DESTDIR}" - --enable-shared - ${_link_cmd} -``` - -Order matters: `--enable-shared` comes first, then `--enable-static --disable-shared` (APPLE) or `--enable-shared` (Linux) — the last flag wins in FFmpeg configure. - -- [ ] **Step 3: Rebuild the FFmpeg dep (slow — several minutes, run in background)** - -The changed CONFIGURE_COMMAND invalidates the ExternalProject stamp, so this re-configures and rebuilds FFmpeg: - -```bash -cmake --build deps/build/arm64 --target dep_FFMPEG -``` - -For a fully clean static-only check (removes the previous shared build tree, which can leave stale `.dylib` files behind in the in-source build): - -```bash -rm -rf deps/build/arm64/dep_FFMPEG-prefix -cmake --build deps/build/arm64 --target dep_FFMPEG -``` - -- [ ] **Step 4: Verify the artifacts** - -```bash -ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.a -ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.dylib 2>/dev/null || echo "OK: no dylibs" -``` - -Expected: `libavcodec.a` present, second command prints `OK: no dylibs`. Check `libavutil` and `libswscale` the same way. - -- [ ] **Step 5: Verify the app still links against the static libs** - -```bash -cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer -otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg" -``` - -Expected: build succeeds, `OK: no dynamic FFmpeg`. - -- [ ] **Step 6: Commit** - -```bash -git add deps/FFMPEG/FFMPEG.cmake -git commit -m "build: build static-only FFmpeg for macOS deps" -``` - ---- - -### Task 3: Remove the old macOS player - -**Files:** -- Delete: `src/slic3r/GUI/wxMediaCtrl2.mm` -- Delete: `src/slic3r/GUI/BambuPlayer/BambuPlayer.h` (and the empty `BambuPlayer/` dir) -- Modify: `src/slic3r/GUI/wxMediaCtrl2.h` (remove the `#ifdef __WXMAC__` section, lines 22–60) - -**Interfaces:** -- Consumes: Task 1 (macOS no longer references `wxMediaCtrl2` — nothing includes `wxMediaCtrl2.h` on macOS anymore; `wxMediaCtrl2` is never instantiated on any platform). -- Produces: a clean tree where the old BambuPlayer-based player is gone from macOS. The `BambuPlayer` ObjC class itself remains inside the network plugin (external prebuilt binary) — only the GUI-side consumer is removed. - -- [ ] **Step 1: Delete the old player files** - -```bash -git rm src/slic3r/GUI/wxMediaCtrl2.mm -git rm src/slic3r/GUI/BambuPlayer/BambuPlayer.h -rmdir src/slic3r/GUI/BambuPlayer 2>/dev/null || true -``` - -- [ ] **Step 2: Strip the __WXMAC__ section from wxMediaCtrl2.h** - -In `src/slic3r/GUI/wxMediaCtrl2.h`, remove the entire macOS branch of the `#ifdef __WXMAC__` guard — from `#ifdef __WXMAC__` (line 22) through the closing `};` of the mac class (line 60), and the `#else` marker — leaving only the non-mac `class wxMediaCtrl2 : public wxMediaCtrl { ... };` definition followed by the final `#endif /* wxMediaCtrl2_h */`. The resulting file keeps its `#ifndef`/`#endif` include guard pair balanced. - -The file stays on disk because Win/Linux compile `wxMediaCtrl2.cpp`, which includes it. - -- [ ] **Step 3: Grep for leftover references** - -```bash -grep -rn "wxMediaCtrl2.mm\|BambuPlayer/BambuPlayer.h\|BambuPlayer" src/slic3r --include="*.cpp" --include="*.h" --include="*.mm" --include="*.txt" -``` - -Expected: no hits in `src/slic3r/GUI` (ignore `localization/i18n/list.txt:196`, which lists the Win/Linux `wxMediaCtrl2.cpp` and stays). - -- [ ] **Step 4: Rebuild the app** - -```bash -cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer -``` - -Expected: configure + compile + link succeed with the deleted files gone. - -- [ ] **Step 5: Commit** - -```bash -git add -A src/slic3r/GUI -git commit -m "refactor: remove old BambuPlayer-based media player from macOS" -``` - ---- - -### Task 4: Runtime verification on hardware - -**Files:** none — manual verification. - -**Interfaces:** consumes all prior tasks. Final gate: the new player must actually stream on a Mac. - -- [ ] **Step 1: Launch the freshly built app** - -```bash -open build_arm64/src/RelWithDebInfo/OrcaSlicer.app -``` - -Expected: app launches normally; no crash in the network/device subsystem. - -- [ ] **Step 2: Load the network plugin and open the Device tab** - -Log in / ensure the network plugin (`libBambuSource.dylib`) loads, select a printer, open the Device tab (camera monitoring panel). - -Expected: the camera preview area shows the idle image initially (no crash — this exercises `wxMediaCtrl3::SetIdleImage` and the `wxImage` load path on macOS for the first time). - -- [ ] **Step 3: Start the stream and watch it render** - -Click play / wait for `MediaPlayCtrl` to start the stream. - -Expected: live video renders in the panel. Check the console/log output (`BOOST_LOG` goes to the terminal if run from it, or check the log file): -- `stat_log ...` lines appear (the `EVT_MEDIA_CTRL_STAT` path is live — proves the Bambu C API dlsym worked from `libBambuSource.dylib`); -- no repeated decode/error messages like `AVVideoDecoder: ...` or `can not find function ...` (proves `StaticBambuLib::get` resolved all Bambu functions); -- Stop/Play toggle works; idle image reappears on stop; -- window resize keeps aspect ratio (exercises `DoSetSize`/`adjust_frame_size`/`paintEvent`). - -- [ ] **Step 4: Confirm the old player is really gone** - -Expected: nothing in the logs references `BambuPlayer` (the ObjC class is no longer dlsym'd); the video path is entirely `wxMediaCtrl3` + `AVVideoDecoder`. - -If a printer is unavailable, at minimum verify Steps 1–2 (launch + idle image) and note in the PR that live-stream verification needs hardware. - -- [ ] **Step 5: Final review pass** - -```bash -git log --oneline -6 -git show --stat HEAD # and each of the three task commits -``` - -Expected: the last 4 commits are the design doc + the 3 implementation tasks (each task commit touches only its listed files). Review the diff for scope: no Linux/Windows changes beyond the two `EVT_MEDIA_CTRL_STAT` lines in Task 1, no `StatusPanel`/`MediaPlayCtrl` changes. 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 deleted file mode 100644 index 3a4e873af6..0000000000 --- a/docs/superpowers/specs/2026-07-23-wx-inspectable-on-dpiaware-design.md +++ /dev/null @@ -1,102 +0,0 @@ -# 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 deleted file mode 100644 index 5715635969..0000000000 --- a/docs/superpowers/specs/2026-07-23-wx-inspector-plugins-design.md +++ /dev/null @@ -1,244 +0,0 @@ -# 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/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md b/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md deleted file mode 100644 index 812a7bf735..0000000000 --- a/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md +++ /dev/null @@ -1,110 +0,0 @@ -# FFmpeg Media Player for macOS — Design - -Date: 2026-08-14 -Branch: `dev/ffmpeg-player-macos` - -## Problem - -The branch's new FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) is used on -Windows and Linux, but macOS still runs the old player: `wxMediaCtrl2.mm`, an ObjC -`BambuPlayer` class dlsym'd from the Bambu network plugin that renders via CALayer. -On macOS, `wxMediaCtrl3` is currently aliased to `wxMediaCtrl2` and FFmpeg is not linked -into the app at all. - -Goal: make macOS use the same FFmpeg player as Windows/Linux, linking the **static** -FFmpeg libraries from the deps build instead of dynamic ones. - -## Current state (verified) - -- New player (Win/Linux): `GUI/wxMediaCtrl3.cpp` + `GUI/AVVideoDecoder.cpp`. Decodes with - FFmpeg (libavcodec/libswscale/libavutil), renders frames into `wxImage` (non-Windows) / - `wxBitmap` (Windows) drawn in a `paintEvent`, feeds via the `Bambu_*` C API - (`BambuTunnel.h`, `BAMBU_DYNAMIC`) dlsym'd from the network plugin through - `StaticBambuLib::get()` (`GUI/Printer/PrinterFileSystem.cpp`, compiled on all platforms). -- Old player (macOS): `GUI/wxMediaCtrl2.mm` uses the ObjC `BambuPlayer` class found via - `dlsym(module, "OBJC_CLASS_$_BambuPlayer")` in `libBambuSource.dylib`. -- The macOS network plugin `libBambuSource.dylib` already exports the full Bambu C API - (verified with `nm`), so the new player needs zero plugin changes. -- FFmpeg linking in `src/slic3r/CMakeLists.txt` is guarded by `if (NOT APPLE)` — - macOS currently does not link FFmpeg. -- `deps/FFMPEG/FFMPEG.cmake`: non-MSVC branch builds FFmpeg from source with - `--enable-shared`. The existing arm64 deps build on the dev machine happened to be - configured with both static and shared enabled, so `libavcodec.a` / `libswscale.a` / - `libavutil.a` are already present at - `deps/build/arm64/OrcaSlicer_dep/usr/local/lib/`. -- `EVT_MEDIA_CTRL_STAT` is `wxDEFINE_EVENT`'d in `wxMediaCtrl2.cpp` (Win/Linux) and - `wxMediaCtrl2.mm` (macOS); the define in `wxMediaCtrl3.cpp` is commented out. -- `wxMediaCtrl2` is never instantiated anywhere on any platform — dead code. -- `StatusPanel` already creates `wxMediaCtrl3`; `MediaPlayCtrl` only uses the - `wxMediaCtrl3` interface (`Load/Play/Stop/GetState/GetVideoSize/GetLastError/SetIdleImage`), - so no UI-side changes are needed. - -## Approach (approved) - -**Reuse the shared player on macOS.** Compile the existing `wxMediaCtrl3.cpp` + -`AVVideoDecoder.cpp` on macOS so all three platforms run one implementation. -Rendering uses the existing `wxImage` → `DrawBitmap` paint path, identical to Linux. -Known trade-off: frames are scaled to the widget's logical (1x) size, so Retina is -slightly soft compared to the old CALayer player. Accepted for now; a Retina-aware -scaling follow-up is possible later. - -Rejected alternative: a native CGImage/CALayer renderer for macOS — faster and -Retina-crisp, but adds a second render implementation to maintain. - -## Changes - -### 1. Enable the FFmpeg player on macOS (source) - -- `GUI/wxMediaCtrl3.h`: remove the `#ifdef __WXMAC__` branch (lines 18–22) that aliases - `wxMediaCtrl3` → `wxMediaCtrl2`. macOS then compiles the real `wxMediaCtrl3` class, - including the `BAMBU_DYNAMIC` BambuTunnel path used on Linux. -- Event symbol fix: move `wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent)` into - `wxMediaCtrl3.cpp` (uncomment the existing line) and remove it from - `wxMediaCtrl2.cpp`. One definition total in the lib; all three platforms resolve it. - -### 2. Static FFmpeg linking (deps + app) - -- `deps/FFMPEG/FFMPEG.cmake`: in the non-MSVC branch, pass - `--disable-shared --enable-static` when `APPLE`. Linux keeps `--enable-shared`; - Windows keeps its prebuilt shared DLL zips. Fresh macOS deps builds install only - `libavcodec.a` / `libswscale.a` / `libavutil.a` — no dylibs to bundle, no - rpath/install_name handling. (The existing local arm64 deps build already contains - the `.a` files, so no deps rebuild is strictly needed to try the change locally, - but a fresh CI deps build must produce them.) -- `src/slic3r/CMakeLists.txt`: - - APPLE branch of `SLIC3R_GUI_SOURCES`: add `GUI/wxMediaCtrl3.cpp`, - `GUI/wxMediaCtrl3.h`, `GUI/AVVideoDecoder.cpp`, `GUI/AVVideoDecoder.hpp`; - remove `GUI/wxMediaCtrl2.mm` and `GUI/wxMediaCtrl2.h` (the `.h` stays on - disk for the Win/Linux build of `wxMediaCtrl2.cpp`, but nothing on macOS - includes it after this change). - - Add an APPLE mirror of the `NOT APPLE` FFmpeg block: `find_library` for - `libavcodec.a`, `libswscale.a`, `libavutil.a` under `${CMAKE_PREFIX_PATH}/lib` - with `NO_DEFAULT_PATH`, link them (order avcodec → swscale → avutil), and add - `${CMAKE_PREFIX_PATH}/include` as a SYSTEM include directory. Deps are built with - `--disable-zlib` and no external codecs, so the three static libs link cleanly. - -### 3. Remove the old player - -- Delete `GUI/wxMediaCtrl2.mm` and `GUI/BambuPlayer/BambuPlayer.h` (header used only - by the `.mm`; the real `BambuPlayer` lives inside the network plugin). -- Remove the now-dead `__WXMAC__` section of `GUI/wxMediaCtrl2.h`. -- `wxMediaCtrl2.cpp` (Win/Linux) stays in the build as-is (dead but harmless; out of - scope to remove on this branch). - -### 4. Verification - -- Build on macOS: `cmake --build build_arm64` (or `build/arm64`). -- Confirm no dynamic FFmpeg dependency: `otool -L` on the app binary shows no `libav*` - dylib references. -- Runtime: with the network plugin loaded, the Device tab camera preview streams via - the FFmpeg player (check the device page / `MediaPlayCtrl`). -- macOS `ctest` still passes — static linking means no test-executable `.so` copying - hacks (unlike the Linux shared-lib setup). - -## Out of scope - -- Linux (shared libs, AppImage/flatpak bundling) and Windows (prebuilt DLL zips) - keep their current FFmpeg setup. -- Retina-aware frame scaling / native CGImage rendering (follow-up if visual quality - is judged insufficient). -- Audio streaming (neither player plays audio in this UI path).