mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b73e9df4f4 |
@@ -12,9 +12,9 @@ name: Daily OFL OTA Update
|
||||
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
|
||||
# uploading - see post_merge_profiles.yml for both sides of that contract.
|
||||
#
|
||||
# At the start of each run, the pending-publish table is cleared up to a captured
|
||||
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after
|
||||
# that timestamp remain pending for the next run.
|
||||
# If at least one branch was dispatched this run, a final step clears OFL's pending-publish
|
||||
# table (POST /api/v1/ota/ofl/pending/clear) - the daily "published everything, reset" signal.
|
||||
# That table is populated only by this pipeline's own auto-publish calls.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -34,32 +34,6 @@ jobs:
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Capture start timestamp and clear OFL pending queue
|
||||
id: start
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -72,12 +46,13 @@ jobs:
|
||||
run: git fetch origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
|
||||
- name: Scan branches and publish changed OFL profiles
|
||||
id: scan
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SCAN_UNTIL: ${{ steps.start.outputs.timestamp }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
published_any=false
|
||||
|
||||
mapfile -t branches < <(
|
||||
gh api "repos/${{ github.repository }}/branches" --paginate --jq '.[].name' \
|
||||
@@ -104,36 +79,68 @@ jobs:
|
||||
--jq '.workflow_runs[0].run_started_at // empty')"
|
||||
|
||||
if [ -z "$since" ]; then
|
||||
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
|
||||
changed_files="$(git log --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
|
||||
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
|
||||
| sed '/^$/d')"
|
||||
else
|
||||
changed_files="$(git log --since="$since" --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
|
||||
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
|
||||
| sed '/^$/d')"
|
||||
fi
|
||||
|
||||
if [ -n "$changed_files" ]; then
|
||||
echo "OFL changed on $branch from ${since:-the beginning} through $SCAN_UNTIL:"
|
||||
echo "$changed_files"
|
||||
echo "No prior successful run for $branch; treating OFL as changed."
|
||||
changed=true
|
||||
else
|
||||
echo "No OFL changes on $branch through $SCAN_UNTIL."
|
||||
changed=false
|
||||
changed_files="$(git log --since="$since" --name-only --pretty=format: "origin/$branch" -- \
|
||||
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
|
||||
| sed '/^$/d')"
|
||||
if [ -n "$changed_files" ]; then
|
||||
echo "OFL changed on $branch since $since:"
|
||||
echo "$changed_files"
|
||||
changed=true
|
||||
else
|
||||
echo "No OFL changes on $branch since $since."
|
||||
changed=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$changed" = true ]; then
|
||||
# Tolerate a per-branch failure (e.g. a pre-existing release branch
|
||||
# whose post_merge_profiles.yml predates the vendor/auto_publish
|
||||
# inputs) rather than aborting the whole scan under set -e.
|
||||
if ! gh workflow run post_merge_profiles.yml \
|
||||
if gh workflow run post_merge_profiles.yml \
|
||||
--repo "${{ github.repository }}" \
|
||||
--ref "$branch" \
|
||||
-f vendor="$VENDOR" -f auto_publish=true; then
|
||||
published_any=true
|
||||
else
|
||||
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
echo "published_any=$published_any" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clear OFL pending queue
|
||||
# Only when this run actually kicked off at least one publish - the
|
||||
# daily reset is scoped to today's real activity, not called on a day
|
||||
# where every branch reported no changes. Note "published_any" reflects
|
||||
# a successful DISPATCH, not a confirmed live publish: gh workflow run
|
||||
# is fire-and-forget, so this workflow never learns whether the
|
||||
# dispatched post_merge_profiles.yml run actually reached its own
|
||||
# auto-publish call. Acceptable since the table is populated only by
|
||||
# our own auto-publish calls, not by anything else.
|
||||
if: steps.scan.outputs.published_any == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1088,6 +1088,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent);
|
||||
|
||||
@@ -185,6 +185,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent);
|
||||
|
||||
@@ -159,12 +159,8 @@ void KBShortcutsDialog::fill_pages()
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
page(_L("Global"), _L("Available anywhere in the window, even while typing in a text field."), ShortcutContext::Global, {
|
||||
fixed(Section::SpeedDial, { alt, "1-9, 0" }, L("Run favorite 1 to 10")),
|
||||
fixed(Section::SpeedDial, { ctrl, "B" }, L("Pin or unpin the selected action")),
|
||||
// wx cycles notebook pages on Ctrl+Tab, which is Cmd+Tab on macOS and never arrives there.
|
||||
#ifndef __APPLE__
|
||||
fixed(Section::Application, { alt, "1-9, 0" }, L("Run a speed dial favorite while the dial is open")),
|
||||
fixed(Section::Application, { ctrl, key(L_CONTEXT("Tab", "Keyboard Shortcut")) }, L("Switch to the next main tab")),
|
||||
#endif
|
||||
});
|
||||
|
||||
page(_L("Prepare"), _L("Available while the 3D view on the Prepare tab has focus."), ShortcutContext::Plater, {
|
||||
@@ -458,10 +454,7 @@ ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut
|
||||
capture_sizer->Add(m_chord_label, 0, wxALIGN_CENTER);
|
||||
capture_sizer->AddStretchSpacer();
|
||||
capture->SetSizer(capture_sizer);
|
||||
capture->Layout(); // the box is created at its final size, so nothing resizes it into laying the sizer out
|
||||
// The hook runs before the window procedure, so Windows does not open its window menu
|
||||
// over the dialog on Alt+Space.
|
||||
Bind(wxEVT_CHAR_HOOK, &ShortcutCaptureDialog::on_key, this);
|
||||
capture->Bind(wxEVT_KEY_DOWN, &ShortcutCaptureDialog::on_key, this);
|
||||
capture->Bind(wxEVT_CHAR, &ShortcutCaptureDialog::on_char, this);
|
||||
capture->Bind(wxEVT_LEFT_DOWN, [capture](wxMouseEvent&) { capture->SetFocus(); });
|
||||
sizer->Add(capture, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
@@ -538,12 +531,10 @@ void ShortcutCaptureDialog::record(const KeyChord& chord)
|
||||
m_ok->Enable(false);
|
||||
};
|
||||
const bool global = (shortcut_info(m_shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
||||
if (chord.is_system_shortcut()) {
|
||||
reject(_L("The system uses this shortcut, so it cannot be assigned."));
|
||||
} else if (global && !chord.is_menu_accelerator()) {
|
||||
if (global && !chord.is_menu_accelerator()) {
|
||||
reject(m_rejection);
|
||||
} else if (const std::optional<Shortcut> owner = wxGetApp().shortcuts().step_owner(m_shortcut, chord); owner.has_value()) {
|
||||
reject(wxString::Format(_L("Shift and Ctrl with this key belong to %s and cannot be assigned."), _(shortcut_info(*owner).name)));
|
||||
reject(wxString::Format(_L("Already used as a step of %s."), _(shortcut_info(*owner).name)));
|
||||
} else {
|
||||
m_conflicts = wxGetApp().shortcuts().conflicts(m_shortcut, chord);
|
||||
m_status->SetForegroundColour(m_status_colour);
|
||||
|
||||
@@ -182,16 +182,6 @@ bool KeyChord::is_menu_accelerator() const
|
||||
return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE));
|
||||
}
|
||||
|
||||
// Only a chord the desktop acts on while still delivering it to the app belongs here.
|
||||
bool KeyChord::is_system_shortcut() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return modifiers == wxMOD_ALT && (key == WXK_F4 || key == WXK_SPACE);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string KeyChord::to_string() const
|
||||
{
|
||||
if (!valid())
|
||||
|
||||
@@ -34,9 +34,6 @@ struct KeyChord
|
||||
// True when Ctrl or Alt is held or the key is non-printable, the chords a menu can own without
|
||||
// swallowing typing in text fields.
|
||||
bool is_menu_accelerator() const;
|
||||
// True for a chord the desktop acts on although the app receives it, so a binding would
|
||||
// take it from the system.
|
||||
bool is_system_shortcut() const;
|
||||
|
||||
// Platform-neutral text ("Ctrl+Shift+S") for persistence and wx accelerator strings.
|
||||
std::string to_string() const;
|
||||
|
||||
@@ -147,11 +147,9 @@ constexpr std::array<ShortcutInfo, size_t(Shortcut::Count)> shortcut_table = {{
|
||||
SHORTCUT(Search, "search", L("Search"), GLOBAL, { 'F', CTRL }),
|
||||
SHORTCUT(SwitchView, "switch_view", L("Switch between Prepare/Preview"), CANVAS, { WXK_TAB }),
|
||||
SHORTCUT(CollapseSidebar, "collapse_sidebar", L("Collapse/Expand the sidebar"), CANVAS, { WXK_TAB, SHIFT }),
|
||||
SHORTCUT(SpeedDial, "speed_dial", L("Open the speed dial"), GLOBAL, { WXK_SPACE }),
|
||||
SHORTCUT(ReloadDevicePage, "reload_device_page", L("Reload the device page"), CANVAS, { WXK_F5 }),
|
||||
SHORTCUT(KeyboardShortcuts, "keyboard_shortcuts", L("Show keyboard shortcuts list"), CANVAS, { '?' }),
|
||||
|
||||
// Speed Dial
|
||||
SHORTCUT(SpeedDial, "speed_dial", L("Open the Speed Dial"), GLOBAL, { WXK_SPACE }),
|
||||
}};
|
||||
|
||||
#undef SHORTCUT
|
||||
@@ -179,7 +177,6 @@ constexpr std::array<SectionInfo, size_t(ShortcutSection::Count)> section_table
|
||||
{ Shortcut::ViewDefault, L("Camera") },
|
||||
{ Shortcut::ShowLabels, L("Display") },
|
||||
{ Shortcut::Preferences, L("Application") },
|
||||
{ Shortcut::SpeedDial, L("Speed Dial") },
|
||||
}};
|
||||
|
||||
constexpr bool sections_follow_table_order()
|
||||
|
||||
@@ -47,9 +47,7 @@ enum class Shortcut : uint8_t {
|
||||
// Display
|
||||
ShowLabels, ShowWireframe, ToggleGcodeWindow, ToggleOneLayerMode,
|
||||
// Application
|
||||
Preferences, Search, SwitchView, CollapseSidebar, ReloadDevicePage, KeyboardShortcuts,
|
||||
// Speed Dial
|
||||
SpeedDial,
|
||||
Preferences, Search, SwitchView, CollapseSidebar, SpeedDial, ReloadDevicePage, KeyboardShortcuts,
|
||||
Count
|
||||
};
|
||||
|
||||
@@ -68,7 +66,7 @@ struct ShortcutInfo
|
||||
|
||||
// Headings of the shortcuts dialog, in listing order.
|
||||
enum class ShortcutSection : uint8_t {
|
||||
Project, SlicingAndPrinting, Selection, Editing, Objects, Placement, Gizmos, Sliders, PaintingTools, Camera, Display, Application, SpeedDial,
|
||||
Project, SlicingAndPrinting, Selection, Editing, Objects, Placement, Gizmos, Sliders, PaintingTools, Camera, Display, Application,
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
@@ -120,20 +120,6 @@ TEST_CASE("Only modified or non-printable chords qualify as menu accelerators",
|
||||
CHECK(registry.accelerator(Shortcut::KeyboardShortcuts).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Chords the desktop keeps for itself are recognized", "[Shortcuts]")
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CHECK(KeyChord{ WXK_F4, wxMOD_ALT }.is_system_shortcut());
|
||||
CHECK(KeyChord{ WXK_SPACE, wxMOD_ALT }.is_system_shortcut());
|
||||
#else
|
||||
CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_ALT }.is_system_shortcut());
|
||||
CHECK_FALSE(KeyChord{ WXK_SPACE, wxMOD_ALT }.is_system_shortcut());
|
||||
#endif
|
||||
CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_ALT | wxMOD_SHIFT }.is_system_shortcut());
|
||||
CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_CONTROL }.is_system_shortcut());
|
||||
CHECK_FALSE(KeyChord{ WXK_SPACE }.is_system_shortcut());
|
||||
}
|
||||
|
||||
TEST_CASE("Chords convert to wx accelerator entries", "[Shortcuts]")
|
||||
{
|
||||
const wxAcceleratorEntry entry = KeyChord{ 'S', wxMOD_CONTROL | wxMOD_SHIFT }.to_accelerator_entry(42);
|
||||
|
||||
Reference in New Issue
Block a user