Compare commits

...
Author SHA1 Message Date
HanifKohandraistlin7447 95d3e8eafc Queue Pull Request macOS Builds for a Free Runner (#16343)
* ci: wait for the base branch's macOS deps instead of rebuilding them

* Hold Pull Request macOS Builds Until a Runner Is Free

A pull request's macOS build now waits in one repo-wide line, from a
Linux job, until the macOS runners every active Build all run holds or
still needs leave two free. A run is let in whole: its first arch takes
both runners and its second goes straight through, since letting arches
in one at a time deadlocks with every runner held by a run waiting for
its other arch. Push, nightly and manual runs reserve two runners until
their macOS work is done, and a pull request run keeps its two until its
universal build and tests finish, so jobs that never pass the line are
accounted for. Queued jobs count as busy. API errors and a four-hour
limit let the build in rather than block it.

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Count Pending Runs That Hold macOS Runners

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Show the macOS Admission Checks Live in the Job Log

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Let a Labelled Pull Request Go Ahead of the macOS Line

A pull request labelled macos-priority when its run starts waits in its
own line under the same rule, and the normal line counts every priority
run still waiting as holding two runners, so the next free runners go
to it. It still waits for runners in use and for main's reserve.

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Let Each macOS Arch In on Its Own Without Holding an Idle Runner

Letting a pull request run in whole deadlocks when its second arch
reaches the line behind another pull request: the run holds two
runners, the pull request in front waits for them, and the second arch
never gets to check. Each arch now waits on its own. A run with one
arch let in holds a runner only until that arch's app build is done,
and a run with both holds two until its universal build and tests
finish, so whoever is first in line always gets in eventually.

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Stop Holding Runners for a Pull Request's Universal Build and Tests

Holding two runners from a pull request's second arch until its
universal build and tests finished kept one idle through the slower
arch's build. Replaying the week of 10-03 against the real queue, that
idle capacity pushed the median pull request from under three hours to
over sixteen. Those jobs take minutes, so they now queue like any other
job, and a pull request holds a runner only for an arch that is still
building. Each arch then always needs exactly one runner.

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Count Runs That Move Between Status Lists

A run is listed as pending while any of its jobs waits in a concurrency group, so it moves to in_progress the moment its last gate leaves the line. A check made in those seconds read the in_progress list before the move and the pending list after it, missed the run, and let in a second arch the limit had no room for. Runs created in the last day are now listed by creation time as well, and the run doing the check is read directly.

Co-authored-by: raistlin7447 <kris.austin@gmail.com>

* Log API Calls and the Rate Limit Left on Each macOS Admission Check

The calls per check grow with the number of active runs, and the token's
hourly limit is shared by the whole repository, so each check now logs how
many calls it made and what the last response says is left.

* Give the macOS Admission Jobs Fixed Names

A skipped job is listed under its unevaluated name, so every Linux and
Windows leg showed the label expression. Split the waiting job into a normal
and a priority job with fixed names, sharing their steps through a YAML
anchor.

---------

Co-authored-by: raistlin7447 <kris.austin@gmail.com>
2026-10-12 01:15:47 +08:00
Kiss Lorand 52eeb0f228 Fix arc fitting with overhang fan control (#16297) 2026-10-11 13:09:16 -03:00
HanifKoh 8145b0792f Free macOS Disk Space Only When the Build Might Not Fit (#16364) 2026-10-11 23:55:23 +08:00
Lam Wei Lun 2ccd73759f Show English context for Speed Dial search matches
Display English action names and breadcrumbs for English-only matches
Show English tab titles when tabs match only by their English names
2026-10-11 23:44:21 +08:00
SoftFever cab7a7d75f Translate the web dialogs and pages through the .po catalogs (#16363) 2026-10-11 21:52:41 +08:00
SoftFever bd125d32da Support searching the Speed Dial by English names in any UI language (#16369) 2026-10-11 21:29:06 +08:00
HanifKoh 7369674d8f Add #pragma once to Headers Without an Include Guard (#16362) 2026-10-11 17:26:05 +08:00
HanifKoh 70da7e89bb Release the Slice Button's Mouse Capture When a Press Loses Its Release (#16358) 2026-10-11 15:49:04 +08:00
HanifKoh a671bf605d Dismiss the Slice-Plate Popup on Every Path Into the Filament Grouping Dialog (#16359)
#16284 closed the Slice-plate hover popup only when the Slice button opened
the filament grouping dialog. The dialog also opens from the plate's filament
grouping icon, the preview legend, slicing notification links and the send
dialog, and a popup still shown on those paths keeps the mouse capture on
macOS and starves the modal dialog of clicks. Dismiss it in
FilamentMapDialog::ShowModal() instead, so every path is covered.

The popup could also stay up on its own on macOS: an enter event stopped its
close timer, and when the timer fired with the cursor inside it returned
without re-arming, so without a later leave event the popup never closed. On
macOS the timer now keeps running while the popup is shown and closes it once
the cursor is outside. The popup also stops and frees its timer when destroyed.
2026-10-11 15:48:49 +08:00
SoftFever bae8f64b8b Complete and refine Simplified Chinese translations (#16356)
* Complete Simplified Chinese translations

* Fix Simplified Chinese 3D view, camera and extruder terms

* Use Chinese CAD view names on the navigator cube
2026-10-11 15:35:29 +08:00
HanifKoh 2df07b7ed8 Verify the TLS Certificate When Downloading a Cloud Plugin (#16253) 2026-10-11 14:25:50 +08:00
Klober81andRodrigo Faselli 295094a594 Pack bed texture and model into printer bundles (#15965)
* feat(preset): pack bed texture and model into printer bundles

Export copies bed_custom_texture and bed_custom_model into the .orca_printer zip under bed/. The manifest lists them in printer_asset. Import extracts each file into the bundle folder and rewrites the preset to that path. A missing file is skipped. A path that leaves the bundle directory is rejected. An old bundle with no printer_asset imports as before.

* Add includes flagged by clang-tidy misc-include-cleaner

* Gate bed asset packing on an export checkbox and key printer_asset per printer

The Export Configs checkbox can leave a custom bed out of a shared printer bundle. printer_asset is keyed by each printer_config zip entry, with files under bed/<n>/, so a later multi-printer bundle does not need a second manifest. The unshipped flat printer_asset object is not read.

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-11 13:32:07 +08:00
Ian Bassi 210bc927bc Revert "Add Thai Localization (Educational / Hybrid Translation)" (#16349) 2026-10-10 22:39:45 -03:00
SoftFever 00cfb5b259 Refresh translation catalogs and fix xgettext warnings (#16346) 2026-10-11 08:45:44 +08:00
chettasit 2bd105b0ce Add Thai Localization (Educational / Hybrid Translation) (#15940)
Hi OrcaSlicer Team,I have completed the Thai localization for OrcaSlicer (over 7,000 lines).Please note: This is a "Hybrid Translation" designed specifically for educational purposes. Instead of direct literal translations (which often confuse beginners due to technical jargon), I have included brief explanations of the mechanical and physics principles directly within the UI strings.Example: For "TPU is not supported by AMS", the translation briefly explains why (because the material is soft, rubber-like, and will jam the long tubes).This approach was developed for the EDrobot project in Khon Kaen, Thailand to help STEM students and beginners understand 3D printing concepts immediately without a steep language barrier.[cite: 8] We focus on empowering students to learn robotics and 3D design effectively.[cite: 8]   I understand this makes some strings longer than standard localizations, but it has proven incredibly helpful for beginners. I would be honored if you consider adding this to the official release as the Thai language option. I am happy to maintain this file for future updates.Thank you for building such an amazing slicer!Best regards,Chetsit Srichan (EDrobot)
2026-10-11 08:34:19 +08:00
Ian BassiandRodrigo Faselli b81d761605 Refresh plate tab after bed type change (#16344)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-10 21:06:29 -03:00
6922edfc20 Fix Anycubic Vyper start G-code hotend wait (#16216)
* Fix Anycubic Vyper start gcode hotend wait

* Bump Anycubic profile version

---------

Co-authored-by: 陈诗健 <chenshijian@chenshijiandeMacBook-Air.local>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
2026-10-11 07:34:11 +08:00
Javad Shafique 7ec147603f Default Bambu profiles to 3MF output (#14767) 2026-10-11 06:55:50 +08:00
jorymorrison 8ee9e5915a Fix Prusa CORE One pressure advance condition in filament profiles (#16093) 2026-10-11 06:39:53 +08:00
SoftFever cd5b786704 Add AnkerMake PLA+ filament profiles (Basic, Matte, Metallic, Silk, Glitter) (#16293)
# Description

Adds Anker's own AnkerMake PLA+ filament line to the Anker vendor
bundle: **Basic, Matte, Metallic, Silk, Glitter**. Until now the bundle
only had `Generic PLA+ @Anker`.

Each filament follows the existing Anker layout. A `@Anker base` profile
(`instantiation: false`, inherits `Generic PLA+ @Anker base`,
`filament_vendor: AnkerMake`) holds the settings. An instantiated
`@Anker` profile holds only `compatible_printers`. `filament_id` and
`setting_id` were minted with `scripts/orca_profile_tool.py
generate-id`, and `Anker.json` was re-indexed with `update-index`. The
Anker vendor version goes from `02.04.00.05` to `02.04.00.06`.

## Source of the values

The values come from the AnkerMake filament profiles bundled with
**eufyMake Studio** (Anker's PrusaSlicer 2.6 fork, AGPL-3.0, source at
https://github.com/eufymake/eufyMake-PrusaSlicer-Release), in
`profiles/Anker-ini/AnkerMake base/base.ini`. OrcaSlicer is also
AGPL-3.0.

Conversion notes:
- PrusaSlicer keys are mapped to their Orca equivalents. For example,
`bed_temperature`/`first_layer_bed_temperature` go to `hot_plate_temp*`
and `textured_plate_temp*`, `fan_below_layer_time` goes to
`fan_cooling_layer_time`, `slowdown_below_layer_time` goes to
`slow_down_layer_time`, and `extrusion_multiplier` goes to
`filament_flow_ratio`. Only keys that differ from the inherited `Generic
PLA+ @Anker base` chain are written.
- Where Studio sets retraction but leaves deretraction unset (Metallic,
Silk), `filament_deretraction_speed` is set equal to the retraction
speed. That matches Prusa's "0 = same as retract" meaning.
- **Max volumetric speed is new.** Studio ships
`filament_max_volumetric_speed = 0` (uncapped) for these filaments, and
Orca has no equivalent. The caps were derived from the peak flow that
Studio's own M5C "Fast" process pushes: Basic/Matte/Glitter 22 mm³/s,
Metallic 20, Silk 13. Please treat these as the least-validated numbers
in the PR.
- Studio has pressure advance disabled for these filaments, so no PA
values are carried over.
- Colour variants are not included because they differ only in colour.
`default_filament_colour` uses each family's base colour.
- `compatible_printers` follows Studio's `compatible_printers_condition`
for each family, limited to the 0.4/0.6 printers that `Generic PLA+
@Anker` covers:
  - Basic, Glitter: M5, M5 All-Metal, M5C at 0.4 and 0.6
- Silk: 0.4 on all three, plus M5C 0.6 and M5 All-Metal 0.6 (Studio
excludes M5 0.6)
  - Matte, Metallic: 0.4 only (Studio restricts these to 0.4)
- Studio also allows Basic on 0.2 nozzles. That variant is not added,
because the 0.2 Generic profiles use a separate low-flow cap.

# Screenshots/Recordings/Graphs

N/A (profile-only change).

## Tests

- `scripts/check_profile.sh` (local twin of the CI job, run against the
full profile tree with the nightly macOS validator) passes all five
checks: `profile_tool`, `validate_system`, `validate_slice`,
`validate_filament_subtypes`, `validate_custom` (fixtures v1.9.0 to
v2.4.2).
- `python3 -m unittest discover -s scripts/tests -t scripts`: OK.
- The submitter printed with these filament settings in eufyMake Studio
on the M5C's stock brass 0.4 nozzle without issues. This Orca conversion
has been loaded and sliced in Orca 2.4.2 (as user presets inheriting the
Anker generic PLA+) but not yet printed on brass. The printer now has a
different nozzle fitted.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-11 06:26:26 +08:00
HanifKoh d715bb760c Let Plugin Windows Be Maximized on X11 (#16335)
wxGTK types every wxDialog as a dialog window, and Mutter offers no
maximize for anything but a normal window, so the maximize button and
shortcut did nothing on GNOME under X11. Modeless plugin windows are now
normal windows, kept off the taskbar as before. Modal ones stay dialogs
so the window manager keeps routing focus from the main window to them.
2026-10-11 04:40:56 +08:00
SoftFever 63918b44a8 Merge branch 'main' into anker-ankermake-pla-plus-filaments 2026-10-10 22:41:16 +08:00
mateuszandClaude Opus 5.5 64d09c9bca Add AnkerMake PLA+ filament profiles for Anker printers
Adds AnkerMake PLA+ Basic, Matte, Metallic, Silk and Glitter, converted from eufyMake Studio's bundled AnkerMake profiles (AGPL-3.0). Bumps the Anker vendor version to 02.04.00.06.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:13:00 -05:00
165 changed files with 39219 additions and 12122 deletions
+198 -73
View File
@@ -1,73 +1,198 @@
name: Check Cache
on:
workflow_call:
inputs:
os:
required: true
type: string
arch:
required: false
type: string
compiler:
required: false
type: string
default: msvc
build-deps-only:
required: false
type: boolean
force-build:
required: false
type: boolean
jobs:
check_cache: # determines if there is a cache and outputs variables used in caching process
name: Check Cache
runs-on: ${{ inputs.os }}
outputs:
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
lfs: 'false'
- name: set outputs
id: set_outputs
env:
# Anything that changes how the tree is built belongs in the key, or a job
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
# 'linux-clang' keeps the cache it already has.
cache-os: ${{ runner.os == 'macOS' && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
# The Windows ARM64 deps build in build-arm64, all others under build;
# build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat.
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
run: |
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
- name: load cache
id: cache_deps
uses: actions/cache@v6
with:
path: ${{ steps.set_outputs.outputs.cache-path }}
key: ${{ steps.set_outputs.outputs.cache-key }}
lookup-only: true
build_deps: # call next step
name: Build Deps
needs: [check_cache]
uses: ./.github/workflows/build_deps.yml
with:
cache-key: ${{ needs.check_cache.outputs.cache-key }}
cache-path: ${{ needs.check_cache.outputs.cache-path }}
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
os: ${{ inputs.os }}
arch: ${{ inputs.arch }}
compiler: ${{ inputs.compiler }}
build-deps-only: ${{ inputs.build-deps-only }}
force-build: ${{ inputs.force-build }}
secrets: inherit
name: Check Cache
on:
workflow_call:
inputs:
os:
required: true
type: string
arch:
required: false
type: string
compiler:
required: false
type: string
default: msvc
build-deps-only:
required: false
type: boolean
force-build:
required: false
type: boolean
# Read by scripts/ci_deps_cache.sh.
env:
REPO: ${{ github.repository }}
WORKFLOW_REF: ${{ github.workflow_ref }}
BASE_REF: ${{ github.base_ref }}
jobs:
check_cache: # determines if there is a cache and outputs variables used in caching process
name: Check Cache
# Hosted macOS runners are scarce, so their lookup runs on Linux.
runs-on: ${{ startsWith(inputs.os, 'macos-') && 'ubuntu-24.04' || inputs.os }}
outputs:
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
macos-path: ${{ steps.macos_path.outputs.path }}
key-inputs: ${{ steps.wait_check.outputs.key-inputs }}
wait-for: ${{ steps.wait_check.outputs.wait-for }}
wait-since: ${{ steps.wait_check.outputs.wait-since }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
lfs: 'false'
# The wait check compares the merge commit with its first parent.
fetch-depth: ${{ startsWith(inputs.os, 'macos-') && 2 || 1 }}
# Fetches only deps/, which the key hashes, and the wait script.
sparse-checkout: |
/deps/
/scripts/ci_deps_cache.sh
sparse-checkout-cone-mode: false
- name: set outputs
id: set_outputs
env:
# Anything that changes how the tree is built belongs in the key, or a job
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
# 'linux-clang' keeps the cache it already has.
cache-os: ${{ (runner.os == 'macOS' || startsWith(inputs.os, 'macos-')) && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
# The Windows ARM64 deps build in build-arm64, all others under build;
# build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat.
dep-folder-name: ${{ (runner.os == 'macOS' || startsWith(inputs.os, 'macos-')) && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
run: |
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
# Relative to the workspace, which differs from the build's when this job runs on Linux.
echo cache-path=deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
# actions/cache matches the path too, so a Linux lookup for macOS uses the
# workspace of a hosted macOS job.
- name: macOS cache path
id: macos_path
if: ${{ startsWith(inputs.os, 'macos-') }}
run: echo "path=/Users/runner/work/${GITHUB_REPOSITORY#*/}/${GITHUB_REPOSITORY#*/}/${{ steps.set_outputs.outputs.cache-path }}" >> "$GITHUB_OUTPUT"
- name: load cache
id: cache_deps
uses: actions/cache@v6
with:
path: ${{ steps.macos_path.outputs.path || format('{0}/{1}', github.workspace, steps.set_outputs.outputs.cache-path) }}
key: ${{ steps.set_outputs.outputs.cache-key }}
lookup-only: true
# A pull request that misses the cache can wait for a base branch build of the same deps.
- name: check for a base branch build to wait for
id: wait_check
if: ${{ startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && steps.cache_deps.outputs.cache-hit != 'true' }}
env:
GH_TOKEN: ${{ github.token }}
run: bash scripts/ci_deps_cache.sh check
wait_for_deps:
name: Wait for base branch deps
needs: [check_cache]
if: ${{ needs.check_cache.outputs.wait-for != '' }}
runs-on: ubuntu-24.04
env:
WAIT_MINUTES: 120
# Must exceed WAIT_MINUTES.
timeout-minutes: 130
# Waiting runs queue here, and only the first one holds a runner.
concurrency:
group: deps-wait-${{ github.base_ref }}-${{ needs.check_cache.outputs.cache-key }}
queue: max
outputs:
hit: ${{ steps.lookup.outputs.cache-hit }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
sparse-checkout: scripts/ci_deps_cache.sh
sparse-checkout-cone-mode: false
- name: wait for the deps cache
env:
GH_TOKEN: ${{ github.token }}
KEY: ${{ needs.check_cache.outputs.cache-key }}
KEY_INPUTS: ${{ needs.check_cache.outputs.key-inputs }}
WAIT_FOR: ${{ needs.check_cache.outputs.wait-for }}
WAIT_SINCE: ${{ needs.check_cache.outputs.wait-since }}
ARCH: ${{ inputs.arch }}
run: bash scripts/ci_deps_cache.sh wait
- name: load cache
id: lookup
uses: actions/cache@v6
with:
path: ${{ needs.check_cache.outputs.macos-path }}
key: ${{ needs.check_cache.outputs.cache-key }}
lookup-only: true
# Hosted macOS runners are scarce, so a pull request's macOS build waits until
# one is free that push and nightly builds do not need. See the script.
macos_admission:
name: Wait for a macOS runner
needs: [check_cache, wait_for_deps]
if: ${{ !cancelled() && needs.check_cache.result == 'success' && startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'macos-priority') }}
runs-on: ubuntu-24.04
env:
WAIT_MINUTES: 240
PRIORITY: false
# Must exceed WAIT_MINUTES.
timeout-minutes: 250
# One line for every arch of every pull request, and only its first job polls.
concurrency:
group: macos-admission
queue: max
steps: &macos_admission_steps
- name: Checkout
uses: actions/checkout@v7
with:
sparse-checkout: scripts/ci_macos_admission.py
sparse-checkout-cone-mode: false
- name: wait for a macOS runner
env:
GH_TOKEN: ${{ github.token }}
MACOS_RUNNER_LIMIT: ${{ vars.MACOS_RUNNER_LIMIT }}
# -u, or the log shows nothing until the wait ends.
run: python3 -u scripts/ci_macos_admission.py
# The macos-priority label, when the run starts, puts a pull request in its own
# line, which the normal one yields to. The names are fixed, because a skipped
# job is listed under its unevaluated name, and the script matches both.
macos_admission_priority:
name: Wait for a macOS runner (priority)
needs: [check_cache, wait_for_deps]
if: ${{ !cancelled() && needs.check_cache.result == 'success' && startsWith(inputs.os, 'macos-') && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'macos-priority') }}
runs-on: ubuntu-24.04
env:
WAIT_MINUTES: 240
PRIORITY: true
# Must exceed WAIT_MINUTES.
timeout-minutes: 250
concurrency:
group: macos-admission-priority
queue: max
steps: *macos_admission_steps
build_deps: # call next step
name: Build Deps
needs: [check_cache, wait_for_deps, macos_admission, macos_admission_priority]
# A failed wait counts as no cache, so the deps are built here. A failed
# admission still builds rather than skip macOS.
if: ${{ !cancelled() && needs.check_cache.result == 'success' }}
uses: ./.github/workflows/build_deps.yml
with:
cache-key: ${{ needs.check_cache.outputs.cache-key }}
cache-path: ${{ needs.check_cache.outputs.cache-path }}
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' || needs.wait_for_deps.outputs.hit == 'true' }}
os: ${{ inputs.os }}
arch: ${{ inputs.arch }}
compiler: ${{ inputs.compiler }}
build-deps-only: ${{ inputs.build-deps-only }}
force-build: ${{ inputs.force-build }}
secrets: inherit
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
- name: load cached deps
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
path: ${{ github.workspace }}/${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
- uses: lukka/get-cmake@latest
@@ -122,7 +122,7 @@ jobs:
choco install strawberryperl
}
# cache-path is the install directory inside the deps build directory.
$deps = (Split-Path "${{ inputs.cache-path }}").Replace('\', '/')
$deps = (Split-Path "${{ github.workspace }}/${{ inputs.cache-path }}").Replace('\', '/')
# -l compiles with Visual Studio's clang-cl and -x builds with Ninja; --msvc --msbuild is cl under the Visual Studio generator.
$flags = if ("${{ inputs.compiler }}" -eq "clang") { "-l", "-x" } else { "--msvc", "--msbuild" }
.\build_win.bat -d --arch ${{ inputs.arch }} --deps-dir $deps @flags
+9 -2
View File
@@ -44,7 +44,7 @@ jobs:
if: ${{ !(runner.os == 'macOS' && inputs.macos-combine-only) }}
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
path: ${{ github.workspace }}/${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
fail-on-cache-miss: true
@@ -179,6 +179,13 @@ jobs:
if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED
run: |
df -hI /
# Deleting the unused Xcodes takes minutes, so only do it when the
# build might not fit. A cold build uses under 4 GiB after this step.
avail_gib=$(df -g / | awk 'NR == 2 { print $4 }')
if [ "$avail_gib" -ge 15 ]; then
echo "$avail_gib GiB free, enough for the build"
exit 0
fi
# Keep only the selected Xcode
xcode=$(basename "$(cd "$(xcode-select -p)/../.." && pwd -P)")
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "$xcode" -exec rm -rf {} +
@@ -460,7 +467,7 @@ jobs:
# --tests builds the unit tests too; the unit_tests_windows_* jobs run them.
run: |
# cache-path is the install directory inside the deps build directory.
$deps = (Split-Path "${{ inputs.cache-path }}").Replace('\', '/')
$deps = (Split-Path "${{ github.workspace }}/${{ inputs.cache-path }}").Replace('\', '/')
# -l compiles with Visual Studio's clang-cl and -x builds with Ninja; --msvc --msbuild is cl under the Visual Studio generator.
$flags = if ("${{ inputs.compiler }}" -eq "clang") { "-l", "-x" } else { "--msvc", "--msbuild" }
.\build_win.bat -s --tests -i --arch ${{ inputs.arch }} --build-dir $env:BUILD_DIR --deps-dir $deps @flags
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -333,6 +333,8 @@ src/slic3r/GUI/OAuthDialog.cpp
src/slic3r/GUI/PluginPickerDialog.cpp
src/slic3r/GUI/PluginsConfigDialog.cpp
src/slic3r/GUI/PluginsDialog.cpp
src/slic3r/GUI/PrinterWebView.cpp
src/slic3r/GUI/Project.cpp
src/slic3r/GUI/PublishSettingsDialog.hpp
src/slic3r/GUI/PurgeModeDialog.cpp
src/slic3r/GUI/TerminalDialog.cpp
@@ -341,7 +343,6 @@ src/slic3r/GUI/TextureLibrary.cpp
src/slic3r/GUI/TextureProjectorFrame.cpp
src/slic3r/GUI/UVEditorCanvas.cpp
src/slic3r/GUI/UserManager.cpp
src/slic3r/GUI/WebDownPluginDlg.cpp
src/slic3r/GUI/WebViewDialog.cpp
src/slic3r/GUI/Widgets/AMSItem.cpp
src/slic3r/GUI/Widgets/DialogButtons.cpp
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+41 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anker",
"version": "02.04.00.05",
"version": "02.04.00.06",
"force_update": "0",
"description": "Anker configurations",
"machine_model_list": [
@@ -304,6 +304,26 @@
"name": "Generic PLA Silk @Anker 0.25 nozzle",
"sub_path": "filament/Generic PLA Silk @Anker 0.25 nozzle.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker base",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker base.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker base",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker base.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker base",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker base.json"
},
{
"name": "Generic PLA+ @Anker",
"sub_path": "filament/Generic PLA+ @Anker.json"
@@ -327,6 +347,26 @@
{
"name": "Generic TPU @Anker",
"sub_path": "filament/Generic TPU @Anker.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker.json"
}
],
"machine_list": [
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFUuZlQL",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"8"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker",
"inherits": "AnkerMake PLA+ Basic @Anker base",
"from": "system",
"setting_id": "EfM5rmn4w6pMBajC",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFxCURdg",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#00AD66"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker",
"inherits": "AnkerMake PLA+ Glitter @Anker base",
"from": "system",
"setting_id": "blGHp0htgHEj7khE",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFKDOyNK",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker",
"inherits": "AnkerMake PLA+ Matte @Anker base",
"from": "system",
"setting_id": "tPgwC1eEXyWPOn9S",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,56 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFBfbmtb",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"textured_plate_temp": [
"60"
],
"textured_plate_temp_initial_layer": [
"60"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"overhang_fan_speed": [
"50"
],
"reduce_fan_stop_start_freq": [
"0"
],
"filament_retraction_length": [
"1"
],
"filament_retraction_speed": [
"50"
],
"filament_deretraction_speed": [
"50"
],
"default_filament_colour": [
"#ACAC8A"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker",
"inherits": "AnkerMake PLA+ Metallic @Anker base",
"from": "system",
"setting_id": "jlbAMNNaEacdUASZ",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,62 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFkxTGQc",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"2"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"0.5"
],
"filament_retraction_speed": [
"20"
],
"filament_wipe": [
"0"
],
"default_filament_colour": [
"#99ADA3"
],
"filament_deretraction_speed": [
"20"
],
"filament_max_volumetric_speed": [
"13"
]
}
@@ -0,0 +1,15 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker",
"inherits": "AnkerMake PLA+ Silk @Anker base",
"from": "system",
"setting_id": "eVM9Ckwjl5vEvEKN",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anycubic",
"version": "02.04.00.08",
"version": "02.04.00.09",
"force_update": "0",
"description": "Anycubic configurations",
"machine_model_list": [
@@ -110,7 +110,7 @@
"default_filament_profile": [
"Generic PLA @Anycubic"
],
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nM109 S[nozzle_temperature_initial_layer] ; wait for nozzle temp to stabilize\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_end_gcode": "G4 ; wait\nG92 E0\nG1{if max_layer_z < printable_height} Z{z_offset+min(max_layer_z+30, printable_height)}{endif} ; move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.08.00.13",
"version": "02.08.00.14",
"force_update": "0",
"description": "the initial version of BBL configurations",
"machine_model_list": [
@@ -8,6 +8,7 @@
"0.4"
],
"printer_variant": "0.4",
"use_3mf": "1",
"bed_exclude_area": [
"0x0",
"28x0",
@@ -9,6 +9,7 @@
"0.4"
],
"printer_variant": "0.4",
"use_3mf": "1",
"best_object_pos": "0.3x0.5",
"bed_exclude_area": [],
"bed_temperature_formula": "by_highest_temp",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Prusa",
"version": "02.04.00.16",
"version": "02.04.00.17",
"force_update": "0",
"description": "Prusa configurations",
"machine_model_list": [
@@ -45,7 +45,7 @@
"35"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"hot_plate_temp": [
"110"
@@ -78,7 +78,7 @@
"35"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"hot_plate_temp": [
"110"
@@ -50,7 +50,7 @@
"0.8"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_wipe": [
"1"
@@ -38,7 +38,7 @@
"15"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One|CORE One L).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_z_hop": [
"0.6"
@@ -58,7 +58,7 @@
"7"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[0]==0.4}0.03{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[0]==0.4}0.03{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_z_hop": [
"0.6",
@@ -63,7 +63,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"filament_type": [
"ASA"
@@ -65,7 +65,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PA11-CF"
@@ -63,7 +63,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PC"
@@ -65,7 +65,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PC-CF"
@@ -69,7 +69,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PETG"
@@ -64,7 +64,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One|CORE One L).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PLA"
@@ -65,7 +65,7 @@
"1"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PVB"
@@ -64,7 +64,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PLA"
File diff suppressed because it is too large Load Diff
@@ -10,7 +10,7 @@
<link rel="stylesheet" type="text/css" href="../css/theme.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script src="./index.js"></script>
@@ -23,14 +23,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
<span class="cbr-search-placeholder" data-i18n="printer">Printer</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="MachineList">
<div class="CValues">
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -38,14 +38,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
<span class="cbr-search-placeholder trans" tid="t16">filament</span>
<span class="cbr-search-placeholder" data-i18n="filament">Filament</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="FilatypeList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllFilament()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -53,22 +53,22 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
<span class="cbr-search-placeholder trans" tid="t17">presets</span>
<span class="cbr-search-placeholder" data-i18n="process">Process</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="PresetList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllPreset()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllPreset()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
</div>
<div id="AcceptArea">
<div id="export_cloud_btn" class="ButtonStyleConfirm ButtonTypeChoice">Export to OrcaCloud</div>
<div id="export_local_btn" class="ButtonStyleRegular ButtonTypeChoice">Export to folder</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
<div id="export_cloud_btn" class="ButtonStyleConfirm ButtonTypeChoice" data-i18n="export_cloud">Export to OrcaCloud</div>
<div id="export_local_btn" class="ButtonStyleRegular ButtonTypeChoice" data-i18n="export_folder">Export to folder</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice" data-i18n="close">Close</div>
</div>
</body>
@@ -12,8 +12,7 @@ var g_search = {
function OnInit()
{
if (typeof TranslatePage === "function")
TranslatePage();
TranslatePage();
InstallInputSafeKeydown();
BindSearchInputs();
@@ -16,29 +16,31 @@
<h1 id="pagePresetName" class="page-title"></h1>
</header>
<div id="configEmpty" class="detail-empty">This preset does not use any plugin capabilities</div>
<div id="configEmpty" class="detail-empty" data-i18n="no_capabilities">This preset does not use any plugin capabilities</div>
<div id="configLayout" class="config-layout" hidden>
<div id="configSidebar" class="config-sidebar thin-scroll" role="listbox"
aria-label="Capabilities used by this preset"></div>
aria-label="Capabilities used by this preset" data-i18n-aria-label="capabilities_label"></div>
<div class="config-view">
<div id="configError" class="config-error" role="status" aria-live="polite" hidden></div>
<div id="configEditor" class="config-editor" hidden>
<textarea id="configText" class="config-textarea thin-scroll" spellcheck="false"
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)"></textarea>
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)"
data-i18n-aria-label="config_json_label"></textarea>
</div>
<!-- Custom capability UI. Sandboxed without allow-same-origin, so the plugin's HTML runs in
an opaque origin and reaches the host only through the injected window.orca bridge. -->
<iframe id="configCustom" class="config-custom" title="Plugin configuration"
<iframe id="configCustom" class="config-custom" title="Plugin configuration" data-i18n-title="config_frame_title"
sandbox="allow-scripts" referrerpolicy="no-referrer" hidden></iframe>
<div id="configFooter" class="config-view-footer" hidden>
<span id="configValidation" class="config-validation" role="status" aria-live="polite"></span>
<div class="config-actions">
<button id="configRestoreBtn" class="ButtonStyleRegular ButtonTypeChoice" type="button"
title="Discard this preset's override and use the global configuration again">
title="Discard this preset's override and use the global configuration again"
data-i18n-title="restore_title" data-i18n="restore_defaults">
Restore defaults
</button>
<button id="configSaveBtn" class="ButtonStyleConfirm ButtonTypeChoice" type="button">Save</button>
<button id="configSaveBtn" class="ButtonStyleConfirm ButtonTypeChoice" type="button" data-i18n="save">Save</button>
</div>
</div>
</div>
@@ -48,6 +50,7 @@
<span id="statusText" class="status-text"></span>
</footer>
</main>
<script src="../../include/i18n.js"></script>
<script src="../js/plugin-config-ui.js"></script>
<script src="index.js"></script>
</body>
@@ -304,7 +304,7 @@ function ValidateConfigText() {
SetConfigValidation("");
return true;
} catch (err) {
SetConfigValidation(String(err?.message || "Invalid JSON"));
SetConfigValidation(String(err?.message || T("invalid_json", "Invalid JSON")));
return false;
}
}
@@ -393,6 +393,8 @@ function OnCustomConfigMessage(event) {
}
document.addEventListener("DOMContentLoaded", () => {
TranslatePage();
const sidebar = document.getElementById("configSidebar");
if (sidebar)
sidebar.addEventListener("click", OnConfigSidebarClick);
+49 -42
View File
@@ -12,7 +12,7 @@
<link rel="stylesheet" type="text/css" href="../css/theme.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script src="../js/plugin-config-ui.js"></script>
@@ -32,8 +32,10 @@
</svg>
</span>
<input id="plugin_search_input" class="plugin-search-input" type="text"
placeholder="Search plugins" autocomplete="off" spellcheck="false" aria-label="Search plugins" />
<button id="plugin_search_clear" class="plugin-search-clear" type="button" title="Clear" aria-label="Clear search">
placeholder="Search plugins" autocomplete="off" spellcheck="false" aria-label="Search plugins"
data-i18n-placeholder="search_plugins" data-i18n-aria-label="search_plugins" />
<button id="plugin_search_clear" class="plugin-search-clear" type="button" title="Clear" aria-label="Clear search"
data-i18n-title="clear" data-i18n-aria-label="clear_search">
<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor"
stroke-width="1.6" stroke-linecap="round" aria-hidden="true">
<line x1="5" y1="5" x2="11" y2="11" />
@@ -41,27 +43,28 @@
</svg>
</button>
<button id="plugin_search_cc" class="plugin-search-toggle" type="button"
aria-pressed="false" title="Match case">Aa</button>
aria-pressed="false" title="Match case" data-i18n-title="match_case">Aa</button>
<button id="plugin_search_w" class="plugin-search-toggle" type="button"
aria-pressed="false" title="Match whole word"><span class="plugin-search-underline">ab</span></button>
aria-pressed="false" title="Match whole word" data-i18n-title="match_whole_word"><span class="plugin-search-underline">ab</span></button>
</div>
<!-- <button id="open_terminal" class="ButtonStyleRegular ButtonTypeChoice left-btn">-->
<!-- Open Terminal-->
<!-- </button>-->
<button id="refresh_btn" class="ButtonStyleRegular ButtonTypeChoice">
<button id="refresh_btn" class="ButtonStyleRegular ButtonTypeChoice" data-i18n="refresh">
Refresh
</button>
<div id="exploreDropdown" class="explore-dropdown">
<button id="explore_menu_btn" class="ButtonStyleConfirm ButtonTypeChoice explore-menu-btn" type="button"
aria-haspopup="true" aria-expanded="false" aria-controls="exploreMenu" title="More plugin install options">
aria-haspopup="true" aria-expanded="false" aria-controls="exploreMenu" title="More plugin install options"
data-i18n-title="install_options">
<span class="explore-menu-icon" aria-hidden="true"></span>
</button>
<button id="explore_btn" class="ButtonStyleConfirm ButtonTypeChoice explore-main-btn" type="button">
Install plugin
</button>
<div id="exploreMenu" class="explore-menu" role="menu" hidden>
<button type="button" class="explore-menu-item" role="menuitem" data-install-action="explore">Install plugin</button>
<button type="button" class="explore-menu-item" role="menuitem" data-install-action="install-local">Install local plugin</button>
<button type="button" class="explore-menu-item" role="menuitem" data-install-action="explore" data-i18n="install_plugin">Install plugin</button>
<button type="button" class="explore-menu-item" role="menuitem" data-install-action="install-local" data-i18n="install_local_plugin">Install local plugin</button>
</div>
</div>
</div>
@@ -69,15 +72,15 @@
<main class="content">
<section class="pane plugin-list-pane">
<div class="hdr plugin-cols">
<span>Activate</span>
<span data-i18n="activate">Activate</span>
<span class="sort-th" data-sort-field="name" role="button" tabindex="0"
title="Sort by name">Name<span class="sort-tri" aria-hidden="true"></span></span>
title="Sort by name" data-i18n-title="sort_by_name" data-i18n="name">Name<span class="sort-tri" aria-hidden="true"></span></span>
<span class="sort-th" data-sort-field="version" role="button" tabindex="0"
title="Sort by version">Plugin Version<span class="sort-tri" aria-hidden="true"></span></span>
title="Sort by version" data-i18n-title="sort_by_version" data-i18n="plugin_version">Plugin Version<span class="sort-tri" aria-hidden="true"></span></span>
<span class="sort-th" data-sort-field="source" role="button" tabindex="0"
title="Sort by source">Source<span class="sort-tri" aria-hidden="true"></span></span>
title="Sort by source" data-i18n-title="sort_by_source" data-i18n="source">Source<span class="sort-tri" aria-hidden="true"></span></span>
<span class="sort-th" data-sort-field="status" role="button" tabindex="0"
title="Sort by status">Status<span class="sort-tri" aria-hidden="true"></span></span>
title="Sort by status" data-i18n-title="sort_by_status" data-i18n="status">Status<span class="sort-tri" aria-hidden="true"></span></span>
</div>
<div id="pluginList" class="body thin-scroll"></div>
</section>
@@ -85,20 +88,21 @@
<!-- Drag to redistribute the dialog's height between the list and the details pane; the hit
area is the whole strip, the visible divider is the line drawn inside it. -->
<div id="paneSplitter" class="pane-splitter" role="separator" aria-orientation="horizontal"
aria-label="Resize the plugin list" title="Drag to resize; double-click to reset"></div>
aria-label="Resize the plugin list" title="Drag to resize; double-click to reset"
data-i18n-aria-label="resize_list" data-i18n-title="resize_hint"></div>
<section class="pane details-pane">
<div class="detail-tabs" role="tablist" aria-label="Plugin details">
<div class="detail-tabs" role="tablist" aria-label="Plugin details" data-i18n-aria-label="details_label">
<button id="pluginInfoTab" class="detail-tab active" type="button" role="tab"
aria-selected="true" aria-controls="pluginInfoPanel" data-tab="plugin-info">Plugin Info</button>
aria-selected="true" aria-controls="pluginInfoPanel" data-tab="plugin-info" data-i18n="tab_info">Plugin Info</button>
<button id="descriptionTab" class="detail-tab" type="button" role="tab" tabindex="-1"
aria-selected="false" aria-controls="descriptionPanel" data-tab="description">Description</button>
aria-selected="false" aria-controls="descriptionPanel" data-tab="description" data-i18n="description">Description</button>
<button id="configTab" class="detail-tab" type="button" role="tab" tabindex="-1"
aria-selected="false" aria-controls="configPanel" data-tab="config">Config</button>
aria-selected="false" aria-controls="configPanel" data-tab="config" data-i18n="tab_config">Config</button>
<button id="changelogTab" class="detail-tab" type="button" role="tab" tabindex="-1"
aria-selected="false" aria-controls="changelogPanel" data-tab="changelog">Changelog</button>
aria-selected="false" aria-controls="changelogPanel" data-tab="changelog" data-i18n="tab_changelog">Changelog</button>
<button id="diagnosticsTab" class="detail-tab" type="button" role="tab" tabindex="-1"
aria-selected="false" aria-controls="diagnosticsPanel" data-tab="diagnostics">Diagnostics</button>
aria-selected="false" aria-controls="diagnosticsPanel" data-tab="diagnostics" data-i18n="tab_diagnostics">Diagnostics</button>
</div>
<div class="detail-tab-panels">
@@ -110,31 +114,32 @@
</div>
<div class="detail-fields">
<div class="detail-field-row">
<div class="detail-label">Source</div>
<div class="detail-label" data-i18n="source">Source</div>
<div id="detailSource" class="detail-value">-</div>
</div>
<div class="detail-field-row">
<div class="detail-label">Types</div>
<div class="detail-label" data-i18n="types">Types</div>
<div id="detailTypes" class="detail-value">-</div>
</div>
<div class="detail-field-row">
<div class="detail-label">Author</div>
<div class="detail-label" data-i18n="author">Author</div>
<div id="detailAuthor" class="detail-value">-</div>
</div>
<div class="detail-field-row">
<div class="detail-label">Installed Version</div>
<div class="detail-label" data-i18n="installed_version">Installed Version</div>
<div class="detail-value">
<span id="detailInstalledVersion">-</span>
</div>
</div>
<div class="detail-field-row">
<div class="detail-label">Latest Version</div>
<div class="detail-label" data-i18n="latest_version">Latest Version</div>
<div class="detail-value detail-version-row">
<span id="detailLatestVersion">-</span>
<span id="detailUpdateBadge" class="version-update-badge" hidden
aria-label="Update available" title="Update available"></span>
aria-label="Update available" title="Update available"
data-i18n-aria-label="update_available" data-i18n-title="update_available"></span>
<button id="detailUpdateBtn" type="button" class="plugin-update-btn" hidden
title="Update to the latest version">Update</button>
title="Update to the latest version" data-i18n-title="update_title" data-i18n="update">Update</button>
</div>
</div>
</div>
@@ -143,26 +148,27 @@
<section id="descriptionPanel" class="detail-tab-panel thin-scroll" role="tabpanel" tabindex="0"
aria-labelledby="descriptionTab" data-panel="description" hidden>
<h2 class="detail-section-title">Description</h2>
<div id="detailDescription" class="detail-description">No description available</div>
<h2 class="detail-section-title" data-i18n="description">Description</h2>
<div id="detailDescription" class="detail-description" data-i18n="no_description">No description available</div>
</section>
<section id="configPanel" class="detail-tab-panel" role="tabpanel" tabindex="0"
aria-labelledby="configTab" data-panel="config" hidden>
<div id="configEmpty" class="detail-empty">Select a plugin to configure its capabilities</div>
<div id="configEmpty" class="detail-empty" data-i18n="config_select_plugin">Select a plugin to configure its capabilities</div>
<div id="configLayout" class="config-layout" hidden>
<div id="configSidebar" class="config-sidebar thin-scroll" role="listbox"
aria-label="Configurable capabilities"></div>
aria-label="Configurable capabilities" data-i18n-aria-label="configurable_capabilities"></div>
<div class="config-view">
<div id="configError" class="config-error" role="status" aria-live="polite" hidden></div>
<div id="configEditor" class="config-editor" hidden>
<textarea id="configText" class="config-textarea thin-scroll" spellcheck="false"
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)"></textarea>
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)"
data-i18n-aria-label="config_json_label"></textarea>
</div>
<!-- Custom capability UI. Sandboxed without allow-same-origin, so the plugin's HTML
runs in an opaque origin and reaches the host only through the injected
window.orca bridge. -->
<iframe id="configCustom" class="config-custom" title="Plugin configuration"
<iframe id="configCustom" class="config-custom" title="Plugin configuration" data-i18n-title="config_frame_title"
sandbox="allow-scripts" referrerpolicy="no-referrer" hidden></iframe>
<!-- JSON-editor chrome only: a custom UI renders its own save/restore controls and
drives them through the window.orca bridge. -->
@@ -170,10 +176,11 @@
<span id="configValidation" class="config-validation" role="status" aria-live="polite"></span>
<div class="config-actions">
<button id="configRestoreBtn" class="ButtonStyleRegular ButtonTypeChoice" type="button"
title="Discard the settings saved for this capability and restore the plugin's defaults">
title="Discard the settings saved for this capability and restore the plugin's defaults"
data-i18n-title="restore_title" data-i18n="restore_defaults">
Restore defaults
</button>
<button id="configSaveBtn" class="ButtonStyleConfirm ButtonTypeChoice" type="button">Save</button>
<button id="configSaveBtn" class="ButtonStyleConfirm ButtonTypeChoice" type="button" data-i18n="save">Save</button>
</div>
</div>
</div>
@@ -182,22 +189,22 @@
<section id="changelogPanel" class="detail-tab-panel thin-scroll" role="tabpanel" tabindex="0"
aria-labelledby="changelogTab" data-panel="changelog" hidden>
<table id="changelogTable" class="detail-table changelog-table" aria-label="Plugin changelog">
<table id="changelogTable" class="detail-table changelog-table" aria-label="Plugin changelog" data-i18n-aria-label="changelog_label">
<thead>
<tr>
<th scope="col">Version</th>
<th scope="col">Date</th>
<th scope="col">Changes</th>
<th scope="col" data-i18n="version">Version</th>
<th scope="col" data-i18n="date">Date</th>
<th scope="col" data-i18n="changes">Changes</th>
</tr>
</thead>
<tbody id="changelogBody"></tbody>
</table>
<div id="changelogEmpty" class="detail-empty" hidden>No changelog available</div>
<div id="changelogEmpty" class="detail-empty" data-i18n="no_changelog" hidden>No changelog available</div>
</section>
<section id="diagnosticsPanel" class="detail-tab-panel thin-scroll" role="tabpanel" tabindex="0"
aria-labelledby="diagnosticsTab" data-panel="diagnostics" hidden>
<div id="detailStatusBody" class="detail-status-body">No issues detected</div>
<div id="detailStatusBody" class="detail-status-body" data-i18n="no_issues">No issues detected</div>
</section>
</div>
</section>
+67 -49
View File
@@ -1,11 +1,11 @@
const pluginsById = new Map();
const pluginInstallActions = {
"explore": {
label: "Install plugin",
label: T("install_plugin", "Install plugin"),
command: "open_plugin_hub"
},
"install-local": {
label: "Install local plugin",
label: T("install_local_plugin", "Install local plugin"),
command: "install_local_plugin"
}
};
@@ -47,8 +47,7 @@ function OnInit() {
exploreMenu = document.getElementById("exploreMenu");
exploreMenuButton = document.getElementById("explore_menu_btn");
if (typeof TranslatePage === "function")
TranslatePage();
TranslatePage();
document.getElementById("refresh_btn")?.addEventListener("click", () => SendMessage("refresh_plugins"));
document.getElementById("explore_btn")?.addEventListener("click", () => {
@@ -449,7 +448,7 @@ function RenderPlugins() {
if (pluginsById.size === 0) {
const empty = document.createElement("div");
empty.className = "empty-state";
empty.textContent = "No plugins found";
empty.textContent = T("no_plugins", "No plugins found");
pluginList.appendChild(empty);
SyncPluginListHeaderGutter();
return;
@@ -504,11 +503,9 @@ function RenderPlugins() {
if (searching && shown === 0) {
const empty = document.createElement("div");
empty.className = "empty-state";
empty.appendChild(document.createTextNode('No plugins match "'));
const term = document.createElement("b");
term.textContent = pluginSearch.query;
empty.appendChild(term);
empty.appendChild(document.createTextNode('"'));
AppendTemplate(empty, T("no_match", 'No plugins match "%s"'), term);
pluginList.appendChild(empty);
}
@@ -599,13 +596,13 @@ function IsPluginLoading(plugin) {
function SourceLabel(source) {
switch (String(source || "").toLowerCase()) {
case "mine":
return "Mine";
return T("source_mine", "Mine");
case "subscribed":
return "Subscribed";
return T("source_subscribed", "Subscribed");
case "orphaned":
return "Orphaned";
return T("source_orphaned", "Orphaned");
default:
return "Local";
return T("source_local", "Local");
}
}
@@ -641,7 +638,7 @@ function CheckCell(row, plugin) {
checkbox.dataset.pluginKey = row.dataset.pluginKey;
if (checkbox.indeterminate) {
checkbox.setAttribute("aria-checked", "mixed");
checkbox.setAttribute("aria-label", "Some plugin capabilities are enabled");
checkbox.setAttribute("aria-label", T("mixed_capabilities", "Some plugin capabilities are enabled"));
}
const checkboxMark = document.createElement("span");
checkboxMark.className = "plugin-checkbox-mark";
@@ -664,9 +661,14 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
const expandButton = document.createElement("button");
expandButton.type = "button";
expandButton.className = "plugin-expand-btn";
expandButton.setAttribute("aria-label", `${isExpanded ? "Collapse" : "Expand"} ${pluginLabelText || "plugin"} capabilities`);
const labelName = pluginLabelText || "plugin";
expandButton.setAttribute("aria-label", isExpanded
? T("collapse_named", "Collapse %s capabilities", labelName)
: T("expand_named", "Expand %s capabilities", labelName));
expandButton.setAttribute("aria-expanded", isExpanded ? "true" : "false");
expandButton.title = isExpanded ? "Collapse capabilities" : "Expand capabilities";
expandButton.title = isExpanded
? T("collapse", "Collapse capabilities")
: T("expand", "Expand capabilities");
const icon = document.createElement("span");
icon.className = "plugin-expand-icon";
expandButton.appendChild(icon);
@@ -688,7 +690,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
if (hasCloudLink) {
labelElement.href = "#";
labelElement.classList.add("plugin-cloud-link");
labelElement.title = "Open this plugin in your browser";
labelElement.title = T("open_in_browser", "Open this plugin in your browser");
}
nameWrap.appendChild(labelElement);
@@ -696,7 +698,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
const countBadge = document.createElement("span");
countBadge.className = "plugin-capability-count";
countBadge.textContent = String(capabilityCount);
countBadge.title = `${capabilityCount} capabilities`;
countBadge.title = T("capability_count", "Capabilities: %s", capabilityCount);
nameWrap.appendChild(countBadge);
}
labelCell.appendChild(nameWrap);
@@ -722,7 +724,7 @@ function RenderCapabilityTree(plugin, capabilities, capRanges = null) {
const tree = document.createElement("div");
tree.className = "capabilities-tree";
tree.setAttribute("role", "group");
tree.setAttribute("aria-label", "Capabilities");
tree.setAttribute("aria-label", T("capabilities", "Capabilities"));
capabilities.forEach((capability, index) => {
tree.appendChild(RenderCapabilityRow(plugin, capability, index === capabilities.length - 1, capRanges));
@@ -769,7 +771,7 @@ function RenderCapabilityRow(plugin, capability, isLast, capRanges = null) {
const runButton = document.createElement("button");
runButton.type = "button";
runButton.className = "capability-run-btn script-run-btn";
runButton.title = `Run "${capabilityName}"`;
runButton.title = T("run_named", 'Run "%s"', capabilityName);
runButton.setAttribute("aria-label", runButton.title);
const runIcon = document.createElement("span");
runIcon.className = "icon16 plugin-run-icon";
@@ -804,7 +806,7 @@ function CapabilityCheckCell(plugin, capability) {
checkbox.dataset.capabilityToggle = "true";
checkbox.dataset.capabilityName = capabilityName;
checkbox.dataset.capabilityType = capabilityType;
checkbox.setAttribute("aria-label", `Enable ${capabilityName || "capability"}`);
checkbox.setAttribute("aria-label", T("enable_named", "Enable %s", capabilityName));
const checkboxMark = document.createElement("span");
checkboxMark.className = "plugin-checkbox-mark";
checkboxLabel.appendChild(checkbox);
@@ -826,7 +828,7 @@ function VersionCell(plugin) {
const versionLabel = document.createElement("span");
versionLabel.className = "version-value";
versionLabel.textContent = GetDisplayVersion(plugin) || "N/A";
versionLabel.textContent = GetDisplayVersion(plugin) || T("not_available", "N/A");
cell.appendChild(versionLabel);
const updateBadge = UpdateStatusBadge(plugin);
@@ -845,11 +847,11 @@ function UpdateStatusBadge(plugin) {
badge.className = "version-update-badge";
if (updateStatus === "unauthorized") {
badge.classList.add("is-warning");
badge.title = "Unauthorized for updates";
badge.setAttribute("aria-label", "Unauthorized for updates");
badge.title = T("unauthorized", "Unauthorized for updates");
badge.setAttribute("aria-label", badge.title);
} else {
badge.title = "Update available";
badge.setAttribute("aria-label", "Update available");
badge.title = T("update_available", "Update available");
badge.setAttribute("aria-label", badge.title);
}
return badge;
@@ -862,7 +864,7 @@ function StatusCell(plugin) {
const statusLabel = document.createElement("span");
statusLabel.className = "status-label";
statusLabel.textContent = GetStatus(plugin);
statusLabel.textContent = StatusLabel(plugin);
cell.appendChild(statusLabel);
return cell;
@@ -875,7 +877,7 @@ function RenderDetails() {
const detailStatusBody = document.getElementById("detailStatusBody");
SetText("detailInstalledVersion",
plugin ? (IsPluginInstalled(plugin) ? (GetInstalledVersion(plugin) || "-") : "Not installed") : "-");
plugin ? (IsPluginInstalled(plugin) ? (GetInstalledVersion(plugin) || "-") : T("not_installed", "Not installed")) : "-");
SetText("detailLatestVersion", plugin ? (GetLatestVersion(plugin) || "-") : "-");
const detailSource = document.getElementById("detailSource");
if (detailSource) {
@@ -933,12 +935,12 @@ function RenderConfig(plugin) {
// Capabilities only exist once the plugin is activated: say that, rather than claiming it has
// none to configure.
empty.textContent = !plugin
? "Select a plugin to configure its capabilities"
? T("config_select_plugin", "Select a plugin to configure its capabilities")
: (IsPluginLoading(plugin)
? "Loading the plugin…"
? T("config_loading", "Loading the plugin…")
: (GetStatus(plugin) === "Activated"
? "This plugin exposes no capabilities"
: "Activate this plugin to configure its capabilities"));
? T("config_no_capabilities", "This plugin exposes no capabilities")
: T("config_activate", "Activate this plugin to configure its capabilities")));
empty.hidden = false;
layout.hidden = true;
sidebar.replaceChildren();
@@ -1121,7 +1123,7 @@ function ValidateConfigText() {
SetConfigValidation("");
return true;
} catch (err) {
SetConfigValidation(String(err?.message || "Invalid JSON"));
SetConfigValidation(String(err?.message || T("invalid_json", "Invalid JSON")));
return false;
}
}
@@ -1237,21 +1239,19 @@ function RenderDescription(plugin) {
const isCloud = plugin && (plugin.source === "mine" || plugin.source === "subscribed" || plugin.source === "orphaned");
if (isCloud && String(plugin?.sharing_token || "")) {
node.appendChild(document.createTextNode("View on OrcaCloud "));
const link = document.createElement("a");
link.href = "#";
link.className = "plugin-cloud-link";
link.textContent = "here";
link.title = "Open this plugin in your browser";
link.title = T("open_in_browser", "Open this plugin in your browser");
link.addEventListener("click", (event) => {
event.preventDefault();
sendOpenPluginOnCloud(String(plugin.plugin_key || ""));
});
node.appendChild(link);
AppendTemplate(node, T("view_on_cloud", "View on OrcaCloud <a>here</a>"), link);
return;
}
node.textContent = "No description available";
node.textContent = T("no_description", "No description available");
}
function RenderChangelog(plugin) {
@@ -1305,8 +1305,8 @@ function ApplyDetailUpdateBadge(node, plugin) {
if (updateStatus === "unauthorized") {
node.hidden = false;
node.classList.add("is-warning");
node.title = "Unauthorized for updates";
node.setAttribute("aria-label", "Unauthorized for updates");
node.title = T("unauthorized", "Unauthorized for updates");
node.setAttribute("aria-label", node.title);
return;
}
@@ -1339,19 +1339,37 @@ function UpdateSelectedPlugin() {
});
}
// The status arrives as an identifier, which also names the status-* CSS classes.
function StatusLabel(plugin) {
switch (GetStatus(plugin)) {
case "Activated":
return T("status_activated", "Activated");
case "Loading":
return T("status_loading", "Loading");
case "Error":
return T("status_error", "Error");
case "RuntimeError":
return T("status_runtime_error", "Runtime error");
case "Inactive":
return T("status_inactive", "Inactive");
default:
return GetStatus(plugin);
}
}
function StatusDescription(plugin) {
switch (GetStatus(plugin)) {
case "Activated":
return "This plugin is active and ready to be used.";
return T("status_activated_desc", "This plugin is active and ready to be used.");
case "Loading":
return "This plugin is still loading.";
return T("status_loading_desc", "This plugin is still loading.");
case "Error":
return "This plugin is blocked until its error is fixed.";
return T("status_error_desc", "This plugin is blocked until its error is fixed.");
case "RuntimeError":
return "This plugin is loaded but a capability reported an error.";
return T("status_runtime_error_desc", "This plugin is loaded but a capability reported an error.");
case "Inactive":
default:
return "This plugin is inactive. Activate it to install or load it.";
return T("status_inactive_desc", "This plugin is inactive. Activate it to install or load it.");
}
}
@@ -1359,13 +1377,13 @@ function RenderDetailSummary(container, plugin) {
container.replaceChildren();
if (!plugin) {
container.textContent = "Select a plugin to view its status and details.";
container.textContent = T("select_plugin", "Select a plugin to view its status and details.");
return;
}
const statusChip = document.createElement("span");
statusChip.className = `detail-status-chip status-${GetStatus(plugin).toLowerCase()}`;
statusChip.textContent = GetStatus(plugin);
statusChip.textContent = StatusLabel(plugin);
container.appendChild(statusChip);
const message = document.createElement("div");
@@ -1377,7 +1395,7 @@ function RenderDetailSummary(container, plugin) {
if (plugin.orphaned === true) {
const warning = document.createElement("div");
warning.className = "detail-description detail-warning-text";
warning.textContent = "Orphaned: This plugin is no longer subscribed or available in OrcaCloud. The local copy remains installed and can still be used.";
warning.textContent = T("orphaned_note", "Orphaned: This plugin is no longer subscribed or available in OrcaCloud. The local copy remains installed and can still be used.");
container.appendChild(warning);
}
@@ -1385,14 +1403,14 @@ function RenderDetailSummary(container, plugin) {
if (updateStatus === "update_available") {
const note = document.createElement("div");
note.className = "detail-description detail-note-text";
note.textContent = "A newer plugin version is available.";
note.textContent = T("newer_version_note", "A newer plugin version is available.");
container.appendChild(note);
}
if (updateStatus === "unauthorized") {
const note = document.createElement("div");
note.className = "detail-description detail-note-text is-warning";
note.textContent = "Cloud updates are unavailable because this plugin is unauthorized for updates.";
note.textContent = T("unauthorized_note", "Cloud updates are unavailable because this plugin is unauthorized for updates.");
container.appendChild(note);
}
}
@@ -11,7 +11,7 @@
<link rel="stylesheet" type="text/css" href="../css/theme.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<!-- <script type="text/javascript" src="./23.js"></script> -->
@@ -21,7 +21,7 @@
<div class="app">
<div class="split">
<div class="top-toolbar">
<button id="refresh_btn" class="ButtonStyleConfirm ButtonTypeChoice toolbar-btn">Refresh</button>
<button id="refresh_btn" class="ButtonStyleConfirm ButtonTypeChoice toolbar-btn" data-i18n="refresh">Refresh</button>
<!-- <label class="auto-update-switch" for="auto_update_toggle">
<span class="auto-update-label">Auto update</span>
@@ -35,18 +35,18 @@
<section class="pane top">
<div class="hdr top-cols">
<span>Name</span>
<span>Type</span>
<span>Version</span>
<span>Update</span>
<span data-i18n="name">Name</span>
<span data-i18n="type">Type</span>
<span data-i18n="version">Version</span>
<span data-i18n="update">Update</span>
</div>
<div id="topList" class="body"></div>
</section>
<section class="pane bottom">
<div class="hdr bot-cols">
<span>Name</span>
<span>Type</span>
<span data-i18n="name">Name</span>
<span data-i18n="type">Type</span>
</div>
<div id="bottomList" class="body"></div>
</section>
@@ -55,15 +55,15 @@
<!-- Footer -->
<div id="AcceptArea">
<div id="export_btn" class="ButtonStyleConfirm ButtonTypeChoice" hidden>Export Presets</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
<div id="export_btn" class="ButtonStyleConfirm ButtonTypeChoice" data-i18n="export_presets" hidden>Export Presets</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice" data-i18n="close">Close</div>
</div>
<!-- Context Menu -->
<div id="ctxMenu" class="ctx" hidden>
<button class="ctx-item" data-action="open_folder">Open folder in explorer</button>
<button id = "delete_btn" class="ctx-item-delete" data-action="delete_bundle" hidden>Delete bundle</button>
<button id = "unsubscribe_btn" class="ctx-item-subscribed" data-action="unsubscribe_bundle" hidden>Unsubscribe bundle</button>
<button class="ctx-item" data-action="open_folder" data-i18n="open_folder">Open folder in explorer</button>
<button id = "delete_btn" class="ctx-item-delete" data-action="delete_bundle" data-i18n="delete_bundle" hidden>Delete bundle</button>
<button id = "unsubscribe_btn" class="ctx-item-subscribed" data-action="unsubscribe_bundle" data-i18n="unsubscribe_bundle" hidden>Unsubscribe bundle</button>
</div>
@@ -3,8 +3,14 @@ const bundlesById = new Map(); // bundleId -> bundle object
const printersByBundle = new Map(); // bundleId -> Map(index -> printerName)
const filamentsByBundle = new Map(); // bundleId -> Map(index -> filamentName)
const processesByBundle = new Map(); // bundleId -> Map(index -> presetName)
const UPDATE_TOOLTIP = "Update available";
const UNAUTHORIZED_TOOLTIP = "Unauthorized bundle";
const UPDATE_TOOLTIP = T("update_available", "Update available");
const UNAUTHORIZED_TOOLTIP = T("unauthorized_bundle", "Unauthorized bundle");
// Display names for the bundle types the native side sends (also kept raw in data-bundle-type).
const BUNDLE_TYPE_LABELS = {
Subscribed: T("type_subscribed", "Subscribed"),
Local: T("type_local", "Local"),
Default: T("type_default", "Default")
};
// ========= DOM =========
let topList = null;
@@ -230,11 +236,11 @@ function renderTop() {
: `<span class="bundle-status-icon-spacer" aria-hidden="true"></span>`}
${
b.type === "Subscribed" ?
`<a href="#" class="bundle-name-text bundle-cloud-link" title="Open this bundle in your browser">${escapeHtml(b.name)}</a>`
`<a href="#" class="bundle-name-text bundle-cloud-link" title="${escapeAttr(T("open_in_browser", "Open this bundle in your browser"))}">${escapeHtml(b.name)}</a>`
: `<span class="bundle-name-text">${escapeHtml(b.name)}</span>`
}
</div>
<span title="${escapeAttr(b.type)}">${escapeHtml(b.type)}</span>
<span title="${escapeAttr(BundleTypeLabel(b.type))}">${escapeHtml(BundleTypeLabel(b.type))}</span>
<span title="${escapeAttr(b.version)}">${escapeHtml(b.version)}</span>
<div class="cell bundle-update-cell">
<button
@@ -242,7 +248,7 @@ function renderTop() {
class="bundle-update-btn ${(!b.unauthorized && b.update_available) ? "is-enabled" : "is-disabled"}"
${(!b.unauthorized && b.update_available) ? "" : "disabled"}
data-id="${escapeAttr(b.id)}"
>Update</button>
>${escapeHtml(T("update", "Update"))}</button>
</div>
</div>
`).join("");
@@ -282,9 +288,9 @@ function renderBottomForBundle(bundleId) {
// Convert to a flat list of rows { typeLabel, name }
const rows = [];
for (const [, name] of printers) rows.push({ type: "Printer", name });
for (const [, name] of filaments) rows.push({ type: "Filament", name });
for (const [, name] of processes) rows.push({ type: "Process", name });
for (const [, name] of printers) rows.push({ type: T("printer", "Printer"), name });
for (const [, name] of filaments) rows.push({ type: T("filament", "Filament"), name });
for (const [, name] of processes) rows.push({ type: T("process", "Process"), name });
bottomList.innerHTML = rows.map((r, idx) => `
<div class="row" data-id="${escapeAttr(bundleId)}" data-idx="${idx}">
@@ -351,6 +357,10 @@ function hideMenu() {
contextRow = null;
}
// ========= Utilities =========
function BundleTypeLabel(type) {
return BUNDLE_TYPE_LABELS[type] || type;
}
function safeJsonParse(s) {
try { return JSON.parse(s); } catch { return null; }
}
+158 -112
View File
@@ -9,6 +9,8 @@
// - action.input "percent"/"tab" -> NativeCommands catalog (phases handled in activateEntry)
// - action.icon SVG base name -> AppAction::icon / resources/images/<name>.svg
// - action.desc/wiki -> AppAction::tooltip / help_url (footer detail strip)
// - action.*_en / tab.title_en -> ActionRegistry action_json / tab_options (English search aliases,
// sent only when they differ from the displayed text)
// - payload.tooltip_expanded -> ActionRegistry::tooltip_expanded (persisted footer state)
// - action list is frecency-sorted -> ActionRegistry::snapshot()
@@ -97,45 +99,85 @@ var ghostEl = null, ghostTypedEl = null, ghostSuffixEl = null;
var activeCompletion = null;
// ---- pure helpers (no DOM; unit-tested) -------------------------------------
// Pre-normalized haystacks, cached on the action object. The fold is length-preserving (1:1 per
// char) so the ranges FuzzyRangesNorm returns slice the ORIGINAL title/group/source text correctly.
// The action objects arrive from C++ and are stable for the dialog's lifetime, so we compute these once.
function titleNorm(a) {
if (a._tn === undefined)
a._tn = NormText(a.title, false);
return a._tn;
// Every searchable field of an action: its text, its ranking weight (title > group/category >
// source/aliases) and the matchIndex key its highlight ranges go to (null for a search-only field).
// - group vs source: the eyebrow (header) line shows group when present, else source. Settings keep
// an empty group so their source path is the eyebrow; commands / plates / recents carry a non-empty
// group. Splitting the two lets a match range stay aligned to whichever string the eyebrow renders.
// - full_label: search-only alias for the descriptive name when the title differs (e.g. "Reverse on
// even" vs "Overhang reversal"). Never rendered, so no highlight ranges.
// - plugin category / type: the visible "Plugins" category, plus the plugin kind as searchable
// metadata, so typing "plugin" finds runnable plugin actions even when neither their capability
// nor plugin name contains that word. Keep both category forms.
// - *_en: English counterparts of the translated fields, sent by C++ only when they differ from the
// displayed text (a non-English UI), so the page can still be searched in English. Each carries the
// weight of the localized field it mirrors, so a match ranks the same in either language. Search-only,
// except their ranges, which drive the row's English hint (see ENGLISH_HINT_FIELDS).
var SEARCH_FIELDS = [
{ text: function (a) { return a.title; }, weight: SCORE_TITLE, range: "title" },
{ text: function (a) { return a.group; }, weight: SCORE_GROUP, range: "group" },
{ text: function (a) { return a.source; }, weight: 0, range: "source" },
{ text: function (a) { return a.full_label; }, weight: 0, range: null },
{ text: function (a) { return a.kind === "plugin" ? T("sd_plugins", "Plugins") : ""; }, weight: SCORE_GROUP, range: "plugin" },
{ text: function (a) { return a.kind === "plugin" ? "plugin plugins" : ""; }, weight: SCORE_GROUP, range: null },
{ text: function (a) { return a.title_en; }, weight: SCORE_TITLE, range: "title_en" },
{ text: function (a) { return a.group_en; }, weight: SCORE_GROUP, range: "group_en" },
{ text: function (a) { return a.source_en; }, weight: 0, range: "source_en" },
{ text: function (a) { return a.full_label_en; }, weight: 0, range: "full_label_en" }
];
var MATCH_RANGE_KEYS = SEARCH_FIELDS.map(function (f) { return f.range; }).filter(Boolean);
// English aliases a row can match through. `range` names the matchIndex key matchFields fills for that
// field; the hint shows the action's English name (title_en), falling back to whichever alias matched.
var ENGLISH_HINT_FIELDS = [
{ range: "title_en", text: function (a) { return a.title_en; } },
{ range: "group_en", text: function (a) { return a.group_en; } },
{ range: "source_en", text: function (a) { return a.source_en; } },
{ range: "full_label_en", text: function (a) { return a.full_label_en; } }
];
// The English alias to show as a muted hint on an action row, or null. Only a row whose match came
// through an English alias gets one (a visible translated highlight, or a local-only alias, needs
// none). The hint is the action's English NAME - highlighted when the name itself matched, otherwise
// shown plain - so the row identifies the action instead of repeating its translated eyebrow: a
// "Process : Speed" breadcrumb match still names the setting ("Inner wall"), not the breadcrumb again.
function englishHint(a, mi, visibleMatch) {
if (!mi || visibleMatch) return null;
var matched = null;
for (var i = 0; i < ENGLISH_HINT_FIELDS.length; i++) {
if (mi[ENGLISH_HINT_FIELDS[i].range]) { matched = ENGLISH_HINT_FIELDS[i]; break; }
}
if (!matched) return null;
if (a.title_en) return { text: a.title_en, ranges: mi.title_en || null };
var text = matched.text(a);
return text ? { text: text, ranges: mi[matched.range] } : null;
}
// The eyebrow (header) line shows group when present, else source. Settings keep an empty group so
// their source path is the eyebrow; commands / plates / recents carry a non-empty group. Splitting the
// two lets a match range stay aligned to whichever string the eyebrow actually renders.
function groupNorm(a) {
if (a._gn === undefined)
a._gn = NormText(a.group || "", false);
return a._gn;
// The English breadcrumb a row matched through (a command's group, a setting's source path), with its
// highlight ranges, or null when neither matched. Shown beside the translated eyebrow so an English
// hit is visible where it landed, alongside the English name hint on the row-name line.
function englishBreadcrumb(a, mi) {
if (!mi) return null;
if (mi.group_en && a.group_en) return { text: a.group_en, ranges: mi.group_en };
if (mi.source_en && a.source_en) return { text: a.source_en, ranges: mi.source_en };
return null;
}
function sourceNorm(a) {
if (a._sn === undefined)
a._sn = NormText(a.source || "", false);
return a._sn;
}
function pluginCategoryNorm(a) {
if (a._pcn === undefined)
a._pcn = a.kind === "plugin" ? NormText(T("sd_plugins", "Plugins"), false) : "";
return a._pcn;
}
// Plugin type is searchable metadata, so typing "plugin" can find runnable plugin actions even
// when neither their capability nor plugin name contains that word. Keep both category forms.
function pluginTypeNorm(a) {
if (a._pn === undefined)
a._pn = a.kind === "plugin" ? NormText("plugin plugins", false) : "";
return a._pn;
}
// Search-only alias for the descriptive name when the title differs (e.g. "Reverse on even" vs
// "Overhang reversal"). Never rendered, so no highlight ranges.
function fullNorm(a) {
if (a._fn === undefined)
a._fn = NormText(a.full_label || "", false);
return a._fn;
// The action's non-empty SEARCH_FIELDS as pre-normalized haystacks ({norm, weight, range}), cached on
// the action object. The action objects arrive from C++ and are stable for the dialog's lifetime, so
// this runs once per action; skipping empty fields keeps the per-keystroke scan to the text an action
// actually has (an English UI pays nothing for the aliases it never receives). The fold is
// length-preserving (1:1 per char) so the ranges FuzzyRangesNorm returns slice the ORIGINAL text.
function searchFields(a) {
if (a._sf === undefined) {
a._sf = [];
for (var i = 0; i < SEARCH_FIELDS.length; i++) {
var norm = NormText(SEARCH_FIELDS[i].text(a) || "", false);
if (norm)
a._sf.push({ norm: norm, weight: SEARCH_FIELDS[i].weight, range: SEARCH_FIELDS[i].range });
}
}
return a._sf;
}
// Match one pre-normalized field vs one pre-normalized needle. Returns {score, ranges, contiguous}
@@ -175,26 +217,22 @@ function tokenWordRe(token) {
return new RegExp("\\b" + EscapeRegExp(token) + "\\b");
}
// One token's best match across an action's searchable fields, keeping per-field ranges for
// highlighting. Returns {score, title, group, source}, or null if no field matches.
function tokenMatch(a, token, wwRe) {
var t = fieldMatchScore(titleNorm(a), token, wwRe);
var g = fieldMatchScore(groupNorm(a), token, wwRe);
var s = fieldMatchScore(sourceNorm(a), token, wwRe);
var f = fieldMatchScore(fullNorm(a), token, wwRe);
var p = fieldMatchScore(pluginCategoryNorm(a), token, wwRe);
var typeMatch = fieldMatchScore(pluginTypeNorm(a), token, wwRe);
if (!t && !g && !s && !f && !p && !typeMatch) return null;
var score = Math.max(
t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity,
g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity,
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity,
f ? (f.contiguous ? SCORE_CONTIGUOUS : 0) + f.score : -Infinity,
p ? (p.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + p.score : -Infinity,
typeMatch ? (typeMatch.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + typeMatch.score : -Infinity
);
return { score: score, title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null,
plugin: p ? p.ranges : null };
// One needle (the whole query, or one token of it) against every SEARCH_FIELDS field of an action.
// Returns {score, ranges}: the best weighted field score (contiguous > fuzzy, then field weight, then
// start/gaps) and the ranges of each matching highlighted field keyed by its range name. Null when no
// field matches.
function matchFields(a, needle, wwRe) {
var fields = searchFields(a);
var best = null, ranges = null; // allocated on the first match: most actions match nothing
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
var m = fieldMatchScore(field.norm, needle, wwRe);
if (!m) continue;
var v = (m.contiguous ? SCORE_CONTIGUOUS : 0) + field.weight + m.score;
if (best === null || v > best) best = v;
if (field.range) (ranges || (ranges = {}))[field.range] = m.ranges;
}
return best === null ? null : { score: best, ranges: ranges || {} };
}
// Merge per-field token ranges into sorted, coalesced ranges for highlighting. Overlapping or adjacent
@@ -217,24 +255,6 @@ function mergeRanges(ranges) {
return out;
}
// Combine per-field scores into one value, or null when nothing matched.
// Ranking: contiguous > fuzzy, then title > group/category > source/full alias, then start/gaps.
function scoreFields(t, g, s, f, p, typeMatch) {
var best = null;
function consider(m, weight) {
if (!m) return;
var v = (m.contiguous ? SCORE_CONTIGUOUS : 0) + weight + m.score;
best = best === null ? v : Math.max(best, v);
}
consider(t, SCORE_TITLE);
consider(g, SCORE_GROUP);
consider(s, 0);
consider(f, 0);
consider(p, SCORE_GROUP);
consider(typeMatch, SCORE_GROUP);
return best;
}
// The unified main-phase search: every action (command/plugin/setting) matching the query, ranked
// by relevance (not by action type). Sets matchIndex so rows highlight their match ranges. The query
// is normalized ONCE per pass - FuzzyRangesNorm then runs against each action's pre-normalized
@@ -245,8 +265,8 @@ function scoreFields(t, g, s, f, p, typeMatch) {
// - tokens: every whitespace-separated word must match SOME field, but different words may match
// different fields. This is what lets "speed acceleration inner" find "Inner wall" whose path is
// "Process : Speed : Acceleration" (title + source breadcrumb together).
// full_label and canonical plugin kind are searchable aliases. The visible category is also searchable
// so its own match can be highlighted in plugin rows.
// full_label, canonical plugin kind and the English counterparts (*_en) are searchable aliases. The
// visible category is also searchable so its own match can be highlighted in plugin rows.
// A phrase match always outranks a distributed token match.
function searchActions(actions, query) {
var q = (query || "").trim();
@@ -262,50 +282,43 @@ function searchActions(actions, query) {
// Compiled once per pass, reused over every field: non-global so no exec()/lastIndex state leaks
// between fields, and EscapeRegExp keeps regex metachars in the query literal.
var wwRe = new RegExp("\\b" + EscapeRegExp(searchNeedle) + "\\b");
// A one-word query's only token is the needle itself, so its token pass would repeat the failed
// phrase match exactly; such an action is dropped without it.
var tokenPass = searchTokens.length !== 1 || searchTokens[0] !== searchNeedle;
var scored = [];
var seen = {};
for (var i = 0; i < list.length; i++) {
var a = list[i];
var t = fieldMatchScore(titleNorm(a), searchNeedle, wwRe);
var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe);
var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe);
var f = fieldMatchScore(fullNorm(a), searchNeedle, wwRe);
var p = fieldMatchScore(pluginCategoryNorm(a), searchNeedle, wwRe);
var typeMatch = fieldMatchScore(pluginTypeNorm(a), searchNeedle, wwRe);
var phrase = scoreFields(t, g, s, f, p, typeMatch);
var score, ranges;
if (phrase !== null) {
score = phrase + SCORE_PHRASE;
ranges = { title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null,
plugin: p ? p.ranges : null };
var phrase = matchFields(a, searchNeedle, wwRe);
var score, perField;
if (phrase) {
score = phrase.score + SCORE_PHRASE;
} else {
if (!tokenPass) continue;
// Require every token; a token that matches nothing drops the action immediately. Ranges
// from all matching tokens are merged per field so each matched word highlights.
var sum = 0, titleR = null, groupR = null, sourceR = null, pluginR = null, all = true;
var sum = 0, all = true;
perField = {};
for (var tokenIndex = 0; tokenIndex < searchTokens.length; tokenIndex++) {
var m = tokenMatch(a, searchTokens[tokenIndex], searchTokenRes[tokenIndex]);
var m = matchFields(a, searchTokens[tokenIndex], searchTokenRes[tokenIndex]);
if (!m) { all = false; break; }
sum += m.score;
if (m.title) (titleR || (titleR = [])).push(m.title);
if (m.group) (groupR || (groupR = [])).push(m.group);
if (m.source) (sourceR || (sourceR = [])).push(m.source);
if (m.plugin) (pluginR || (pluginR = [])).push(m.plugin);
for (var key in m.ranges)
(perField[key] || (perField[key] = [])).push(m.ranges[key]);
}
if (!all) continue;
score = sum;
ranges = { title: mergeRanges(titleR), group: mergeRanges(groupR), source: mergeRanges(sourceR),
plugin: mergeRanges(pluginR) };
}
// Ranges are per-field against the ACTUAL text drawn: title for the row-name, and group (or
// source when group is empty) for the eyebrow - so highlight offsets stay aligned to the label.
matchIndex[a.id] = {
title: ranges.title,
group: ranges.group,
source: ranges.source,
plugin: ranges.plugin,
useEyebrowGroup: !!(a.group)
};
// Ranges are per-field against the ACTUAL text drawn: title for the row-name, group (or source
// when group is empty) for the eyebrow, and title_en for the English hint - so highlight offsets
// stay aligned to the label.
var entry = { useEyebrowGroup: !!(a.group) };
for (var k = 0; k < MATCH_RANGE_KEYS.length; k++) {
var rangeKey = MATCH_RANGE_KEYS[k];
entry[rangeKey] = phrase ? phrase.ranges[rangeKey] || null : mergeRanges(perField[rangeKey]);
}
matchIndex[a.id] = entry;
scored.push({ a: a, s: score });
seen[a.id] = true;
}
@@ -350,7 +363,8 @@ function completionFor(query, list) {
var top = (list || []).slice(0, 10);
for (var i = 0; i < top.length; i++) {
var a = top[i];
var fields = [a.title, a.group, a.source, a.full_label];
// English aliases after the displayed text, so English typing completes in a localized UI.
var fields = [a.title, a.group, a.source, a.full_label, a.title_en, a.group_en, a.source_en, a.full_label_en];
for (var f = 0; f < fields.length; f++) {
var words = completionWords(fields[f]);
for (var w = 0; w < words.length; w++) {
@@ -427,16 +441,30 @@ function tabTitle(t) {
return title || prettySource((t && t.id) || "");
}
// Filter the tab list by a fuzzy title/id match. Pure so the node-vm test can exercise it.
// Filter the tab list by a fuzzy title/id/English-title match (title_en is sent only when the page
// label is translated). Pure so the node-vm test can exercise it.
function filterTabs(tabs, query) {
var q = (query || "").trim();
if (!q)
return (tabs || []).slice(0);
return (tabs || []).filter(function (t) {
return FuzzyRanges(tabTitle(t), q, false) || FuzzyRanges(t.id, q, false);
return FuzzyRanges(tabTitle(t), q, false) || FuzzyRanges(t.id, q, false) ||
(t.title_en && FuzzyRanges(t.title_en, q, false));
});
}
// The English title of a tab matched only through it, with its highlight ranges, or null when the tab
// matched its translated title or id (so it already shows why) or has no English title.
function tabAliasHint(t, query) {
var q = (query || "").trim();
if (!q || !t || !t.title_en)
return null;
if (FuzzyRanges(tabTitle(t), q, false) || FuzzyRanges(t.id, q, false))
return null;
var ranges = FuzzyRanges(t.title_en, q, false);
return ranges ? { text: t.title_en, ranges: ranges } : null;
}
function shouldRenderActionList(query) {
return !!((query || "").trim());
}
@@ -971,8 +999,21 @@ function renderActionRow(a, i) {
var showPluginCategory = a.kind === "plugin" && (typedQuery || isRecent);
var eyebrow = actionEyebrow(a, query, isRecent);
var eyebrowMatch = mi ? (showPluginCategory ? mi.plugin : (mi.useEyebrowGroup ? mi.group : mi.source)) : null;
shell.left.insertBefore(markedText("row-eyebrow", eyebrow, eyebrowMatch), shell.line);
// The eyebrow line: the translated breadcrumb, and beside it the English breadcrumb when the query
// matched that (non-English UI), so an English hit on a breadcrumb is visible where it landed.
var eyebrowLine = document.createElement("div");
eyebrowLine.className = "row-eyebrow-line";
eyebrowLine.appendChild(markedText("row-eyebrow", eyebrow, eyebrowMatch));
var enBreadcrumb = englishBreadcrumb(a, mi);
if (enBreadcrumb)
eyebrowLine.appendChild(markedText("row-eyebrow-en", enBreadcrumb.text, enBreadcrumb.ranges));
shell.left.insertBefore(eyebrowLine, shell.line);
shell.line.appendChild(markedText("row-name", a.title, mi ? mi.title : null));
// A row found only through an English alias shows the action's English name, highlighted when the
// name itself matched, so the match makes sense even when the translated row resembles nothing.
var hint = englishHint(a, mi, mi ? (mi.title || eyebrowMatch) : null);
if (hint)
shell.line.appendChild(markedText("row-alias", hint.text, hint.ranges));
var badge = modeBadge(a, USER_MODE);
if (badge) {
var tag = document.createElement("span");
@@ -1150,6 +1191,11 @@ function renderTabRow(t, i) {
name.className = "row-name";
name.textContent = label;
shell.line.appendChild(name);
// A tab found only through its English title shows that title, highlighted, so the match is
// explained even when the translated label looks nothing like the query.
var hint = tabAliasHint(t, query);
if (hint)
shell.line.appendChild(markedText("row-alias", hint.text, hint.ranges));
shell.row.onclick = function () { sel = { zone: "list", i: i }; render({ resize: true }); };
shell.row.ondblclick = function () { sel = { zone: "list", i: i }; jumpToTab(t); };
return shell.row;
@@ -1421,8 +1467,8 @@ function focusInput() { setTimeout(function () { if (qEl) qEl.focus(); }, 0); }
function OnInit() {
qEl = $("q"); listEl = $("list"); favEl = $("favBar"); clearEl = $("clear"); eyeEl = $("favEyebrow"); countEl = $("count"); detailEl = $("detail");
ghostEl = $("ghost"); ghostTypedEl = $("ghostTyped"); ghostSuffixEl = $("ghostSuffix");
// text.js's TranslatePage() targets jQuery `.trans` nodes; this page has none and defines its own
// `$`, so don't call it. Runtime strings go through T() instead.
// This page has no data-i18n markup and defines its own `$` and T(), so it does not load
// include/i18n.js. Its strings all go through T().
qEl.placeholder = T("sd_search", "Search actions");
qEl.setAttribute("aria-label", T("sd_search", "Search actions"));
if (clearEl) {
@@ -50,6 +50,25 @@ assert.equal(ctx.filterTabs(tabOptions, "Device").length, 1,
"a typed query matches a tab title");
assert.deepEqual(ctx.filterTabs(tabOptions, "zzz"), [],
"a typed query with no match returns an empty list");
// A translated tab whose id is not its English name carries title_en, so English still finds it.
const localizedTabs = [
{ id: "monitor", title: "设备", title_en: "Device" },
{ id: "prepare", title: "准备" }
];
assert.deepEqual(ctx.filterTabs(localizedTabs, "device").map(function (t) { return t.id; }), ["monitor"],
"a localized tab matches its English title");
assert.deepEqual(ctx.filterTabs(localizedTabs, "设备").map(function (t) { return t.id; }), ["monitor"],
"a localized tab still matches its translated title");
// A tab found only through its English title shows it as a hint; one that matched its translated
// title or id already shows why, and one without an English title has nothing to show.
assert.deepEqual(ctx.tabAliasHint(localizedTabs[0], "device"), { text: "Device", ranges: [[0, 6]] },
"an English-only tab match yields the English title and its highlight ranges");
assert.equal(ctx.tabAliasHint(localizedTabs[0], "设备"), null,
"a translated-title tab match draws no English hint");
assert.equal(ctx.tabAliasHint(localizedTabs[0], "monitor"), null,
"a tab matched by its id draws no English hint");
assert.equal(ctx.tabAliasHint(localizedTabs[1], "Device"), null,
"a tab with no English title has no hint");
// tabTitle: pages added with an empty title (e.g. MainFrame's Home tab) fall back to the id.
assert.equal(ctx.tabTitle({ id: "home", title: "" }), "Home",
@@ -192,6 +211,89 @@ assert.equal(ctx.completionFor("", compPool), null, "an empty query has no compl
assert.equal(ctx.completionFor("zzz", ctx.searchActions(compPool, "zzz")), null,
"a query with no match has no completion");
// English aliases: in a non-English UI each translated field may carry its English counterpart
// (*_en), searched with the weight of the field it mirrors. An English UI sends none.
const zhPool = [
{ id: "rev", title: "反转偶数层悬垂方向", full_label: "悬垂反转", source: "工艺 : 质量 : 悬垂",
title_en: "Reverse on even", full_label_en: "Overhang reversal", source_en: "Process : Quality : Overhangs",
group: "", input: "" },
{ id: "slice", title: "切片并预览", source: "OrcaSlicer", group: "切片和导出",
title_en: "Slice and Preview", group_en: "Slice & Export", input: "" },
{ id: "brim", title: "Brim宽度", source: "工艺 : 其他 : Brim", source_en: "Process : Others : Brim",
group: "", input: "" }
];
assert.deepEqual(ctx.searchActions(zhPool, "reverse on even").map(function (a) { return a.id; }), ["rev"],
"an English query finds a translated setting through its English title");
assert.equal(ctx.matchIndex.rev.title, null, "an English-only match does not highlight the translated title");
assert.deepEqual(ctx.matchIndex.rev.title_en, [[0, 15]], "the English title keeps its ranges for the hint");
assert.deepEqual(ctx.searchActions(zhPool, "偶数层").map(function (a) { return a.id; }), ["rev"],
"the translated title is still searchable");
assert.equal(ctx.matchIndex.rev.title_en, null, "a translated-title match leaves no English hint ranges");
assert.deepEqual(ctx.searchActions(zhPool, "reverse 悬垂").map(function (a) { return a.id; }), ["rev"],
"a mixed query may match one token in English and another in the translation");
assert.deepEqual(ctx.searchActions(zhPool, "overhang reversal").map(function (a) { return a.id; }), ["rev"],
"the English full_label alias is searchable");
assert.deepEqual(ctx.searchActions(zhPool, "export").map(function (a) { return a.id; }), ["slice"],
"the English group alias is searchable");
assert.deepEqual(ctx.searchActions(zhPool, "brim").map(function (a) { return a.id; }), ["brim"],
"a translation that keeps the English word matches the displayed title");
assert.deepEqual(ctx.matchIndex.brim.title, [[0, 4]], "that match highlights the displayed title, so no hint is drawn");
// Weights mirror the localized fields: an English title match outranks an English breadcrumb match.
const zhRankPool = [
{ id: "src", title: "内壁", source: "工艺 : 速度", source_en: "Process : Speed", group: "", input: "" },
{ id: "ttl", title: "速度", source: "工艺 : 其他", title_en: "Speed", group: "", input: "" }
];
assert.deepEqual(ctx.searchActions(zhRankPool, "speed").map(function (a) { return a.id; }), ["ttl", "src"],
"an English title match outranks an English breadcrumb match, as their localized fields do");
// Absent aliases (an English UI) leave no English hint; every pool above has none and ranks as before.
ctx.searchActions(pool, "layer");
assert.equal(ctx.matchIndex.c1.title_en, null, "an action without English aliases has no English hint ranges");
assert.equal(ctx.completionFor("reverse o", ctx.searchActions(zhPool, "reverse o")).suffix, "n",
"completion extends an English word from title_en");
assert.equal(ctx.completionFor("quality overh", ctx.searchActions(zhPool, "quality overh")).suffix, "angs",
"completion also reaches the English breadcrumb");
// A row matched only through an English group/source/full_label alias still names the action in
// English: the hint is title_en (the English name), not the alias that matched, so a breadcrumb hit
// does not just repeat the translated eyebrow.
ctx.searchActions(zhPool, "export");
assert.deepEqual(ctx.matchIndex.slice.group_en, [[8, 14]], "the English group keeps its ranges");
assert.deepEqual(ctx.englishHint(zhPool[1], ctx.matchIndex.slice, null), { text: "Slice and Preview", ranges: null },
"an English group match names the action, unhighlighted");
assert.deepEqual(ctx.englishBreadcrumb(zhPool[1], ctx.matchIndex.slice), { text: "Slice & Export", ranges: [[8, 14]] },
"an English group match shows the English breadcrumb beside the eyebrow");
ctx.searchActions(zhPool, "overhang reversal");
assert.deepEqual(ctx.matchIndex.rev.full_label_en, [[0, 17]], "the English full_label keeps its ranges");
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: null },
"an English full_label match names the action, unhighlighted");
ctx.searchActions(zhPool, "quality");
assert.deepEqual(ctx.matchIndex.rev.source_en, [[10, 17]], "the English breadcrumb keeps its ranges");
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: null },
"an English breadcrumb match names the action, not the breadcrumb");
assert.deepEqual(ctx.englishBreadcrumb(zhPool[0], ctx.matchIndex.rev), { text: "Process : Quality : Overhangs", ranges: [[10, 17]] },
"an English breadcrumb match shows the English breadcrumb beside the eyebrow");
// The name itself matching is the one case that highlights the hint.
ctx.searchActions(zhPool, "reverse on even");
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: [[0, 15]] },
"an English title match names and highlights the action");
assert.equal(ctx.englishBreadcrumb(zhPool[0], ctx.matchIndex.rev), null,
"an English title match shows no breadcrumb hint");
// A breadcrumb match with no English name falls back to the matched alias.
assert.deepEqual(ctx.englishHint({ title_en: "", source_en: "Process : Speed" }, { source_en: [[10, 15]] }, null),
{ text: "Process : Speed", ranges: [[10, 15]] }, "without an English name the matched alias is shown");
// A visible translated highlight suppresses the hint even when an English alias also matched.
ctx.searchActions(zhPool, "brim");
assert.equal(ctx.englishHint(zhPool[2], ctx.matchIndex.brim, ctx.matchIndex.brim.title), null,
"a row that highlights its translated title draws no English hint");
// The reported case: "process speed" matches only the English breadcrumb of "Inner wall", so the hint
// names the setting rather than echoing the breadcrumb.
const zhInner = [{ id: "inner", title: "内壁", title_en: "Inner wall", source: "工艺 : 速度",
source_en: "Process : Speed", group: "", input: "" }];
ctx.searchActions(zhInner, "process speed");
assert.deepEqual(ctx.englishHint(zhInner[0], ctx.matchIndex.inner, null), { text: "Inner wall", ranges: null },
"a breadcrumb-only match names the setting in English");
assert.deepEqual(ctx.englishBreadcrumb(zhInner[0], ctx.matchIndex.inner), { text: "Process : Speed", ranges: [[0, 8], [10, 15]] },
"a breadcrumb-only match shows the English breadcrumb beside the eyebrow");
// actionCategory: a command/dynamic action's group is its category; a setting uses the top-level
// source segment; every plugin shares one header; a category-less action falls back to "Other".
assert.equal(ctx.actionCategory({ id: "c", group: "Help", source: "OrcaSlicer", kind: "command" }), "Help",
@@ -456,9 +558,9 @@ assert.equal(ctx.stateFromPayload({}).tooltipExpanded, true, "expansion defaults
assert.equal(ctx.stateFromPayload({ tooltip_expanded: false }).tooltipExpanded, false, "a collapsed payload is honored");
assert.equal(ctx.stateFromPayload({ tooltip_expanded: true }).tooltipExpanded, true, "an expanded payload is honored");
// resultCountText: a search counts the shown matches only ("Showing N actions"); the total is used
// resultCountText: a search counts the shown matches only ("Matching actions: N"); the total is used
// solely for the empty-query count.
assert.equal(ctx.resultCountText(100, 3, "lay"), "Showing 3 actions", "a search reports the shown match count only");
assert.equal(ctx.resultCountText(100, 100, ""), "100 actions", "an empty query reports the total");
assert.equal(ctx.resultCountText(100, 3, "lay"), "Matching actions: 3", "a search reports the shown match count only");
assert.equal(ctx.resultCountText(100, 100, ""), "Actions: 100", "an empty query reports the total");
console.log("ok");
+38 -1
View File
@@ -390,7 +390,31 @@ body {
flex-direction: column;
}
/* Eyebrow line: the translated breadcrumb, plus the English breadcrumb beside it when it matched. */
.row-eyebrow-line {
display: flex;
align-items: baseline;
gap: 6px;
min-width: 0;
overflow: hidden;
}
.row-eyebrow {
font-size: 10px;
line-height: 1.3;
color: var(--muted, var(--orca-muted, #6b7280));
opacity: .85;
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* English breadcrumb of a row found only through it (non-English UI); gives way to the translation. */
.row-eyebrow-en {
flex: 0 100 auto;
min-width: 0;
font-size: 10px;
line-height: 1.3;
color: var(--muted, var(--orca-muted, #6b7280));
@@ -414,8 +438,21 @@ body {
white-space: nowrap;
}
/* English title of a row found only through it (non-English UI); gives way to the title first. */
.row-alias {
flex: 0 100 auto;
min-width: 0;
font-size: 11px;
color: var(--muted, var(--orca-muted, #6b7280));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.row-name mark,
.row-eyebrow mark {
.row-eyebrow mark,
.row-eyebrow-en mark,
.row-alias mark {
background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn);
border-radius: 2px;
@@ -11,7 +11,7 @@
<link rel="stylesheet" href="./styles.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../../include/xterm/xterm.js"></script>
<script type="text/javascript" src="../../include/xterm/xterm-addon-fit.js"></script>
@@ -21,8 +21,8 @@
<div class="app">
<div id="terminal-container"></div>
<div class="input-row">
<input type="text" id="cmd-input" placeholder="python ... or uv ..." />
<button id="run-btn">Run</button>
<input type="text" id="cmd-input" placeholder="python ... or uv ..." data-i18n-placeholder="command_hint" />
<button id="run-btn" data-i18n="run">Run</button>
</div>
</div>
</body>
+3 -4
View File
@@ -36,8 +36,7 @@ function WatchXtermTheme() {
}
function OnInit() {
if (typeof TranslatePage === "function")
TranslatePage();
TranslatePage();
term = new Terminal({
cursorBlink: true,
@@ -77,7 +76,7 @@ function OnInit() {
onRun();
});
term.writeln("Plugin terminal ready.");
term.writeln(T("ready", "Plugin terminal ready."));
}
function onRun() {
@@ -122,7 +121,7 @@ function HandleStudio(value) {
case "process_done":
setRunning(false);
term.writeln("\x1b[90mProcess exited with code: " + payload.exit_code + "\x1b[0m");
term.writeln("\x1b[90m" + T("exit_code", "Process exited with code: %s", payload.exit_code) + "\x1b[0m");
break;
case "process_error":
+2 -2
View File
@@ -9,7 +9,7 @@
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="load.js"></script>
@@ -17,7 +17,7 @@
<body onLoad="OnInit()">
<div id="LoadBlock">
<img id="LoadingSvg" src="loading.svg" />
<div id="LoadTip" class="trans" tid="t126">Loading……</div>
<div id="LoadTip" data-i18n="loading">Loading…</div>
</div>
</body>
</html>
+4 -4
View File
@@ -9,7 +9,7 @@
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<link rel="stylesheet" type="text/css" href="1.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
</head>
@@ -17,10 +17,10 @@
<div id="SlicerIcon" style="text-align: center;">
<img src="../../image/logo.png"/>
</div>
<div id="Title" class="trans" tid="t1">Welcome to Orca Slicer</div>
<div id="STip" class="trans" tid="t2">Orca Slicer will be setup in several steps. Let's start!</div>
<div id="Title" data-i18n="welcome">Welcome to Orca Slicer</div>
<div id="STip" data-i18n="setup_intro">Orca Slicer will be set up in several steps. Let's start!</div>
<div id="BtnArea">
<a id="StartBtn" class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t24" href="../11/index.html">Get Started</a>
<a id="StartBtn" class="ButtonStyleConfirm ButtonTypeChoice" href="../11/index.html" data-i18n="get_started">Get Started</a>
</div>
</body>
</html>
+8 -8
View File
@@ -10,23 +10,23 @@
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="11.js"></script>
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t47">Please select your login region</div>
<div data-i18n="login_region">Please select your login region</div>
</div>
<div id="Content">
<div id="EarthIcon"><img src="earth.png"/></div>
<div id="RegionList">
<div class="trans RegionItem RegionSelected TextS1" tid="t48" region="Asia-Pacific" onclick="ChooseRegion('Asia-Pacific')">Asia-Pacific</div>
<div class="trans RegionItem" tid="t49" region="China" onclick="ChooseRegion('China')">China</div>
<div class="trans RegionItem" tid="t60" region="Europe" onclick="ChooseRegion('Europe')">Europe</div>
<div class="trans RegionItem" tid="t61" region="North America" onclick="ChooseRegion('North America')">North America</div>
<div class="trans RegionItem" tid="t62" region="Others" onclick="ChooseRegion('Others')">Others</div>
<div class="RegionItem RegionSelected TextS1" region="Asia-Pacific" onclick="ChooseRegion('Asia-Pacific')" data-i18n="asia_pacific">Asia-Pacific</div>
<div class="RegionItem" region="China" onclick="ChooseRegion('China')" data-i18n="china">China</div>
<div class="RegionItem" region="Europe" onclick="ChooseRegion('Europe')" data-i18n="europe">Europe</div>
<div class="RegionItem" region="North America" onclick="ChooseRegion('North America')" data-i18n="north_america">North America</div>
<div class="RegionItem" region="Others" onclick="ChooseRegion('Others')" data-i18n="others">Others</div>
</div>
<!-- <div id="ChangeTip">
<div><img class="TipIcon" src="tip.png" /></div>
@@ -34,7 +34,7 @@
</div>-->
</div>
<div id="AcceptArea">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" id="AcceptBtn" tid="t9" onclick="GotoPolicyPage()">Next</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="AcceptBtn" onclick="GotoPolicyPage()" data-i18n="next">Next</div>
</div>
</body>
</html>
+11 -10
View File
@@ -50,17 +50,17 @@ function HandleStudio( pVal )
let hasError = pVal.hasOwnProperty("error");
noticeBar.classList.toggle("notice-error", hasError);
if (hasError) {
noticeBar.textContent = "Error";
noticeBar.textContent = T("error", "Error");
noticeText.textContent = pVal["error"];
} else if (pVal["vendors"] && pVal["vendors"].length > 0) {
noticeBar.textContent = "New printers found";
noticeText.textContent = "New printer vendors installed: " + pVal["vendors"].join(", ");
noticeBar.textContent = T("new_printers_found", "New printers found");
noticeText.textContent = T("new_vendors_installed", "New printer vendors installed: %s", pVal["vendors"].join(", "));
} else if (pVal["declined"]) {
noticeBar.textContent = "Information";
noticeText.textContent = "New printer vendors were found, but installation was cancelled.";
noticeBar.textContent = T("information", "Information");
noticeText.textContent = T("vendors_declined", "New printer vendors were found, but installation was cancelled.");
} else {
noticeBar.textContent = "Information";
noticeText.textContent = "No new printers found.";
noticeBar.textContent = T("information", "Information");
noticeText.textContent = T("no_new_printers", "No new printers found.");
}
ShowNotice(1);
@@ -272,7 +272,7 @@ function CreateVendorBlock(vendorName)
if( alt == "BBL" )
alt = "Bambu Lab";
if( alt == "Custom")
alt = "Custom Printer";
alt = T("custom_printer", "Custom Printer");
if( alt == "Other")
alt = "Orca colosseum";
@@ -315,7 +315,7 @@ function CreatePrinterBlock(OneModel)
' </div>'+
' <div class="PrinterInfoMark">?</div>'+
' <div class="PrinterInfo">'+
' <div class="title trans">Nozzle</div>'+
' <div class="title">' + T("nozzle", "Nozzle") + '</div>'+
' <div class="value">' + OneModel['nozzle_diameter'].replaceAll(";", " · ") + '</div>'+
' </div>'+
' <div style="display: flex;">'+
@@ -377,7 +377,8 @@ function OnExit()
if( nTotal==0 ) {
let noticeBar = document.getElementById("NoticeBar");
noticeBar.classList.add("notice-error");
noticeBar.textContent = "Error";
noticeBar.textContent = T("error", "Error");
document.getElementById("NoticeText").textContent = T("printer_required", "At least one printer must be selected.");
ShowNotice(1);
return 0;
}
+9 -9
View File
@@ -12,7 +12,7 @@
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../21/common.js"></script> <!-- ORCA use common sources for setup guide and standalone dialog -->
@@ -20,12 +20,12 @@
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t10">Printer Selection</div>
<div data-i18n="printer_selection">Printer Selection</div>
</div>
<div id="SidebarContainer">
<div id="Sidebar" class="thin-scroll">
<div class="title">Vendors</div>
<div class="title" data-i18n="vendors">Vendors</div>
<div class="area" id="SidebarVendors">
<!-- <div class="SidebarItem"></div> -->
</div>
@@ -39,7 +39,7 @@
<div class="search" hasvalue="0">
<div class="search-icon icon16"></div>
<input type="text" class="searchTerm" placeholder=" " oninput="textInput(this)">
<span class="search-placeholder trans">Search...</span>
<span class="search-placeholder" data-i18n="search">Search</span>
</div>
<div class="LayoutSelector">
<div class="TabGroup">
@@ -81,17 +81,17 @@
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" id="PreBtn" tid="t8" onclick="window.open('../1/index.html','_self')">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" id="AcceptBtn" tid="t9" onclick="GotoFilamentPage()">Next</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="window.open('../1/index.html','_self')" data-i18n="back">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="AcceptBtn" onclick="GotoFilamentPage()" data-i18n="next">Next</div>
</div>
<div id="NoticeMask"></div>
<div id="NoticeBody">
<div id="NoticeBar" class="trans" tid="t18">error</div>
<div id="NoticeBar" data-i18n="error">Error</div>
<div id="NoticeContent">
<div id="NoticeText" class="trans" tid="t37">At least one printer must be selected.</div>
<div id="NoticeText" data-i18n="printer_required">At least one printer must be selected.</div>
<div id="NoticeBtns">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t36" onClick="ShowNotice(0)">ok</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" onClick="ShowNotice(0)" data-i18n="ok">OK</div>
</div>
</div>
</div>
+27 -27
View File
@@ -12,7 +12,7 @@
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../22/common.js"></script> <!-- ORCA use common sources for setup guide and standalone dialog -->
@@ -20,7 +20,7 @@
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t14">Filament Selection</div>
<div data-i18n="filament_selection">Filament Selection</div>
</div>
<div id="Content" class="ZScrol">
<!-- ORCA column browser -->
@@ -29,14 +29,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
<span class="cbr-search-placeholder" data-i18n="printer">Printer</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="MachineList">
<div class="CValues">
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -44,14 +44,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
<span class="cbr-search-placeholder trans" tid="t16">filament type</span>
<span class="cbr-search-placeholder" data-i18n="filament_type">Filament type</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="FilatypeList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllFilament()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -59,14 +59,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
<span class="cbr-search-placeholder trans" tid="t17">vendor</span>
<span class="cbr-search-placeholder" data-i18n="vendor">Vendor</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="VendorList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllVendor()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllVendor()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -75,48 +75,48 @@
<div class="cbr-filter-box">
<div class="filter-icon"></div>
<input type="text" class="cbr-filter-bar" placeholder=" " tabindex="4"/>
<span class="cbr-filter-placeholder trans">Filter items</span>
<span class="cbr-filter-placeholder" data-i18n="filter_items">Filter items</span>
<div class="clear-icon"></div>
</div>
<div class="ComboBox NoLabel">
<div class="arrow-icon"></div>
<select id="filter-tags">
<option value="0" disabled selected hidden></option>
<option value="1">Checked</option>
<option value="2">Unchecked</option>
<option value="1" data-i18n="checked">Checked</option>
<option value="2" data-i18n="unchecked">Unchecked</option>
</select>
</div>
<div class="list-item-count"></div>
<div class="cbr-filter-btns">
<span class="cbr-filter-mode-filter trans">Select filtered</span>
<span class="cbr-filter-mode-visible trans">Select visible</span>
<div class="ButtonStyleConfirm ButtonTypeWindow trans" tid="t11" onClick="SelectAllFilament(1)">all</div>
<div class="ButtonStyleRegular ButtonTypeWindow trans" tid="t12" onClick="SelectAllFilament(0)">Clear all</div>
<span class="cbr-filter-mode-filter" data-i18n="select_filtered">Select filtered</span>
<span class="cbr-filter-mode-visible" data-i18n="select_visible">Select visible</span>
<div class="ButtonStyleConfirm ButtonTypeWindow" onClick="SelectAllFilament(1)" data-i18n="all">All</div>
<div class="ButtonStyleRegular ButtonTypeWindow" onClick="SelectAllFilament(0)" data-i18n="clear_all">Clear all</div>
</div>
</div>
<div class="cbr-content thin-scroll" id="ItemBlockArea">
<!-- <label class="MItem"><input type="checkbox" class="trans" tid="t11" onClick="SelectAllFilament()" /><span class="trans" tid="t11">all</span></label> -->
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
</div>
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t8" id="PreBtn" onclick="ReturnPreviewPage()">Back</div>
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t25" id="AcceptBtn" onclick="FinishGuide()">finish</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t9" id="GotoNetPluginBtn" onclick="GotoNetPluginPage()">Next</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="ReturnPreviewPage()" data-i18n="back">Back</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="AcceptBtn" onclick="FinishGuide()" data-i18n="finish">Finish</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="GotoNetPluginBtn" onclick="GotoNetPluginPage()" data-i18n="next">Next</div>
</div>
<div id="NoticeMask">
</div>
<div id="NoticeBody">
<div id="NoticeBar" class="trans" tid="t18">error</div>
<div id="NoticeBar" data-i18n="error">Error</div>
<div id="NoticeContent">
<div id="NoticeText" class="trans" tid="t19">At least one filament must be selected. </div>
<div id="NoticeText" class="trans" tid="t20">Do you want to use default filament ?</div>
<div id="NoticeText" data-i18n="filament_required">At least one filament must be selected.</div>
<div id="NoticeText" data-i18n="use_default_filament">Do you want to use the default filament?</div>
<div id="NoticeBtns">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t21" onClick="ChooseDefaultFilament()">Yes</div>
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t22" onClick="ShowNotice(0)">No</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" onClick="ChooseDefaultFilament()" data-i18n="yes">Yes</div>
<div class="ButtonStyleRegular ButtonTypeChoice" onClick="ShowNotice(0)" data-i18n="no">No</div>
</div>
</div>
</div>
+28 -28
View File
@@ -11,7 +11,7 @@
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../22/common.js"></script> <!-- ORCA use common sources for setup guide and standalone dialog -->
@@ -19,8 +19,8 @@
</head>
<body onLoad="OnInit()">
<div id="Title">
<div id="SystemFilamentBtn" class="TitleSelected trans" tid="t109" onClick="OnSelectMenu(1)">System Filaments</div>
<div id="CustomFilamentBtn" class="TitleUnselected trans" tid="t110" onClick="OnSelectMenu(2)">Custom Filaments</div>
<div id="SystemFilamentBtn" class="TitleSelected" onClick="OnSelectMenu(1)" data-i18n="system_filaments">System Filaments</div>
<div id="CustomFilamentBtn" class="TitleUnselected" onClick="OnSelectMenu(2)" data-i18n="custom_filaments">Custom Filaments</div>
</div>
<div id="Content" class="ZScrol">
@@ -32,14 +32,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
<span class="cbr-search-placeholder" data-i18n="printer">Printer</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="MachineList">
<div class="CValues">
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -47,14 +47,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
<span class="cbr-search-placeholder trans" tid="t16">filament type</span>
<span class="cbr-search-placeholder" data-i18n="filament_type">Filament type</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="FilatypeList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllFilament()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -62,14 +62,14 @@
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
<span class="cbr-search-placeholder trans" tid="t17">vendor</span>
<span class="cbr-search-placeholder" data-i18n="vendor">Vendor</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="VendorList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllVendor()" /><span class="trans" tid="t11">all</span></label>
<label><input type="checkbox" onClick="ChooseAllVendor()" /><span data-i18n="all">All</span></label>
</div>
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
@@ -78,28 +78,28 @@
<div class="cbr-filter-box">
<div class="filter-icon"></div>
<input type="text" class="cbr-filter-bar" placeholder=" " tabindex="4"/>
<span class="cbr-filter-placeholder trans">Filter items</span>
<span class="cbr-filter-placeholder" data-i18n="filter_items">Filter items</span>
<div class="clear-icon"></div>
</div>
<div class="ComboBox NoLabel">
<div class="arrow-icon"></div>
<select id="filter-tags">
<option value="0" disabled selected hidden></option>
<option value="1">Checked</option>
<option value="2">Unchecked</option>
<option value="1" data-i18n="checked">Checked</option>
<option value="2" data-i18n="unchecked">Unchecked</option>
</select>
</div>
<div class="list-item-count"></div>
<div class="cbr-filter-btns">
<span class="cbr-filter-mode-filter trans">Select filtered</span>
<span class="cbr-filter-mode-visible trans">Select visible</span>
<div class="ButtonStyleConfirm ButtonTypeWindow trans" tid="t11" onClick="SelectAllFilament(1)">all</div>
<div class="ButtonStyleRegular ButtonTypeWindow trans" tid="t12" onClick="SelectAllFilament(0)">Clear all</div>
<span class="cbr-filter-mode-filter" data-i18n="select_filtered">Select filtered</span>
<span class="cbr-filter-mode-visible" data-i18n="select_visible">Select visible</span>
<div class="ButtonStyleConfirm ButtonTypeWindow" onClick="SelectAllFilament(1)" data-i18n="all">All</div>
<div class="ButtonStyleRegular ButtonTypeWindow" onClick="SelectAllFilament(0)" data-i18n="clear_all">Clear all</div>
</div>
</div>
<div class="cbr-content thin-scroll" id="ItemBlockArea">
<!-- <label class="MItem"><input type="checkbox" class="trans" tid="t11" onClick="SelectAllFilament()" /><span class="trans" tid="t11">all</span></label> -->
<div class="cbr-no-items">No items</div>
<div class="cbr-no-items" data-i18n="no_items">No items</div>
</div>
</div>
</div>
@@ -108,7 +108,7 @@
<div id="CustomFilamentsArea">
<div id="CFilament_Btn_Area">
<div class="ButtonStyleConfirm ButtonTypeWindow trans" tid="t111" onClick="OnClickCustomFilamentAdd()">Create New</div>
<div class="ButtonStyleConfirm ButtonTypeWindow" onClick="OnClickCustomFilamentAdd()" data-i18n="create_new">Create New</div>
</div>
<div id="CFilament_List" class="thin-scroll">
</div>
@@ -116,19 +116,19 @@
</div>
<div id="AcceptArea">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t39" id="AcceptBtn" onclick="ConfirmSelect()">Confirm</div>
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t38" id="PreBtn" onclick="CancelSelect()">Cancel</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="AcceptBtn" onclick="ConfirmSelect()" data-i18n="confirm">Confirm</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="CancelSelect()" data-i18n="cancel">Cancel</div>
</div>
<div id="NoticeMask">
</div>
<div id="NoticeBody">
<div id="NoticeBar" class="trans" tid="t18">error</div>
<div id="NoticeBar" data-i18n="error">Error</div>
<div id="NoticeContent">
<div id="NoticeText" class="trans" tid="t19">At least one filament must be selected. </div>
<div id="NoticeText" class="trans" tid="t20">Do you want to use default filament ?</div>
<div id="NoticeText" data-i18n="filament_required">At least one filament must be selected.</div>
<div id="NoticeText" data-i18n="use_default_filament">Do you want to use the default filament?</div>
<div id="NoticeBtns">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t21" onClick="ChooseDefaultFilament()">yes</div>
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t22" onClick="ShowNotice(0)">no</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" onClick="ChooseDefaultFilament()" data-i18n="yes">Yes</div>
<div class="ButtonStyleRegular ButtonTypeChoice" onClick="ShowNotice(0)" data-i18n="no">No</div>
</div>
</div>
</div>
+11 -11
View File
@@ -12,7 +12,7 @@
<!-- <script type="text/javascript" src="test.js"></script> -->
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="../21/common.js"></script> <!-- ORCA use common sources for setup guide and standalone dialog -->
@@ -20,12 +20,12 @@
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t10">Printer Selection</div>
<div data-i18n="printer_selection">Printer Selection</div>
</div>
<div id="SidebarContainer">
<div id="Sidebar" class="thin-scroll">
<div class="title">Vendors</div>
<div class="title" data-i18n="vendors">Vendors</div>
<div class="area" id="SidebarVendors">
<!-- <div class="SidebarItem"></div> -->
</div>
@@ -39,7 +39,7 @@
<div class="search" hasvalue="0">
<div class="search-icon icon16"></div>
<input type="text" class="searchTerm" placeholder=" " oninput="textInput(this)">
<span class="search-placeholder trans">Search...</span>
<span class="search-placeholder" data-i18n="search">Search</span>
</div>
<div class="LayoutSelector">
<div class="TabGroup">
@@ -81,19 +81,19 @@
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" id="CreateBtn" onclick="CreateNewPrinter()">Create</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="CheckNewPrintersBtn" onclick="CheckForNewPrinters()">Check for new printers</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t39" id="AcceptBtn" onclick="ConfirmSelect()">Confirm</div>
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t38" id="PreBtn" onclick="CancelSelect()">Cancel</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="CreateBtn" onclick="CreateNewPrinter()" data-i18n="create">Create</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="CheckNewPrintersBtn" onclick="CheckForNewPrinters()" data-i18n="check_new_printers">Check for new printers</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="AcceptBtn" onclick="ConfirmSelect()" data-i18n="confirm">Confirm</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="CancelSelect()" data-i18n="cancel">Cancel</div>
</div>
<div id="NoticeMask"></div>
<div id="NoticeBody">
<div id="NoticeBar" class="trans" tid="t18">error</div>
<div id="NoticeBar" data-i18n="error">Error</div>
<div id="NoticeContent">
<div id="NoticeText" class="trans" tid="t37">At least one printer must be selected.</div>
<div id="NoticeText" data-i18n="printer_required">At least one printer must be selected.</div>
<div id="NoticeBtns">
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t36" onClick="ShowNotice(0)">ok</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" onClick="ShowNotice(0)" data-i18n="ok">OK</div>
</div>
</div>
</div>
-46
View File
@@ -1,46 +0,0 @@
.PolicyArea
{
background-color: #4A4A51;
}
#PolicyTxt
{
padding: 12px;
word-wrap:break-word;
overflow-y: auto;
min-height: 200px;
text-indent: 2em;
border-style: solid;
border-width: 1px;
border-color: #D7D7D7;
}
#PolicyTxt::-webkit-scrollbar {/*滚动条整体样式*/
width: 5px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
#PolicyTxt::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
border-radius: 10px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: #AAAAAA;
}
#PolicyTxt::-webkit-scrollbar-track {/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 10px;
background: #EDEDED;
}
#C_List li
{
margin-left: 4em;
}
p
{
text-indent:2em;
margin-bottom: 2px;
}
-80
View File
@@ -1,80 +0,0 @@
function OnInit()
{
TranslatePage();
SendPrivacySelect();
}
function SendPrivacySelect()
{
let nVal="refuse";
if( $('#ChoosePrivacy').is(':checked') )
nVal="agree";
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="user_private_choice";
tSend['data']={};
tSend['data']['action']=nVal;
SendWXMessage( JSON.stringify(tSend) );
}
function GotoNextPage()
{
RequestProfile();
}
function RequestProfile()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_userguide_profile";
SendWXMessage( JSON.stringify(tSend) );
}
function HandleStudio( pVal )
{
let strCmd=pVal['command'];
//alert(strCmd);
if(strCmd=='response_userguide_profile')
{
HandleModelInfo(pVal['response']);
}
}
function HandleModelInfo( pVal )
{
let Modellist=pVal["model"];
let nModel=Modellist.length;
if(nModel==1)
{
// let pModel=Modellist[0];
//
// var tSend={};
// tSend['sequence_id']=Math.round(new Date() / 1000);
// tSend['command']="save_userguide_models";
// tSend['data']={};
//
// let ModelName=pModel['model'];
//
// tSend['data'][ModelName]={};
// tSend['data'][ModelName]['model']=pModel['model'];
// tSend['data'][ModelName]['nozzle_diameter']=pModel['nozzle_diameter'];
// tSend['data'][ModelName]['vendor']=pModel['vendor'];
//
// SendWXMessage( JSON.stringify(tSend) );
window.location.href="../22/index.html";
return;
}
window.location.href="../21/index.html";
}
-40
View File
@@ -1,40 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=7200" />
<title>引导_P1</title>
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="3.css" />
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="./3.js"></script>
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t6">We kindly request your help to improve<br/> everyone's printing</div>
</div>
<div id="Content">
<div id="PolicyTxt" class="TextS2 TextArea1 ZScrol">
<a class="trans" tid="t54">In the 3D Printing community, we learn from each other’s successes and failures to adjust our own slicing parameters and settings. Orca Slicer follows the same principle and uses machine learning to improve its performance from the successes and failures of the vast number of prints by our users. We are training Orca Slicer to be smarter by feeding them the real-world data. If you are willing, this service will access information from your error logs and usage logs, which may include information described in </a><a class="HyperLink trans" tid="t55" href="https://bambulab.com/policies/privacy" target="_blank">Privacy Policy</a><a class="trans" tid="t56">
. We will not collect any Personal Data by which an individual can be identified directly or indirectly, including without limitation names, addresses, payment information, or phone numbers. By enabling this service, you agree to these terms and the statement about Privacy Policy.
</a>
</div>
<br/>
<label> <!-- ORCA use label tag to allow checkbox to toggle when user clicked to text -->
<input id="ChoosePrivacy" type="checkbox" onClick="SendPrivacySelect()" onChange="SendPrivacySelect()" />
<span class="trans TextS1" tid="t7">Allow sending anonymous data</span>
</label>
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t8" id="PreBtn" onclick="window.open('../11/index.html','_self')">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t9" id="AcceptBtn" onclick="GotoNextPage()">Next</div>
</div>
</body>
</html>
-40
View File
@@ -1,40 +0,0 @@
#PolicyTxt
{
background-color: #EBEBEB;
padding: 12px;
word-wrap:break-word;
overflow-y: scroll;
min-height: 200px;
text-indent: 2em;
}
#PolicyTxt::-webkit-scrollbar {/*滚动条整体样式*/
width: 5px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
#PolicyTxt::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
border-radius: 10px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: #AAAAAA;
}
#PolicyTxt::-webkit-scrollbar-track {/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 10px;
background: #EDEDED;
}
#C_List li
{
margin-left: 4em;
}
p
{
text-indent:2em;
margin-bottom: 2px;
}
-80
View File
@@ -1,80 +0,0 @@
function OnInit()
{
TranslatePage();
SendPrivacySelect();
}
function SendPrivacySelect()
{
let nVal="refuse";
if( $('#ChoosePrivacy').is(':checked') )
nVal="agree";
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="user_private_choice";
tSend['data']={};
tSend['data']['action']=nVal;
SendWXMessage( JSON.stringify(tSend) );
}
function GotoNextPage()
{
RequestProfile();
}
function RequestProfile()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_userguide_profile";
SendWXMessage( JSON.stringify(tSend) );
}
function HandleStudio( pVal )
{
let strCmd=pVal['command'];
//alert(strCmd);
if(strCmd=='response_userguide_profile')
{
HandleModelInfo(pVal['response']);
}
}
function HandleModelInfo( pVal )
{
let Modellist=pVal["model"];
let nModel=Modellist.length;
if(nModel==1)
{
// let pModel=Modellist[0];
//
// var tSend={};
// tSend['sequence_id']=Math.round(new Date() / 1000);
// tSend['command']="save_userguide_models";
// tSend['data']={};
//
// let ModelName=pModel['model'];
//
// tSend['data'][ModelName]={};
// tSend['data'][ModelName]['model']=pModel['model'];
// tSend['data'][ModelName]['nozzle_diameter']=pModel['nozzle_diameter'];
// tSend['data'][ModelName]['vendor']=pModel['vendor'];
//
// SendWXMessage( JSON.stringify(tSend) );
window.location.href="../22/index.html";
return;
}
window.location.href="../21/index.html";
}
-39
View File
@@ -1,39 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=7200" />
<title>引导_P1</title>
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="31.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="./31.js"></script>
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t6">We kindly request your help to improve<br/> everyone's printing</div>
</div>
<div id="Content">
<div id="PolicyTxt">
<a class="trans" tid="t54">In the 3D Printing community, we learn from each other’s successes and failures to adjust our own slicing parameters and settings. Orca Slicer follows the same principle and uses machine learning to improve its performance from the successes and failures of the vast number of prints by our users. We are training Orca Slicer to be smarter by feeding them the real-world data. If you are willing, this service will access information from your error logs and usage logs, which may include information described in </a><a class="HyperLink trans" tid="t55" href="https://bambulab.com/policies/privacy" target="_blank">Privacy Policy</a><a class="trans" tid="t56">
. We will not collect any Personal Data by which an individual can be identified directly or indirectly, including without limitation names, addresses, payment information, or phone numbers. By enabling this service, you agree to these terms and the statement about Privacy Policy.
</a>
</div>
<br/>
<label><!-- ORCA use label tag to allow checkbox to toggle when user clicked to text -->
<input id="ChoosePrivacy" type="checkbox" onClick="SendPrivacySelect()" onChange="SendPrivacySelect()" />
<span class="trans" tid="t7">Allow sending anonymous data</span>
</label>
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t8" id="PreBtn" onclick="window.open('../11/index.html','_self')">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t9" id="AcceptBtn" onclick="GotoNextPage()">Next</div>
</div>
</body>
</html>
+8 -8
View File
@@ -10,32 +10,32 @@
<link rel="stylesheet" type="text/css" href="4orca.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="4orca.js"></script>
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="orca3">Stealth Mode</div>
<div data-i18n="stealth_mode">Stealth Mode</div>
</div>
<div id="Content">
<div id="FeatureText">
<div><a tid="orca4" class="trans TextS1">This disables all cloud features, including Orca Cloud profile syncing. Users who prefer to work entirely offline can enable this option.</a></div>
<div><a class="TextS1" data-i18n="stealth_mode_desc">This disables all cloud features, including Orca Cloud profile syncing. Users who prefer to work entirely offline can enable this option.</a></div>
<br/>
<div><a tid="orca12" class="trans TextS2">Note: When Stealth Mode is enabled, your user profiles will not be backed up to Orca Cloud.</a></div>
<div><a class="TextS2" data-i18n="stealth_mode_note">Note: When Stealth Mode is enabled, your user profiles will not be backed up to Orca Cloud.</a></div>
</div>
<!-- ORCA use label tag to allow checkbox to toggle when user clicked to text -->
<label id="CheckArea"><input id="StealthMode" onClick="SendStealthModeCheck()" type="checkbox"><a tid="orca5" class="trans TextS1">Enable Stealth Mode.</a></label>
<div id="RestartText" tid="t70" class="trans" ></div>
<label id="CheckArea"><input id="StealthMode" onClick="SendStealthModeCheck()" type="checkbox"><a class="TextS1" data-i18n="enable_stealth_mode">Enable Stealth Mode.</a></label>
<div id="RestartText"></div>
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t8" id="PreBtn" onclick="window.history.back()">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t9" id="GotoNetPluginBtn" onclick="GotoNetPluginPage()">Next</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="window.history.back()" data-i18n="back">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="GotoNetPluginBtn" onclick="GotoNetPluginPage()" data-i18n="next">Next</div>
</div>
</body>
</html>
+8 -8
View File
@@ -10,20 +10,20 @@
<link rel="stylesheet" type="text/css" href="5.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../../include/i18n.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="5.js"></script>
</head>
<body onLoad="OnInit()">
<div id="Title">
<div class="trans" tid="t64">Proprietary Plugins</div>
<div data-i18n="proprietary_plugins">Proprietary Plugins</div>
</div>
<div id="Content">
<div id="FeatureText">
<div><a tid="t75" class="trans TextS1">Some printer vendors require proprietary plugins for communication with their printers. Please select the corresponding plugin if you use such printers.</a></div>
<div><a tid="t65" class="trans TextS1">Please be aware that these plugins are not developed or maintained by OrcaSlicer. They should be used at your own discretion and risk.</a></div>
<div><a class="TextS1" data-i18n="proprietary_plugins_desc">Some printer vendors require proprietary plugins for communication with their printers. Please select the corresponding plugin if you use such printers.</a></div>
<div><a class="TextS1" data-i18n="proprietary_plugins_risk">Please be aware that these plugins are not developed or maintained by OrcaSlicer. They should be used at your own discretion and risk.</a></div>
<!-- <div><span class="TextPoint">●</span>&nbsp;&nbsp;<a tid="t65" class="trans" ></a></div>
<div><span class="TextPoint">●</span>&nbsp;&nbsp;<a tid="t66" class="trans" ></a></div>
@@ -33,13 +33,13 @@
<!-- ORCA use label tag to allow checkbox to toggle when user clicked to text -->
<label id="CheckArea"><input id="InstallCheck" onClick="SendInstallPluginCheck()" type="checkbox"><a tid="t69" class="trans TextS1">Install Bambu Network Plug-in. </a></label>
<div id="RestartText" tid="t70" class="trans" ></div>
<label id="CheckArea"><input id="InstallCheck" onClick="SendInstallPluginCheck()" type="checkbox"><a class="TextS1" data-i18n="install_bambu_plugin">Install Bambu Network plug-in</a></label>
<div id="RestartText"></div>
</div>
<div id="AcceptArea">
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t8" id="PreBtn" onclick="window.history.back()">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t25" id="AcceptBtn" onclick="FinishGuide()">Finish</div>
<div class="ButtonStyleRegular ButtonTypeChoice" id="PreBtn" onclick="window.history.back()" data-i18n="back">Back</div>
<div class="ButtonStyleConfirm ButtonTypeChoice" id="AcceptBtn" onclick="FinishGuide()" data-i18n="finish">Finish</div>
</div>
</body>
</html>
-138
View File
@@ -1,138 +0,0 @@
*
{
padding: 0;
border: 0;
margin: 0;
font-family: "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-sans;
}
html,body
{
font-size: 14px;
height:100%;
}
.LinkBtn
{
color: #1200FF;
cursor: pointer;
}
.LinkBtn:hover
{
text-decoration:underline;
}
.Btn_Green
{
padding: 1.5mm 6mm;
font-size: 14px;
text-align: center;
background-color: #009688;
border-radius: 6px;
color: #fff;
cursor: pointer;
display: inline-block;
border: 1px solid #02c7b3;
}
.Btn_Green:hover
{
background-color:#4db6ac;
}
#DownBlock
{
height: 60%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#DownArea
{
width:80%;
height: 50%;
line-height: 50px;
display: inline;
}
#DownText
{
display: flex;
align-items: center;
}
#RetryBtn
{
background-image:url("restart.png");
background-size: 100% 100%;
width: 20px;
height: 20px;
margin-left: 20px;
display: none;
}
#DownPercent
{
height:20px;
}
#PercentBkg
{
height:20px;
background-color:#CFCFCF;
border-radius: 20px;
}
#PercentTip
{
float: left;
height:20px;
width: 0%;
background-color:#009688;
border-radius: 20px;
}
#DownSuccessTip
{
width:80%;
display: none;
text-align: center;
}
#InstallFailedTip
{
display:none;
height: auto;
}
/*------Btn Area------*/
#BtnBlock
{
height:40%;
display: flex;
justify-content: center;
align-items: center;
}
#CancelBtn
{
display: inline;
}
#RestartBtn
{
display: none;
}
#CloseBtn
{
display: none;
}
-167
View File
@@ -1,167 +0,0 @@
function OnInit()
{
//let strInput=JSON.stringify(cData);
//HandleStudio(strInput);
// let tVal={};
// tVal['status']=3;
// tVal['percent']=30;
//
// HandStatusPercent(tVal);
TranslatePage();
SendDownloadCmd();
}
function SendDownloadCmd()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="Begin_Download_network_plugin";
SendWXMessage( JSON.stringify(tSend) );
}
function HandleStudio( pVal )
{
let strCmd=pVal['command'];
//alert(strCmd);
if(strCmd=='ShowStatusPercent')
{
HandStatusPercent(pVal['data']);
}
}
function HandStatusPercent( pVal )
{
let nStatus=pVal['status']*1;
let nPercent=pVal['percent'];
if(nStatus==0)
{
//正常下载
$('#DownStepText').attr("tid","t71");
$('#RetryBtn').hide();
$('#PercentTip').css("width",nPercent+'%');
}
else if(nStatus==1)
{
//下载失败 或 解压缩补丁包失败
$('#DownStepText').attr("tid","t72");
$('#PercentTip').css("width",0+'%');
$('#RetryBtn').show();
}
else if(nStatus==2)
{
//下载完成
$('#PercentTip').css("width",100+'%');
setTimeout("SendInstallPluginCmd()",100);
}
else if(nStatus==3)
{
//解压缩补丁包失败
$('#DownArea').hide();
$("#InstallFailedTip").show();
$('#CancelBtn').hide();
$('#RestartBtn').hide();
$('#CloseBtn').show();
}
else if(nStatus==4)
{
//安装补丁包完成
$('#DownArea').hide();
$('#DownSuccessTip').show();
$('#CancelBtn').hide();
$('#CloseBtn').hide();
$('#RestartBtn').show();
//pTimer=setInterval("RunInverse()",1000);
}
TranslatePage();
}
var nCount=3;
var pTimer=null;
function RunInverse()
{
$('#CountNumber').text(nCount+'');
nCount--;
if(nCount==-1)
{
RestartBambuStudio();
}
}
function RetryDownload()
{
$('#DownStepText').attr("tid","t71");
$('#PercentTip').css("width",0+'%');
SendDownloadCmd();
}
function CancelDownload()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="netplugin_download_cancel";
SendWXMessage( JSON.stringify(tSend) );
}
function SendInstallPluginCmd()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="begin_install_plugin";
SendWXMessage( JSON.stringify(tSend) );
}
function RestartBambuStudio()
{
if( pTimer!=null )
{
clearInterval(pTimer);
pTimer=null;
}
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="restart_studio";
SendWXMessage( JSON.stringify(tSend) );
}
function CloseDownDialog()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="close_download_dialog";
SendWXMessage( JSON.stringify(tSend) );
}
function OpenPluginFolder()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="open_plugin_folder";
SendWXMessage( JSON.stringify(tSend) );
}
-49
View File
@@ -1,49 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=7200" />
<title>引导_P1</title>
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="6.css" />
<script type="text/javascript" src="../../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="6.js"></script>
</head>
<body onLoad="OnInit()">
<div id="DownBlock">
<div id="DownArea">
<div id="DownText">
<div id="DownStepText" class="trans" tid="t71">Downloading</div>
<div id="RetryBtn" onClick="RetryDownload()"></div>
</div>
<div>
<div id="PercentTip"></div>
<div id="PercentBkg"></div>
</div>
</div>
<div id="DownSuccessTip" class="trans" tid="t73">Installation successful.</div>
<div id="InstallFailedTip">
<div><a class="trans" tid="t79">Failed to install plug-in. </a><a class="trans" tid="t80">Try the following steps:</a></div>
<div><a class="trans" tid="t81">1, Click </a><a class="trans LinkBtn" tid="t77" onClick="OpenPluginFolder()">here</a><a class="trans" tid="t82"> to open the plug-in directory</a> </div>
<div class="trans" tid="t83">2, Close all open Orca Slicer</div>
<div class="trans" tid="t84">3, Delete all files under the plug-in directory</div>
<div class="trans" tid="t85">4, Reopen Orca Slicer and install the plug-in again</div>
</div>
</div>
<div id="BtnBlock">
<div id="CancelBtn" class="ButtonStyleRegular ButtonTypeChoice trans" tid="t38" onClick="CancelDownload()" >Cancel</div>
<div id="RestartBtn" class="ButtonStyleRegular ButtonTypeChoice trans" tid="t25" onClick="RestartBambuStudio()">Finish</div>
<div id="CloseBtn" class="ButtonStyleRegular ButtonTypeChoice trans" tid="t86" onClick="CloseDownDialog()" >Close </div>
</div>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

+15 -15
View File
@@ -15,7 +15,7 @@
<link rel="stylesheet" type="text/css" href="../include/swiper/swiper-bundle.min.css" />
<script type="text/javascript" src="../include/swiper/swiper-bundle.min.js"></script>
<script type="text/javascript" src="../data/text.js"></script>
<script type="text/javascript" src="../include/i18n.js"></script>
<script type="text/javascript" src="js/home.js"></script>
</head>
@@ -33,7 +33,7 @@
<span id="UserName" class="AccountName"></span>
<span class="AccountSecondary">Orca Cloud</span>
</span>
<span class="AccountSignIn trans" tid="t26">Login</span>
<span class="AccountSignIn" data-i18n="login">Login</span>
<svg class="AccountChevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M3 4.75L6 7.75L9 4.75" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -46,7 +46,7 @@
<svg class="SectionChevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="SectionLabel trans" tid="orca6">Bambu Cloud</span>
<span class="SectionLabel">Bambu Cloud</span>
<span id="BambuStatusDot"></span>
</div>
@@ -58,16 +58,16 @@
</span>
<span class="AccountText">
<span id="BambuUserName" class="AccountName"></span>
<span class="AccountSecondary trans" tid="orca8">Not signed in</span>
<span class="AccountSecondary" data-i18n="not_signed_in">Not signed in</span>
</span>
<span class="AccountSignIn trans" tid="t26">Login</span>
<span class="AccountSignIn" data-i18n="login">Login</span>
<svg class="AccountChevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M3 4.75L6 7.75L9 4.75" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div id="NoPluginTip">
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
<div id="NoPluginText" class="RedFont"><a class="LinkBtn" onClick="BeginDownloadNetworkPlugin()"></a></div>
</div>
</div>
</div>
@@ -79,7 +79,7 @@
<div id="AccountMenuHandle" role="presentation"></div>
<div id="AccountMenuLogout" role="menuitem" tabindex="0" onClick="OnAccountMenuLogout()">
<span id="AccountMenuLogoutIcon"></span>
<span class="trans" tid="t50">Log out</span>
<span data-i18n="log_out">Log Out</span>
</div>
</div>
@@ -88,7 +88,7 @@
<div id="BtnArea">
<div menu="recent" class="BtnItem BtnItemSelected" onClick="GotoMenu('recent')">
<div class="BtnIcon "><img class="LeftIcon" src="img/i2.png" /></div>
<div class="trans" tid="t28">recent</div>
<div data-i18n="recent">Recent</div>
</div>
<div class="BtnItem BtnShortcut" onClick="OpenUrlInLocalBrowser('https://cloud.orcaslicer.com')">
<div class="BtnIcon "><img class="ShortcutBrandIcon" src="../image/logo.png" /></div>
@@ -119,14 +119,14 @@
<div class="MenuItem" onClick="OnClickNewProject()">
<div class="MenuIcon"><img src="img/i4.png"></div>
<div>
<div class="trans TextS1" tid="t31">new project</div>
<div class="MenuDesc trans" tid="t32">create new project</div>
<div class="TextS1" data-i18n="new_project">New Project</div>
<div class="MenuDesc" data-i18n="create_new_project">Create new project</div>
</div>
</div>
<div class="MenuItem" onClick="OnClickOpenProject()">
<div class="MenuIcon"><img src="img/i5.png"></div>
<div>
<div class="trans TextS1" tid="t33">open project</div>
<div class="TextS1" data-i18n="open_project">Open Project</div>
<div class="MenuDesc">3mf</div>
</div>
</div>
@@ -135,8 +135,8 @@
<div id="DebugText"></div>
<div id="RecentFileArea">
<div id="RecentTitleBlock">
<div id="RecentTitle" class="Content-Title trans TextS1" tid="t35">recent open</div>
<div id="RecentClearAllBtn" class="ButtonStyleRegular ButtonTypeWindow trans" tid="t12" onClick="OnDeleteAllRecentFiles()">Clear all</div>
<div id="RecentTitle" class="Content-Title TextS1" data-i18n="recently_opened">Recently opened</div>
<div id="RecentClearAllBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnDeleteAllRecentFiles()" data-i18n="clear_all">Clear all</div>
</div>
<div id="FileList">
<!-- <div class="FileItem" onClick="OnOpenRecentFile('aaaa')" fpath='d:\model\11.3mf'>
@@ -219,11 +219,11 @@
<div id="recnet_context_menu">
<div id="CT_Delete_Bar" class="CT_Item" onClick="OnDeleteRecentFile()">
<div class="CT_Icon CT_Delete"></div>
<div class="CT_Text trans" tid="t88">clear</div>
<div class="CT_Text" data-i18n="remove">Remove</div>
</div>
<div id="CT_Folder_Bar" class="CT_Item " onClick="OnExploreRecentFile()" >
<div class="CT_Icon CT_Explore"></div>
<div class="CT_Text trans" tid="t89">open in explorer </div>
<div class="CT_Text" data-i18n="open_containing_folder">Open Containing Folder</div>
</div>
</div>
+4
View File
@@ -6,6 +6,10 @@ function OnInit()
{
//-----Official-----
TranslatePage();
let pluginTip = document.getElementById("NoPluginText");
if (pluginTip)
AppendTemplate(pluginTip, T("plugin_not_detected", "Bambu Network plug-in not detected. Click <a>here</a> to install it."),
pluginTip.querySelector("a"));
SendMsg_GetLoginInfo();
SendMsg_GetBambuLoginInfo();
+55
View File
@@ -0,0 +1,55 @@
// Localized text for the bundled web pages. The hosting window injects the page's strings, translated
// through the .po catalogs, as window.ORCA_UI_STRINGS before any page script runs
// (WebViewHostDialog::localized_strings_script). The English in the markup and in the T() calls is the
// fallback for a page opened outside the app.
// The string for `key`, else `fallback`. Each further argument fills the next %s.
function T(key, fallback, ...args) {
const strings = (typeof ORCA_UI_STRINGS !== "undefined" && ORCA_UI_STRINGS) || {};
let text = strings[key] !== undefined ? String(strings[key]) : fallback;
// A replacer function, so a "$" in a value (a plugin name, say) is not read as a pattern.
args.forEach((arg) => { text = text.replace("%s", () => String(arg)); });
return text;
}
// Appends `template` to `node`, putting the nodes that follow in place of its markers, in order, so a
// translation can move them within the sentence. %s inserts the node as it is (a search term, say);
// <a>label</a> inserts it with the label as its text, so the translation words the link together with
// the sentence around it.
function AppendTemplate(node, template, ...nodes) {
let end = 0;
let next = 0;
for (const marker of template.matchAll(/%s|<a>(.*?)<\/a>/g)) {
if (marker.index > end)
node.appendChild(document.createTextNode(template.slice(end, marker.index)));
const inserted = nodes[next++];
if (inserted) {
if (marker[1] !== undefined)
inserted.textContent = marker[1];
node.appendChild(inserted);
}
end = marker.index + marker[0].length;
}
if (end < template.length)
node.appendChild(document.createTextNode(template.slice(end)));
}
// Translates the static markup: data-i18n names the key for an element's own text (child elements
// such as icons and sort arrows are kept), data-i18n-title, data-i18n-placeholder and
// data-i18n-aria-label the key for that attribute. Translating again is harmless, so a page can call
// this after adding markup.
function TranslatePage() {
document.querySelectorAll("[data-i18n]").forEach((el) => {
const textNode = Array.from(el.childNodes).find((n) => n.nodeType === Node.TEXT_NODE && n.nodeValue.trim());
const text = T(el.dataset.i18n, textNode ? textNode.nodeValue.trim() : "");
if (textNode)
textNode.nodeValue = text;
else
el.insertBefore(document.createTextNode(text), el.firstChild);
});
["title", "placeholder", "aria-label"].forEach((attr) => {
document.querySelectorAll(`[data-i18n-${attr}]`).forEach((el) => {
el.setAttribute(attr, T(el.getAttribute(`data-i18n-${attr}`), el.getAttribute(attr) || ""));
});
});
}
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -221,15 +221,15 @@ function ShowModelInfo( pModel )
switch(UploadType)
{
case 'remix':
$('#ModelAuthorType').attr('tid','t93');
$('#ModelAuthorType').text(T('remixed_by', 'Remixed by'));
break;
case 'shared':
$('#ModelAuthorType').attr('tid','t94');
$('#ModelAuthorType').text(T('shared_by', 'Shared by'));
break;
case 'origin':
case 'profile':
default:
$('#ModelAuthorType').attr('tid','t92');
$('#ModelAuthorType').text(T('created_by', 'Created by'));
break;
}
+3 -3
View File
@@ -6,7 +6,7 @@
<title>Printer Connection Required</title>
<link rel="stylesheet" type="text/css" href="css/home.css" />
<link rel="stylesheet" type="text/css" href="css/dark.css" />
<script type="text/javascript" src="../data/text.js"></script>
<script type="text/javascript" src="../include/i18n.js"></script>
<script type="text/javascript" src="../include/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../homepage/js/json2.js"></script>
<script type="text/javascript" src="../homepage/js/globalapi.js"></script>
@@ -14,8 +14,8 @@
</head>
<body onLoad="OnInit()">
<div class="container">
<h1 class="trans">Printer Connection</h1>
<p class="trans">Please set up your printer connection to view the device.</p>
<h1 data-i18n="printer_connection">Printer Connection</h1>
<p data-i18n="printer_connection_setup">Please set up your printer connection to view the device.</p>
<img src="setup_connection.gif" alt="Printer connection setup demonstration" style="max-width: 100%; height: auto; display: block;"/>
</div>
</body>
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Lets a pull request's macOS deps build wait for a push build of the base
# branch that builds the same deps, from a Linux job. `check` runs after a cache
# miss and names a queued or running base build with the same deps/ and .github/
# to wait for, unless the pull request changes either. `wait` waits until that
# build saves KEY. actions/cache then confirms the entry can be restored.
#
# Environment: GH_TOKEN, REPO, WORKFLOW_REF and BASE_REF, plus KEY_INPUTS,
# WAIT_FOR, WAIT_SINCE, WAIT_MINUTES, KEY and ARCH for `wait`.
# Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT.
set -uo pipefail
workflow=${WORKFLOW_REF%%@*}
workflow=${workflow##*/}
# Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml.
check_job_name="build_macos_arch (${ARCH:-}) / Check Cache"
deps_job_name="build_macos_arch (${ARCH:-}) / Build Deps / Build Deps"
out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; }
# The cache key depends on deps/ and on the workflows under .github/.
local_key_inputs() { echo "$(git rev-parse "$1:.github") $(git rev-parse "$1:deps")"; }
remote_key_inputs() {
gh api "repos/$REPO/git/trees/$1" < /dev/null \
--jq '[.tree[] | select(.path == ".github" or .path == "deps") | {key: .path, value: .sha}] | from_entries | "\(.[".github"]) \(.deps)"'
}
# Prints the id of a push build of the base branch that has not finished, is not
# one of the ids in $1, and has KEY_INPUTS.
matching_base_build() {
local status runs id sha
runs=$(for status in in_progress queued pending waiting requested; do
gh api -X GET "repos/$REPO/actions/workflows/$workflow/runs" \
-f branch="$BASE_REF" -f event=push -f status="$status" -f per_page=10 < /dev/null \
| jq -r '.workflow_runs[] | "\(.id) \(.head_sha)"'
done)
while read -r id sha; do
[ -n "$id" ] || continue
[[ " ${1:-} " == *" $id "* ]] && continue
if [ "$(remote_key_inputs "$sha")" = "$KEY_INPUTS" ]; then
echo "$id"
return
fi
done <<< "$runs"
}
check() {
KEY_INPUTS=$(local_key_inputs HEAD)
out "key-inputs=$KEY_INPUTS"
if [ "$KEY_INPUTS" != "$(local_key_inputs HEAD^1)" ]; then
echo "This pull request changes deps/ or .github/, so it builds its own deps."
return
fi
local id
id=$(matching_base_build)
if [ -n "$id" ]; then
echo "Build $id of $BASE_REF has the same deps/ and .github/. Waiting for its cache."
out "wait-for=$id"
out "wait-since=$(date +%s)"
else
echo "No build of $BASE_REF is building these deps."
fi
}
key_saved() {
gh cache list -R "$REPO" --ref "refs/heads/$BASE_REF" --key "$KEY" --json key < /dev/null \
| jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null
}
wait_for() {
# Counts the limit from the check, including time queued behind other waiting
# pull requests.
local id=$WAIT_FOR seen=$WAIT_FOR deadline=$((${WAIT_SINCE:-$(date +%s)} + WAIT_MINUTES * 60)) jobs job check warned=
while [ "$(date +%s)" -lt "$deadline" ]; do
if key_saved; then
echo "Found $KEY."
return
fi
jobs=$(gh api -X GET "repos/$REPO/actions/runs/$id/jobs" -f filter=latest -f per_page=100 < /dev/null)
job=$(jq -r --arg name "$deps_job_name" 'first(.jobs[] | select(.name == $name) | "\(.status) \(.conclusion)") // ""' <<< "$jobs")
case "$job" in
"completed success" | "completed skipped")
# The new entry can take a moment to be listed, and a skipped job means
# the build found the cache, possibly under the default branch.
for _ in 1 2 3; do
key_saved && { echo "Found $KEY."; return; }
sleep 20
done
echo "Build $id of $BASE_REF finished its deps without saving $KEY."
return ;;
"completed cancelled") ;;
completed*)
echo "The deps job of build $id of $BASE_REF did not succeed."
return ;;
# The deps job exists only once the build's cache check has succeeded.
"")
case "$(gh api "repos/$REPO/actions/runs/$id" --jq '"\(.status) \(.conclusion)"' < /dev/null)" in
"completed cancelled") ;;
completed*)
check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs")
if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then
echo "::warning title=Deps wait::Build $id of $BASE_REF has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
warned=1
fi ;;
*)
sleep 120
continue ;;
esac ;;
*)
sleep 120
continue ;;
esac
# Follows another unfinished build with the same key inputs, such as the
# push that replaced a cancelled one.
id=$(matching_base_build "$seen")
[ -n "$id" ] || break
seen="$seen $id"
echo "Following build $id of $BASE_REF."
done
echo "No build of $BASE_REF saved $KEY."
}
case "${1:-}" in
check) check ;;
wait) wait_for ;;
*) echo "usage: $0 check|wait" >&2; exit 2 ;;
esac
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env python3
"""Holds a pull request's macOS build until a hosted macOS runner is free for it.
Runs from a Linux job in build_check_cache.yml, once per macOS arch, in a single
repo-wide line (a concurrency group with queue: max), so only the job at the
front of the line polls. It lets its arch in when the runners every active Build
all run holds or still needs, plus one for this arch, fit in MACOS_RUNNER_LIMIT:
- A push, nightly or manual run holds RESERVE runners until its macOS work is
done. The jobs API lists only the jobs a run has reached, so what it still
needs cannot be counted and is reserved instead.
- A pull request run holds one runner for each arch let in until that arch's
app build is done, and none after. A run holding a runner while it waits for
its other arch would deadlock: the other arch can sit in the line behind a
pull request that waits for that runner.
- A run holds at least the macOS jobs it has queued or running.
A pull request's universal build and tests skip the line and are not held for
in advance. They take minutes, and holding two runners for them through the
slower arch's build left runners idle while other builds waited.
- In the minutes around the nightly's cron time, RESERVE runners are held for it
until its run appears.
A pull request labelled macos-priority when its run starts waits in a separate
line under the same rule, and the normal line counts every priority arch still
waiting as holding a runner, so the next free runners go to it.
Any API error repeated FAILURES_BEFORE_ADMIT times, and the WAIT_MINUTES limit,
let the arch in, so a fault here never blocks pull requests.
Environment: GH_TOKEN, REPO, WORKFLOW_REF, GITHUB_RUN_ID, and optionally PRIORITY,
MACOS_RUNNER_LIMIT, WAIT_MINUTES, POLL_SECONDS and GITHUB_API_URL.
"""
import datetime
import json
import os
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
# A push, nightly or manual run builds arm64 and x86_64 at the same time, then
# the universal build and the macOS tests at the same time.
RESERVE = 2
# Must match the job names in build_all.yml and build_check_cache.yml.
ARCH_PREFIX = "build_macos_arch ("
GATE = "Wait for a macOS runner"
PRIORITY_GATE = GATE + " (priority)"
FINAL_JOBS = {"Build macOS Universal", "macOS arm64"}
# Must match the cron in build_all.yml.
NIGHTLY_UTC = datetime.time(2, 15)
NIGHTLY_REPO = "OrcaSlicer/OrcaSlicer"
NIGHTLY_LEAD = datetime.timedelta(minutes=10)
NIGHTLY_GRACE = datetime.timedelta(minutes=45)
ACTIVE = {"queued", "in_progress", "waiting", "pending", "requested"}
# Runs created this recently are listed whatever their status.
RECENT = datetime.timedelta(hours=24)
FAILURES_BEFORE_ADMIT = 3
def is_macos(job):
return any(label.startswith("macos-") for label in job.get("labels") or [])
def macos_done(jobs):
"""True once the universal build and the macOS tests have finished or been skipped.
Both start together when the arch builds finish."""
# A skipped caller job is listed under its own name, a started one as "<name> / <job>".
final = [job for job in jobs if job["name"].split(" / ")[0] in FINAL_JOBS]
return bool(final) and all(job["status"] == "completed" for job in final)
def gates(jobs, names=(GATE, PRIORITY_GATE)):
return [job for job in jobs if job["name"].split(" / ")[-1] in names]
def arch_of(job):
name = job["name"]
return name[len(ARCH_PREFIX):name.find(")")] if name.startswith(ARCH_PREFIX) else None
def admitted_arches(jobs):
return {arch_of(job) for job in gates(jobs) if job["conclusion"] == "success"}
def arch_built(jobs, arch):
"""True once the arch's app build finished, or one of its jobs failed or was cancelled."""
own = [job for job in jobs if arch_of(job) == arch]
return any(job["conclusion"] in ("failure", "cancelled") for job in own) or \
any(" / Build OrcaSlicer" in job["name"] and job["status"] == "completed" for job in own)
def priority_waiting(jobs):
"""How many of the run's arches wait in the priority line."""
return sum(1 for job in gates(jobs, (PRIORITY_GATE,)) if job["status"] != "completed")
def run_demand(run, jobs, yield_to_priority=False):
"""macOS runners a run holds or still needs. With yield_to_priority, each
priority arch still waiting counts as holding one."""
# A run with no jobs that is pending waits behind another run of its
# concurrency group, which holds the runners for both.
if not jobs and run["status"] in ("pending", "waiting"):
return 0
active = sum(1 for job in jobs if is_macos(job) and job["status"] in ACTIVE)
if macos_done(jobs):
return active
if run["event"] != "pull_request":
return max(active, RESERVE)
held = sum(1 for arch in admitted_arches(jobs) if not arch_built(jobs, arch))
if yield_to_priority:
held += priority_waiting(jobs)
return max(active, held)
def nightly_window(now):
"""The window around today's nightly cron time, as (start, end) in UTC."""
cron = datetime.datetime.combine(now.date(), NIGHTLY_UTC, tzinfo=datetime.timezone.utc)
return cron - NIGHTLY_LEAD, cron + NIGHTLY_GRACE
def parse_time(value):
return datetime.datetime.fromisoformat(value.replace("Z", "+00:00"))
class Api:
def __init__(self, token, url="https://api.github.com"):
self.token = token
self.url = url.rstrip("/")
# The token's hourly limit is shared by every workflow in the repository,
# so what the last response says is left shows their use too.
self.calls = 0
self.remaining = self.limit = "?"
def get(self, path, **params):
query = urllib.parse.urlencode(params)
request = urllib.request.Request(f"{self.url}/{path}?{query}", headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {self.token}",
"X-GitHub-Api-Version": "2022-11-28",
})
self.calls += 1
try:
with urllib.request.urlopen(request, timeout=30) as response:
self.note_rate_limit(response.headers)
return json.load(response)
except urllib.error.HTTPError as error:
# A rate-limited request fails, and its headers say so.
self.note_rate_limit(error.headers)
raise
def note_rate_limit(self, headers):
self.remaining = headers.get("X-RateLimit-Remaining", "?")
self.limit = headers.get("X-RateLimit-Limit", "?")
def usage(self, calls_before):
return f" API calls this check: {self.calls - calls_before}, rate limit left: {self.remaining} of {self.limit}"
def list_runs(api, repo, workflow, **params):
runs, page = [], 1
while True:
body = api.get(f"repos/{repo}/actions/workflows/{workflow}/runs",
per_page=100, page=page, **params)
runs += body["workflow_runs"]
if len(runs) >= body["total_count"] or not body["workflow_runs"]:
return runs
page += 1
def latest_run(api, repo, workflow, **params):
runs = api.get(f"repos/{repo}/actions/workflows/{workflow}/runs",
per_page=1, **params)["workflow_runs"]
return runs[0] if runs else None
def measure(api, repo, workflow, run_id, now, priority=False):
"""Total macOS runners held or needed, and one line per run that holds any.
The priority line does not count the priority runs waiting behind it."""
total, lines = 0, []
# A run is listed as queued whenever one of its jobs waits for a runner, and
# as pending whenever one waits in a concurrency group, so runs in any of these
# can hold runners. A run that moves between the lists between the calls is
# in neither, as one is whenever the job ahead of this one leaves the line, so
# runs created recently are listed by creation time as well, and this run is
# read directly.
since = (now - RECENT).strftime("%Y-%m-%dT%H:%M:%SZ")
runs = {run["id"]: run for status in ("in_progress", "queued", "pending", "waiting")
for run in list_runs(api, repo, workflow, status=status)}
runs.update((run["id"], run) for run in list_runs(api, repo, workflow, created=f">={since}")
if run["status"] != "completed")
runs[run_id] = api.get(f"repos/{repo}/actions/runs/{run_id}")
for run in runs.values():
jobs = api.get(f"repos/{repo}/actions/runs/{run['id']}/jobs",
filter="latest", per_page=100)["jobs"]
own = run["id"] == run_id
demand = run_demand(run, jobs, yield_to_priority=not priority and not own)
if demand:
total += demand
waiting = ", priority, waiting" if priority_waiting(jobs) and not own else ""
lines.append(f" {demand} run {run['id']} ({run['event']}, {run['head_branch']}{waiting})")
# build_all.yml runs the nightly only in the main repository.
start, end = nightly_window(now)
if repo == NIGHTLY_REPO and start <= now < end:
last = latest_run(api, repo, workflow, event="schedule")
if not last or parse_time(last["created_at"]) < start:
total += RESERVE
lines.append(f" {RESERVE} the nightly, due at {NIGHTLY_UTC:%H:%M} UTC")
return total, lines
def wait(measure_now, limit, wait_minutes, poll_seconds,
clock=time.monotonic, sleep=time.sleep, log=print):
"""Polls until this arch fits. Returns the reason it was let in."""
deadline = clock() + wait_minutes * 60
failures = 0
while True:
try:
total, lines = measure_now()
except (urllib.error.URLError, OSError, ValueError, KeyError, TypeError) as error:
failures += 1
log(f"::warning title=macOS admission::Could not read the queue ({error}).")
if failures >= FAILURES_BEFORE_ADMIT:
return "the queue could not be read"
else:
failures = 0
log(f"{total} of {limit} macOS runners held or needed:")
for line in lines:
log(line)
if total + 1 <= limit:
return "a runner is free"
if clock() + poll_seconds >= deadline:
return f"it waited {wait_minutes} minutes"
sleep(poll_seconds)
def main():
repo = os.environ["REPO"]
workflow = os.environ["WORKFLOW_REF"].split("@")[0].rsplit("/", 1)[-1]
api = Api(os.environ["GH_TOKEN"], os.environ.get("GITHUB_API_URL", "https://api.github.com"))
run_id = int(os.environ["GITHUB_RUN_ID"])
limit = int(os.environ.get("MACOS_RUNNER_LIMIT") or 5)
def measure_now():
calls_before = api.calls
try:
total, lines = measure(api, repo, workflow, run_id, datetime.datetime.now(datetime.timezone.utc),
priority=os.environ.get("PRIORITY") == "true")
except Exception:
print(api.usage(calls_before).strip())
raise
return total, lines + [api.usage(calls_before)]
reason = wait(
measure_now,
limit,
wait_minutes=int(os.environ.get("WAIT_MINUTES") or 240),
poll_seconds=int(os.environ.get("POLL_SECONDS") or 180),
)
print(f"Letting this macOS build in: {reason}.")
if __name__ == "__main__":
sys.exit(main())

Some files were not shown because too many files have changed in this diff Show More